pax_global_header00006660000000000000000000000064147115476310014523gustar00rootroot0000000000000052 comment=fdb7b6614837428e5bfb0c612f9f07877329de37 chafa-1.14.5/000077500000000000000000000000001471154763100126555ustar00rootroot00000000000000chafa-1.14.5/.github/000077500000000000000000000000001471154763100142155ustar00rootroot00000000000000chafa-1.14.5/.github/workflows/000077500000000000000000000000001471154763100162525ustar00rootroot00000000000000chafa-1.14.5/.github/workflows/c-cpp.yml000066400000000000000000000034511471154763100200020ustar00rootroot00000000000000name: C/C++ CI on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Running prerequisites run: > sudo apt update -y sudo apt install -y automake libtool libglib2.0-dev libavif-dev libjpeg-dev librsvg2-dev libtiff-dev libwebp-dev gtk-doc-tools docbook-xml libxml2-utils export LD_LIBRARY_PATH=$(if [[ $CC == "clang" ]]; then echo -n '/usr/local/clang/lib'; fi) mkdir build - name: Building working-directory: ./build run: > CFLAGS=' -g -O2 -fsanitize=address,undefined -fsanitize-undefined-trap-on-error -fstack-protector-all -Werror -Wno-error=unused -Wno-error=unused-function -Wno-error=unused-macros -Wno-error=unused-parameter -Wno-error=unused-variable -Wno-error=unused-const-variable -Wno-error=unused-value -Wno-error=comment -Wno-error=missing-braces' ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man make -j4 - name: Running tests working-directory: ./build run: make check || { RET=$$?; find . -name "test-suite.log" -exec cat {} + ; exit $RET; } - name: Installing chafa working-directory: ./build run: > sudo make install chafa --version - name: Run post install working-directory: ./tests run: ./postinstall.sh - name: After failure if: failure() run: > touch tests/test-suite.log cat tests/test-suite.log chafa-1.14.5/.gitignore000066400000000000000000000006461471154763100146530ustar00rootroot00000000000000*~ \#*\# *.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.14.5/.travis.yml000066400000000000000000000020711471154763100147660ustar00rootroot00000000000000language: c dist: bionic compiler: - clang - gcc before_install: - sudo apt-get install -qq -y automake libtool libglib2.0-dev libmagickwand-dev libjpeg-dev librsvg2-dev libtiff-dev libwebp-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 && CFLAGS='-g -O2 -fsanitize=address,undefined -fsanitize-undefined-trap-on-error -Werror -Wno-error=unused -Wno-error=unused-function -Wno-error=unused-parameter -Wno-error=unused-variable -Wno-error=unused-const-variable -Wno-error=unused-value -Wno-error=comment -Wno-error=missing-braces' ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man --without-imagemagick && make -j4 && make check && rm -Rf ../build/* && ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man && make -j4 && sudo make install && chafa --version && cd ../tests && ./postinstall.sh after_failure: - touch tests/test-suite.log && cat tests/test-suite.log chafa-1.14.5/AUTHORS000066400000000000000000000026231471154763100137300ustar00rootroot00000000000000Chafa 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 2024-06-17, this yields the following (sans duplicates): alkahest begasus Biswapriyo Nath Daniel Eklöf data-man Delgan Emanuel Haupt Erica Felix Yan Hans Petter Jansson Johan Mattsson <39247600+mjunix@users.noreply.github.com> Launch Lee <80872691+LaunchLee@users.noreply.github.com> Michael Vetter Mikel Olasagasti Uranga Mo Zhou oshaboy <35503208+oshaboy@users.noreply.github.com> oupson Ricardo Arguello Robert-André Mauchin Roman Wagner Samuel Thibault Simon Law sitiom Sotiris Papatheodorou Sudhakar Verma Tim Gates Wu Zhenyu Øyvind Kolås chafa-1.14.5/COPYING000066400000000000000000001045151471154763100137160ustar00rootroot00000000000000 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.14.5/COPYING.LESSER000066400000000000000000000167441471154763100147200ustar00rootroot00000000000000 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.14.5/HACKING000066400000000000000000000066071471154763100136550ustar00rootroot00000000000000How 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. |- lodepng ........... Private copy of a PNG library, used by tools. |- tests ............. Tests for library and 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.14.5/Makefile.am000066400000000000000000000007461471154763100147200ustar00rootroot00000000000000SUBDIRS = chafa docs libnsgif lodepng tools tests EXTRA_DIST = \ HACKING \ README.md \ SECURITY.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 -e '--- \e[0;91mNOTE:\e[0m You may have to run \e[0;93msudo ldconfig\e[0m after installing.' @echo --- @echo chafa-1.14.5/NEWS000066400000000000000000000657151471154763100133720ustar00rootroot00000000000000Chafa releases ============== 1.14.5 (2024-11-03) ------------------- This release addresses a handful of medium-severity bugs. * Bug fixes: github#217 Improve sixel and general terminal support inside tmux (found by Steven Walton). github#221 Support JPEGs with CMYK color space (found by Lionel Dricot). github#225 Fix --font-ratio doing nothing (found by @johnd0e). [unfiled] Ensure CLI tool gets linked with libm. 1.14.4 (2024-09-10) ------------------- This quick follow-up release corrects the incomplete fix for the CLI argument fraction parsing. It now accepts C locale formatting in addition to that of the current locale. * Bug fixes: github#216 Dither intensity does not work properly (found by @veltza). 1.14.3 (2024-09-09) ------------------- This release brings important fixes for sixel rendering and Windows Terminal, plus a few other odds and ends. * Sixel transparency has been reenabled for still frames. Animations will be pre-composited on an opaque background to prevent flicker/glitchiness (discussed in github#211, big thanks to James Holderness). * Now supports the Ghostty terminal, defaulting to the Kitty graphics protocol there. * Bug fixes: github#185 Chafa version 1.14 breaks image preview on lf (found by @Steven79203). github#210 Crash with -f sixels (found by Chris Antos). github#211 Sixels are too small in Windows Terminal (found by @veltza). github#212 Aspect-preserving calculations are off in some cases. [unfiled] Inconsistent fraction parsing in CLI arguments. [unfiled] A few small memory leaks in the JPEG XL loader. 1.14.2 (2024-07-26) ------------------- This is a bugfix release. github#205 is particularly serious, since it affects many users on distributions that build with LTO enabled. * Bug fixes: github#203 Chafa 1.14.1 fails to build on i686 (reported by @tranzystorekk). github#205 Symbols mode not working since 1.14.1 (reported by Folke Lemaitre). github#206 Crash when importing more than 32767 glyphs (reported by @stgiga). [unfiled] Fix erroneous base64 encoding of final byte in some circumstances. 1.14.1 (2024-06-17) ------------------- This release brings bug fixes, compatibility improvements and some performance enhancements. * A JPEG XL (.jxl) loader was added using libjxl (github#188, @oupson). * Added detection of the Eat "Emulate a Terminal" Emacs terminal (Simon Law). * Symbols mode was sped up significantly on AVX2-capable platforms. * Tests: A new test driver was added. It will log the specifics of any failures, which aids debugging of CI builds (issue found by Mo Zhou). * Bug fixes: github#189 Fix installation of zsh completions (Felix Yan). github#190 Fix a small memory leak in ChafaCanvas (found by @jstkdng). github#192 Wrong sixel padding in some circumstances (found by @dnkl). github#195 tmux passthrough enabled when already set (found by Ron Lau). github#196 iTerm mode not enabled automatically (found by @sudo-burger). 1.14.0 (2024-01-08) ------------------- This is a feature release with mostly practical improvements and bug fixes. It also adds a modest amount of new API in preparation for things to come. * Removed ImageMagick loader support. Packagers can now remove this dependency (github#157). * Polite mode is now off by default. The new default eliminates cursor flicker and makes the output more robust against unusual terminal settings. The old behavior can be restored with "--polite on". * Added image loaders for the AVIF and QOI formats. Thanks to @jerch for suggesting the latter. * sRGB gamma is now handled correctly in scaling operations. * New option: --passthrough=. This allows passing graphics protocols like Sixels, iTerm and Kitty through a terminal multiplexer. It will be enabled automatically for Kitty, and can be enabled manually for other protocols with more limited support (github#116, github#162, thanks to Samantha Collins and @m040601). * New option: --view-size=. Specifies width and height of the viewport, overriding the detected terminal size (github#140, reported by Jamin Thornsberry). * New option: --fit-width. Fits images to the width of the viewport, allowing them to be taller than the viewport's height (github#115, thanks to @SuperDuperDeou). * New option: --relative=. Enables relative cursor positioning. Useful if you've pre-positioned the cursor at a particular offset where you want frames to appear, but tends to make the output illegible in pagers, e.g. 'less -R' (github#146, thanks to @Delgan and @AnonymouX47). * New option: --exact-size=. Preserves the input pixel size when possible. Useful to avoid artifacts caused by resampling (github#119, reported by @ErrorNoInternet). * New symbol selector: "imported". This selects glyphs loaded with --glyph-file (github#124, reported by @clort81). * MS Windows: Experimental support for ConHost output (-f conhost). This allows direct output on older versions of MS Windows (github#170, @oshaboy). * Fontgen: Added a BDF font writer (Mo Zhou). * Fontgen: Cleanup and modernization (Mo Zhou). * The help text and manual page were overhauled for readability, and the API documentation now includes symbol indexes by version and deprecation status. * Added a zsh completion script (Wu Zhenyu). * Installation methods added: Scoop (Launch Lee) and Winget (@sitiom). * CI: Ported to GitHub actions (Erica Ferrua Edwardsdóttir). * Bug fixes: github#107 "Unknown file format" when using AVIF on stdin (found by @ndren). github#152 Broken linking with libwebp-1.3.1 (found by Vladimír Čunát). [unfiled] Fix make check with --without-tools (Samuel Thibault). [unfiled] Fix --duration not working well with still images (@Delgan). [unfiled] Fix sixel rendering of animations (@Delgan). [unfiled] Fix operator precedence in geometry calculation (Johan Mattsson). 1.12.5 (2023-05-21) ------------------- This is a bugfix release. * Bug fixes: github#123 Crash on canvas created with chafa_canvas_new_similar() (reported by Erica Ferrua Edwardsdóttir). github#141 Distortion when rowstride is not a multiple of 4 (reported by @jstkdng). [unfiled] Missing comma in --help output (reported by @Freed-Wu). 1.12.4 (2022-11-12) ------------------- This release improves support for Microsoft Windows and fixes several bugs. * MS Windows: Added support for Unicode command-line arguments. * MS Windows: Enabled support for building a DLL. * Improved quality of accelerated symbol picking (the default with -w 6 and lower), especially along sharp edges. * The XWD loader now supports unaligned image data, which can occur when the header is of an uneven length. Such images will no longer be rejected. * Bug fixes: github#100 Reading image data from stdin fails on Windows (reported by @TransparentLC). github#104 Artifacts with transparent animations on Kitty (reported by Akash Patel). github#112 Bad documentation for -c default (reported by Jakub Wilk). github#113 Autogen/build fails on CentOS 7 (partial solution) (reported by Ivan Shatsky). huntr.dev Uncontrolled memory allocation in lodepng (reported by @JieyongMa). [unfiled] -lm should not be in pkg-config Libs: list (Tomasz Kłoczko). [unfiled] The --watch switch was broken with the introduction of --animate. 1.12.3 (2022-07-01) ------------------- A bug crept into the previous release that prevented successful package builds in some environments. This has been fixed. * Increased GLib minimum version to 2.26. * Enabled a few compiler warnings not included in '-Wall -Wextra' in order to catch more potential issues early. Also started using '-Werror' selectively. * Silenced deprecation warnings for older GLib APIs that we would like to keep using a bit longer. * Bug fixes: github#96 Regression: Fails to build on Linux/Debian (reported by Mo Zhou). 1.12.2 (2022-06-28) ------------------- This release adds basic support for Microsoft Windows. * Made everything compile for the x86_64-w64-mingw32 target using gcc. * Added support for the Windows 10 Command Prompt. * Improved error reporting. 1.12.1 (2022-06-20) ------------------- This release fixes one important input validation bug and several instances of undefined behavior revealed by fuzzing. * Increased GLib minimum version to 2.20. * Added 12 new test inputs, including bad inputs to handle gracefully. * Added a few symbols to API documentation that were accidentally left out. * Bug fixes: huntr.dev CVE-2022-2061: Out-of-bounds read in libnsgif's lzw_decode() (Sudhakar Verma of CrowdStrike). [unfiled] Undefined behavior in libnsgif due to uninitialized frame fields. [unfiled] Signed integer overflow in chafa_pack_color(). [unfiled] Integer overflow in normalization pass on some images. [unfiled] Potential unaligned access with corrupt XWD images. [unfiled] Integer overflow in quantization on some images. [unfiled] Calculating offset from NULL pointer in LodePNG. 1.12.0 (2022-06-06) ------------------- This is a feature release with new image loaders aimed at phasing out ImageMagick. It also has new convenience functions, character art improvements, tests and bug fixes. Special thanks go to the very fine security researchers at huntr.dev for their help in hardening Chafa over the last few releases. * The ImageMagick dependency is now optional and deprecated. Packagers are encouraged to drop the ImageMagick dependency (--without-imagemagick) and add direct dependencies on relevant image codecs. ImageMagick support will be removed in a future release. * Added image loaders for the following formats: JPEG, SVG, TIFF, WebP. Like the existing GIF, PNG and XWD loaders, these are much faster and generally safer than their ImageMagick counterparts. If ImageMagick is enabled, it will be used as a fallback. Supported image loaders will be listed in the --version output. * Implemented a 16/8 mode producing 8 colors and an additional 8 bright foreground-only colors for a total of 16 foreground and 8 background colors. When using ANSI escape sequences in symbol mode, the sequence for bold text is used to enable bright colors. This scheme corresponds to that of the IBM PC's VGA hardware text modes and was popular with terminal emulators in the late 1980s to early 2000s. With a few tweaks, output from this mode can be turned into ANSI art scene .ANS files compatible with utilities like the wonderful Ansilove, e.g: $ chafa -f symbols -c 16/8 -s 80 --symbols space+solid+half --fill stipple \ in.jpg | tr -d '\n' | iconv -c -f utf8 -t cp437 > out.ans $ ansilove out.ans -o out.png * New builtin Latin symbols (available with --symbols latin). This class comprises most of the symbols from the Latin-1 Supplement, Latin Extended-A and -B, IPA Extensions and Spacing Modifier Letters plus a few Latin-like symbols from other ranges, using Terminus as the reference font. The ASCII class is also a subset of this class. * Reworked ASCII symbols to be more representative of modern terminal fonts. As with the other Latin ranges, the reference font is now Terminus. * New option: --scale . This takes a real number specifying the on-screen scaling factor relative to the input's pixel size, respecting the terminal size. The special argument 'max' will fit the output to the terminal. The defaults are 1.0 for iTerm, Kitty and sixels, and 4.0 for symbols. Suggested by Lionel Dricot in github#84. * Deprecated option: --zoom. Use '--scale max' instead. * Added a battery of simple tests that can be run with 'make check'. * Made 'configure' friendlier. It's now more lenient with dependencies, and the summary is more detailed and colorized if possible. * Bug fixes: github#62 Too big alloc on bogus terminal dimensions (reported by Sotiris Papatheodorou and Mo Zhou). huntr.dev Null pointer dereference caused by calling post_func on unused batch entries (reported by @han0nly). [unfiled] Small memory leaks when using iTerm and Kitty formats. [unfiled] Wide symbol coverages leaked in symbol map destructor. [unfiled] No error code if files failed to load. 1.10.3 (2022-05-04) ------------------- This release fixes multiple input validation issues. These were found in the 'chafa' command-line tool and do not affect the library backend. * Improved input validation in the XWD loader. * Bug fixes: huntr.dev Buffer over-read when compiled with -O0 or non-x86 target (reported by @JieyongMa). 1.10.2 (2022-04-25) ------------------- This release adds security/responsible disclosure guidelines and fixes a few issues with input validation in the 'chafa' command-line tool. * Added disclosure guidelines in SECURITY.md (suggested by Jamie Slome). * Bug fixes: huntr.dev Null pointer dereference in libnsgif with crafted GIF file (reported by @JieyongMa). [unfiled] File magic would not effectively rule out internal loaders. [unfiled] Very big images could cause absurd allocation requests triggering an abort in the loader. 1.10.1 (2022-04-04) ------------------- This release brings one small but important fix and a few minor corrections to the documentation. * Bug fixes: github#87 Garbled last row of pixels on some images (found by @hydrargyrum). [unfiled] Correctly label new functions since 1.10. 1.10.0 (2022-03-20) ------------------- This is a feature release focused on compatibility, presentation and ergonomics. * New option: --animate . On by default. Can be turned off to replace animations with a still frame. Suggested by Lionel Dricot in github#79. * New option: --center or -C . Off by default. Can be turned on to center images. Suggested by Lionel Dricot in github#83. * New options: --margin-bottom and --margin-right . These permit using all available space (with --margin-bottom 0) or some smaller amount. Suggested by @crmabs in github#61. * New option: --polite . On by default. Can be turned off to correct issues caused by previous terminal state (e.g. no sixel scrolling) and improve presentation (e.g. by temporarily hiding the cursor). This can leave the terminal in an altered state, which is somewhat rude. * New option: --threads . Allows manually specifying the number of threads to use. Defaults to one per detected logical CPU core. * When invoked with redirected input and no arguments, act like a filter as if invoked with "-". Here's an example that downloads an image, converts it to Unicode text and mails it to hello@example.com: $ curl https://hpjansson.org/chafa/img/lc.jpg \ | chafa -f symbols -c none -s 77 --invert --dither bayer \ | mailx hello@example.com * If input or output is being redirected, default to playing animations only once instead of looping forever. This can be overridden with '-d inf'. * Replaced obsolete Autoconf macros (Mikel Olasagasti Uranga). * Improved image loaders: - GIF: Load GIF87a images as well as GIF89a. - PNG: Add an internal copy of LodePNG, bypassing ImageMagick. This improves performance and helps with eventually phasing out the latter. - XWD: Support images generated by 'convert'. * Improved terminal support: - Contour: Enable sixel support (Dmitry Atamanov). - foot: Open-ended TERM string matching (Daniel Eklöf). - Konsole: Enable sixel support (Dmitry Atamanov). - WezTerm: Enable sixel support. * Bug fixes: github#76 Smolscale uses too much stack space (found by Hoang Nguyen). github#81 Chafa --version is returned to stderr, not stdout (found by Lionel Dricot). [unfiled] Potential memory overrun when using Floyd-Steinberg dithering in symbols mode. [unfiled] iTerm2 compatibility issue with WezTerm and possibly others. 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.14.5/README000066400000000000000000000056071471154763100135450ustar00rootroot00000000000000Chafa ===== Chafa is a command-line utility that converts image data, including animated GIFs, into graphics formats or ANSI/Unicode character art suitable for display in a terminal. It has broad feature support, allowing it to be used on devices ranging from historical teleprinters to modern terminal emulators and everything in between. 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 MacPorts ...... port install chafa OpenBSD ....... pkg_add chafa openSUSE ...... zypper in chafa Ubuntu ........ apt install chafa On Windows, Chafa can be installed via Scoop and Winget: Scoop ....... scoop install chafa Winget ....... winget install hpjansson.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 development packages for: * FreeType2 * libjpeg (optional) * librsvg (optional) * libtiff (optional) * libwebp (optional) * ImageMagick (optional, deprecated) 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 Python bindings --------------- Erica Ferrua Edwardsdóttir maintains excellent Python bindings for Chafa. If Python's your thing, check them out. They are easy to use and come with a detailed tutorial: https://chafapy.mage.black/ chafa-1.14.5/README.md000066400000000000000000000072261471154763100141430ustar00rootroot00000000000000

Master Build StatusLatest ReleaseFriendly Chat

AboutGalleryPackagesDevelopment

## About Chafa is a command-line utility that converts image data, including animated GIFs, into graphics formats or ANSI/Unicode character art suitable for display in a terminal. It has broad feature support, allowing it to be used on devices ranging from historical teleprinters to modern terminal emulators and everything in between. 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 development packages for: * FreeType2. Often packaged as `libfreetype6-dev` or `freetype2-devel`. * libjpeg (optional). Look for `libjpeg-dev`, `libjpeg62-devel` or `libjpeg8-devel`. * librsvg (optional). Look for `librsvg2-dev` or `librsvg-devel`. * libtiff (optional). Look for `libtiff5-dev` or `libtiff-devel`. * libwebp (optional). Look for `libwebp-dev` or `libwebp-devel`. 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! ## Python bindings [Erica Ferrua Edwardsdóttir](https://mage.black/) maintains [excellent Python bindings](https://chafapy.mage.black/) for Chafa. If Python's your thing, check them out. They are easy to use and come with a [detailed tutorial](https://chafapy.mage.black/usage/tutorial.html). ## JavaScript bindings [Héctor Molinero Fernández](https://hector.molinero.dev/) maintains [wonderful JavaScript bindings](https://github.com/hectorm/chafa-wasm) built around his WebAssembly port. These are available from NPM and can be used in Node.js, web browsers, and more. chafa-1.14.5/SECURITY.md000066400000000000000000000026371471154763100144560ustar00rootroot00000000000000# Security Policies and Procedures This document outlines security procedures and general policies for Chafa. ## Reporting a Bug We are grateful for the testing and analysis carried out by the community. All bug reports are taken seriously. Normally, bugs can be filed directly in the public GitHub issue tracker, but if you believe there is a security impact, please contact the lead maintainer at his e-mail address instead. We will most likely respond within 48 hours, but since Chafa is a volunteer project, please allow up to a week for those rare times we're away from the keyboard or general connectivity. When a fix is published, you will receive credit under your real name or bug tracker handle in the NEWS document and possibly elsewhere (GitHub, blog post, etc). If you prefer to remain anonymous or pseudonymous, you should mention this in your e-mail. ## Disclosure Policy The maintainer will coordinate the fix and release process, involving the following steps: * Confirm the problem and determine the affected versions. * Audit code to find any potential similar problems. * Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. You may be asked to provide further information in pursuit of a fix. ## Comments on this Policy If you have suggestions on how this process could be improved, please submit an issue or pull request. chafa-1.14.5/TODO000066400000000000000000000066741471154763100133620ustar00rootroot00000000000000TODO ==== If you're particularly interested in any of this, send patches/pull requests or maybe just prod me a little. Minor Features/UX ----------------- - Accept -o, --output to write to file. - Verbose output. Show file names. Call it --annotate. - Add a --test option to print a test page. - Add a --show-symbols op to print matching symbols. - 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. - 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 symbols/symbol aliases: - CP437. - Output to retro art formats: - RexPaint. - lvllvl.com. - CharPad/CharPad Pro. - Marq's PETSCII Editor (http://www.kameli.net/marq/?page_id=2717). - PabloDraw. - ANSILove (direct to .ANS, other formats?). - Others? - More image loaders: - BMP. The GIMP has a fairly complete decoder. - XPM. https://en.wikipedia.org/wiki/X_PixMap - PBM, PGM, PPM. https://en.wikipedia.org/wiki/Netpbm - Run image decoders (and Chafa backend?) in sandboxed subprocess. - "Auto" modes for ChafaCanvasMode and ChafaPixelMode. Major features -------------- - Selection from multiple internal named font bitmap sets (IBM, C64, etc). - Custom palettes. - External (e.g. in GIMP format, or extract from image) or named internal. - Using different palettes for BG and FG allows for retro modes like the C64's Extended Color Character Mode (ECM). - Pixmap export (raw image buffer from backend, PNG or TIFF export in frontend). - 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 ------------ - 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 $ Xvfb :99 -ac -fbdir t -screen 0 1440x900x24 & $ DISPLAY=:99 lament -root & $ 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 - - Compact listing of icons: $ montage -tile 6x -background none -geometry 64x64+1+1 /usr/share/icons/Mint-X/*/16/*.png - \ | chafa -s 80 chafa-1.14.5/acinclude.m4000066400000000000000000000073331471154763100150540ustar00rootroot00000000000000# 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], AS_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.14.5/autogen.sh000077500000000000000000000055551471154763100146700ustar00rootroot00000000000000#!/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 if test "$DIE" -eq 1; then exit 1 fi test $TEST_TYPE $FILE || { ${MY_ECHO} ${MY_ECHO} "You must run this script in the top-level $PROJECT directory." ${MY_ECHO} exit 1 } if test x$NOCONFIGURE = x && test -z "$*"; then ${MY_ECHO} ${MY_ECHO} "I am going to run ./configure with no arguments - if you wish " ${MY_ECHO} "to pass any to it, please specify them on the $0 command line." ${MY_ECHO} fi am_opt="--include-deps --add-missing" ${MY_ECHO} "Running libtoolize..." libtoolize --force --copy 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.14.5/chafa.pc.in000066400000000000000000000004271471154763100146530ustar00rootroot00000000000000prefix=@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 Libs.private: -lm Cflags: -I${includedir}/chafa -I${libdir}/chafa/include chafa-1.14.5/chafa/000077500000000000000000000000001471154763100137175ustar00rootroot00000000000000chafa-1.14.5/chafa/Makefile.am000066400000000000000000000037411471154763100157600ustar00rootroot00000000000000SUBDIRS = 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) -no-undefined -version-info 9:5:9 libchafa_la_LIBADD = $(GLIB_LIBS) internal/libchafa-internal.la -lm libchafa_la_SOURCES = \ chafa-canvas.c \ chafa-canvas-config.c \ chafa-features.c \ chafa-frame.c \ chafa-image.c \ chafa-placement.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-frame.h \ chafa-image.h \ chafa-placement.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) $(CPPFLAGS) -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.14.5/chafa/chafa-canvas-config.c000066400000000000000000000726311471154763100176520ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_16_8: 16 FG colors (8 of which enabled with bold/bright) and 8 BG colors. * @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. **/ /** * ChafaPassthrough: * @CHAFA_PASSTHROUGH_NONE: No passthrough guards will be used. * @CHAFA_PASSTHROUGH_SCREEN: Passthrough guards for GNU Screen will be used. * @CHAFA_PASSTHROUGH_TMUX: Passthrough guards for tmux will be used. * @CHAFA_PASSTHROUGH_MAX: Last supported passthrough mode plus one. **/ /* 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_canvas_config_get_passthrough: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaPassthrough setting. This defaults to * #CHAFA_PASSTHROUGH_NONE. * * Passthrough is needed to transmit certain escape codes to the outer terminal * when running in an inner terminal environment like tmux. When enabled, this * will happen automatically as needed, dictated by information contained in a * #ChafaTermInfo. * * Returns: The #ChafaPassthrough setting * * Since: 1.14 **/ ChafaPassthrough chafa_canvas_config_get_passthrough (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_PASSTHROUGH_NONE); g_return_val_if_fail (config->refs > 0, CHAFA_PASSTHROUGH_NONE); return config->passthrough; } /** * chafa_canvas_config_set_passthrough: * @config: A #ChafaCanvasConfig * @passthrough: A #ChafaPassthrough value * * Indicates which passthrough mode to use. This defaults to * #CHAFA_PASSTHROUGH_NONE. * * Passthrough is needed to transmit certain escape codes to the outer terminal * when running in an inner terminal environment like tmux. When enabled, this * will happen automatically as needed, dictated by information contained in a * #ChafaTermInfo. * * Since: 1.14 **/ void chafa_canvas_config_set_passthrough (ChafaCanvasConfig *config, ChafaPassthrough passthrough) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->passthrough = passthrough; } chafa-1.14.5/chafa/chafa-canvas-config.h000066400000000000000000000170731471154763100176560ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_INDEXED_16_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; /* Passthrough modes */ typedef enum { CHAFA_PASSTHROUGH_NONE, CHAFA_PASSTHROUGH_SCREEN, CHAFA_PASSTHROUGH_TMUX, CHAFA_PASSTHROUGH_MAX } ChafaPassthrough; /* 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); CHAFA_AVAILABLE_IN_1_14 ChafaPassthrough chafa_canvas_config_get_passthrough (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_14 void chafa_canvas_config_set_passthrough (ChafaCanvasConfig *config, ChafaPassthrough passthrough); G_END_DECLS #endif /* __CHAFA_CANVAS_CONFIG_H__ */ chafa-1.14.5/chafa/chafa-canvas.c000066400000000000000000002322721471154763100164060ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-batch.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 void eval_symbol_error (const ChafaWorkCell *wcell, const ChafaSymbol *sym, SymbolEval *eval, const ChafaPalette *fg_palette, const ChafaPalette *bg_palette, ChafaColorSpace color_space) { const guint8 *covp = (guint8 *) &sym->coverage [0]; ChafaColorPair pair; gint error; if (!bg_palette) bg_palette = fg_palette; if (!fg_palette) fg_palette = bg_palette; if (fg_palette) { pair.colors [CHAFA_COLOR_PAIR_FG] = *chafa_palette_get_color ( fg_palette, color_space, chafa_palette_lookup_nearest (fg_palette, color_space, &eval->colors.colors [CHAFA_COLOR_PAIR_FG], NULL)); pair.colors [CHAFA_COLOR_PAIR_BG] = *chafa_palette_get_color ( bg_palette, color_space, chafa_palette_lookup_nearest (bg_palette, color_space, &eval->colors.colors [CHAFA_COLOR_PAIR_BG], NULL)); } else { pair = eval->colors; } #ifdef HAVE_AVX2_INTRINSICS if (chafa_have_avx2 ()) error = calc_error_avx2 (wcell->pixels, &pair, sym->mask_u32); else #endif #ifdef HAVE_SSE41_INTRINSICS if (chafa_have_sse41 ()) error = calc_error_sse41 (wcell->pixels, &pair, covp); else #endif error = calc_error_plain (wcell->pixels, &pair, covp); eval->error = error; } static void eval_symbol_error_wide (const ChafaWorkCell *wcell_a, const ChafaWorkCell *wcell_b, const ChafaSymbol2 *sym, SymbolEval2 *wide_eval, const ChafaPalette *fg_palette, const ChafaPalette *bg_palette, ChafaColorSpace color_space) { SymbolEval eval [2]; eval [0].colors = wide_eval->colors; eval [1].colors = wide_eval->colors; eval_symbol_error (wcell_a, &sym->sym [0], &eval [0], fg_palette, bg_palette, color_space); eval_symbol_error (wcell_b, &sym->sym [1], &eval [1], fg_palette, bg_palette, color_space); 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.fg_only_enabled) { eval.colors = canvas->default_colors; } else { eval_symbol_colors (canvas, wcell, sym, &eval); } if (canvas->use_quantized_error) { eval_symbol_error (wcell, sym, &eval, &canvas->fg_palette, &canvas->bg_palette, canvas->config.color_space); } else { eval_symbol_error (wcell, sym, &eval, NULL, NULL, canvas->config.color_space); } 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.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); } if (canvas->use_quantized_error) { eval_symbol_error_wide (wcell_a, wcell_b, sym2, &eval, &canvas->fg_palette, &canvas->bg_palette, canvas->config.color_space); } else { eval_symbol_error_wide (wcell_a, wcell_b, sym2, &eval, NULL, NULL, canvas->config.color_space); } 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->extract_colors && 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->extract_colors && 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->extract_colors && !canvas->config.fg_only_enabled) { chafa_work_cell_get_contrasting_color_pair (wcell, &color_pair); } else { color_pair = canvas->default_colors; } 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->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->extract_colors && 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->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->extract_colors && 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 (ChafaPalette *palette, gint index, ChafaColorSpace cs) { return chafa_palette_get_color (palette, cs, index); } static const ChafaColor * get_palette_color (ChafaCanvas *canvas, ChafaPalette *palette, gint index) { return get_palette_color_with_color_space (palette, 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->fg_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->fg_palette, canvas->config.color_space, &mean, &ccand); } else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8) { ChafaColorCandidates ccand_bg; chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, &mean, &ccand); chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, &mean, &ccand_bg); if (ccand.index [0] != ccand_bg.index [0]) { if (ccand.index [1] == ccand_bg.index [0]) ccand.index [1] = ccand_bg.index [1]; ccand.index [0] = ccand_bg.index [0]; } } 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, &canvas->fg_palette, ccand.index [0]); col [1] = *get_palette_color (canvas, &canvas->fg_palette, 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_fast (&mean, &col [2]); 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->consider_inverted && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_INDEXED_16_8, &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 && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_INDEXED_16_8) { 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 void quantize_colors_for_cell_16_8 (ChafaCanvas *canvas, ChafaCanvasCell *cell, const ChafaColorPair *color_pair) { /* First pick both colors from FG palette to see if we should eliminate the FG/BG * distinction. This is necessary to prevent artifacts in solid color (fg-bg-fg-bg etc). */ /* TODO: Investigate if we could just force evaluation of the solid symbol instead. */ cell->fg_color = chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); cell->bg_color = chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); if (cell->fg_color == cell->bg_color && cell->fg_color >= 8 && cell->fg_color <= 15) { /* Chosen FG and BG colors should ideally be the same, but BG palette does not allow it. * Use the solid char with FG color if we have one, else fall back to using the closest * match from the BG palette for both FG and BG. */ if (canvas->solid_char) { cell->c = canvas->solid_char; cell->bg_color = chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); } else { cell->fg_color = cell->bg_color = chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); } } else { cell->bg_color = chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); } } static void update_cell_colors (ChafaCanvas *canvas, ChafaCanvasCell *cell_out, const ChafaColorPair *color_pair) { 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) { cell_out->fg_color = chafa_palette_lookup_nearest (&canvas->fg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_FG], NULL); cell_out->bg_color = chafa_palette_lookup_nearest (&canvas->bg_palette, canvas->config.color_space, &color_pair->colors [CHAFA_COLOR_PAIR_BG], NULL); } else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8) { quantize_colors_for_cell_16_8 (canvas, cell_out, color_pair); } 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); } static gint update_cell (ChafaCanvas *canvas, ChafaWorkCell *work_cell, ChafaCanvasCell *cell_out) { gunichar sym = 0; 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; update_cell_colors (canvas, cell_out, &color_pair); /* 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; 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; update_cell_colors (canvas, cell_a_out, &color_pair); cell_b_out->fg_color = cell_a_out->fg_color; cell_b_out->bg_color = cell_a_out->bg_color; /* quantize_colors_for_cell_16_8() can revert the char to solid, and * the solid char is always narrow. Extend it to both cells. */ if (cell_a_out->c == canvas->solid_char) cell_b_out->c = cell_a_out->c; } /* 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]; 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; /* We may use inverted colors when the foreground is transparent. * Some downstream tools don't handle this and will keep * modulating the wrong pen. In order to suppress long runs of * artifacts, make the (unused) foreground pen opaque (gh#108). */ if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) cells [cx].fg_color |= 0xff000000; else if (cells [cx].fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) cells [cx].fg_color = CHAFA_PALETTE_INDEX_FG; } } } } static void cell_build_worker (ChafaBatchInfo *batch, ChafaCanvas *canvas) { gint i; for (i = 0; i < batch->n_rows; i++) { update_cells_row (canvas, batch->first_row + i); } } static void update_cells (ChafaCanvas *canvas) { chafa_process_batches (canvas, (GFunc) cell_build_worker, NULL, /* _post */ canvas->config.height, chafa_get_n_actual_threads (), 1); } 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->extract_colors && canvas->config.fg_only_enabled) { 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 fg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; ChafaPaletteType bg_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; /* The repetition here kind of sucks, but it'll get better once the * palette refactoring is done and subtypes go away. */ switch (chafa_canvas_config_get_canvas_mode (&canvas->config)) { case CHAFA_CANVAS_MODE_TRUECOLOR: fg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; bg_pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; break; case CHAFA_CANVAS_MODE_INDEXED_256: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_256; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_256; break; case CHAFA_CANVAS_MODE_INDEXED_240: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_240; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_240; break; case CHAFA_CANVAS_MODE_INDEXED_16: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; break; case CHAFA_CANVAS_MODE_INDEXED_16_8: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_16; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; break; case CHAFA_CANVAS_MODE_INDEXED_8: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_8; break; case CHAFA_CANVAS_MODE_FGBG_BGFG: case CHAFA_CANVAS_MODE_FGBG: fg_pal_type = CHAFA_PALETTE_TYPE_FIXED_FGBG; bg_pal_type = CHAFA_PALETTE_TYPE_FIXED_FGBG; break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); } chafa_palette_init (&canvas->fg_palette, fg_pal_type); chafa_palette_set_color (&canvas->fg_palette, CHAFA_PALETTE_INDEX_FG, &fg_col); chafa_palette_set_color (&canvas->fg_palette, CHAFA_PALETTE_INDEX_BG, &bg_col); chafa_palette_set_alpha_threshold (&canvas->fg_palette, canvas->config.alpha_threshold); chafa_palette_set_transparent_index (&canvas->fg_palette, CHAFA_PALETTE_INDEX_TRANSPARENT); chafa_palette_init (&canvas->bg_palette, bg_pal_type); chafa_palette_set_color (&canvas->bg_palette, CHAFA_PALETTE_INDEX_FG, &fg_col); chafa_palette_set_color (&canvas->bg_palette, CHAFA_PALETTE_INDEX_BG, &bg_col); chafa_palette_set_alpha_threshold (&canvas->bg_palette, canvas->config.alpha_threshold); chafa_palette_set_transparent_index (&canvas->bg_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; } static gunichar find_best_solid_char (ChafaCanvas *canvas) { ChafaCandidate candidates [N_CANDIDATES_MAX]; gint n_candidates; gunichar best_char = 0; /* Try solid block (0x2588) first */ if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, 0x2588) || chafa_symbol_map_has_symbol (&canvas->config.fill_symbol_map, 0x2588)) return 0x2588; n_candidates = N_CANDIDATES_MAX; chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, 64, FALSE, candidates, &n_candidates); if (n_candidates > 0 && candidates [0].hamming_distance <= 32) { 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, 0xffffffffffffffff, FALSE, candidates, &n_candidates); if (n_candidates > 0 && candidates [0].hamming_distance <= 32) { best_char = canvas->config.symbol_map.symbols [candidates [0].symbol_index].c; } } return best_char; } 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; } } static void draw_all_pixels (ChafaCanvas *canvas, ChafaPixelType src_pixel_type, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride) { ChafaAlign halign = CHAFA_ALIGN_START, valign = CHAFA_ALIGN_START; ChafaTuck tuck = CHAFA_TUCK_STRETCH; if (src_width == 0 || src_height == 0) return; if (canvas->placement) { halign = chafa_placement_get_halign (canvas->placement); valign = chafa_placement_get_valign (canvas->placement); tuck = chafa_placement_get_tuck (canvas->placement); } 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 */ /* FIXME: The allocation can fail if the canvas is ridiculously large. * Since there's no way to report an error from here, we'll silently * skip the update instead. * * We really shouldn't need this much temporary memory in the first place; * it'd be possible to process the image in cell_height strips and hand * each strip off to the update_cells() pass independently. The pipelining * would improve throughput too. */ canvas->pixels = g_try_new (ChafaPixel, (gsize) canvas->width_pixels * canvas->height_pixels); if (canvas->pixels) { chafa_prepare_pixel_data_for_symbols (&canvas->fg_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 0 g_warning ("ChafaCanvas: Out of memory allocating %ux%u pixels.", canvas->width_pixels, canvas->height_pixels); #endif } } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) { /* Sixel mode */ canvas->fg_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->fg_palette, &canvas->dither); chafa_sixel_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride, halign, valign, tuck); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY) { ChafaColor bg_color; chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_color); bg_color.ch [3] = canvas->config.alpha_threshold < 1 ? 0x00 : 0xff; /* Kitty mode */ canvas->fg_palette.alpha_threshold = canvas->config.alpha_threshold; canvas->pixel_canvas = chafa_kitty_canvas_new (canvas->width_pixels, canvas->height_pixels); if (canvas->pixel_canvas) chafa_kitty_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride, bg_color, halign, valign, tuck); } else /* if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) */ { /* iTerm2 mode */ canvas->fg_palette.alpha_threshold = canvas->config.alpha_threshold; canvas->pixel_canvas = chafa_iterm2_canvas_new (canvas->width_pixels, canvas->height_pixels); if (canvas->pixel_canvas) chafa_iterm2_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride, halign, valign, tuck); } } /** * 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.5f; canvas->needs_clear = TRUE; canvas->have_alpha = FALSE; canvas->placement = NULL; canvas->consider_inverted = !(canvas->config.fg_only_enabled || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG); canvas->extract_colors = !(canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG); if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) canvas->config.fg_only_enabled = TRUE; canvas->use_quantized_error = (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16_8 && !canvas->config.fg_only_enabled); 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); canvas->solid_char = find_best_solid_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: case CHAFA_CANVAS_MODE_INDEXED_16_8: 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); canvas->placement = NULL; 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); } /** * 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)) { if (canvas->placement) chafa_placement_unref (canvas->placement); chafa_canvas_config_deinit (&canvas->config); destroy_pixel_canvas (canvas); chafa_dither_deinit (&canvas->dither); chafa_palette_deinit (&canvas->fg_palette); chafa_palette_deinit (&canvas->bg_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_set_placement: * @canvas: Canvas to place the placement on * @placement: Placement to place * * Places @placement on @canvas, replacing the latter's content. The placement * will cover the entire canvas. * * The canvas will keep a reference to the placement until it is replaced or the * canvas itself is freed. * * Since: 1.14 */ void chafa_canvas_set_placement (ChafaCanvas *canvas, ChafaPlacement *placement) { ChafaImage *image; ChafaFrame *frame; g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); chafa_placement_ref (placement); if (canvas->placement) chafa_placement_unref (canvas->placement); canvas->placement = placement; image = placement->image; g_assert (image != NULL); frame = image->frame; if (!frame) return; draw_all_pixels (canvas, frame->pixel_type, frame->data, frame->width, frame->height, frame->rowstride); } /** * 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); draw_all_pixels (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) { 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 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) && canvas->pixel_canvas) { /* Sixel mode */ str = g_string_new (""); chafa_sixel_canvas_build_ansi (canvas->pixel_canvas, term_info, str, canvas->config.passthrough); } 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) && canvas->pixel_canvas) { /* Kitty mode */ str = g_string_new (""); chafa_kitty_canvas_build_ansi (canvas->pixel_canvas, term_info, str, canvas->config.width, canvas->config.height, canvas->placement ? canvas->placement->id : -1, canvas->config.passthrough); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2 && canvas->pixel_canvas) { /* 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_print_rows: * @canvas: The canvas to generate a printable representation of * @term_info: Terminal to format for, or %NULL for fallback * @array_out: Pointer to storage for resulting array pointer * @array_len_out: Pointer to storage for array's element count, or %NULL * * Builds an array of UTF-8 strings made up of terminal control sequences * and symbols representing the canvas' current contents. These can 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. * * The array will be %NULL-terminated. The element count does not include * the terminator. * * When the canvas' pixel mode is %CHAFA_PIXEL_MODE_SYMBOLS, each element * will hold the contents of exactly one symbol row. There will be no row * separators, newlines or control sequences to reposition the cursor between * rows. Row positioning is left to the caller. * * In other pixel modes, there may be one or more strings, but the splitting * criteria should not be relied on. They must be printed in sequence, exactly * as they appear. * * Since: 1.14 **/ void chafa_canvas_print_rows (ChafaCanvas *canvas, ChafaTermInfo *term_info, GString ***array_out, gint *array_len_out) { g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (array_out != 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); chafa_canvas_print_symbol_rows (canvas, term_info, array_out, array_len_out); } else { GString *gs = chafa_canvas_print (canvas, term_info); *array_out = g_new (GString *, 2); (*array_out) [0] = gs; (*array_out) [1] = NULL; if (array_len_out) *array_len_out = 1; } } /** * chafa_canvas_print_rows_strv: * @canvas: The canvas to generate a printable representation of * @term_info: Terminal to format for, or %NULL for fallback * * Builds an array of UTF-8 strings made up of terminal control sequences * and symbols representing the canvas' current contents. These can 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. * * The array will be %NULL-terminated and can be freed with g_strfreev(). * * When the canvas' pixel mode is %CHAFA_PIXEL_MODE_SYMBOLS, each element * will hold the contents of exactly one symbol row. There will be no row * separators, newlines or control sequences to reposition the cursor between * rows. Row positioning is left to the caller. * * In other pixel modes, there may be one or more strings, but the splitting * criteria should not be relied on. They must be printed in sequence, exactly * as they appear. * * Returns: A %NULL-terminated array of string pointers * * Since: 1.14 **/ gchar ** chafa_canvas_print_rows_strv (ChafaCanvas *canvas, ChafaTermInfo *term_info) { GString **gsa; gint gsa_len; gchar **strv; gint i; g_return_val_if_fail (canvas != NULL, NULL); g_return_val_if_fail (canvas->refs > 0, NULL); chafa_canvas_print_rows (canvas, term_info, &gsa, &gsa_len); strv = g_new (gchar *, gsa_len + 1); for (i = 0; i < gsa_len; i++) { /* FIXME: Switch to g_string_free_and_steal() in 2025 */ strv [i] = g_string_free (gsa [i], FALSE); } strv [gsa_len] = NULL; g_free (gsa); return strv; } /** * 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 cell to the right of it will automatically be set to 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_16_8: 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->fg_palette, 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->bg_palette, 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_16_8: case CHAFA_CANVAS_MODE_INDEXED_8: cell->fg_color = packed_rgb_to_index (&canvas->fg_palette, canvas->config.color_space, fg); cell->bg_color = packed_rgb_to_index (&canvas->bg_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_16_8: 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_16_8: 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.14.5/chafa/chafa-canvas.h000066400000000000000000000065411471154763100164110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_14 void chafa_canvas_set_placement (ChafaCanvas *canvas, ChafaPlacement *placement); 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_14 void chafa_canvas_print_rows (ChafaCanvas *canvas, ChafaTermInfo *term_info, GString ***array_out, gint *array_len_out); CHAFA_AVAILABLE_IN_1_14 gchar **chafa_canvas_print_rows_strv (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.14.5/chafa/chafa-common.h000066400000000000000000000070241471154763100164230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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; /** * ChafaAlign: * @CHAFA_ALIGN_START: Align flush with beginning of the area (top or left in LTR locales). * @CHAFA_ALIGN_END: Align flush with end of the area (bottom or right in LTR locales). * @CHAFA_ALIGN_CENTER: Align in the middle of the area. * @CHAFA_ALIGN_MAX: Last supported alignment, plus one. * * Alignment options when placing an element within an area. * * Since: 1.14 **/ typedef enum { CHAFA_ALIGN_START, CHAFA_ALIGN_END, CHAFA_ALIGN_CENTER, CHAFA_ALIGN_MAX } ChafaAlign; /** * ChafaTuck: * @CHAFA_TUCK_STRETCH: Resize element to fit the area exactly, changing its aspect ratio. * @CHAFA_TUCK_FIT: Resize element to fit the area, preserving its aspect ratio by adding padding. * @CHAFA_TUCK_SHRINK_TO_FIT: Like @CHAFA_TUCK_FIT, but prohibit enlargement. * @CHAFA_TUCK_MAX: Last supported tucking policy, plus one. * * Resizing options when placing an element within an area. Usually used in conjunction * with #ChafaAlign to control the padding. * * Since: 1.14 **/ typedef enum { CHAFA_TUCK_STRETCH, CHAFA_TUCK_FIT, CHAFA_TUCK_SHRINK_TO_FIT, CHAFA_TUCK_MAX } ChafaTuck; G_END_DECLS #endif /* __CHAFA_COMMON_H__ */ chafa-1.14.5/chafa/chafa-features.c000066400000000000000000000135571471154763100167540ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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. * @CHAFA_FEATURE_AVX2: Flag indicating AVX2 support. **/ static gboolean chafa_initialized; static gboolean have_mmx; static gboolean have_sse41; static gboolean have_popcnt; static gboolean have_avx2; static gint n_threads = -1; 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 # ifdef HAVE_AVX2_INTRINSICS if (__builtin_cpu_supports ("avx2")) have_avx2 = 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; } gboolean chafa_have_avx2 (void) { return have_avx2; } /* 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 #ifdef HAVE_AVX2_INTRINSICS features |= CHAFA_FEATURE_AVX2; #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) | (have_avx2 ? CHAFA_FEATURE_AVX2 : 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 & CHAFA_FEATURE_AVX2) g_string_append (features_gstr, "avx2 "); 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_get_n_threads: * * Queries the maximum number of worker threads to use for parallel processing. * * Returns: The number of threads, or -1 if determined automatically **/ gint chafa_get_n_threads (void) { return g_atomic_int_get (&n_threads); } /** * chafa_set_n_threads: * @n: Number of threads * * Sets the maximum number of worker threads to use for parallel processing, * or -1 to determine this automatically. The default is -1. * * Setting this to 0 or 1 will avoid using thread pools and instead perform * all processing in the main thread. **/ void chafa_set_n_threads (gint n) { g_return_if_fail (n >= -1); return g_atomic_int_set (&n_threads, n); } /** * chafa_get_n_actual_threads: * * Queries the number of worker threads that will actually be used for * parallel processing. * * Returns: Number of threads, always >= 1 **/ gint chafa_get_n_actual_threads (void) { gint n_threads; n_threads = chafa_get_n_threads (); if (n_threads < 0) n_threads = g_get_num_processors (); if (n_threads <= 0) n_threads = 1; return n_threads; } chafa-1.14.5/chafa/chafa-features.h000066400000000000000000000033021471154763100167440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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), CHAFA_FEATURE_AVX2 = (1 << 3) } 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); CHAFA_AVAILABLE_IN_1_10 gint chafa_get_n_threads (void); CHAFA_AVAILABLE_IN_1_10 void chafa_set_n_threads (gint n); CHAFA_AVAILABLE_IN_1_10 gint chafa_get_n_actual_threads (void); G_END_DECLS #endif /* __CHAFA_FEATURES_H__ */ chafa-1.14.5/chafa/chafa-frame.c000066400000000000000000000120671471154763100162230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-frame * @title: ChafaFrame * @short_description: A raster image frame that can be added to a ChafaImage * * A #ChafaFrame contains the specific of a single frame of image data. It can * be added to a #ChafaImage. **/ static ChafaFrame * new_frame (gpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride, gboolean data_is_owned) { ChafaFrame *frame; frame = g_new0 (ChafaFrame, 1); frame->refs = 1; frame->pixel_type = pixel_type; frame->width = width; frame->height = height; frame->rowstride = rowstride; frame->data = data; frame->data_is_owned = data_is_owned; return frame; } /** * chafa_frame_new: * @data: Pointer to an image data buffer to copy from * @pixel_type: The #ChafaPixelType of the source data * @width: Width of the image, in pixels * @height: Height of the image, in pixels * @rowstride: Number of bytes to advance from the start of one row to the next * * Creates a new #ChafaFrame containing a copy of the image data pointed to * by @data. * * Returns: The new frame * * Since: 1.14 **/ ChafaFrame * chafa_frame_new (gconstpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride) { gpointer owned_data; owned_data = g_malloc (height * rowstride); memcpy (owned_data, data, height * rowstride); return new_frame (owned_data, pixel_type, width, height, rowstride, TRUE); } /** * chafa_frame_new_steal: * @data: Pointer to an image data buffer to assign * @pixel_type: The #ChafaPixelType of the buffer * @width: Width of the image, in pixels * @height: Height of the image, in pixels * @rowstride: Number of bytes to advance from the start of one row to the next * * Creates a new #ChafaFrame, which takes ownership of the @data buffer. The * buffer will be freed with #g_free() when the frame's reference count drops * to zero. * * Returns: The new frame * * Since: 1.14 **/ ChafaFrame * chafa_frame_new_steal (gpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride) { return new_frame (data, pixel_type, width, height, rowstride, TRUE); } /** * chafa_frame_new_borrow: * @data: Pointer to an image data buffer to assign * @pixel_type: The #ChafaPixelType of the buffer * @width: Width of the image, in pixels * @height: Height of the image, in pixels * @rowstride: Number of bytes to advance from the start of one row to the next * * Creates a new #ChafaFrame embedding the @data pointer. It's the caller's * responsibility to ensure the pointer remains valid for the lifetime of * the frame. The frame will not free the buffer when its reference count drops * to zero. * * THIS IS DANGEROUS API which should only be used when the life cycle of the * frame is short, stealing the buffer is impossible, and copying would cause * unacceptable performance degradation. * * Use #chafa_frame_new() instead. * * Returns: The new frame * * Since: 1.14 **/ ChafaFrame * chafa_frame_new_borrow (gpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride) { return new_frame (data, pixel_type, width, height, rowstride, FALSE); } /** * chafa_frame_ref: * @frame: Frame to add a reference to * * Adds a reference to @frame. * * Since: 1.14 **/ void chafa_frame_ref (ChafaFrame *frame) { gint refs; g_return_if_fail (frame != NULL); refs = g_atomic_int_get (&frame->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&frame->refs); } /** * chafa_frame_unref: * @frame: Frame to remove a reference from * * Removes a reference from @frame. When the reference count drops to zero, * the frame is freed and can no longer be used. * * Since: 1.14 **/ void chafa_frame_unref (ChafaFrame *frame) { gint refs; g_return_if_fail (frame != NULL); refs = g_atomic_int_get (&frame->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&frame->refs)) { if (frame->data_is_owned) g_free (frame->data); g_free (frame); } } chafa-1.14.5/chafa/chafa-frame.h000066400000000000000000000035501471154763100162250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_FRAME_H__ #define __CHAFA_FRAME_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS typedef struct ChafaFrame ChafaFrame; CHAFA_AVAILABLE_IN_1_14 ChafaFrame *chafa_frame_new (gconstpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride); CHAFA_AVAILABLE_IN_1_14 ChafaFrame *chafa_frame_new_steal (gpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride); CHAFA_AVAILABLE_IN_1_14 ChafaFrame *chafa_frame_new_borrow (gpointer data, ChafaPixelType pixel_type, gint width, gint height, gint rowstride); CHAFA_AVAILABLE_IN_1_14 void chafa_frame_ref (ChafaFrame *frame); CHAFA_AVAILABLE_IN_1_14 void chafa_frame_unref (ChafaFrame *frame); G_END_DECLS #endif /* __CHAFA_FRAME_H__ */ chafa-1.14.5/chafa/chafa-image.c000066400000000000000000000055731471154763100162170ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-image * @title: ChafaImage * @short_description: An image to be placed on a #ChafaCanvas * * A #ChafaImage represents a raster image for placement on a #ChafaCanvas. It * can currently hold a single #ChafaFrame. * * To place an image on a canvas, it must first be assigned to a #ChafaPlacement. **/ /** * chafa_image_new: * * Creates a new #ChafaImage. The image is initially transparent and dimensionless. * * Returns: The new image * * Since: 1.14 **/ ChafaImage * chafa_image_new (void) { ChafaImage *image; image = g_new0 (ChafaImage, 1); image->refs = 1; return image; } /** * chafa_image_ref: * @image: Image to add a reference to * * Adds a reference to @image. * * Since: 1.14 **/ void chafa_image_ref (ChafaImage *image) { gint refs; g_return_if_fail (image != NULL); refs = g_atomic_int_get (&image->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&image->refs); } /** * chafa_image_unref: * @image: Image to remove a reference from * * Removes a reference from @image. When the reference count drops to zero, * the image is freed and can no longer be used. * * Since: 1.14 **/ void chafa_image_unref (ChafaImage *image) { gint refs; g_return_if_fail (image != NULL); refs = g_atomic_int_get (&image->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&image->refs)) { if (image->frame) chafa_frame_unref (image->frame); g_free (image); } } /** * chafa_image_set_frame: * @image: Image to assign frame to * @frame: Frame to be assigned to image * * Assigns @frame as the content for @image. The image will keep its own * reference to the frame. * * Since: 1.14 **/ void chafa_image_set_frame (ChafaImage *image, ChafaFrame *frame) { g_return_if_fail (image != NULL); if (frame) chafa_frame_ref (frame); if (image->frame) chafa_frame_unref (image->frame); image->frame = frame; } chafa-1.14.5/chafa/chafa-image.h000066400000000000000000000026121471154763100162130ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_IMAGE_H__ #define __CHAFA_IMAGE_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS typedef struct ChafaImage ChafaImage; CHAFA_AVAILABLE_IN_1_14 ChafaImage *chafa_image_new (void); CHAFA_AVAILABLE_IN_1_14 void chafa_image_ref (ChafaImage *image); CHAFA_AVAILABLE_IN_1_14 void chafa_image_unref (ChafaImage *image); CHAFA_AVAILABLE_IN_1_14 void chafa_image_set_frame (ChafaImage *image, ChafaFrame *frame); G_END_DECLS #endif /* __CHAFA_IMAGE_H__ */ chafa-1.14.5/chafa/chafa-placement.c000066400000000000000000000127641471154763100171050ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-placement * @title: ChafaPlacement * @short_description: Placement of a #ChafaImage on a #ChafaCanvas * * A #ChafaPlacement describes how an image is placed on a #ChafaCanvas. It * contains information about the image, its alignment and tucking policy. **/ /** * chafa_placement_new: * @image: The image to place on the canvas * @id: An ID to assign to the placement, or <= 0 to assign one automatically * * Creates a new #ChafaPlacement. * * Returns: The new placement * * Since: 1.14 **/ ChafaPlacement * chafa_placement_new (ChafaImage *image, gint id) { ChafaPlacement *placement; g_return_val_if_fail (image != NULL, NULL); if (id < 1) id = -1; placement = g_new0 (ChafaPlacement, 1); placement->refs = 1; chafa_image_ref (image); placement->image = image; placement->id = id; placement->halign = CHAFA_ALIGN_START; placement->valign = CHAFA_ALIGN_START; placement->tuck = CHAFA_TUCK_STRETCH; return placement; } /** * chafa_placement_ref: * @placement: Placement to add a reference to * * Adds a reference to @placement. * * Since: 1.14 **/ void chafa_placement_ref (ChafaPlacement *placement) { gint refs; g_return_if_fail (placement != NULL); refs = g_atomic_int_get (&placement->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&placement->refs); } /** * chafa_placement_unref: * @placement: Placement to remove a reference from * * Removes a reference from @placement. When remaining references drops to * zero, the placement is freed and can no longer be used. * * Since: 1.14 **/ void chafa_placement_unref (ChafaPlacement *placement) { gint refs; g_return_if_fail (placement != NULL); refs = g_atomic_int_get (&placement->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&placement->refs)) { chafa_image_unref (placement->image); g_free (placement); } } /** * chafa_placement_get_tuck: * @placement: A #ChafaPlacement * * Gets the tucking policy of @placement. This describes how the image is * resized to fit @placement's extents, and defaults to #CHAFA_TUCK_STRETCH. * * Returns: The tucking policy of @placement * * Since: 1.14 **/ ChafaTuck chafa_placement_get_tuck (ChafaPlacement *placement) { g_return_val_if_fail (placement != NULL, CHAFA_TUCK_FIT); return placement->tuck; } /** * chafa_placement_set_tuck: * @placement: A #ChafaPlacement * @tuck: Desired tucking policy * * Sets the tucking policy for @placement to @tuck. This describes how the image is * resized to fit @placement's extents, and defaults to #CHAFA_TUCK_STRETCH. * * Since: 1.14 **/ void chafa_placement_set_tuck (ChafaPlacement *placement, ChafaTuck tuck) { g_return_if_fail (placement != NULL); placement->tuck = tuck; } /** * chafa_placement_get_halign: * @placement: A #ChafaPlacement * * Gets the horizontal alignment of @placement. This determines how any * padding added by the tucking policy is distributed, and defaults to * #CHAFA_ALIGN_START. * * Returns: The horizontal alignment of @placement * * Since: 1.14 **/ ChafaAlign chafa_placement_get_halign (ChafaPlacement *placement) { g_return_val_if_fail (placement != NULL, CHAFA_ALIGN_START); return placement->halign; } /** * chafa_placement_set_halign: * @placement: A #ChafaPlacement * @align: Desired horizontal alignment * * Sets the horizontal alignment of @placement. This determines how any * padding added by the tucking policy is distributed, and defaults to * #CHAFA_ALIGN_START. * * Since: 1.14 **/ void chafa_placement_set_halign (ChafaPlacement *placement, ChafaAlign align) { g_return_if_fail (placement != NULL); placement->halign = align; } /** * chafa_placement_get_valign: * @placement: A #ChafaPlacement * * Gets the vertical alignment of @placement. This determines how any * padding added by the tucking policy is distributed, and defaults to * #CHAFA_ALIGN_START. * * Returns: The vertical alignment of @placement * * Since: 1.14 **/ ChafaAlign chafa_placement_get_valign (ChafaPlacement *placement) { g_return_val_if_fail (placement != NULL, CHAFA_ALIGN_START); return placement->valign; } /** * chafa_placement_set_valign: * @placement: A #ChafaPlacement * @align: Desired vertical alignment * * Sets the vertical alignment of @placement. This determines how any * padding added by the tucking policy is distributed. * * Since: 1.14 **/ void chafa_placement_set_valign (ChafaPlacement *placement, ChafaAlign align) { g_return_if_fail (placement != NULL); placement->valign = align; } chafa-1.14.5/chafa/chafa-placement.h000066400000000000000000000036721471154763100171100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_PLACEMENT_H__ #define __CHAFA_PLACEMENT_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS typedef struct ChafaPlacement ChafaPlacement; CHAFA_AVAILABLE_IN_1_14 ChafaPlacement *chafa_placement_new (ChafaImage *image, gint id); CHAFA_AVAILABLE_IN_1_14 void chafa_placement_ref (ChafaPlacement *placement); CHAFA_AVAILABLE_IN_1_14 void chafa_placement_unref (ChafaPlacement *placement); CHAFA_AVAILABLE_IN_1_14 ChafaTuck chafa_placement_get_tuck (ChafaPlacement *placement); CHAFA_AVAILABLE_IN_1_14 void chafa_placement_set_tuck (ChafaPlacement *placement, ChafaTuck tuck); CHAFA_AVAILABLE_IN_1_14 ChafaAlign chafa_placement_get_halign (ChafaPlacement *placement); CHAFA_AVAILABLE_IN_1_14 void chafa_placement_set_halign (ChafaPlacement *placement, ChafaAlign align); CHAFA_AVAILABLE_IN_1_14 ChafaAlign chafa_placement_get_valign (ChafaPlacement *placement); CHAFA_AVAILABLE_IN_1_14 void chafa_placement_set_valign (ChafaPlacement *placement, ChafaAlign align); G_END_DECLS #endif /* __CHAFA_PLACEMENT_H__ */ chafa-1.14.5/chafa/chafa-symbol-map.c000066400000000000000000001572621471154763100172200ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" /* 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_LATIN: Latin and Latin-like symbols (superset of ASCII). * @CHAFA_SYMBOL_TAG_IMPORTED: Symbols for which glyphs were imported with chafa_symbol_map_add_glyph (). * @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; } static void bitmap_to_argb (guint64 bitmap, guint8 *argb, gint rowstride) { guint8 *p; gint x, y; for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) { p = argb + y * rowstride; for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) { guint32 *p32 = (guint32 *) p; /* Set = 0xffffffff, clear = 0x00000000 */ *p32 = (-(guint32) ((bitmap >> 63) & 1)); p += 4; bitmap <<= 1; } } } static gpointer bitmap_to_argb_alloc (guint64 bitmap) { gpointer argb; argb = g_malloc (CHAFA_SYMBOL_N_PIXELS * 4); bitmap_to_argb (bitmap, argb, CHAFA_SYMBOL_WIDTH_PIXELS * 4); return argb; } static gpointer bitmap2_to_argb_alloc (guint64 bitmap_0, guint64 bitmap_1) { gpointer argb; argb = g_malloc (CHAFA_SYMBOL_N_PIXELS * 4 * 2); bitmap_to_argb (bitmap_0, argb, CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); bitmap_to_argb (bitmap_1, ((guint8 *) argb) + CHAFA_SYMBOL_WIDTH_PIXELS * 4, CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); return argb; } 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 (pixels, (SmolPixelType) pixel_format, width, height, rowstride, (gpointer) scaled_pixels, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* FIXME: Premul */ CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 4, SMOL_NO_FLAGS); /* 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); 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 (pixels, (SmolPixelType) pixel_format, width, height, rowstride, (gpointer) scaled_pixels, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* FIXME: Premul */ CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2, SMOL_NO_FLAGS); /* 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); *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 [i].mask_u32); } 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); symbol_map->symbols [i].mask_u32 = bitmap_to_argb_alloc (symbol_map->symbols [i].bitmap); 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 [0].mask_u32); g_free (symbol_map->symbols2 [i].sym [1].coverage); g_free (symbol_map->symbols2 [i].sym [1].mask_u32); } 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 [0].mask_u32 = bitmap_to_argb_alloc (symbol_map->symbols2 [i].sym [0].bitmap); symbol_map->symbols2 [i].sym [1].coverage = g_memdup (symbol_map->symbols2 [i].sym [1].coverage, CHAFA_SYMBOL_N_PIXELS); symbol_map->symbols2 [i].sym [1].mask_u32 = bitmap_to_argb_alloc (symbol_map->symbols2 [i].sym [1].bitmap); 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->mask_u32); 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 [0].mask_u32); g_free (sym->sym [1].coverage); g_free (sym->sym [1].mask_u32); 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) | CHAFA_SYMBOL_TAG_IMPORTED; 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) | CHAFA_SYMBOL_TAG_IMPORTED; 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 (GArray *selectors, ChafaSymbolTags tags) { Selector s = { 0 }; s.selector_type = SELECTOR_TAG; s.additive = TRUE; s.tags = tags; g_array_append_val (selectors, s); } static void remove_by_tags (GArray *selectors, ChafaSymbolTags tags) { Selector s = { 0 }; s.selector_type = SELECTOR_TAG; s.additive = FALSE; s.tags = tags; g_array_append_val (selectors, s); } static void add_by_range (GArray *selectors, 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 (selectors, s); } static void remove_by_range (GArray *selectors, 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 (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 }, { "latin", CHAFA_SYMBOL_TAG_LATIN }, { "import", CHAFA_SYMBOL_TAG_IMPORTED }, { "imported", CHAFA_SYMBOL_TAG_IMPORTED }, { 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, do_clear = FALSE; gboolean result = FALSE; GArray *selector_array = g_array_new (FALSE, FALSE, sizeof (Selector)); 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; if (!is_add && !is_remove) { do_clear = TRUE; is_add = TRUE; } if (*p0 == '[') { gboolean escape = FALSE; p0++; for ( ; p0 && *p0; p0 = g_utf8_next_char (p0)) { gunichar c = g_utf8_get_char (p0); if (c == '\\' && !escape) { escape = TRUE; continue; } if (c == ']' && !escape) break; if (is_add) add_by_range (selector_array, c, c); else if (is_remove) remove_by_range (selector_array, c, c); escape = FALSE; } if (!p0 || *p0 != ']') { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Syntax error in symbol selector set."); goto out; } n = 1; } else if (!(n = strspn (p0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."))) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Syntax error in symbol tag selectors."); goto out; } else if (!parse_symbol_tag (p0, n, &sel_type, &sc, &first, &last, error)) { goto out; } else { if (sel_type == SELECTOR_TAG) { if (is_add) add_by_tags (selector_array, sc); else if (is_remove) remove_by_tags (selector_array, sc); } else { if (is_add) add_by_range (selector_array, first, last); else if (is_remove) remove_by_range (selector_array, first, last); } } p0 += n; } if (do_clear) g_array_set_size (symbol_map->selectors, 0); g_array_append_vals (symbol_map->selectors, selector_array->data, selector_array->len); symbol_map->need_rebuild = TRUE; result = TRUE; out: g_array_free (selector_array, TRUE); 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_free (symbol_map->symbols [i].mask_u32); } 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 [0].mask_u32); g_free (symbol_map->symbols2 [i].sym [1].coverage); g_free (symbol_map->symbols2 [i].sym [1].mask_u32); } 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->n_symbols = 0; dest->symbols2 = NULL; dest->n_symbols2 = 0; dest->packed_bitmaps = NULL; dest->packed_bitmaps2 = NULL; dest->need_rebuild = TRUE; dest->refs = 1; if (!src->need_rebuild) chafa_symbol_map_prepare (dest); } 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_new (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)); g_free (ham_dist); } 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_new (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)); g_free (ham_dist); } /* 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->selectors, 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->selectors, 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->selectors, 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->selectors, 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; if (g_hash_table_size (symbol_map->glyphs2) >= G_MAXINT - 1) return; 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; if (g_hash_table_size (symbol_map->glyphs) >= G_MAXINT - 1) return; 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_symbol_map_get_glyph: * @symbol_map: A symbol map * @code_point: A Unicode code point * @pixel_format: Desired pixel format of @pixels_out * @pixels_out: Storage for a pointer to exported glyph data * @width_out: Storage for width of glyph, in pixels * @height_out: Storage for height of glyph, in pixels * @rowstride_out: Storage for offset from start of one row to the next, in bytes * * Returns data for the glyph corresponding to @code_point stored in @symbol_map. * Any of @pixels_out, @width_out, @height_out and @rowstride_out can be NULL, * in which case the corresponding data is not retrieved. * * If @pixels_out is not NULL, a pointer to freshly allocated memory containing * height * rowstride bytes in the pixel format specified by @pixel_format * will be stored at this address. It must be freed using g_free() when you're * done with it. * * Monochrome glyphs (the only kind currently supported) will be rendered as * opaque white on a transparent black background (0xffffffff for inked * pixels and 0x00000000 for uninked). * * Since: 1.12 **/ gboolean chafa_symbol_map_get_glyph (ChafaSymbolMap *symbol_map, gunichar code_point, ChafaPixelType pixel_format, gpointer *pixels_out, gint *width_out, gint *height_out, gint *rowstride_out) { gboolean success = FALSE; gint width, height, rowstride; g_return_val_if_fail (symbol_map != NULL, FALSE); if (g_unichar_iswide (code_point)) { Glyph2 *glyph2; glyph2 = g_hash_table_lookup (symbol_map->glyphs2, GUINT_TO_POINTER (code_point)); if (!glyph2) goto out; g_assert (glyph2->c == code_point); if (pixels_out) *pixels_out = bitmap2_to_argb_alloc (glyph2->bitmap [0], glyph2->bitmap [1]); width = CHAFA_SYMBOL_WIDTH_PIXELS * 2; height = CHAFA_SYMBOL_HEIGHT_PIXELS; rowstride = CHAFA_SYMBOL_WIDTH_PIXELS * 2 * 4; } else { Glyph *glyph; glyph = g_hash_table_lookup (symbol_map->glyphs, GUINT_TO_POINTER (code_point)); if (!glyph) goto out; g_assert (glyph->c == code_point); if (pixels_out) *pixels_out = bitmap_to_argb_alloc (glyph->bitmap); width = CHAFA_SYMBOL_WIDTH_PIXELS; height = CHAFA_SYMBOL_HEIGHT_PIXELS; rowstride = CHAFA_SYMBOL_WIDTH_PIXELS * 4; } if (width_out) *width_out = width; if (height_out) *height_out = height; if (rowstride_out) *rowstride_out = rowstride; if (pixels_out && pixel_format != CHAFA_PIXEL_ARGB8_PREMULTIPLIED) { gpointer temp_pixels = g_malloc (width * CHAFA_SYMBOL_HEIGHT_PIXELS * 4); /* Convert to desired pixel format */ smol_scale_simple (*pixels_out, SMOL_PIXEL_ARGB8_UNASSOCIATED, /* FIXME: Premul */ width, height, rowstride, temp_pixels, (SmolPixelType) pixel_format, width, height, rowstride, SMOL_NO_FLAGS); g_free (*pixels_out); *pixels_out = temp_pixels; } success = TRUE; out: return success; } chafa-1.14.5/chafa/chafa-symbol-map.h000066400000000000000000000116411471154763100172130ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_LATIN = (1 << 24), CHAFA_SYMBOL_TAG_IMPORTED = (1 << 25), 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); CHAFA_AVAILABLE_IN_1_12 gboolean chafa_symbol_map_get_glyph (ChafaSymbolMap *symbol_map, gunichar code_point, ChafaPixelType pixel_format, gpointer *pixels_out, gint *width_out, gint *height_out, gint *rowstride_out); G_END_DECLS #endif /* __CHAFA_SYMBOL_MAP_H__ */ chafa-1.14.5/chafa/chafa-term-db.c000066400000000000000000000604211471154763100164600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 /* strtoul */ #include /* strlen, strncmp, strcmp, memcpy */ /** * 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; const 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_ENABLE_BOLD, "\033[1m" }, { 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, "\033M" }, { CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL, "\033D" }, { 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_RESET_SCROLLING_ROWS, "\033[r" }, { CHAFA_TERM_SEQ_SAVE_CURSOR_POS, "\033[s" }, { CHAFA_TERM_SEQ_RESTORE_CURSOR_POS, "\033[u" }, /* These are actually xterm seqs, but we'll allow it */ { CHAFA_TERM_SEQ_ENABLE_ALT_SCREEN, "\033[1049h" }, { CHAFA_TERM_SEQ_DISABLE_ALT_SCREEN, "\033[1049l" }, { 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_ENABLE_SIXEL_SCROLLING, "\033[?80l" }, { CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING, "\033[?80h" }, { CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_DOWN, "\033[?8452l" }, { CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_RIGHT, "\033[?8452h" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr default_color_seqs [] = { { CHAFA_TERM_SEQ_RESET_DEFAULT_FG, "\033]110\033\\" }, { CHAFA_TERM_SEQ_SET_DEFAULT_FG, "\033]10;rgb:%1/%2/%3\e\\" }, { CHAFA_TERM_SEQ_QUERY_DEFAULT_FG, "\033]10;?\033\\" }, { CHAFA_TERM_SEQ_RESET_DEFAULT_BG, "\033]111\033\\" }, { CHAFA_TERM_SEQ_SET_DEFAULT_BG, "\033]11;rgb:%1/%2/%3\e\\" }, { CHAFA_TERM_SEQ_QUERY_DEFAULT_BG, "\033]11;?\033\\" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr default_key_seqs [] = { { CHAFA_TERM_SEQ_RETURN_KEY, "\x0d" }, /* ASCII CR */ { CHAFA_TERM_SEQ_BACKSPACE_KEY, "\x7f" }, /* ASCII DEL */ { CHAFA_TERM_SEQ_TAB_KEY, "\x09" }, /* ASCII HT */ { CHAFA_TERM_SEQ_TAB_SHIFT_KEY, "\033[Z" }, { CHAFA_TERM_SEQ_UP_KEY, "\033[A" }, { CHAFA_TERM_SEQ_UP_CTRL_KEY, "\033[1;5A" }, { CHAFA_TERM_SEQ_UP_SHIFT_KEY, "\033[1;2A" }, { CHAFA_TERM_SEQ_DOWN_KEY, "\033[B" }, { CHAFA_TERM_SEQ_DOWN_CTRL_KEY, "\033[1;5B" }, { CHAFA_TERM_SEQ_DOWN_SHIFT_KEY, "\033[1;2B" }, { CHAFA_TERM_SEQ_LEFT_KEY, "\033[D" }, { CHAFA_TERM_SEQ_LEFT_CTRL_KEY, "\033[1;5D" }, { CHAFA_TERM_SEQ_LEFT_SHIFT_KEY, "\033[1;2D" }, { CHAFA_TERM_SEQ_RIGHT_KEY, "\033[C" }, { CHAFA_TERM_SEQ_RIGHT_CTRL_KEY, "\033[1;5C" }, { CHAFA_TERM_SEQ_RIGHT_SHIFT_KEY, "\033[1;2C" }, { CHAFA_TERM_SEQ_PAGE_UP_KEY, "\033[5~" }, { CHAFA_TERM_SEQ_PAGE_UP_CTRL_KEY, "\033[5;5~" }, { CHAFA_TERM_SEQ_PAGE_UP_SHIFT_KEY, "\033[5;2~" }, { CHAFA_TERM_SEQ_PAGE_DOWN_KEY, "\033[6~" }, { CHAFA_TERM_SEQ_PAGE_DOWN_CTRL_KEY, "\033[6;5~" }, { CHAFA_TERM_SEQ_PAGE_DOWN_SHIFT_KEY, "\033[6;2~" }, { CHAFA_TERM_SEQ_HOME_KEY, "\033[H" }, { CHAFA_TERM_SEQ_HOME_CTRL_KEY, "\033[1;5H" }, { CHAFA_TERM_SEQ_HOME_SHIFT_KEY, "\033[1;2H" }, { CHAFA_TERM_SEQ_END_KEY, "\033[F" }, { CHAFA_TERM_SEQ_END_CTRL_KEY, "\033[1;5F" }, { CHAFA_TERM_SEQ_END_SHIFT_KEY, "\033[1;2F" }, { CHAFA_TERM_SEQ_INSERT_KEY, "\033[2~" }, { CHAFA_TERM_SEQ_INSERT_CTRL_KEY, "\033[2;5~" }, { CHAFA_TERM_SEQ_INSERT_SHIFT_KEY, "\033[2;2~" }, { CHAFA_TERM_SEQ_DELETE_KEY, "\033[3~" }, { CHAFA_TERM_SEQ_DELETE_CTRL_KEY, "\033[3;5~" }, { CHAFA_TERM_SEQ_DELETE_SHIFT_KEY, "\033[3;2~" }, { CHAFA_TERM_SEQ_F1_KEY, "\033OP" }, { CHAFA_TERM_SEQ_F1_CTRL_KEY, "\033[1;5P" }, { CHAFA_TERM_SEQ_F1_SHIFT_KEY, "\033[1;2P" }, { CHAFA_TERM_SEQ_F2_KEY, "\033OQ" }, { CHAFA_TERM_SEQ_F2_CTRL_KEY, "\033[1;5Q" }, { CHAFA_TERM_SEQ_F2_SHIFT_KEY, "\033[1;2Q" }, { CHAFA_TERM_SEQ_F3_KEY, "\033R" }, { CHAFA_TERM_SEQ_F3_CTRL_KEY, "\033[1;5R" }, { CHAFA_TERM_SEQ_F3_SHIFT_KEY, "\033[1;2R" }, { CHAFA_TERM_SEQ_F4_KEY, "\033OS" }, { CHAFA_TERM_SEQ_F4_CTRL_KEY, "\033[1;5S" }, { CHAFA_TERM_SEQ_F4_SHIFT_KEY, "\033[1;2S" }, { CHAFA_TERM_SEQ_F5_KEY, "\033[15~" }, { CHAFA_TERM_SEQ_F5_CTRL_KEY, "\033[15;5~" }, { CHAFA_TERM_SEQ_F5_SHIFT_KEY, "\033[15;2~" }, { CHAFA_TERM_SEQ_F6_KEY, "\033[17~" }, { CHAFA_TERM_SEQ_F6_CTRL_KEY, "\033[17;5~" }, { CHAFA_TERM_SEQ_F6_SHIFT_KEY, "\033[17;2~" }, { CHAFA_TERM_SEQ_F7_KEY, "\033[18~" }, { CHAFA_TERM_SEQ_F7_CTRL_KEY, "\033[18;5~" }, { CHAFA_TERM_SEQ_F7_SHIFT_KEY, "\033[18;2~" }, { CHAFA_TERM_SEQ_F8_KEY, "\033[19~" }, { CHAFA_TERM_SEQ_F8_CTRL_KEY, "\033[19;5~" }, { CHAFA_TERM_SEQ_F8_SHIFT_KEY, "\033[19;2~" }, { CHAFA_TERM_SEQ_F9_KEY, "\033[20~" }, { CHAFA_TERM_SEQ_F9_CTRL_KEY, "\033[20;5~" }, { CHAFA_TERM_SEQ_F9_SHIFT_KEY, "\033[20;2~" }, { CHAFA_TERM_SEQ_F10_KEY, "\033[21~" }, { CHAFA_TERM_SEQ_F10_CTRL_KEY, "\033[21;5~" }, { CHAFA_TERM_SEQ_F10_SHIFT_KEY, "\033[21;2~" }, { CHAFA_TERM_SEQ_F11_KEY, "\033[23~" }, { CHAFA_TERM_SEQ_F11_CTRL_KEY, "\033[23;5~" }, { CHAFA_TERM_SEQ_F11_SHIFT_KEY, "\033[23;2~" }, { CHAFA_TERM_SEQ_F12_KEY, "\033[24~" }, { CHAFA_TERM_SEQ_F12_CTRL_KEY, "\033[24;5~" }, { CHAFA_TERM_SEQ_F12_SHIFT_KEY, "\033[24;2~" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr color_direct_seqs [] = { /* ISO 8613-6 */ { 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_8_seqs [] = { { CHAFA_TERM_SEQ_SET_COLOR_FG_8, "\033[%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_8, "\033[%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_8, "\033[%1;%2m" }, /* ECMA-48 3rd ed. March 1984 */ { CHAFA_TERM_SEQ_RESET_COLOR_FG, "\033[39m" }, { CHAFA_TERM_SEQ_RESET_COLOR_BG, "\033[49m" }, { CHAFA_TERM_SEQ_RESET_COLOR_FGBG, "\033[39;49m" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr *color_direct_list [] = { color_direct_seqs, color_256_seqs, color_16_seqs, color_8_seqs, NULL }; static const SeqStr *color_256_list [] = { color_256_seqs, color_16_seqs, color_8_seqs, NULL }; static const SeqStr *color_16_list [] = { color_16_seqs, color_8_seqs, NULL }; static const SeqStr *color_8_list [] = { color_8_seqs, NULL }; static const SeqStr *color_mono_list [] = { 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, color_8_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_BEGIN_KITTY_IMMEDIATE_VIRT_IMAGE_V1, "\033_Ga=T,U=1,q=2,f=%1,s=%2,v=%3,c=%4,r=%5,i=%6,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 tmux_seqs [] = { { CHAFA_TERM_SEQ_BEGIN_TMUX_PASSTHROUGH, "\033Ptmux;" }, { CHAFA_TERM_SEQ_END_TMUX_PASSTHROUGH, "\033\\" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr screen_seqs [] = { { CHAFA_TERM_SEQ_BEGIN_SCREEN_PASSTHROUGH, "\033P" }, { CHAFA_TERM_SEQ_END_SCREEN_PASSTHROUGH, "\033\\" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr *fallback_list [] = { vt220_seqs, color_direct_seqs, color_256_seqs, color_16_seqs, color_8_seqs, sixel_seqs, kitty_seqs, iterm2_seqs, screen_seqs, tmux_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 const gchar * getenv_or_blank (gchar **envp, const gchar *key) { const gchar *value; value = g_environ_getenv (envp, key); if (!value) value = ""; return value; } static void detect_capabilities (ChafaTermInfo *ti, gchar **envp) { const gchar *term; const gchar *colorterm; const gchar *konsole_version; const gchar *vte_version; const gchar *term_program; const gchar *term_name; const gchar *tmux; const gchar *ctx_backend; const gchar *lc_terminal; const gchar *kitty_pid; const gchar *mlterm; const gchar *nvim; const gchar *nvim_tui_enable_true_color; const gchar *eat_shell_integration_dir; const gchar *wezterm_executable; gchar *comspec = NULL; const SeqStr **color_seq_list = color_256_list; const SeqStr *gfx_seqs = NULL; const SeqStr *rep_seqs_local = NULL; const SeqStr *inner_seqs = NULL; term = getenv_or_blank (envp, "TERM"); colorterm = getenv_or_blank (envp, "COLORTERM"); konsole_version = getenv_or_blank (envp, "KONSOLE_VERSION"); vte_version = getenv_or_blank (envp, "VTE_VERSION"); term_program = getenv_or_blank (envp, "TERM_PROGRAM"); term_name = getenv_or_blank (envp, "TERMINAL_NAME"); tmux = getenv_or_blank (envp, "TMUX"); ctx_backend = getenv_or_blank (envp, "CTX_BACKEND"); lc_terminal = getenv_or_blank (envp, "LC_TERMINAL"); kitty_pid = getenv_or_blank (envp, "KITTY_PID"); mlterm = getenv_or_blank (envp, "MLTERM"); nvim = getenv_or_blank (envp, "NVIM"); nvim_tui_enable_true_color = getenv_or_blank (envp, "NVIM_TUI_ENABLE_TRUE_COLOR"); eat_shell_integration_dir = getenv_or_blank (envp, "EAT_SHELL_INTEGRATION_DIR"); wezterm_executable = getenv_or_blank (envp, "WEZTERM_EXECUTABLE"); /* The MS Windows 10 TH2 (v1511+) console supports ANSI escape codes, * including AIX and DirectColor sequences. We detect this early and allow * TERM to override, if present. */ comspec = (gchar *) g_environ_getenv (envp, "ComSpec"); if (comspec) { comspec = g_ascii_strdown (comspec, -1); if (g_str_has_suffix (comspec, "\\cmd.exe")) color_seq_list = color_direct_list; g_free (comspec); comspec = NULL; } /* 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; } /* Konsole exports KONSOLE_VERSION */ if (strtoul (konsole_version, NULL, 10) >= 220370) { /* Konsole version 22.03.70+ supports sixel graphics */ gfx_seqs = sixel_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. It is also supported by Ghostty. */ if (!strcmp (term, "xterm-kitty") || strlen (kitty_pid) > 0 || !strcmp (term, "xterm-ghostty") || !strcmp (term_program, "ghostty")) gfx_seqs = kitty_seqs; /* iTerm2 supports truecolor and has a unique graphics protocol */ if (!g_ascii_strcasecmp (lc_terminal, "iTerm2") || !g_ascii_strcasecmp (term_program, "iTerm.app")) { color_seq_list = color_direct_list; gfx_seqs = iterm2_seqs; } if (!g_ascii_strcasecmp (term_program, "WezTerm") || strlen (wezterm_executable) > 0) { gfx_seqs = sixel_seqs; } if (!g_ascii_strcasecmp (term_name, "contour")) { gfx_seqs = sixel_seqs; } /* Check for Neovim early. It pretends to be xterm-256color, and may or * may not support directcolor. */ if (strlen (nvim) > 0) { /* The Neovim terminal defaults to 256 colors unless termguicolors has * been set to true. */ color_seq_list = color_256_list; /* If COLORTERM was explicitly set to truecolor, honor it. Neovim may do * this when termguicolors has been set to true *and* COLORTERM was * previously set. See Neovim commit d8963c434f01e6a7316 (Nov 26, 2020). * * The user may also set NVIM_TUI_ENABLE_TRUE_COLOR=1 in older Neovim * versions. We'll honor that one blindly, since it's specific and there * seems to be no better option. */ if (!g_ascii_strcasecmp (colorterm, "truecolor") || !g_ascii_strcasecmp (nvim_tui_enable_true_color, "1")) { color_seq_list = color_direct_list; } } /* 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") || strlen (mlterm) > 0 || !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") || !strncmp (term, "foot-", 5)) 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; /* Eat uses the "eat-" prefix for TERM. * Eat also sets EAT_SHELL_INTEGRATION_DIR in the environment. */ if (!strncmp (term, "eat-", 4) || strcmp (eat_shell_integration_dir, "")) gfx_seqs = sixel_seqs; if (!strcmp (term, "eat-truecolor")) color_seq_list = color_direct_list; if (!strcmp (term, "eat-256color")) color_seq_list = color_256_list; if (!strcmp (term, "eat-16color")) color_seq_list = color_16_list; if (!strcmp (term, "eat-color")) color_seq_list = color_8_list; if (!strcmp (term, "eat-mono")) color_seq_list = color_mono_list; /* 'tmux' sets TERM=screen or =screen-256color, 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 || !g_ascii_strcasecmp (term_program, "tmux")) { color_seq_list = color_direct_list; inner_seqs = tmux_seqs; /* Older tmux does not support REP. Newer tmux does, but there's no * reliable way to tell which version we're dealing with */ rep_seqs_local = NULL; } else if (!strncmp (term, "screen", 6)) { /* '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 */ color_seq_list = color_256_list; inner_seqs = screen_seqs; /* 'screen' does not support REP. */ 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_seqs (ti, vt220_seqs); add_seq_list (ti, color_seq_list); add_seqs (ti, gfx_seqs); add_seqs (ti, rep_seqs_local); add_seqs (ti, inner_seqs); } 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.14.5/chafa/chafa-term-db.h000066400000000000000000000032721471154763100164660ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/chafa-term-info.c000066400000000000000000001121001471154763100170160ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" #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 (disable with RESET_ATTRIBUTES). * @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_ENABLE_SIXEL_SCROLLING: Enable sixel scrolling. * @CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING: Disable sixel scrolling. * @CHAFA_TERM_SEQ_ENABLE_BOLD: Enable boldface (disable with RESET_ATTRIBUTES). * @CHAFA_TERM_SEQ_SET_COLOR_FG_8: Set foreground color (8 colors). * @CHAFA_TERM_SEQ_SET_COLOR_BG_8: Set background color (8 colors). * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_8: Set foreground and background colors (8 colors). * @CHAFA_TERM_SEQ_RESET_DEFAULT_FG: Reset the default FG color to the terminal's default. * @CHAFA_TERM_SEQ_SET_DEFAULT_FG: Set the default FG to a specific RGB color. * @CHAFA_TERM_SEQ_QUERY_DEFAULT_FG: Query the default FG color. * @CHAFA_TERM_SEQ_RESET_DEFAULT_BG: Reset the default BG color to the terminal's default. * @CHAFA_TERM_SEQ_SET_DEFAULT_BG: Set the default BG to a specific RGB color. * @CHAFA_TERM_SEQ_QUERY_DEFAULT_BG: Query the default BG color. * @CHAFA_TERM_SEQ_RETURN_KEY: Return key. * @CHAFA_TERM_SEQ_BACKSPACE_KEY: Backspace key. * @CHAFA_TERM_SEQ_TAB_KEY: Tab key. * @CHAFA_TERM_SEQ_TAB_SHIFT_KEY: Shift + tab key. * @CHAFA_TERM_SEQ_UP_KEY: Up key. * @CHAFA_TERM_SEQ_UP_CTRL_KEY: Ctrl + up key. * @CHAFA_TERM_SEQ_UP_SHIFT_KEY: Shift + up key. * @CHAFA_TERM_SEQ_DOWN_KEY: Down key. * @CHAFA_TERM_SEQ_DOWN_CTRL_KEY: Ctrl + down key. * @CHAFA_TERM_SEQ_DOWN_SHIFT_KEY: Shift + down key. * @CHAFA_TERM_SEQ_LEFT_KEY: Left key. * @CHAFA_TERM_SEQ_LEFT_CTRL_KEY: Ctrl + left key. * @CHAFA_TERM_SEQ_LEFT_SHIFT_KEY: Shift + left key. * @CHAFA_TERM_SEQ_RIGHT_KEY: Right key. * @CHAFA_TERM_SEQ_RIGHT_CTRL_KEY: Ctrl + right key. * @CHAFA_TERM_SEQ_RIGHT_SHIFT_KEY: Shift + right key. * @CHAFA_TERM_SEQ_PAGE_UP_KEY: Page up key. * @CHAFA_TERM_SEQ_PAGE_UP_CTRL_KEY: Ctrl + page up key. * @CHAFA_TERM_SEQ_PAGE_UP_SHIFT_KEY: Shift + page up key. * @CHAFA_TERM_SEQ_PAGE_DOWN_KEY: Page down key. * @CHAFA_TERM_SEQ_PAGE_DOWN_CTRL_KEY: Ctrl + page down key. * @CHAFA_TERM_SEQ_PAGE_DOWN_SHIFT_KEY: Shift + page down key. * @CHAFA_TERM_SEQ_HOME_KEY: Home key. * @CHAFA_TERM_SEQ_HOME_CTRL_KEY: Ctrl + home key. * @CHAFA_TERM_SEQ_HOME_SHIFT_KEY: Shift + home key. * @CHAFA_TERM_SEQ_END_KEY: End key. * @CHAFA_TERM_SEQ_END_CTRL_KEY: Ctrl + end key. * @CHAFA_TERM_SEQ_END_SHIFT_KEY: Shift + end key. * @CHAFA_TERM_SEQ_INSERT_KEY: Insert key. * @CHAFA_TERM_SEQ_INSERT_CTRL_KEY: Ctrl + insert key. * @CHAFA_TERM_SEQ_INSERT_SHIFT_KEY: Shift + insert key. * @CHAFA_TERM_SEQ_DELETE_KEY: Delete key. * @CHAFA_TERM_SEQ_DELETE_CTRL_KEY: Ctrl + delete key. * @CHAFA_TERM_SEQ_DELETE_SHIFT_KEY: Shift + delete key. * @CHAFA_TERM_SEQ_F1_KEY: F1 key. * @CHAFA_TERM_SEQ_F1_CTRL_KEY: Ctrl + F1 key. * @CHAFA_TERM_SEQ_F1_SHIFT_KEY: Shift + F1 key. * @CHAFA_TERM_SEQ_F2_KEY: F2 key. * @CHAFA_TERM_SEQ_F2_CTRL_KEY: Ctrl + F2 key. * @CHAFA_TERM_SEQ_F2_SHIFT_KEY: Shift + F2 key. * @CHAFA_TERM_SEQ_F3_KEY: F3 key. * @CHAFA_TERM_SEQ_F3_CTRL_KEY: Ctrl + F3 key. * @CHAFA_TERM_SEQ_F3_SHIFT_KEY: Shift + F3 key. * @CHAFA_TERM_SEQ_F4_KEY: F4 key. * @CHAFA_TERM_SEQ_F4_CTRL_KEY: Ctrl + F4 key. * @CHAFA_TERM_SEQ_F4_SHIFT_KEY: Shift + F4 key. * @CHAFA_TERM_SEQ_F5_KEY: F5 key. * @CHAFA_TERM_SEQ_F5_CTRL_KEY: Ctrl + F5 key. * @CHAFA_TERM_SEQ_F5_SHIFT_KEY: Shift + F5 key. * @CHAFA_TERM_SEQ_F6_KEY: F6 key. * @CHAFA_TERM_SEQ_F6_CTRL_KEY: Ctrl + F6 key. * @CHAFA_TERM_SEQ_F6_SHIFT_KEY: Shift + F6 key. * @CHAFA_TERM_SEQ_F7_KEY: F7 key. * @CHAFA_TERM_SEQ_F7_CTRL_KEY: Ctrl + F7 key. * @CHAFA_TERM_SEQ_F7_SHIFT_KEY: Shift + F7 key. * @CHAFA_TERM_SEQ_F8_KEY: F8 key. * @CHAFA_TERM_SEQ_F8_CTRL_KEY: Ctrl + F8 key. * @CHAFA_TERM_SEQ_F8_SHIFT_KEY: Shift + F8 key. * @CHAFA_TERM_SEQ_F9_KEY: F9 key. * @CHAFA_TERM_SEQ_F9_CTRL_KEY: Ctrl + F9 key. * @CHAFA_TERM_SEQ_F9_SHIFT_KEY: Shift + F9 key. * @CHAFA_TERM_SEQ_F10_KEY: F10 key. * @CHAFA_TERM_SEQ_F10_CTRL_KEY: Ctrl + F10 key. * @CHAFA_TERM_SEQ_F10_SHIFT_KEY: Shift + F10 key. * @CHAFA_TERM_SEQ_F11_KEY: F11 key. * @CHAFA_TERM_SEQ_F11_CTRL_KEY: Ctrl + F11 key. * @CHAFA_TERM_SEQ_F11_SHIFT_KEY: Shift + F11 key. * @CHAFA_TERM_SEQ_F12_KEY: F12 key. * @CHAFA_TERM_SEQ_F12_CTRL_KEY: Ctrl + F12 key. * @CHAFA_TERM_SEQ_F12_SHIFT_KEY: Shift + F12 key. * @CHAFA_TERM_SEQ_RESET_COLOR_FG: Reset foreground color to the default. * @CHAFA_TERM_SEQ_RESET_COLOR_BG: Reset background color to the default. * @CHAFA_TERM_SEQ_RESET_COLOR_FGBG: Reset foreground and background colors to the default. * @CHAFA_TERM_SEQ_RESET_SCROLLING_ROWS: Reset scrolling area extent to that of the entire screen. * @CHAFA_TERM_SEQ_SAVE_CURSOR_POS: Save the cursor's position. * @CHAFA_TERM_SEQ_RESTORE_CURSOR_POS: Move cursor to the last saved position. * @CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_DOWN: After showing a sixel image, leave cursor somewhere on the row below the image. * @CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_RIGHT: After showing a sixel image, leave cursor to the right of the last row. * @CHAFA_TERM_SEQ_ENABLE_ALT_SCREEN: Switch to the alternate screen buffer. * @CHAFA_TERM_SEQ_DISABLE_ALT_SCREEN: Switch back to the regular screen buffer. * @CHAFA_TERM_SEQ_MAX: Last control sequence plus one. * @CHAFA_TERM_SEQ_BEGIN_SCREEN_PASSTHROUGH: Begins OSC passthrough for the GNU Screen multiplexer. * @CHAFA_TERM_SEQ_END_SCREEN_PASSTHROUGH: Ends OSC passthrough for the GNU Screen multiplexer. * @CHAFA_TERM_SEQ_BEGIN_TMUX_PASSTHROUGH: Begins OSC passthrough for the tmux multiplexer. * @CHAFA_TERM_SEQ_END_TMUX_PASSTHROUGH: Ends OSC passthrough for the tmux multiplexer. * @CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_VIRT_IMAGE_V1: Begin upload of virtual Kitty image for immediate display at cursor. * * An enumeration of the control sequences supported by #ChafaTermInfo. **/ #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 <= '7') /* arg # 0-6 */ { /* "%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(name, inttype, intformatter) \ static gchar * \ emit_seq_##name (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, guint, chafa_format_dec_uint_0_to_9999) EMIT_SEQ_DEF(guint8, guint8, chafa_format_dec_u8) EMIT_SEQ_DEF(guint16_hex, guint16, chafa_format_dec_u16_hex) 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_6_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1, guint arg2, guint arg3, guint arg4, guint arg5) { guint args [6]; args [0] = arg0; args [1] = arg1; args [2] = arg2; args [3] = arg3; args [4] = arg4; args [5] = arg5; return emit_seq_guint (term_info, out, seq, args, 6); } 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); } static gchar * emit_seq_3_args_uint16_hex (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint16 arg0, guint16 arg1, guint16 arg2) { guint16 args [3]; args [0] = arg0; args [1] = arg1; args [2] = arg2; return emit_seq_guint16_hex (term_info, out, seq, args, 3); } /* Stream parsing */ static gint parse_dec (const gchar *in, gint in_len, guint *args_out) { gint i = 0; guint result = 0; while (in_len > 0 && *in >= '0' && *in <= '9') { result *= 10; result += *in - '0'; in++; in_len--; i++; } *args_out = result; return i; } static gint parse_hex4 (const gchar *in, gint in_len, guint *args_out) { gint i = 0; guint result = 0; while (in_len > 0) { gchar c = g_ascii_tolower (*in); if (c >= '0' && c <= '9') { result *= 16; result += c - '0'; } else if (c >= 'a' && c <= 'f') { result *= 16; result += c - 'a' + 10; } else break; in++; in_len--; i++; } *args_out = result; return i; } static ChafaParseResult try_parse_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq, gchar **input, gint *input_len, guint *args_out) { gchar *in = *input; gint in_len = *input_len; const gchar *seq_str; const SeqArgInfo *seq_args; gint pofs = 0; guint i = 0; seq_str = &term_info->seq_str [seq] [0]; seq_args = &term_info->seq_args [seq] [0]; memset (args_out, 0, seq_meta [seq].n_args * sizeof (guint)); for ( ; ; i++) { gint len; if (memcmp (in, &seq_str [pofs], MIN (in_len, seq_args [i].pre_len))) return CHAFA_PARSE_FAILURE; if (in_len < seq_args [i].pre_len) return CHAFA_PARSE_AGAIN; in += seq_args [i].pre_len; in_len -= seq_args [i].pre_len; pofs += seq_args [i].pre_len; if (i >= seq_meta [seq].n_args) break; if (in_len == 0) return CHAFA_PARSE_AGAIN; if (seq_meta [seq].type_size == 1) len = parse_dec (in, in_len, args_out + seq_args [i].arg_index); else if (seq_meta [seq].type_size == 2) len = parse_hex4 (in, in_len, args_out + seq_args [i].arg_index); else len = parse_dec (in, in_len, args_out + seq_args [i].arg_index); if (len == 0) return CHAFA_PARSE_FAILURE; in += len; in_len -= len; } if (*input == in) return CHAFA_PARSE_FAILURE; *input = in; *input_len = in_len; return CHAFA_PARSE_SUCCESS; } /* 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_emit_seq: * @term_info: A #ChafaTermInfo * @seq: A #ChafaTermSeq to emit * @...: A list of int arguments to insert in @seq, terminated by -1 * * Formats the terminal sequence @seq, inserting positional arguments. The seq's * number of arguments must be supplied exactly. * * The argument list must be terminated by -1, or undefined behavior will result. * * If the wrong number of arguments is supplied, or an argument is out of range, * this function will return %NULL. Otherwise, it returns a zero-terminated string * that must be freed with g_free(). * * If you want compile-time validation of arguments, consider using one of the * specific chafa_term_info_emit_*() functions. They are also faster, but require * you to allocate at least CHAFA_TERM_SEQ_LENGTH_MAX bytes up front. * * Returns: A newly allocated, zero-terminated formatted string, or %NULL on error * * Since: 1.14 **/ gchar * chafa_term_info_emit_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, ...) { va_list ap; guint args_uint [CHAFA_TERM_SEQ_ARGS_MAX]; guint16 args_u16 [CHAFA_TERM_SEQ_ARGS_MAX]; guint8 args_u8 [CHAFA_TERM_SEQ_ARGS_MAX]; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX]; guint n_args = 0; gchar *p; gchar *result = NULL; g_return_val_if_fail (term_info != NULL, NULL); g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, NULL); va_start (ap, seq); for (;;) { gint arg = va_arg (ap, gint); if (arg < 0) break; if (n_args == CHAFA_TERM_SEQ_ARGS_MAX || n_args == seq_meta [seq].n_args) goto out; if (seq_meta [seq].type_size == 1) { if (arg > 0xff) goto out; args_u8 [n_args] = arg; } else if (seq_meta [seq].type_size == 2) { if (arg > 0xffff) goto out; args_u16 [n_args] = arg; } else { args_uint [n_args] = arg; } n_args++; } if (n_args != seq_meta [seq].n_args) goto out; /* 16-bit args are always hex for now. It has no other uses. */ if (seq_meta [seq].n_args == 0) p = emit_seq_0_args_uint (term_info, buf, seq); else if (seq_meta [seq].type_size == 1) p = emit_seq_guint8 (term_info, buf, seq, args_u8, n_args); else if (seq_meta [seq].type_size == 2) p = emit_seq_guint16_hex (term_info, buf, seq, args_u16, n_args); else p = emit_seq_guint (term_info, buf, seq, args_uint, n_args); if (p == buf) goto out; result = g_strndup (buf, p - buf); out: va_end (ap); return result; } /** * chafa_term_info_parse_seq: * @term_info: A #ChafaTermInfo * @seq: A #ChafaTermSeq to attempt to parse * @input: Pointer to pointer to input data * @input_len: Pointer to maximum input data length * @args_out: Pointer to parsed argument values * * Attempts to parse a terminal sequence from an input data array. If successful, * #CHAFA_PARSE_SUCCESS will be returned, the @input pointer will be advanced and * the parsed length will be subtracted from @input_len. * * Returns: A #ChafaParseResult indicating success, failure or insufficient input data * * Since: 1.14 **/ ChafaParseResult chafa_term_info_parse_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, gchar **input, gint *input_len, guint *args_out) { guint dummy_args_out [CHAFA_TERM_SEQ_ARGS_MAX]; g_return_val_if_fail (term_info != NULL, CHAFA_PARSE_FAILURE); g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, CHAFA_PARSE_FAILURE); g_return_val_if_fail (input != NULL, CHAFA_PARSE_FAILURE); g_return_val_if_fail (*input != NULL, CHAFA_PARSE_FAILURE); g_return_val_if_fail (input_len != NULL, CHAFA_PARSE_FAILURE); if (!chafa_term_info_have_seq (term_info, seq)) return CHAFA_PARSE_FAILURE; if (!args_out) args_out = dummy_args_out; return try_parse_seq (term_info, seq, input, input_len, args_out); } /** * 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_char(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_8fg_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 + 30); } #define DEFINE_EMIT_SEQ_1_8bg_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 + 40); } #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_8fgbg_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 + 30, arg1 + 40); } #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_6_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, guint arg5) \ { return emit_seq_6_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2, arg3, arg4, arg5); } #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 DEFINE_EMIT_SEQ_3_u16hex_guint16(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint16 arg0, guint16 arg1, guint16 arg2) \ { return emit_seq_3_args_uint16_hex (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2); } #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.14.5/chafa/chafa-term-info.h000066400000000000000000000111531471154763100170310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 /* Maximum number of arguments + 1 for sentinel */ #define CHAFA_TERM_SEQ_ARGS_MAX 8 #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; /** * ChafaParseResult: * @CHAFA_PARSE_SUCCESS: Parsed successfully * @CHAFA_PARSE_FAILURE: Data mismatch * @CHAFA_PARSE_AGAIN: Partial success, but not enough input * * An enumeration of the possible return values from the parsing function. **/ typedef enum { CHAFA_PARSE_SUCCESS, CHAFA_PARSE_FAILURE, CHAFA_PARSE_AGAIN } ChafaParseResult; 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_14 gchar *chafa_term_info_emit_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, ...); CHAFA_AVAILABLE_IN_1_14 ChafaParseResult chafa_term_info_parse_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, gchar **input, gint *input_len, guint *args_out); 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.14.5/chafa/chafa-term-seq-def.h000066400000000000000000002335051471154763100174310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 */ /* For zero-argument functions, we use "char" as the argument type instead * of the more appropriate "void", since we need to be able to use it with * sizeof() and -Wpointer-arith. */ /* __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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /* 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /* 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /** * 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, char) /* 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, char) /** * 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, char) /** * 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, char) /** * 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_IMMEDIATE_IMAGE_V1. * * @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, char) /** * 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, char) /** * 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, char) /** * 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. * * @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, char) /* --- Available in 1.10+ --- */ #undef CHAFA_TERM_SEQ_AVAILABILITY #define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_10 /** * chafa_term_info_emit_enable_sixel_scrolling: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_SIXEL_SCROLLING. * * @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.10 **/ CHAFA_TERM_SEQ_DEF(enable_sixel_scrolling, ENABLE_SIXEL_SCROLLING, 0, none, char) /** * chafa_term_info_emit_disable_sixel_scrolling: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_SIXEL_SCROLLING. * * @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.10 **/ CHAFA_TERM_SEQ_DEF(disable_sixel_scrolling, DISABLE_SIXEL_SCROLLING, 0, none, char) /* --- Available in 1.12+ --- */ #undef CHAFA_TERM_SEQ_AVAILABILITY #define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_12 /** * chafa_term_info_emit_enable_bold: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_BOLD. * * @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.12 **/ CHAFA_TERM_SEQ_DEF(enable_bold, ENABLE_BOLD, 0, none, char) /** * chafa_term_info_emit_set_color_fg_8: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-7 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_8. * * @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.12 **/ CHAFA_TERM_SEQ_DEF(set_color_fg_8, SET_COLOR_FG_8, 1, 8fg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_bg_8: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-7 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_8. * * @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.12 **/ CHAFA_TERM_SEQ_DEF(set_color_bg_8, SET_COLOR_BG_8, 1, 8bg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_fgbg_8: * @term_info: A #ChafaTermInfo * @dest: String destination * @fg_pen: Foreground pen number, 0-7 * @bg_pen: Background pen number, 0-7 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_8. * * @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.12 **/ CHAFA_TERM_SEQ_DEF(set_color_fgbg_8, SET_COLOR_FGBG_8, 2, 8fgbg, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) /* --- Available in 1.14+ --- */ #undef CHAFA_TERM_SEQ_AVAILABILITY #define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_14 /** * chafa_term_info_emit_reset_default_fg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_DEFAULT_FG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(reset_default_fg, RESET_DEFAULT_FG, 0, none, char) /** * chafa_term_info_emit_set_default_fg: * @term_info: A #ChafaTermInfo * @dest: String destination * @r: Red component (0-65535) * @g: Green component (0-65535) * @b: Blue component (0-65535) * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_DEFAULT_FG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(set_default_fg, SET_DEFAULT_FG, 3, u16hex, guint16, CHAFA_TERM_SEQ_ARGS guint16 r, guint16 g, guint16 b) /** * chafa_term_info_emit_query_default_fg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_QUERY_DEFAULT_FG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(query_default_fg, QUERY_DEFAULT_FG, 0, none, char) /** * chafa_term_info_emit_reset_default_bg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_DEFAULT_BG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(reset_default_bg, RESET_DEFAULT_BG, 0, none, char) /** * chafa_term_info_emit_set_default_bg: * @term_info: A #ChafaTermInfo * @dest: String destination * @r: Red component (0-65535) * @g: Green component (0-65535) * @b: Blue component (0-65535) * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_DEFAULT_BG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(set_default_bg, SET_DEFAULT_BG, 3, u16hex, guint16, CHAFA_TERM_SEQ_ARGS guint16 r, guint16 g, guint16 b) /** * chafa_term_info_emit_query_default_bg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_QUERY_DEFAULT_BG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(query_default_bg, QUERY_DEFAULT_BG, 0, none, char) /** * chafa_term_info_emit_return_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RETURN_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(return_key, RETURN_KEY, 0, none, char) /** * chafa_term_info_emit_backspace_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_BACKSPACE_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(backspace_key, BACKSPACE_KEY, 0, none, char) /** * chafa_term_info_emit_tab_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_TAB_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(tab_key, TAB_KEY, 0, none, char) /** * chafa_term_info_emit_tab_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_TAB_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(tab_shift_key, TAB_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_up_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_UP_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(up_key, UP_KEY, 0, none, char) /** * chafa_term_info_emit_up_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_UP_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(up_ctrl_key, UP_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_up_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_UP_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(up_shift_key, UP_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_down_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DOWN_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(down_key, DOWN_KEY, 0, none, char) /** * chafa_term_info_emit_down_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DOWN_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(down_ctrl_key, DOWN_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_down_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DOWN_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(down_shift_key, DOWN_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_left_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_LEFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(left_key, LEFT_KEY, 0, none, char) /** * chafa_term_info_emit_left_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_LEFT_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(left_ctrl_key, LEFT_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_left_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_LEFT_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(left_shift_key, LEFT_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_right_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RIGHT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(right_key, RIGHT_KEY, 0, none, char) /** * chafa_term_info_emit_right_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RIGHT_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(right_ctrl_key, RIGHT_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_right_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RIGHT_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(right_shift_key, RIGHT_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_page_up_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_UP_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_up_key, PAGE_UP_KEY, 0, none, char) /** * chafa_term_info_emit_page_up_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_UP_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_up_ctrl_key, PAGE_UP_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_page_up_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_UP_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_up_shift_key, PAGE_UP_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_page_down_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_DOWN_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_down_key, PAGE_DOWN_KEY, 0, none, char) /** * chafa_term_info_emit_page_down_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_DOWN_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_down_ctrl_key, PAGE_DOWN_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_page_down_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_PAGE_DOWN_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(page_down_shift_key, PAGE_DOWN_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_home_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_HOME_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(home_key, HOME_KEY, 0, none, char) /** * chafa_term_info_emit_home_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_HOME_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(home_ctrl_key, HOME_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_home_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_HOME_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(home_shift_key, HOME_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_end_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(end_key, END_KEY, 0, none, char) /** * chafa_term_info_emit_end_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(end_ctrl_key, END_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_end_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(end_shift_key, END_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_insert_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(insert_key, INSERT_KEY, 0, none, char) /** * chafa_term_info_emit_insert_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(insert_ctrl_key, INSERT_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_insert_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(insert_shift_key, INSERT_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_delete_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(delete_key, DELETE_KEY, 0, none, char) /** * chafa_term_info_emit_delete_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(delete_ctrl_key, DELETE_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_delete_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(delete_shift_key, DELETE_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f1_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F1_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f1_key, F1_KEY, 0, none, char) /** * chafa_term_info_emit_f1_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F1_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f1_ctrl_key, F1_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f1_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F1_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f1_shift_key, F1_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f2_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F2_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f2_key, F2_KEY, 0, none, char) /** * chafa_term_info_emit_f2_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F2_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f2_ctrl_key, F2_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f2_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F2_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f2_shift_key, F2_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f3_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F3_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f3_key, F3_KEY, 0, none, char) /** * chafa_term_info_emit_f3_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F3_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f3_ctrl_key, F3_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f3_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F3_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f3_shift_key, F3_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f4_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F4_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f4_key, F4_KEY, 0, none, char) /** * chafa_term_info_emit_f4_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F4_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f4_ctrl_key, F4_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f4_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F4_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f4_shift_key, F4_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f5_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F5_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f5_key, F5_KEY, 0, none, char) /** * chafa_term_info_emit_f5_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F5_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f5_ctrl_key, F5_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f5_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F5_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f5_shift_key, F5_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f6_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F6_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f6_key, F6_KEY, 0, none, char) /** * chafa_term_info_emit_f6_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F6_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f6_ctrl_key, F6_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f6_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F6_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f6_shift_key, F6_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f7_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F7_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f7_key, F7_KEY, 0, none, char) /** * chafa_term_info_emit_f7_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F7_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f7_ctrl_key, F7_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f7_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F7_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f7_shift_key, F7_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f8_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F8_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f8_key, F8_KEY, 0, none, char) /** * chafa_term_info_emit_f8_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F8_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f8_ctrl_key, F8_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f8_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F8_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f8_shift_key, F8_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f9_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F9_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f9_key, F9_KEY, 0, none, char) /** * chafa_term_info_emit_f9_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F9_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f9_ctrl_key, F9_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f9_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F9_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f9_shift_key, F9_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f10_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F10_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f10_key, F10_KEY, 0, none, char) /** * chafa_term_info_emit_f10_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F10_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f10_ctrl_key, F10_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f10_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F10_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f10_shift_key, F10_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f11_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F11_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f11_key, F11_KEY, 0, none, char) /** * chafa_term_info_emit_f11_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F11_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f11_ctrl_key, F11_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f11_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F11_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f11_shift_key, F11_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_f12_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F12_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f12_key, F12_KEY, 0, none, char) /** * chafa_term_info_emit_f12_ctrl_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F12_CTRL_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f12_ctrl_key, F12_CTRL_KEY, 0, none, char) /** * chafa_term_info_emit_f12_shift_key: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_F12_SHIFT_KEY. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(f12_shift_key, F12_SHIFT_KEY, 0, none, char) /** * chafa_term_info_emit_reset_color_fg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_COLOR_FG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(reset_color_fg, RESET_COLOR_FG, 0, none, char) /** * chafa_term_info_emit_reset_color_bg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_COLOR_BG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(reset_color_bg, RESET_COLOR_BG, 0, none, char) /** * chafa_term_info_emit_reset_color_fgbg: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_COLOR_FGBG. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(reset_color_fgbg, RESET_COLOR_FGBG, 0, none, char) /** * chafa_term_info_emit_reset_scrolling_rows: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_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.14 **/ CHAFA_TERM_SEQ_DEF(reset_scrolling_rows, RESET_SCROLLING_ROWS, 0, none, char) /** * chafa_term_info_emit_save_cursor_pos: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_SAVE_CURSOR_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.14 **/ CHAFA_TERM_SEQ_DEF(save_cursor_pos, SAVE_CURSOR_POS, 0, none, char) /** * chafa_term_info_emit_restore_cursor_pos: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESTORE_CURSOR_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.14 **/ CHAFA_TERM_SEQ_DEF(restore_cursor_pos, RESTORE_CURSOR_POS, 0, none, char) /** * chafa_term_info_emit_set_sixel_advance_down: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_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.14 **/ CHAFA_TERM_SEQ_DEF(set_sixel_advance_down, SET_SIXEL_ADVANCE_DOWN, 0, none, char) /** * chafa_term_info_emit_set_sixel_advance_right: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_SIXEL_ADVANCE_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.14 **/ CHAFA_TERM_SEQ_DEF(set_sixel_advance_right, SET_SIXEL_ADVANCE_RIGHT, 0, none, char) /** * chafa_term_info_emit_enable_alt_screen: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_ALT_SCREEN. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(enable_alt_screen, ENABLE_ALT_SCREEN, 0, none, char) /** * chafa_term_info_emit_disable_alt_screen: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_ALT_SCREEN. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(disable_alt_screen, DISABLE_ALT_SCREEN, 0, none, char) /** * chafa_term_info_emit_begin_screen_passthrough: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_SCREEN_PASSTHROUGH. * * @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. * * Any control sequences between the beginning and end passthrough seqs * must be escaped by turning \033 into \033\033. * * Returns: Pointer to first byte after emitted string * * Since: 1.14 **/ CHAFA_TERM_SEQ_DEF(begin_screen_passthrough, BEGIN_SCREEN_PASSTHROUGH, 0, none, char) /** * chafa_term_info_emit_end_screen_passthrough: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_SCREEN_PASSTHROUGH. * * @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. * * Any control sequences between the beginning and end passthrough seqs * must be escaped by turning \033 into \033\033. * * Returns: Pointer to first byte after emitted string * * Since: 1.14 **/ CHAFA_TERM_SEQ_DEF(end_screen_passthrough, END_SCREEN_PASSTHROUGH, 0, none, char) /** * chafa_term_info_emit_begin_tmux_passthrough: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_TMUX_PASSTHROUGH. * * @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. * * Any control sequences between the beginning and end passthrough seqs * must be escaped by turning \033 into \033\033. * * Returns: Pointer to first byte after emitted string * * Since: 1.14 **/ CHAFA_TERM_SEQ_DEF(begin_tmux_passthrough, BEGIN_TMUX_PASSTHROUGH, 0, none, char) /** * chafa_term_info_emit_end_tmux_passthrough: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_TMUX_PASSTHROUGH. * * @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. * * Any control sequences between the beginning and end passthrough seqs * must be escaped by turning \033 into \033\033. * * Returns: Pointer to first byte after emitted string * * Since: 1.14 **/ CHAFA_TERM_SEQ_DEF(end_tmux_passthrough, END_TMUX_PASSTHROUGH, 0, none, char) /** * chafa_term_info_emit_begin_kitty_immediate_virt_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 * @id: Image ID * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1. * * @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.14 **/ CHAFA_TERM_SEQ_DEF(begin_kitty_immediate_virt_image_v1, BEGIN_KITTY_IMMEDIATE_VIRT_IMAGE_V1, 6, none, guint, CHAFA_TERM_SEQ_ARGS guint bpp, guint width_pixels, guint height_pixels, guint width_cells, guint height_cells, guint id) #undef CHAFA_TERM_SEQ_AVAILABILITY #undef CHAFA_TERM_SEQ_ARGS chafa-1.14.5/chafa/chafa-term-seq-doc-in.h000066400000000000000000000024171471154763100200400ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/chafa-util.c000066400000000000000000000157311471154763100161070ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" /** * 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.0f); 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 = ceil (dest_height * (src_aspect / font_ratio)); } else if (dest_height < 1) { dest_height = ceil ((dest_width / src_aspect) * font_ratio); } else if (src_aspect > dest_aspect) { dest_height = ceil (dest_width * (font_ratio / src_aspect)); } else { dest_width = ceil (dest_height * (src_aspect / font_ratio)); } } /* Clamp dest dimensions */ dest_width = MAX (dest_width, 1); dest_height = MAX (dest_height, 1); if (dest_width_inout && *dest_width_inout > 0) dest_width = MIN (dest_width, *dest_width_inout); if (dest_height_inout && *dest_height_inout > 0) dest_height = MIN (dest_height, *dest_height_inout); if (dest_width_inout) *dest_width_inout = dest_width; if (dest_height_inout) *dest_height_inout = dest_height; } /** * chafa_free_gstring_array: * @gsa: Pointer to a %NULL-terminated array of pointers to #GString * * Frees an array of #GString. If @gsa is %NULL, simply returns without * doing anything. **/ void chafa_free_gstring_array (GString **gsa) { gint i; if (!gsa) return; for (i = 0; gsa [i]; i++) g_string_free (gsa [i], TRUE); g_free (gsa); } /* --- 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.14.5/chafa/chafa-util.h000066400000000000000000000030561471154763100161110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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); CHAFA_AVAILABLE_IN_1_14 void chafa_free_gstring_array (GString **gsa); G_END_DECLS #endif /* __CHAFA_UTIL_H__ */ chafa-1.14.5/chafa/chafa-version-macros.h000066400000000000000000000205671471154763100201110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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)) #define CHAFA_VERSION_1_12 (G_ENCODE_VERSION (1, 12)) #define CHAFA_VERSION_1_14 (G_ENCODE_VERSION (1, 14)) /* 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 #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_12 # define CHAFA_DEPRECATED_IN_1_12 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_12_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_12 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_12_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_12 # define CHAFA_AVAILABLE_IN_1_12 G_UNAVAILABLE(1, 12) #else # define CHAFA_AVAILABLE_IN_1_12 _CHAFA_EXTERN #endif #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_14 # define CHAFA_DEPRECATED_IN_1_14 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_14_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_14 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_14_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_14 # define CHAFA_AVAILABLE_IN_1_14 G_UNAVAILABLE(1, 14) #else # define CHAFA_AVAILABLE_IN_1_14 _CHAFA_EXTERN #endif G_END_DECLS #endif /* __CHAFA_VERSION_MACROS_H__ */ chafa-1.14.5/chafa/chafa.h000066400000000000000000000025761471154763100151440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include #include #include G_END_DECLS #undef __CHAFA_H_INSIDE__ #endif /* __CHAFA_H__ */ chafa-1.14.5/chafa/chafaconfig.h.in000066400000000000000000000005371471154763100167320ustar00rootroot00000000000000/* 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.14.5/chafa/internal/000077500000000000000000000000001471154763100155335ustar00rootroot00000000000000chafa-1.14.5/chafa/internal/Makefile.am000066400000000000000000000051331471154763100175710ustar00rootroot00000000000000SUBDIRS = 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-math-util.c \ chafa-math-util.h \ chafa-palette.c \ chafa-palette.h \ chafa-passthrough-encoder.c \ chafa-passthrough-encoder.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-latin.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 if HAVE_AVX2_INTRINSICS noinst_LTLIBRARIES += libchafa-avx2.la libchafa_internal_la_LIBADD += libchafa-avx2.la libchafa_avx2_la_SOURCES = chafa-avx2.c libchafa_avx2_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mavx2 -DCHAFA_COMPILATION libchafa_avx2_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.14.5/chafa/internal/chafa-avx2.c000066400000000000000000000147461471154763100176330ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" /* _mm_extract_epi64() (pextrq) is not available in 32-bit mode. Work around * it. This needs to be a macro, as the compiler expects an integer constant * for n. */ #if defined __x86_64__ && !defined __ILP32__ # define extract_128_epi64(i, n) _mm_extract_epi64 ((i), (n)) #else # define extract_128_epi64(i, n) \ ((((guint64) _mm_extract_epi32 ((i), (n) * 2 + 1)) << 32) \ | _mm_extract_epi32 ((i), (n) * 2)) #endif gint calc_error_avx2 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint32 *sym_mask_u32) { __m256i err_8x_u32 = { 0 }; __m128i err_4x_u32; __m128i fg_4x_u32, bg_4x_u32; __m256i fg_4x_u64, bg_4x_u64; const __m128i *pixels_4x_p = (const __m128i *) pixels; const __m128i *sym_mask_4x_p = (const __m128i *) sym_mask_u32; gint i; fg_4x_u32 = _mm_set1_epi32 (CHAFA_COLOR8_U32 (color_pair->colors [CHAFA_COLOR_PAIR_FG])); fg_4x_u64 = _mm256_cvtepu8_epi16 (fg_4x_u32); bg_4x_u32 = _mm_set1_epi32 (CHAFA_COLOR8_U32 (color_pair->colors [CHAFA_COLOR_PAIR_BG])); bg_4x_u64 = _mm256_cvtepu8_epi16 (bg_4x_u32); for (i = 0; i < CHAFA_SYMBOL_N_PIXELS / 4; i++) { __m128i pixels_4x, sym_mask_4x; __m256i p0, m0, fg0, bg0, d0; pixels_4x = _mm_loadu_si128 (pixels_4x_p++); sym_mask_4x = _mm_loadu_si128 (sym_mask_4x_p++); p0 = _mm256_cvtepu8_epi16 (pixels_4x); m0 = _mm256_cvtepi8_epi16 (sym_mask_4x); fg0 = _mm256_and_si256 (m0, _mm256_sub_epi16 (fg_4x_u64, p0)); bg0 = _mm256_andnot_si256 (m0, _mm256_sub_epi16 (bg_4x_u64, p0)); d0 = _mm256_or_si256 (fg0, bg0); d0 = _mm256_madd_epi16 (d0, d0); err_8x_u32 = _mm256_add_epi32 (err_8x_u32, d0); } err_4x_u32 = _mm_add_epi32 (_mm256_extracti128_si256 (err_8x_u32, 0), _mm256_extracti128_si256 (err_8x_u32, 1)); err_4x_u32 = _mm_hadd_epi32 (err_4x_u32, err_4x_u32); err_4x_u32 = _mm_hadd_epi32 (err_4x_u32, err_4x_u32); return _mm_extract_epi32 (err_4x_u32, 0); } void calc_colors_avx2 (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint32 *sym_mask_u32) { const __m128i *pixels_4x_p = (const __m128i *) pixels; const __m128i *sym_mask_4x_p = (const __m128i *) sym_mask_u32; __m256i accum_fg = { 0 }; __m256i accum_bg = { 0 }; __m128i accum_fg_128; __m128i accum_bg_128; guint64 accums_u64 [2]; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS / 4; i++) { __m128i pixels_4x, sym_mask_4x; pixels_4x = _mm_loadu_si128 (pixels_4x_p++); sym_mask_4x = _mm_loadu_si128 (sym_mask_4x_p++); accum_fg = _mm256_add_epi16 (accum_fg, _mm256_cvtepu8_epi16 (_mm_and_si128 (sym_mask_4x, pixels_4x))); accum_bg = _mm256_add_epi16 (accum_bg, _mm256_cvtepu8_epi16 (_mm_andnot_si128 (sym_mask_4x, pixels_4x))); } accum_bg_128 = _mm_add_epi16 (_mm256_extracti128_si256 (accum_bg, 0), _mm256_extracti128_si256 (accum_bg, 1)); accums_u64 [0] = extract_128_epi64 (accum_bg_128, 0) + extract_128_epi64 (accum_bg_128, 1); accum_fg_128 = _mm_add_epi16 (_mm256_extracti128_si256 (accum_fg, 0), _mm256_extracti128_si256 (accum_fg, 1)); accums_u64 [1] = extract_128_epi64 (accum_fg_128, 0) + extract_128_epi64 (accum_fg_128, 1); memcpy (accums_out, accums_u64, 2 * sizeof (guint64)); } /* 32768 divided by index. Divide by zero is defined as zero. */ static const guint16 invdiv16 [257] = { 0, 32768, 16384, 10922, 8192, 6553, 5461, 4681, 4096, 3640, 3276, 2978, 2730, 2520, 2340, 2184, 2048, 1927, 1820, 1724, 1638, 1560, 1489, 1424, 1365, 1310, 1260, 1213, 1170, 1129, 1092, 1057, 1024, 992, 963, 936, 910, 885, 862, 840, 819, 799, 780, 762, 744, 728, 712, 697, 682, 668, 655, 642, 630, 618, 606, 595, 585, 574, 564, 555, 546, 537, 528, 520, 512, 504, 496, 489, 481, 474, 468, 461, 455, 448, 442, 436, 431, 425, 420, 414, 409, 404, 399, 394, 390, 385, 381, 376, 372, 368, 364, 360, 356, 352, 348, 344, 341, 337, 334, 330, 327, 324, 321, 318, 315, 312, 309, 306, 303, 300, 297, 295, 292, 289, 287, 284, 282, 280, 277, 275, 273, 270, 268, 266, 264, 262, 260, 258, 256, 254, 252, 250, 248, 246, 244, 242, 240, 239, 237, 235, 234, 232, 230, 229, 227, 225, 224, 222, 221, 219, 218, 217, 215, 214, 212, 211, 210, 208, 207, 206, 204, 203, 202, 201, 199, 198, 197, 196, 195, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 173, 172, 171, 170, 169, 168, 168, 167, 166, 165, 164, 163, 163, 162, 161, 160, 159, 159, 158, 157, 156, 156, 155, 154, 153, 153, 152, 151, 151, 150, 149, 148, 148, 147, 146, 146, 145, 144, 144, 143, 143, 142, 141, 141, 140, 140, 139, 138, 138, 137, 137, 136, 135, 135, 134, 134, 133, 133, 132, 132, 131, 131, 130, 130, 129, 129, 128, 128 }; /* Divisor must be in the range [0..256] inclusive. */ void chafa_color_accum_div_scalar_avx2 (ChafaColorAccum *accum, guint16 divisor) { __m128i accum_128, divisor_128; guint64 accum_u64; /* Not using _mm_loadu_si64() here because it's not available on * older versions of GCC. The opcode is the same. */ accum_128 = _mm_loadl_epi64 ((const __m128i *) accum); divisor_128 = _mm_set1_epi16 (invdiv16 [divisor]); accum_128 = _mm_mulhrs_epi16 (accum_128, divisor_128); accum_u64 = extract_128_epi64 (accum_128, 0); memcpy (accum, &accum_u64, sizeof (guint64)); } chafa-1.14.5/chafa/internal/chafa-base64.c000066400000000000000000000063601471154763100200300ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "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] << 2) & 0x3c]); g_string_append_c (gs_out, '='); } base64->buf_len = 0; } chafa-1.14.5/chafa/internal/chafa-base64.h000066400000000000000000000026721471154763100200370ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-batch.c000066400000000000000000000066441471154763100200320ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "chafa.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 = NULL; ChafaBatchInfo *batches; gint n_threads; gint n_units; gfloat units_per_batch; gfloat ofs [2] = { .0f, .0f }; gint i; g_assert (n_batches >= 1); g_assert (batch_unit >= 1); if (n_rows < 1) return; n_threads = MIN (chafa_get_n_actual_threads (), n_batches); n_units = (n_rows + batch_unit - 1) / batch_unit; units_per_batch = (gfloat) n_units / (gfloat) n_batches; batches = g_new0 (ChafaBatchInfo, n_batches); if (n_threads >= 2) thread_pool = g_thread_pool_new (batch_func, (gpointer) ctx, n_threads, 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 || i == n_batches - 1) { ofs [1] = n_rows + 0.5; row_ofs [1] = n_rows; } if (row_ofs [0] >= row_ofs [1]) { /* Save the number of batches actually produced to use in * post_func loop later. */ n_batches = i; 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 if (n_threads >= 2) { g_thread_pool_push (thread_pool, batch, NULL); } else { batch_func (batch, ctx); } ofs [0] = ofs [1]; } if (n_threads >= 2) { /* 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.14.5/chafa/internal/chafa-batch.h000066400000000000000000000023411471154763100200250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-bitfield.h000066400000000000000000000042201471154763100205240ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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__ #include #include 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.14.5/chafa/internal/chafa-canvas-internal.h000066400000000000000000000054741471154763100220430ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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; /* Whether to consider inverted symbols; FALSE if using FG only */ guint consider_inverted : 1; /* Whether to extract symbol colors; FALSE if using default colors */ guint extract_colors : 1; /* Whether to quantize colors before calculating error (slower, but * yields better results in palettized modes, especially 16/8) */ guint use_quantized_error : 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; /* Character to use in cells where fg color == bg color and the color * is only legal in FG. Typically 0x2588 (solid block), but could be * something else depending on the symbol map. Can be zero if there is * no good candidate! */ gunichar solid_char; ChafaCanvasConfig config; /* Used when setting pixel data */ ChafaDither dither; /* This is NULL in CHAFA_PIXEL_MODE_SYMBOLS, otherwise one of: * (ChafaSixelCanvas *), (ChafaKittyCanvas *), (ChafaIterm2Canvas *) */ gpointer pixel_canvas; /* It's possible to have a single placement that covers the entire * canvas. In this case, it is stored here. */ ChafaPlacement *placement; /* Our palettes. Kind of a big structure, so they go last. */ ChafaPalette fg_palette; ChafaPalette bg_palette; }; G_END_DECLS #endif /* __CHAFA_CANVAS_INTERNAL_H__ */ chafa-1.14.5/chafa/internal/chafa-canvas-printer.c000066400000000000000000000575511471154763100217100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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; guint cur_bold : 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_fg (PrintCtx *ctx, gchar *out) { out = chafa_term_info_emit_reset_color_fg (ctx->term_info, out); ctx->cur_fg = CHAFA_PALETTE_INDEX_TRANSPARENT; ctx->cur_fg_direct.ch [3] = 0; 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_bold = 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_attrs_with_reuse (PrintCtx *ctx, gchar *out, guint32 fg, guint32 bg, gboolean inverted, gboolean bold) { /* In FG-only mode, we can't use inverse color or bold because the * attribute reset would mess with the background color. */ if (ctx->canvas->config.fg_only_enabled) return out; if ((ctx->cur_inverted && !inverted) || (ctx->cur_bold && !bold) || (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); } if (!ctx->cur_bold && bold) { out = flush_chars (ctx, out); out = chafa_term_info_emit_enable_bold (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_attrs_with_reuse (ctx, out, fg, bg, inverted, FALSE); 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_attrs_with_reuse (ctx, out, fg, bg, inverted, FALSE); 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_attributes_16_8 (PrintCtx *ctx, gchar *out, guint32 fg, guint32 bg, gboolean inverted) { if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) { out = handle_attrs_with_reuse (ctx, out, fg, bg, inverted, fg > 7 && fg < 256 ? TRUE : FALSE); 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_8 (ctx->term_info, out, fg & 7, bg); } else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fg_8 (ctx->term_info, out, fg & 7); } } else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_bg_8 (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 > 7) out = chafa_term_info_emit_enable_bold (ctx->term_info, out); if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_fgbg_8 (ctx->term_info, out, fg & 7, bg); } else { out = chafa_term_info_emit_set_color_fg_8 (ctx->term_info, out, fg & 7); } } else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_bg_8 (ctx->term_info, out, bg); } } ctx->cur_fg = fg; ctx->cur_bg = bg; ctx->cur_inverted = inverted; ctx->cur_bold = fg > 7 && fg < 256 ? TRUE : FALSE; return out; } /* Uses bold for bright FG colors. */ G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_16_8 (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_8 (ctx, out, bg, fg, TRUE); else out = emit_attributes_16_8 (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; } } G_GNUC_WARN_UNUSED_RESULT static gchar * build_ansi_row (PrintCtx *ctx, gint row, gchar *out) { ChafaCanvas *canvas; gint i, i_max; canvas = ctx->canvas; i = row * canvas->config.width; i_max = (row + 1) * canvas->config.width; if (row == 0 && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG) { if (canvas->config.fg_only_enabled) out = reset_fg (ctx, out); else out = reset_attributes (ctx, out); } switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: out = emit_ansi_truecolor (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: out = emit_ansi_256 (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_INDEXED_16: out = emit_ansi_16 (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_INDEXED_16_8: out = emit_ansi_16_8 (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_INDEXED_8: out = emit_ansi_16 (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_FGBG_BGFG: out = emit_ansi_fgbg_bgfg (ctx, out, i, i_max); break; case CHAFA_CANVAS_MODE_FGBG: out = emit_ansi_fgbg (ctx, out, i, i_max); 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) { if (canvas->config.fg_only_enabled) out = reset_fg (ctx, out); else out = reset_attributes (ctx, out); } return out; } static GString * build_ansi_gstring (ChafaCanvas *canvas, ChafaTermInfo *ti) { GString *gs = g_string_new (""); PrintCtx ctx = { 0 }; gint i; ctx.canvas = canvas; ctx.term_info = ti; for (i = 0; i < canvas->config.height; i++) { gchar *out; prealloc_string (gs, canvas->config.width); out = gs->str + gs->len; out = build_ansi_row (&ctx, i, out); /* Rows end in a newline, except for the last one. This allows for * filling the display area while avoiding a blank bottom row. */ if (i < canvas->config.height - 1) *(out++) = '\n'; *out = '\0'; gs->len = out - gs->str; } return gs; } static void build_ansi_gstring_array (ChafaCanvas *canvas, ChafaTermInfo *ti, GString ***array_out, gint *array_len_out) { PrintCtx ctx = { 0 }; GString **array; gint i; ctx.canvas = canvas; ctx.term_info = ti; array = g_new (GString *, canvas->config.height + 1); for (i = 0; i < canvas->config.height; i++) { GString *gs = g_string_new (""); gchar *out; prealloc_string (gs, canvas->config.width); out = gs->str + gs->len; out = build_ansi_row (&ctx, i, out); *out = '\0'; gs->len = out - gs->str; array [i] = gs; } array [canvas->config.height] = NULL; *array_out = array; if (array_len_out) *array_len_out = canvas->config.height; } GString * chafa_canvas_print_symbols (ChafaCanvas *canvas, ChafaTermInfo *ti) { g_assert (canvas != NULL); g_assert (ti != NULL); return build_ansi_gstring (canvas, ti); } void chafa_canvas_print_symbol_rows (ChafaCanvas *canvas, ChafaTermInfo *ti, GString ***array_out, gint *array_len_out) { g_assert (canvas != NULL); g_assert (ti != NULL); g_assert (array_out != NULL); build_ansi_gstring_array (canvas, ti, array_out, array_len_out); } chafa-1.14.5/chafa/internal/chafa-canvas-printer.h000066400000000000000000000025501471154763100217020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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); void chafa_canvas_print_symbol_rows (ChafaCanvas *canvas, ChafaTermInfo *ti, GString ***array_out, gint *array_len_out); G_END_DECLS #endif /* __CHAFA_CANVAS_PRINTER_H__ */ chafa-1.14.5/chafa/internal/chafa-color-hash.c000066400000000000000000000025361471154763100210040ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-color-hash.h000066400000000000000000000036161471154763100210110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-color-table.c000066400000000000000000000251341471154763100211470ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-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, gint64 *best_diff) { const ChafaColorTableEntry *pj = &color_table->entries [j]; gint64 a, b, d; a = POW2 ((gint64) pj->v [0] - v [0]); profile_counter_inc (n_a); DEBUG_PEN_CHOICE (g_printerr ("a=%d\n", a)); if (a <= *best_diff) { /* TODO: When using gint32, the POW2 multiplication can result in overflow. * Our workaround is to use gint64 for best_diff, a, b and d. This probably * slows us down, though (we should measure). Is there a better fix? */ b = POW2 ((gint64) 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) { gint64 best_diff = G_MAXINT64; 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; gint64 best_diff_2 = G_MAXINT64; for (i = 0; i < color_table->n_entries; i++) { const ChafaColorTableEntry *pi = &color_table->entries [i]; gint64 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.14.5/chafa/internal/chafa-color-table.h000066400000000000000000000037361471154763100211600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-color.c000066400000000000000000000106501471154763100200570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" #include "internal/chafa-color.h" guint32 chafa_pack_color (const ChafaColor *color) { /* Assumes each channel 0 <= value <= 255 */ return ((guint32) color->ch [0] << 16) | ((guint32) color->ch [1] << 8) | ((guint32) color->ch [2]) | ((guint32) 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) { #ifdef HAVE_AVX2_INTRINSICS if (chafa_have_avx2 ()) { chafa_color_accum_div_scalar_avx2 (accum, scalar); } else #endif { 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]; } chafa-1.14.5/chafa/internal/chafa-color.h000066400000000000000000000065551471154763100200750ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 */ 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])) void chafa_color_accum_div_scalar (ChafaColorAccum *color, gint scalar); void chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99); G_END_DECLS #endif /* __CHAFA_COLOR_H__ */ chafa-1.14.5/chafa/internal/chafa-dither.c000066400000000000000000000061371471154763100202250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "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.14.5/chafa/internal/chafa-dither.h000066400000000000000000000031201471154763100202170ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-indexed-image.c000066400000000000000000000443671471154763100214550ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-math-util.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 (gpointer row_inout, int width, void *user_data) { const DrawPixelsCtx *ctx = user_data; guint32 *row_inout_u32 = row_inout; guint32 *row_inout_end = row_inout_u32 + width; /* Composite on solid background color */ for ( ; row_inout_u32 < row_inout_end; row_inout_u32++) { ChafaColor c = chafa_color8_fetch_from_rgba8 (row_inout_u32); *row_inout_u32 += 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 ChafaPalette *palette, ChafaColorSpace color_space, ChafaColorHash *color_hash, ChafaColor color) { ChafaColor cached_color; gint index; if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (palette)) return chafa_palette_get_transparent_index (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 (color_space == CHAFA_COLOR_SPACE_DIN99D) chafa_color_rgb_to_din99d (&color, &color); index = chafa_palette_lookup_nearest (palette, color_space, &color, NULL) - chafa_palette_get_first_color (palette); /* Don't insert transparent pixels, since color hash does not store transparency */ if (index != chafa_palette_get_transparent_index (palette)) chafa_color_hash_replace (color_hash, CHAFA_COLOR8_U32 (cached_color), index); } return index; } static gint quantize_pixel_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, ChafaColor color, ChafaColorAccum *error_inout) { gint index; if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (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 (palette); } if (color_space == CHAFA_COLOR_SPACE_DIN99D) chafa_color_rgb_to_din99d (&color, &color); index = chafa_palette_lookup_with_error (palette, color_space, color, error_inout) - chafa_palette_get_first_color (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->indexed_image->palette, ctx->color_space, 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->indexed_image->palette, ctx->color_space, 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->indexed_image->palette, ctx->color_space, 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] = g_malloc (ctx->dest_width * sizeof (ChafaColorAccum)); error_row [1] = g_malloc (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; } g_free (error_row [1]); g_free (error_row [0]); } 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, chafa_get_n_actual_threads (), 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 : chafa_get_n_actual_threads (), 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_try_malloc ((gsize) width * height); if (!indexed_image->pixels) { #if 0 g_warning ("ChafaIndexedImage: Out of memory allocating %ux%u pixels.", width, height); #endif g_free (indexed_image); return NULL; } 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck) { DrawPixelsCtx ctx; ChafaColor bg; gint placement_x, placement_y; gint placement_width, placement_height; 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; #if 0 /* FIXME: Need a new smolscale compositing mode that preserves src * alpha before this can be implemented */ bg = *chafa_palette_get_color (&indexed_image->palette, CHAFA_COLOR_SPACE_RGB, CHAFA_PALETTE_INDEX_BG); bg.ch [3] = 0xff; #else gen_color_lut_rgba8 (ctx.bg_color_lut, *chafa_palette_get_color (&indexed_image->palette, CHAFA_COLOR_SPACE_RGB, CHAFA_PALETTE_INDEX_BG)); #endif chafa_tuck_and_align (src_width, src_height, dest_width, dest_height, halign, valign, tuck, &placement_x, &placement_y, &placement_width, &placement_height); /* FIXME: Save temp memory by sampling the image in strips. ChafaPalette * will need a batch API for this. */ ctx.scaled_data = g_try_new (guint32, (gsize) dest_width * dest_height); if (!ctx.scaled_data) { #if 0 g_warning ("ChafaIndexedImage: Out of memory allocating %ux%u temp pixels.", dest_width, dest_height); #endif return; } ctx.scale_ctx = smol_scale_new_full (/* Source */ (const guint32 *) src_pixels, (SmolPixelType) src_pixel_type, src_width, src_height, src_rowstride, /* Fill */ #if 0 bg.ch, #else NULL, #endif SMOL_PIXEL_RGBA8_UNASSOCIATED, /* Destination */ NULL, SMOL_PIXEL_RGBA8_PREMULTIPLIED, dest_width, dest_height, dest_width * sizeof (guint32), /* Placement */ placement_x * SMOL_SUBPIXEL_MUL, placement_y * SMOL_SUBPIXEL_MUL, placement_width * SMOL_SUBPIXEL_MUL, placement_height * SMOL_SUBPIXEL_MUL, /* Extra args */ SMOL_COMPOSITE_SRC_CLEAR_DEST, #if 0 SMOL_NO_FLAGS, NULL, #else SMOL_DISABLE_SRGB_LINEARIZATION, post_scale_row, #endif &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.14.5/chafa/internal/chafa-indexed-image.h000066400000000000000000000040001471154763100214360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck); G_END_DECLS #endif /* __CHAFA_INDEXED_IMAGE_H__ */ chafa-1.14.5/chafa/internal/chafa-iterm2-canvas.c000066400000000000000000000244301471154763100214150ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-math-util.h" #include "internal/chafa-string-util.h" /* We support iTerm2 images by embedding them as uncompressed TIFF files. * * See: https://www.adobe.io/open/standards/TIFF.html */ 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; typedef enum { TIFF_EXTRA_SAMPLE_UNSPECIFIED = 0, TIFF_EXTRA_SAMPLE_ASSOC_ALPHA = 1, TIFF_EXTRA_SAMPLE_UNASSOC_ALPHA = 2 } TiffExtraSampleType; #define TIFF_PHOTOMETRIC_INTERPRETATION_RGB 2 #define TIFF_ORIENTATION_TOPLEFT 1 #define TIFF_PLANAR_CONFIGURATION_CONTIGUOUS 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck) { DrawCtx ctx; gint placement_x, placement_y; gint placement_width, placement_height; 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; chafa_tuck_and_align (src_width, src_height, iterm2_canvas->width, iterm2_canvas->height, halign, valign, tuck, &placement_x, &placement_y, &placement_width, &placement_height); ctx.iterm2_canvas = iterm2_canvas; ctx.scale_ctx = smol_scale_new_full (/* Source */ (const guint32 *) src_pixels, (SmolPixelType) src_pixel_type, src_width, src_height, src_rowstride, /* Fill */ NULL, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* Destination */ NULL, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* FIXME: Premul? */ iterm2_canvas->width, iterm2_canvas->height, iterm2_canvas->width * sizeof (guint32), /* Placement */ placement_x * SMOL_SUBPIXEL_MUL, placement_y * SMOL_SUBPIXEL_MUL, placement_width * SMOL_SUBPIXEL_MUL, placement_height * SMOL_SUBPIXEL_MUL, /* Extra args */ SMOL_COMPOSITE_SRC_CLEAR_DEST, SMOL_NO_FLAGS, NULL, &ctx); chafa_process_batches (&ctx, (GFunc) draw_pixels_worker, NULL, iterm2_canvas->height, chafa_get_n_actual_threads (), 1); smol_scale_destroy (ctx.scale_ctx); } 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); /* For BitsPerSample, the data field points to external data towards the end of file */ generate_tag (&base64, out_str, TIFF_TAG_BITS_PER_SAMPLE, TIFF_TYPE_SHORT, 4, iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32) + sizeof (guint32) * 2 + sizeof (guint16) + sizeof (TiffTag) * 11 /* Tag count */ + sizeof (guint32)); 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_UNASSOC_ALPHA); /* Next IFD offset (terminator) */ u32 = GUINT32_TO_LE (0); chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); /* Bits per sample external data */ u16 = GUINT16_TO_LE (8); chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); 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.14.5/chafa/internal/chafa-iterm2-canvas.h000066400000000000000000000034571471154763100214300ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck); 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.14.5/chafa/internal/chafa-kitty-canvas.c000066400000000000000000000432741471154763100213660ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-math-util.h" #include "internal/chafa-kitty-canvas.h" #include "internal/chafa-passthrough-encoder.h" #include "internal/chafa-pixops.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; /* Kitty's cell-based placeholders use Unicode diacritics to encode each * cell's row/col offsets. The below table maps integers to code points * using this scheme. */ #define ROWCOLUMN_UNICHAR 0x10eeeeU #define ENCODING_DIACRITIC_MAX 297 static const guint32 encoding_diacritics [ENCODING_DIACRITIC_MAX] = { 0x0305, 0x030d, 0x030e, 0x0310, 0x0312, 0x033d, 0x033e, 0x033f, 0x0346, 0x034a, 0x034b, 0x034c, 0x0350, 0x0351, 0x0352, 0x0357, 0x035b, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0592, 0x0593, 0x0594, 0x0595, 0x0597, 0x0598, 0x0599, 0x059c, 0x059d, 0x059e, 0x059f, 0x05a0, 0x05a1, 0x05a8, 0x05a9, 0x05ab, 0x05ac, 0x05af, 0x05c4, 0x0610, 0x0611, 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617, 0x0657, 0x0658, 0x0659, 0x065a, 0x065b, 0x065d, 0x065e, 0x06d6, 0x06d7, 0x06d8, 0x06d9, 0x06da, 0x06db, 0x06dc, 0x06df, 0x06e0, 0x06e1, 0x06e2, 0x06e4, 0x06e7, 0x06e8, 0x06eb, 0x06ec, 0x0730, 0x0732, 0x0733, 0x0735, 0x0736, 0x073a, 0x073d, 0x073f, 0x0740, 0x0741, 0x0743, 0x0745, 0x0747, 0x0749, 0x074a, 0x07eb, 0x07ec, 0x07ed, 0x07ee, 0x07ef, 0x07f0, 0x07f1, 0x07f3, 0x0816, 0x0817, 0x0818, 0x0819, 0x081b, 0x081c, 0x081d, 0x081e, 0x081f, 0x0820, 0x0821, 0x0822, 0x0823, 0x0825, 0x0826, 0x0827, 0x0829, 0x082a, 0x082b, 0x082c, /* 128 */ 0x082d, 0x0951, 0x0953, 0x0954, 0x0f82, 0x0f83, 0x0f86, 0x0f87, 0x135d, 0x135e, 0x135f, 0x17dd, 0x193a, 0x1a17, 0x1a75, 0x1a76, 0x1a77, 0x1a78, 0x1a79, 0x1a7a, 0x1a7b, 0x1a7c, 0x1b6b, 0x1b6d, 0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1cd0, 0x1cd1, 0x1cd2, 0x1cda, 0x1cdb, 0x1ce0, 0x1dc0, 0x1dc1, 0x1dc3, 0x1dc4, 0x1dc5, 0x1dc6, 0x1dc7, 0x1dc8, 0x1dc9, 0x1dcb, 0x1dcc, 0x1dd1, 0x1dd2, 0x1dd3, 0x1dd4, 0x1dd5, 0x1dd6, 0x1dd7, 0x1dd8, 0x1dd9, 0x1dda, 0x1ddb, 0x1ddc, 0x1ddd, 0x1dde, 0x1ddf, 0x1de0, 0x1de1, 0x1de2, 0x1de3, 0x1de4, 0x1de5, 0x1de6, 0x1dfe, 0x20d0, 0x20d1, 0x20d4, 0x20d5, 0x20d6, 0x20d7, 0x20db, 0x20dc, 0x20e1, 0x20e7, 0x20e9, 0x20f0, 0x2cef, 0x2cf0, 0x2cf1, 0x2de0, 0x2de1, 0x2de2, 0x2de3, 0x2de4, 0x2de5, 0x2de6, 0x2de7, 0x2de8, 0x2de9, 0x2dea, 0x2deb, 0x2dec, 0x2ded, 0x2dee, 0x2def, 0x2df0, 0x2df1, 0x2df2, 0x2df3, 0x2df4, 0x2df5, 0x2df6, 0x2df7, 0x2df8, 0x2df9, 0x2dfa, 0x2dfb, 0x2dfc, 0x2dfd, 0x2dfe, 0x2dff, 0xa66f, 0xa67c, 0xa67d, 0xa6f0, 0xa6f1, 0xa8e0, 0xa8e1, 0xa8e2, 0xa8e3, 0xa8e4, 0xa8e5, /* 256 */ 0xa8e6, 0xa8e7, 0xa8e8, 0xa8e9, 0xa8ea, 0xa8eb, 0xa8ec, 0xa8ed, 0xa8ee, 0xa8ef, 0xa8f0, 0xa8f1, 0xaab0, 0xaab2, 0xaab3, 0xaab7, 0xaab8, 0xaabe, 0xaabf, 0xaac1, 0xfe20, 0xfe21, 0xfe22, 0xfe23, 0xfe24, 0xfe25, 0xfe26, 0x10a0f, 0x10a38, 0x1d185, 0x1d186, 0x1d187, 0x1d188, 0x1d189, 0x1d1aa, 0x1d1ab, 0x1d1ac, 0x1d1ad, 0x1d242, 0x1d243, 0x1d244 /* 297 */ }; ChafaKittyCanvas * chafa_kitty_canvas_new (gint width, gint height) { ChafaKittyCanvas *kitty_canvas; kitty_canvas = g_new0 (ChafaKittyCanvas, 1); kitty_canvas->width = width; kitty_canvas->height = height; kitty_canvas->rgba_image = g_try_malloc ((gsize) width * height * sizeof (guint32)); if (!kitty_canvas->rgba_image) { #if 0 g_warning ("ChafaKittyCanvas: Out of memory allocating %ux%u pixels.", width, height); #endif g_free (kitty_canvas); kitty_canvas = NULL; } 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, ChafaColor bg_color, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck) { uint8_t bg_color_rgba [4]; DrawCtx ctx; gboolean flatten_alpha; gint placement_x, placement_y; gint placement_width, placement_height; 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; flatten_alpha = bg_color.ch [3] == 0; bg_color.ch [3] = 0xff; chafa_color8_store_to_rgba8 (bg_color, bg_color_rgba); chafa_tuck_and_align (src_width, src_height, kitty_canvas->width, kitty_canvas->height, halign, valign, tuck, &placement_x, &placement_y, &placement_width, &placement_height); ctx.kitty_canvas = kitty_canvas; ctx.scale_ctx = smol_scale_new_full (/* Source */ (const guint32 *) src_pixels, (SmolPixelType) src_pixel_type, src_width, src_height, src_rowstride, /* Fill */ flatten_alpha ? bg_color_rgba : NULL, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* Destination */ NULL, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* FIXME: Opaque? */ kitty_canvas->width, kitty_canvas->height, kitty_canvas->width * sizeof (guint32), /* Placement */ placement_x * SMOL_SUBPIXEL_MUL, placement_y * SMOL_SUBPIXEL_MUL, placement_width * SMOL_SUBPIXEL_MUL, placement_height * SMOL_SUBPIXEL_MUL, /* Extra args */ SMOL_COMPOSITE_SRC_CLEAR_DEST, SMOL_NO_FLAGS, NULL, &ctx); chafa_process_batches (&ctx, (GFunc) draw_pixels_worker, NULL, kitty_canvas->height, chafa_get_n_actual_threads (), 1); smol_scale_destroy (ctx.scale_ctx); } 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); } static void end_passthrough (ChafaPassthroughEncoder *ptenc) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; if (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN) { gint i; *chafa_term_info_emit_end_screen_passthrough (ptenc->term_info, buf) = '\0'; for (i = 0; buf [i]; i++) { chafa_passthrough_encoder_flush (ptenc); chafa_passthrough_encoder_append_len (ptenc, buf + i, 1); } } else if (ptenc->mode == CHAFA_PASSTHROUGH_TMUX) { *chafa_term_info_emit_end_tmux_passthrough (ptenc->term_info, buf) = '\0'; chafa_passthrough_encoder_flush (ptenc); g_string_append (ptenc->out, buf); } chafa_passthrough_encoder_flush (ptenc); } static void build_image_chunks (ChafaKittyCanvas *kitty_canvas, ChafaPassthroughEncoder *ptenc) { const guint8 *p, *last; gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; last = ((guint8 *) 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 + (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN ? 64 : 512); if (end > last) end = last; *chafa_term_info_emit_begin_kitty_image_chunk (ptenc->term_info, seq) = '\0'; chafa_passthrough_encoder_append (ptenc, seq); encode_chunk (ptenc->out, p, end); *chafa_term_info_emit_end_kitty_image_chunk (ptenc->term_info, seq) = '\0'; chafa_passthrough_encoder_append (ptenc, seq); chafa_passthrough_encoder_reset (ptenc); end_passthrough (ptenc); p = end; } *chafa_term_info_emit_end_kitty_image (ptenc->term_info, seq) = '\0'; chafa_passthrough_encoder_append (ptenc, seq); chafa_passthrough_encoder_reset (ptenc); end_passthrough (ptenc); } static void build_immediate (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells) { ChafaPassthroughEncoder ptenc; gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; chafa_passthrough_encoder_begin (&ptenc, CHAFA_PASSTHROUGH_NONE, term_info, out_str); *chafa_term_info_emit_begin_kitty_immediate_image_v1 (term_info, seq, 32, kitty_canvas->width, kitty_canvas->height, width_cells, height_cells) = '\0'; chafa_passthrough_encoder_append (&ptenc, seq); chafa_passthrough_encoder_flush (&ptenc); build_image_chunks (kitty_canvas, &ptenc); chafa_passthrough_encoder_end (&ptenc); } static gboolean screen_is_wide_diacritic (gint diacritic_index) { if (diacritic_index == 35 || diacritic_index == 61 || diacritic_index == 62) return TRUE; return FALSE; } static void build_begin_row (ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint row, ChafaPassthrough passthrough) { gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 1]; gchar *p0; if (row > 0) { /* Screen advances the cursor by one position too much for some of the * diacritics. We compensate for the first few, since they will come up * fairly frequently. We don't compensate for every single instance, * since Screen only exhibits this behavior when printing and scrolling * up in the current dpy, and not when scrolling down or redrawing after * switching dpys, making the corrected graphics illegible in those * cases. * * I.e. there's no perfect workaround here, so we try to make the common * case look good and the uncommon case not terrible. * * Another option would've been to save/restore the cursor position * between rows, but we don't want to clobber the register, as the CLI * tool uses it to home the cursor between animation frames. It's * also good policy in general to reserve it for client use. */ p0 = chafa_term_info_emit_cursor_left (term_info, seq, width_cells + ((passthrough == CHAFA_PASSTHROUGH_SCREEN && screen_is_wide_diacritic (row)) ? 1 : 0)); p0 = chafa_term_info_emit_cursor_down_scroll (term_info, p0); g_string_append_len (out_str, seq, p0 - seq); } } static void build_unicode_placement (ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells, gint placement_id, ChafaPassthrough passthrough) { gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 1]; gchar *p0; gchar *row; gint row_ofs; gint i, j; g_assert (placement_id >= 1); g_assert (placement_id <= 255); width_cells = MIN (width_cells, ENCODING_DIACRITIC_MAX - 1); height_cells = MIN (height_cells, ENCODING_DIACRITIC_MAX - 1); row = g_malloc (width_cells * (6 * 3) + 1); for (i = 0; i < height_cells; i++) { /* Reposition after previous row */ build_begin_row (term_info, out_str, width_cells, i, passthrough); /* Encode the image ID in the foreground color */ p0 = chafa_term_info_emit_set_color_fg_256 (term_info, seq, placement_id); g_string_append_len (out_str, seq, p0 - seq); /* Print the row */ row_ofs = 0; for (j = 0; j < width_cells; j++) { row_ofs += g_unichar_to_utf8 (ROWCOLUMN_UNICHAR, row + row_ofs); /* Screen has issues with some diacritics. We can compensate for this once * per row, but doing it for every col is pushing it. So we omit all offsets * except the row offsets in the first col. This harms overlapping images * and horizontal scrolling, but oh well. */ if (passthrough != CHAFA_PASSTHROUGH_SCREEN || j == 0) row_ofs += g_unichar_to_utf8 (encoding_diacritics [i], row + row_ofs); if (passthrough != CHAFA_PASSTHROUGH_SCREEN) row_ofs += g_unichar_to_utf8 (encoding_diacritics [j], row + row_ofs); } g_string_append_len (out_str, row, row_ofs); } /* Reset foreground color */ p0 = chafa_term_info_emit_reset_color_fg (term_info, seq); g_string_append_len (out_str, seq, p0 - seq); g_free (row); } static void build_unicode_virtual (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells, gint placement_id, ChafaPassthrough passthrough) { ChafaPassthroughEncoder ptenc; gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; chafa_passthrough_encoder_begin (&ptenc, passthrough, term_info, out_str); *chafa_term_info_emit_begin_kitty_immediate_virt_image_v1 (term_info, seq, 32, kitty_canvas->width, kitty_canvas->height, width_cells, height_cells, placement_id) = '\0'; chafa_passthrough_encoder_append (&ptenc, seq); chafa_passthrough_encoder_reset (&ptenc); end_passthrough (&ptenc); build_image_chunks (kitty_canvas, &ptenc); end_passthrough (&ptenc); chafa_passthrough_encoder_end (&ptenc); build_unicode_placement (term_info, out_str, width_cells, height_cells, placement_id, passthrough); } void chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells, gint placement_id, ChafaPassthrough passthrough) { if (passthrough == CHAFA_PASSTHROUGH_NONE) { build_immediate (kitty_canvas, term_info, out_str, width_cells, height_cells); } else { /* Make IDs in the first <256 range predictable, but as the range * cycles we add one to skip over every ID==0 */ if (placement_id > 255) placement_id = 1 + (placement_id % 255); build_unicode_virtual (kitty_canvas, term_info, out_str, width_cells, height_cells, placement_id, passthrough); } } chafa-1.14.5/chafa/internal/chafa-kitty-canvas.h000066400000000000000000000037731471154763100213730ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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, ChafaColor bg_color, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck); void chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells, gint placement_id, ChafaPassthrough passthrough); G_END_DECLS #endif /* __CHAFA_KITTY_CANVAS_H__ */ chafa-1.14.5/chafa/internal/chafa-math-util.c000066400000000000000000000063411471154763100206470ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "internal/chafa-private.h" #include "internal/chafa-math-util.h" static gint align_dim (ChafaAlign align, gint src_size, gint dest_size) { gint ofs; g_return_val_if_fail (src_size <= dest_size, 0); switch (align) { case CHAFA_ALIGN_START: ofs = 0; break; case CHAFA_ALIGN_CENTER: ofs = (dest_size - src_size) / 2; break; case CHAFA_ALIGN_END: ofs = dest_size - src_size; break; default: g_assert_not_reached (); } return ofs; } void chafa_tuck_and_align (gint src_width, gint src_height, gint dest_width, gint dest_height, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck, gint *ofs_x_out, gint *ofs_y_out, gint *width_out, gint *height_out) { gfloat ratio [2]; switch (tuck) { case CHAFA_TUCK_STRETCH: *ofs_x_out = 0; *ofs_y_out = 0; *width_out = dest_width; *height_out = dest_height; break; case CHAFA_TUCK_SHRINK_TO_FIT: if (src_width <= dest_width && src_height <= dest_height) { /* Image fits entirely in dest. Do alignment only, no scaling. */ *width_out = src_width; *height_out = src_height; break; } /* Fall through */ case CHAFA_TUCK_FIT: ratio [0] = (gfloat) dest_width / (gfloat) src_width; ratio [1] = (gfloat) dest_height / (gfloat) src_height; *width_out = ceilf (src_width * MIN (ratio [0], ratio [1])); *height_out = ceilf (src_height * MIN (ratio [0], ratio [1])); break; default: g_assert_not_reached (); } /* Never exceed the dest size */ *width_out = MIN (*width_out, dest_width); *height_out = MIN (*height_out, dest_height); *ofs_x_out = align_dim (halign, *width_out, dest_width); *ofs_y_out = align_dim (valign, *height_out, dest_height); #if 0 g_printerr ("src=(%dx%d) dest=(%dx%d) out=(%dx%d) @ (%d,%d)\n", src_width, src_height, dest_width, dest_height, *width_out, *height_out, *ofs_x_out, *ofs_y_out); #endif } chafa-1.14.5/chafa/internal/chafa-math-util.h000066400000000000000000000025111471154763100206470ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_MATH_UTIL_H__ #define __CHAFA_MATH_UTIL_H__ #include G_BEGIN_DECLS void chafa_tuck_and_align (gint src_width, gint src_height, gint dest_width, gint dest_height, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck, gint *ofs_x_out, gint *ofs_y_out, gint *width_out, gint *height_out); G_END_DECLS #endif /* __CHAFA_MATH_UTIL_H__ */ chafa-1.14.5/chafa/internal/chafa-mmx.c000066400000000000000000000033301471154763100175370ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-palette.c000066400000000000000000000761061471154763100204070ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 /* memcpy, memset */ #include /* pow, cbrt, log, sqrt, atan2, cos, sin */ #include "chafa.h" #include "internal/chafa-private.h" #define DEBUG(x) /* ---------------- * * Color candidates * * ---------------- */ /* Some situations (like fill symbols) call for both a best and a second-best * match. ChafaColorCandidates is used to track and return these. */ 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; } /* -------------------- * * Fixed system palette * * -------------------- */ static const guint32 term_colors_256 [CHAFA_PALETTE_INDEX_MAX] = { /* First 16 colors; these are usually set by the terminal and can vary quite a * bit. We try to strike a balance. */ 0x000000, 0x800000, 0x007000, 0x707000, 0x000070, 0x700070, 0x007070, 0xc0c0c0, 0x404040, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, /* 240 universal colors; a 216-entry color cube followed by 24 grays */ 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, /* Special colors */ 0x808080, /* Transparent */ 0xffffff, /* Terminal's default foreground */ 0x000000, /* Terminal's default background */ }; static ChafaPaletteColor fixed_palette_256 [CHAFA_PALETTE_INDEX_MAX]; static guchar color_cube_216_channel_index [256]; static gboolean palette_initialized; static const ChafaColor * get_fixed_palette_color (guint index, ChafaColorSpace color_space) { return &fixed_palette_256 [index].col [color_space]; } 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], &fixed_palette_256 [i].col [0]); chafa_color_rgb_to_din99d (&fixed_palette_256 [i].col [0], &fixed_palette_256 [i].col [1]); fixed_palette_256 [i].col [0].ch [3] = 0xff; /* Fully opaque */ fixed_palette_256 [i].col [1].ch [3] = 0xff; /* Fully opaque */ } /* Transparent color */ fixed_palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [0].ch [3] = 0x00; fixed_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; } 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 = get_fixed_palette_color (index, color_space); error = chafa_color_diff_fast (color, palette_color); update_candidates (candidates, index, error); return error; } static void pick_color_fixed_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_fixed_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 = get_fixed_palette_color (i + 1, color_space); error = chafa_color_diff_fast (color, palette_color); if (error < last_error) { update_candidates (candidates, i, error); last_error = error; step = 1; i++; } else { step = -1; } do { i += step; palette_color = get_fixed_palette_color (i, color_space); error = chafa_color_diff_fast (color, palette_color); if (error > last_error) break; update_candidates (candidates, i, error); last_error = error; } while (i >= 232 && i <= 255); } static void pick_color_fixed_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); } } static void pick_color_fixed_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); } } static void pick_color_fixed_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; if (color_space == CHAFA_COLOR_SPACE_RGB) { pick_color_fixed_216_cube (color, color_space, candidates); pick_color_fixed_24_grays (color, color_space, candidates); /* Do this last so ties are broken in favor of high-index colors. */ pick_color_fixed_16 (color, color_space, candidates); } else { for (i = 0; i < 256; i++) { update_candidates_with_color_index_diff (candidates, color_space, color, i); } } } static void pick_color_fixed_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; if (color_space == CHAFA_COLOR_SPACE_RGB) { pick_color_fixed_216_cube (color, color_space, candidates); pick_color_fixed_24_grays (color, color_space, candidates); } else { /* Check color cube, but not lower 16, bg or fg. Slow! */ for (i = 16; i < 256; 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 */ static void pick_color_fixed_fgbg (const ChafaColor *color, const ChafaColor *fg_color, const ChafaColor *bg_color, ChafaColorCandidates *candidates) { gint error; error = chafa_color_diff_fast (color, fg_color); update_candidates (candidates, CHAFA_PALETTE_INDEX_FG, error); error = chafa_color_diff_fast (color, bg_color); update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); } /* --------------------------------- * * Quantization for dynamic palettes * * --------------------------------- */ 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)); } } } /* --- * * API * * --- */ void chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type) { gint i; chafa_init_palette (); palette_out->type = type; palette_out->transparent_index = CHAFA_PALETTE_INDEX_TRANSPARENT; for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++) { palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB] = *get_fixed_palette_color (i, CHAFA_COLOR_SPACE_RGB); palette_out->colors [i].col [CHAFA_COLOR_SPACE_DIN99D] = *get_fixed_palette_color (i, CHAFA_COLOR_SPACE_DIN99D); } switch (type) { case CHAFA_PALETTE_TYPE_FIXED_FGBG: palette_out->first_color = CHAFA_PALETTE_INDEX_FG; palette_out->n_colors = 2; break; case CHAFA_PALETTE_TYPE_FIXED_8: palette_out->n_colors = 8; break; case CHAFA_PALETTE_TYPE_FIXED_16: palette_out->n_colors = 16; break; case CHAFA_PALETTE_TYPE_FIXED_240: palette_out->first_color = 16; palette_out->n_colors = 240; break; case CHAFA_PALETTE_TYPE_FIXED_256: palette_out->first_color = 0; palette_out->n_colors = 256; break; case CHAFA_PALETTE_TYPE_DYNAMIC_256: for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++) chafa_color_table_init (&palette_out->table [i]); break; } } 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 */ /* FIXME: Rowstride etc? */ 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; init_candidates (candidates); if (color->ch [3] < palette->alpha_threshold) { /* Transparency */ candidates->index [0] = palette->transparent_index; candidates->index [1] = palette->transparent_index; candidates->error [0] = 0; candidates->error [1] = 0; } else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_256) pick_color_fixed_256 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_240) pick_color_fixed_240 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_16) pick_color_fixed_16 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_8) pick_color_fixed_8 (color, color_space, candidates); else /* CHAFA_PALETTE_TYPE_FIXED_FGBG */ pick_color_fixed_fgbg (color, &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.9f) / 16); compensated_color.ch [1] = ((gint16) color.ch [1]) + ((error_inout->ch [1] * 0.9f) / 16); compensated_color.ch [2] = ((gint16) color.ch [2]) + ((error_inout->ch [2] * 0.9f) / 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.14.5/chafa/internal/chafa-palette.h000066400000000000000000000060371471154763100204100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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; /* Global init */ void chafa_init_palette (void); 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.14.5/chafa/internal/chafa-passthrough-encoder.c000066400000000000000000000112641471154763100227270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-passthrough-encoder.h" #define ESCAPE_BUF_SIZE 1024 static const gint packet_size_max [CHAFA_PASSTHROUGH_MAX] = { /* Screen's OSC buffer size was increased to 2560 in bfb05c34ba1f961a15ccea04c5. * This was quite a while ago, but it appears it still hasn't made its way into * some of the important OS distributions. */ [CHAFA_PASSTHROUGH_SCREEN] = 200, [CHAFA_PASSTHROUGH_TMUX] = 1000000 }; static void append_begin (ChafaPassthroughEncoder *ptenc) { gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; if (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN) { *chafa_term_info_emit_begin_screen_passthrough (ptenc->term_info, seq) = '\0'; g_string_append (ptenc->out, seq); } else if (ptenc->mode == CHAFA_PASSTHROUGH_TMUX) { *chafa_term_info_emit_begin_tmux_passthrough (ptenc->term_info, seq) = '\0'; g_string_append (ptenc->out, seq); } } static void append_end (ChafaPassthroughEncoder *ptenc) { gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; if (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN) { *chafa_term_info_emit_end_screen_passthrough (ptenc->term_info, seq) = '\0'; g_string_append (ptenc->out, seq); } else if (ptenc->mode == CHAFA_PASSTHROUGH_TMUX) { *chafa_term_info_emit_end_tmux_passthrough (ptenc->term_info, seq) = '\0'; g_string_append (ptenc->out, seq); } } static void append_packetized (ChafaPassthroughEncoder *ptenc, const gchar *in, gint len) { while (len > 0) { gssize remain = packet_size_max [ptenc->mode] - ptenc->packet_size; gint n; if (remain == 0) { append_end (ptenc); ptenc->packet_size = 0; remain = packet_size_max [ptenc->mode]; } if (ptenc->packet_size == 0) { append_begin (ptenc); } n = MIN (len, remain); g_string_append_len (ptenc->out, in, n); len -= n; ptenc->packet_size += n; in += n; } } static void append_escaped (ChafaPassthroughEncoder *ptenc, const gchar *in, gint len) { gchar buf [ESCAPE_BUF_SIZE]; gint i, j; for (i = 0, j = 0; i < len; i++) { buf [j++] = in [i]; if (in [i] == 0x1b) buf [j++] = 0x1b; if (j + 2 > ESCAPE_BUF_SIZE) { append_packetized (ptenc, buf, j); j = 0; } } append_packetized (ptenc, buf, j); } void chafa_passthrough_encoder_begin (ChafaPassthroughEncoder *ptenc, ChafaPassthrough passthrough, ChafaTermInfo *term_info, GString *out_str) { ptenc->mode = passthrough; ptenc->term_info = term_info; chafa_term_info_ref (term_info); ptenc->out = out_str; ptenc->packet_size = 0; } void chafa_passthrough_encoder_end (ChafaPassthroughEncoder *ptenc) { if (ptenc->packet_size > 0) chafa_passthrough_encoder_flush (ptenc); chafa_term_info_unref (ptenc->term_info); } void chafa_passthrough_encoder_append_len (ChafaPassthroughEncoder *ptenc, const gchar *in, gint len) { if (ptenc->mode == CHAFA_PASSTHROUGH_NONE) { g_string_append_len (ptenc->out, in, len); } else if (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN) { append_packetized (ptenc, in, len); } else { append_escaped (ptenc, in, len); } } void chafa_passthrough_encoder_append (ChafaPassthroughEncoder *ptenc, const gchar *in) { chafa_passthrough_encoder_append_len (ptenc, in, strlen (in)); } void chafa_passthrough_encoder_flush (ChafaPassthroughEncoder *ptenc) { if (ptenc->packet_size > 0) { append_end (ptenc); ptenc->packet_size = 0; } } void chafa_passthrough_encoder_reset (ChafaPassthroughEncoder *ptenc) { ptenc->packet_size = 0; } chafa-1.14.5/chafa/internal/chafa-passthrough-encoder.h000066400000000000000000000036511471154763100227350ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_PASSTHROUGH_ENCODER_H__ #define __CHAFA_PASSTHROUGH_ENCODER_H__ #include "chafa.h" G_BEGIN_DECLS typedef struct { ChafaPassthrough mode; ChafaTermInfo *term_info; GString *out; gint packet_size; } ChafaPassthroughEncoder; void chafa_passthrough_encoder_begin (ChafaPassthroughEncoder *ptenc, ChafaPassthrough passthrough, ChafaTermInfo *term_info, GString *out_str); void chafa_passthrough_encoder_end (ChafaPassthroughEncoder *ptenc); void chafa_passthrough_encoder_append (ChafaPassthroughEncoder *ptenc, const gchar *in); void chafa_passthrough_encoder_append_len (ChafaPassthroughEncoder *ptenc, const gchar *in, gint len); void chafa_passthrough_encoder_flush (ChafaPassthroughEncoder *ptenc); void chafa_passthrough_encoder_reset (ChafaPassthroughEncoder *ptenc); G_END_DECLS #endif /* __CHAFA_PASSTHROUGH_ENCODER_H__ */ chafa-1.14.5/chafa/internal/chafa-pca.c000066400000000000000000000111541471154763100175040ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 /* memcpy */ #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 = g_malloc (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); g_free (v); } chafa-1.14.5/chafa/internal/chafa-pca.h000066400000000000000000000117671471154763100175230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-pixops.c000066400000000000000000000617761471154763100203020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" #include "internal/chafa-pixops.h" #include "internal/smolscale/smolscale.h" /* Fixed point multiplier */ #define FIXED_MULT 4096 /* 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 /* Ensure there's no overflow in normalize_ch() */ G_STATIC_ASSERT (FIXED_MULT * INTENSITY_MAX * (gint64) 255 <= G_MAXINT); G_STATIC_ASSERT (FIXED_MULT * INTENSITY_MAX * (gint64) -255 >= G_MININT); 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; SmolScaleCtx *scale_ctx; } PrepareContext; typedef struct { Histogram hist; } PreparePixelsBatch1Ret; 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 }; ChafaPixel *p; ChafaColor acol; const ChafaColor *col; gint index; 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; index = chafa_palette_lookup_nearest (palette, color_space, &acol, NULL); col = chafa_palette_get_color (palette, color_space, index); 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 = g_malloc (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 + width - 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; } g_free (error_rows); } 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 (PreparePixelsBatch1Ret *ret, 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); ret->hist.c [v]++; ret->hist.n_samples++; } } static void prepare_pixels_1_worker_nearest (ChafaBatchInfo *batch, 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; PreparePixelsBatch1Ret *ret; ret = g_new (PreparePixelsBatch1Ret, 1); batch->ret_p = ret; dest_y = batch->first_row; data = prep_ctx->src_pixels; n_rows = batch->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 (ret, 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 (ChafaBatchInfo *batch, PrepareContext *prep_ctx) { ChafaPixel *pixel, *pixel_max; gint alpha_sum = 0; guint8 *scaled_data; const guint8 *data_p; PreparePixelsBatch1Ret *ret; ret = g_new0 (PreparePixelsBatch1Ret, 1); batch->ret_p = ret; scaled_data = g_malloc (prep_ctx->dest_width * batch->n_rows * sizeof (guint32)); smol_scale_batch_full (prep_ctx->scale_ctx, scaled_data, batch->first_row, batch->n_rows); data_p = scaled_data; pixel = prep_ctx->dest_pixels + batch->first_row * prep_ctx->dest_width; pixel_max = pixel + batch->n_rows * prep_ctx->dest_width; while (pixel < pixel_max) { prepare_pixels_1_inner (ret, 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 pass_1_post (ChafaBatchInfo *batch, PrepareContext *prep_ctx) { PreparePixelsBatch1Ret *ret = batch->ret_p; if (prep_ctx->preprocessing_enabled) { sum_histograms (&ret->hist, &prep_ctx->hist); } g_free (ret); } static void prepare_pixels_pass_1 (PrepareContext *prep_ctx) { GFunc batch_func; /* 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 */ batch_func = (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); chafa_process_batches (prep_ctx, (GFunc) batch_func, (GFunc) pass_1_post, prep_ctx->dest_height, chafa_get_n_actual_threads (), 1); /* Generate final histogram */ if (prep_ctx->preprocessing_enabled) { 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; } } } 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; /* FIXME: This is slow and bad. We should fix it with a new Smolscale * compositing mode. */ for ( ; p0 < p1; p0++) { p0->col.ch [0] = (p0->col.ch [0] * (guint32) p0->col.ch [3] + bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255; p0->col.ch [1] = (p0->col.ch [1] * (guint32) p0->col.ch [3] + bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255; p0->col.ch [2] = (p0->col.ch [2] * (guint32) p0->col.ch [3] + bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255; } } static void prepare_pixels_2_worker (ChafaBatchInfo *batch, 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, batch->first_row, batch->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, batch->first_row, batch->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, batch->first_row, batch->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, batch->first_row, batch->n_rows); } else { convert_rgb_to_din99d (prep_ctx->dest_pixels, prep_ctx->dest_width, batch->first_row, batch->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, batch->first_row, batch->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, batch->first_row, batch->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) { gint n_batches; gint batch_unit = 1; /* Second pass * ----------- * * - Normalization (optional) * - Dithering (optional) * - Color space conversion; DIN99d (optional) */ if (!need_pass_2 (prep_ctx)) return; n_batches = chafa_get_n_actual_threads (); /* Floyd-Steinberg diffusion needs the batch size to be a multiple of the * grain height. It also needs to run in a single thread to propagate the * quantization error correctly. */ if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) { n_batches = 1; batch_unit = 1 << prep_ctx->dither->grain_height_shift; } chafa_process_batches (prep_ctx, (GFunc) prepare_pixels_2_worker, NULL, /* _post */ prep_ctx->dest_height, n_batches, batch_unit); } 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 }; 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.scale_ctx = smol_scale_new_simple (prep_ctx.src_pixels, (SmolPixelType) prep_ctx.src_pixel_type, prep_ctx.src_width, prep_ctx.src_height, prep_ctx.src_rowstride, NULL, SMOL_PIXEL_RGBA8_UNASSOCIATED, /* FIXME: Premul */ prep_ctx.dest_width, prep_ctx.dest_height, prep_ctx.dest_width * sizeof (guint32), SMOL_NO_FLAGS); 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) { guint8 buckets [256] [64]; guint8 bucket_size [256] = { 0 }; gint i, j, k; g_assert (n_pixels <= 64); for (i = 0; i < n_pixels; i++) { guint8 bucket = pixels [i].col.ch [ch]; buckets [bucket] [bucket_size [bucket]++] = i; } for (i = 0, k = 0; i < 256; i++) { for (j = 0; j < bucket_size [i]; j++) index [k++] = buckets [i] [j]; } } chafa-1.14.5/chafa/internal/chafa-pixops.h000066400000000000000000000040361471154763100202710ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-popcnt.c000066400000000000000000000066001471154763100202440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-private.h000066400000000000000000000223721471154763100204240ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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; guint32 *mask_u32; 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 { gint 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; ChafaPassthrough passthrough; }; /* Frame */ struct ChafaFrame { gint refs; ChafaPixelType pixel_type; gint width, height, rowstride; gpointer data; guint data_is_owned : 1; }; /* Image */ struct ChafaImage { gint refs; ChafaFrame *frame; }; /* Placement */ struct ChafaPlacement { gint refs; ChafaImage *image; gint id; ChafaAlign halign, valign; ChafaTuck tuck; }; /* 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; gboolean chafa_have_avx2 (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 #ifdef HAVE_AVX2_INTRINSICS gint calc_error_avx2 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint32 *sym_mask_u32) G_GNUC_PURE; void calc_colors_avx2 (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint32 *sym_mask_u32); void chafa_color_accum_div_scalar_avx2 (ChafaColorAccum *accum, guint16 divisor); #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.14.5/chafa/internal/chafa-sixel-canvas.c000066400000000000000000000353171471154763100213450ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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-passthrough-encoder.h" #include "internal/chafa-sixel-canvas.h" #include "internal/chafa-string-util.h" #define SIXEL_CELL_HEIGHT 6 typedef struct { ChafaSixelCanvas *sixel_canvas; ChafaPassthroughEncoder *ptenc; } 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); if (!sixel_canvas->image) { g_free (sixel_canvas); sixel_canvas = NULL; } 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck) { 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, halign, valign, tuck); } #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)); 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_malloc (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++) { gboolean is_global_first_row = batch->first_row + i == 0; gboolean is_global_last_row = batch->first_row + (i + 1) * SIXEL_CELL_HEIGHT >= ctx->sixel_canvas->height; 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, (is_global_first_row) || (is_global_last_row) ? TRUE : FALSE); chafa_bitfield_clear (&srow.filter_bits); /* GNL after every row except final */ if (!is_global_last_row) *(p++) = '-'; } batch->ret_p = sixel_ansi; batch->ret_n = p - sixel_ansi; chafa_bitfield_deinit (&srow.filter_bits); g_free (srow.data); } static void build_sixel_row_post (ChafaBatchInfo *batch, BuildSixelsCtx *ctx) { chafa_passthrough_encoder_append_len (ctx->ptenc, batch->ret_p, batch->ret_n); g_free (batch->ret_p); } static void build_sixel_palette (ChafaSixelCanvas *sixel_canvas, ChafaPassthroughEncoder *ptenc) { 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); } chafa_passthrough_encoder_append_len (ptenc, str, p - str); } static void end_sixels (ChafaPassthroughEncoder *ptenc, ChafaTermInfo *term_info) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; gint i; *chafa_term_info_emit_end_sixels (term_info, buf) = '\0'; if (ptenc->mode == CHAFA_PASSTHROUGH_SCREEN) { /* In GNU Screen, the end of an emitted sixel passthrough sequence should * look something like this: \e P \e \e \\ \e P \\ \e \\ */ for (i = 0; buf [i]; i++) { chafa_passthrough_encoder_flush (ptenc); chafa_passthrough_encoder_append_len (ptenc, buf + i, 1); } } else { chafa_passthrough_encoder_append (ptenc, buf); } chafa_passthrough_encoder_flush (ptenc); } void chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, ChafaTermInfo *term_info, GString *str, ChafaPassthrough passthrough) { ChafaPassthroughEncoder ptenc; BuildSixelsCtx ctx; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; g_assert (sixel_canvas->image->height % SIXEL_CELL_HEIGHT == 0); chafa_passthrough_encoder_begin (&ptenc, passthrough, term_info, str); *chafa_term_info_emit_begin_sixels (term_info, buf, 0, 1, 0) = '\0'; chafa_passthrough_encoder_append (&ptenc, buf); g_snprintf (buf, CHAFA_TERM_SEQ_LENGTH_MAX, "\"1;1;%d;%d", sixel_canvas->image->width, sixel_canvas->image->height); chafa_passthrough_encoder_append (&ptenc, buf); ctx.sixel_canvas = sixel_canvas; ctx.ptenc = &ptenc; build_sixel_palette (sixel_canvas, &ptenc); chafa_process_batches (&ctx, (GFunc) build_sixel_row_worker, (GFunc) build_sixel_row_post, sixel_canvas->image->height, chafa_get_n_actual_threads (), SIXEL_CELL_HEIGHT); end_sixels (&ptenc, term_info); chafa_passthrough_encoder_end (&ptenc); } chafa-1.14.5/chafa/internal/chafa-sixel-canvas.h000066400000000000000000000040151471154763100213410ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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, ChafaAlign halign, ChafaAlign valign, ChafaTuck tuck); void chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, ChafaTermInfo *term_info, GString *out_str, ChafaPassthrough passthrough); G_END_DECLS #endif /* __CHAFA_SIXEL_CANVAS_H__ */ chafa-1.14.5/chafa/internal/chafa-sse41.c000066400000000000000000000030701471154763100176760ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-string-util.c000066400000000000000000000121711471154763100212220ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "-Wunknown-pragmas" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-warning-option" #pragma GCC diagnostic ignored "-Wuninitialized" #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #pragma clang diagnostic ignored "-Wuninitialized" 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 clang diagnostic pop #pragma GCC diagnostic pop static gchar format_hex_digit (guchar n) { g_assert (n < 16); return (n < 10) ? '0' + n : 'a' - 10 + n; } gchar * chafa_format_dec_u16_hex (char *dest, guint16 arg) { *(dest++) = format_hex_digit ((arg >> 12) & 0xf); *(dest++) = format_hex_digit ((arg >> 8) & 0xf); *(dest++) = format_hex_digit ((arg >> 4) & 0xf); *(dest++) = format_hex_digit (arg & 0xf); return dest; } chafa-1.14.5/chafa/internal/chafa-string-util.h000066400000000000000000000034011471154763100212230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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); /* Will overwrite 4 bytes starting at dest. Returns a pointer to the first * byte after the formatted ASCII hexadecimal number (dest + 4). */ gchar *chafa_format_dec_u16_hex (char *dest, guint16 arg); G_END_DECLS #endif /* __CHAFA_STRING_UTIL_H__ */ chafa-1.14.5/chafa/internal/chafa-symbols-ascii-ibm.h000066400000000000000000000722141471154763100222750ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-symbols-ascii.h000066400000000000000000000761701471154763100215350ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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. * * These are 7-bit ASCII symbols. The bitmaps are a close match to the * Terminus font (specifically ter-x14n.pcf). * * ASCII symbols are also "Latin" symbols, loosely corresponding to the * symbols you'd find in the European ISO/IEC 8859 charsets. */ { /* [ ] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN | CHAFA_SYMBOL_TAG_SPACE, 0x20, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " " " ") }, { /* [!] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x21, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X " " X " " " " X " " ") }, { /* ["] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x22, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " X X " " " " " " " " " " " " ") }, { /* [#] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x23, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXX XX " " XX XXX " " X X " " X X " " ") }, { /* [$] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x24, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " XXXXX " "X X " " XXXXXX " " X X " " XXXXX " " X ") }, { /* [%] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x25, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX X " " XX X " " XX " " XX " " X XX " " X XX " " ") }, { /* [&] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x26, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XXX " " X X X " " X X " " XXX X " " ") }, { /* ['] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x27, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " " " " " " " " " ") }, { /* [(] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x28, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XX " " ") }, { /* [)] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x29, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XX " " ") }, { /* [*] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXX " " XXXXXX " " X X " " " " ") }, { /* [+] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " XXXXX " " X " " " " ") }, { /* [,] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " X " " X ") }, { /* [-] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XXXXXX " " " " " " ") }, { /* [.] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " X " " ") }, { /* [/] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x2f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " XX " " X " " X " " ") }, { /* [0] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x30, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X XXX " " XXX X " " X X " " XXXX " " ") }, { /* [1] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x31, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " X " " X " " X " " XXXXX " " ") }, { /* [2] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x32, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [3] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x33, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXX " " X " " X X " " XXXX " " ") }, { /* [4] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x34, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX X " " X X " " XXXXXX " " X " " X " " ") }, { /* [5] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x35, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXXX " " X " " X X " " XXXX " " ") }, { /* [6] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x36, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X " " XXXXX " " X X " " X X " " XXXX " " ") }, { /* [7] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x37, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " X " " X " " X " " X " " ") }, { /* [8] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x38, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXXX " " X X " " X X " " XXXX " " ") }, { /* [9] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x39, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " XXXXX " " X " " XXXX " " ") }, { /* [:] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " " " " " X " " ") }, { /* [;] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " " " " " X " " X ") }, { /* [<] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " XX " " XX " " X " " ") }, { /* [=] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXXXX " " " " " " ") }, { /* [>] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " XX " " XX " " X " " ") }, { /* [?] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x3f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X XX " " X " " " " X " " ") }, { /* [@] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x40, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " "X X " "X XXX X " "X X XX " "X XX " " XXXXXX " " ") }, { /* [A] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x41, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [B] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x42, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " XXXXX " " X X " " X X " " XXXXX " " ") }, { /* [C] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x43, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [D] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x44, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [E] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x45, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [F] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x46, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXX " " X " " X " " X " " ") }, { /* [G] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x47, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [H] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x48, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [I] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x49, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [J] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X X " " XXX " " ") }, { /* [K] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X XX " " XXX " " XXX " " X XX " " X X " " ") }, { /* [L] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X " " X " " X " " XXXXXX " " ") }, { /* [M] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XX XX " "X X X X " "X X X " "X X " "X X " "X X " " ") }, { /* [N] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX X " " X X X " " X X X " " X XX " " X X " " ") }, { /* [O] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x4f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [P] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x50, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " X X " " XXXXX " " X " " X " " ") }, { /* [Q] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x51, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " X ") }, { /* [R] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x52, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " X X " " XXXXX " " X XX " " X X " " ") }, { /* [S] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x53, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXXXX " " X " " X X " " XXXX " " ") }, { /* [T] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x54, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXX " " X " " X " " X " " X " " X " " ") }, { /* [U] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x55, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [V] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x56, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " X X " " X X " " X X " " XX " " ") }, { /* [W] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x57, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X " "X X " "X X " "X X X " "X X X X " "XX XX " " ") }, { /* [X] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x58, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XX " " XX " " X X " " X X " " ") }, { /* [Y] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x59, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X " " X X " " XX XX " " X " " X " " X " " ") }, { /* [Z] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [[] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [\] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " XX " " X " " X " " ") }, { /* []] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [^] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5e, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X X " " X X " " " " " " " " " " ") }, { /* [_] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x5f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " " " XXXXXX ") }, { /* [`] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x60, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " " " " " " " " " " " ") }, { /* [a] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x61, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [b] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x62, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXXX " " ") }, { /* [c] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x63, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [d] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x64, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXXX " " ") }, { /* [e] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x65, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [f] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x66, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " XXXXX " " X " " X " " X " " ") }, { /* [g] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x67, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [h] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x68, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [i] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x69, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XX " " X " " X " " XXX " " ") }, { /* [j] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XX " " X " " X " " X X " " XXX ") }, { /* [k] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X XX " " XXXX " " X X " " X XX " " ") }, { /* [l] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XXX " " ") }, { /* [m] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXX " "X X X " "X X X " "X X X " " ") }, { /* [n] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [o] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x6f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [p] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x70, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXXX " " X ") }, { /* [q] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x71, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXXX " " X ") }, { /* [r] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x72, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X " " X " " ") }, { /* [s] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x73, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXXX " " X " " XXXXX " " ") }, { /* [t] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x74, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " X " " X " " XXX " " ") }, { /* [u] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x75, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X X " " XXXXX " " ") }, { /* [v] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x76, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " XX XX " " X X " " XX " " ") }, { /* [w] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x77, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "X X " "X X X " "X X X " " XXXXX " " ") }, { /* [x] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x78, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " XXXX " " X X " " X X " " ") }, { /* [y] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x79, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X X " " XXXXX " " XXXX ") }, { /* [z] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x7a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XX " " XX " " XXXXXX " " ") }, { /* [{] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x7b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XX " " ") }, { /* [|] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x7c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X " " X " " X " " X " " ") }, { /* [}] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x7d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XX " " ") }, { /* [~] */ CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_LATIN, 0x7e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX X " "X XXX " " " " " " " " " " " " ") }, chafa-1.14.5/chafa/internal/chafa-symbols-block.h000066400000000000000000001773511471154763100215420ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 ") }, { 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, 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.14.5/chafa/internal/chafa-symbols-kana.h000066400000000000000000002055451471154763100213570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/chafa-symbols-latin.h000066400000000000000000003271651471154763100215570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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. * * These are Latin symbols not in the ASCII 7-bit range. The bitmaps are a * close match to the Terminus font (specifically ter-x14n.pcf). */ { /* [¡] */ CHAFA_SYMBOL_TAG_LATIN, 0xa1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " X " " X " " X " " X " " ") }, { /* [¢] */ CHAFA_SYMBOL_TAG_LATIN, 0xa2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " X " "XXXXXXX " "X X " "X X X " " XXXXX " " X ") }, { /* [£] */ CHAFA_SYMBOL_TAG_LATIN, 0xa3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [¤] */ CHAFA_SYMBOL_TAG_LATIN, 0xa4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " X X " " XXXXX " " X X " " XXX " " X X " " ") }, { /* [¥] */ CHAFA_SYMBOL_TAG_LATIN, 0xa5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X " " X X " " XXX " " XXXXX " " XXXXX " " X " " ") }, { /* [¦] */ CHAFA_SYMBOL_TAG_LATIN, 0xa6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X " " " " X " " X " " ") }, { /* [§] */ CHAFA_SYMBOL_TAG_LATIN, 0xa7, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X X " " XX " " X XX " " XX X " " XX " " X X " " XXX ") }, { /* [¨] */ CHAFA_SYMBOL_TAG_LATIN, 0xa8, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " " " " " " " " " " " " " ") }, { /* [©] */ CHAFA_SYMBOL_TAG_LATIN, 0xa9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXXXX" "X XXXX X" "X X X" "X XXX X" " XXXXXX " " ") }, { /* [ª] */ CHAFA_SYMBOL_TAG_LATIN, 0xaa, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X " " XX X " " XXX " " XXXXX " " " " " " ") }, { /* [«] */ CHAFA_SYMBOL_TAG_LATIN, 0xab, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX XX " "XX XX " " XX XX " " X X " " ") }, { /* [¬] */ CHAFA_SYMBOL_TAG_LATIN, 0xac, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " " " " " ") }, { /* [®] */ CHAFA_SYMBOL_TAG_LATIN, 0xae, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXXXX" "X X X X" "X XXX X" "X X X X" " XXXXXX " " ") }, { /* [¯] */ CHAFA_SYMBOL_TAG_LATIN, 0xaf, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " " " " " " " " " " " " ") }, { /* [°] */ CHAFA_SYMBOL_TAG_LATIN, 0xb0, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " XX " " " " " " " " " " ") }, { /* [±] */ CHAFA_SYMBOL_TAG_LATIN, 0xb1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " XXXXX " " X " " XXXXX " " ") }, { /* [²] */ CHAFA_SYMBOL_TAG_LATIN, 0xb2, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X " " XXXX " " " " " " " " ") }, { /* [³] */ CHAFA_SYMBOL_TAG_LATIN, 0xb3, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX " " X " " XXXX " " " " " " " " ") }, { /* [´] */ CHAFA_SYMBOL_TAG_LATIN, 0xb4, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " " " " " " " " " " " ") }, { /* [µ] */ CHAFA_SYMBOL_TAG_LATIN, 0xb5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X XX " " XXXX X " " X ") }, { /* [¶] */ CHAFA_SYMBOL_TAG_LATIN, 0xb6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " "X X X " "X X X " " XXX X " " X X " " X X " " ") }, { /* [·] */ CHAFA_SYMBOL_TAG_LATIN, 0xb7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " X " " " " " " ") }, { /* [¸] */ CHAFA_SYMBOL_TAG_LATIN, 0xb8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " X " " X ") }, { /* [¹] */ CHAFA_SYMBOL_TAG_LATIN, 0xb9, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XX " " X " " XXX " " " " " " " " ") }, { /* [º] */ CHAFA_SYMBOL_TAG_LATIN, 0xba, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X X " " X X " " XXX " " XXXXX " " " " " " ") }, { /* [»] */ CHAFA_SYMBOL_TAG_LATIN, 0xbb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XX XX " " XX XX " " XX XX " "X X " " ") }, { /* [¼] */ CHAFA_SYMBOL_TAG_LATIN, 0xbc, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " " XX " " XX XX " "X X X " " XXX " " X ") }, { /* [½] */ CHAFA_SYMBOL_TAG_LATIN, 0xbd, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " " XX " " XX XX " "X X X " " X " " XXXX ") }, { /* [¾] */ CHAFA_SYMBOL_TAG_LATIN, 0xbe, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " " XX X " "XXX X " " XX " " XX XX " "X X X " " XXX " " X ") }, { /* [¿] */ CHAFA_SYMBOL_TAG_LATIN, 0xbf, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " X " " XX X " " X X " " XXXX " " ") }, { /* [À] */ CHAFA_SYMBOL_TAG_LATIN, 0xc0, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Á] */ CHAFA_SYMBOL_TAG_LATIN, 0xc1, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Â] */ CHAFA_SYMBOL_TAG_LATIN, 0xc2, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Ã] */ CHAFA_SYMBOL_TAG_LATIN, 0xc3, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Ä] */ CHAFA_SYMBOL_TAG_LATIN, 0xc4, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Å] */ CHAFA_SYMBOL_TAG_LATIN, 0xc5, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Æ] */ CHAFA_SYMBOL_TAG_LATIN, 0xc6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " "X X " "XXXXXX " "X X " "X X " "X XXXX " " ") }, { /* [Ç] */ CHAFA_SYMBOL_TAG_LATIN, 0xc7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X " " X X " " XXXX " " X ") }, { /* [È] */ CHAFA_SYMBOL_TAG_LATIN, 0xc8, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXXXX " " X " " X " " XXXX " " X " " XXXXXX " " ") }, { /* [É] */ CHAFA_SYMBOL_TAG_LATIN, 0xc9, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXXXX " " X " " X " " XXXX " " X " " XXXXXX " " ") }, { /* [Ê] */ CHAFA_SYMBOL_TAG_LATIN, 0xca, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXXX " " X " " X " " XXXX " " X " " XXXXXX " " ") }, { /* [Ë] */ CHAFA_SYMBOL_TAG_LATIN, 0xcb, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXXXXX " " X " " X " " XXXX " " X " " XXXXXX " " ") }, { /* [Ì] */ CHAFA_SYMBOL_TAG_LATIN, 0xcc, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Í] */ CHAFA_SYMBOL_TAG_LATIN, 0xcd, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Î] */ CHAFA_SYMBOL_TAG_LATIN, 0xce, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ï] */ CHAFA_SYMBOL_TAG_LATIN, 0xcf, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ð] */ CHAFA_SYMBOL_TAG_LATIN, 0xd0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " "XXXX X " " X X " " X X " " XXXXX " " ") }, { /* [Ñ] */ CHAFA_SYMBOL_TAG_LATIN, 0xd1, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXX " " X X " " X X " " XXX X " " X XXX " " X X " " X X " " ") }, { /* [Ò] */ CHAFA_SYMBOL_TAG_LATIN, 0xd2, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ó] */ CHAFA_SYMBOL_TAG_LATIN, 0xd3, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ô] */ CHAFA_SYMBOL_TAG_LATIN, 0xd4, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Õ] */ CHAFA_SYMBOL_TAG_LATIN, 0xd5, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ö] */ CHAFA_SYMBOL_TAG_LATIN, 0xd6, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [×] */ CHAFA_SYMBOL_TAG_LATIN, 0xd7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX XX " " XX " " X X " " X X " " ") }, { /* [Ø] */ CHAFA_SYMBOL_TAG_LATIN, 0xd8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X XX" " X XXX " " XXX X " "XX X " " XXXX " " ") }, { /* [Ù] */ CHAFA_SYMBOL_TAG_LATIN, 0xd9, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ú] */ CHAFA_SYMBOL_TAG_LATIN, 0xda, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Û] */ CHAFA_SYMBOL_TAG_LATIN, 0xdb, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ü] */ CHAFA_SYMBOL_TAG_LATIN, 0xdc, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ý] */ CHAFA_SYMBOL_TAG_LATIN, 0xdd, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " "X X " " X X " " XX XX " " X " " X " " X " " ") }, { /* [Þ] */ CHAFA_SYMBOL_TAG_LATIN, 0xde, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " XXXXX " " X X " " X X " " XXXXX " " X " " ") }, { /* [ß] */ CHAFA_SYMBOL_TAG_LATIN, 0xdf, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X X " " XXXXX " " X X " " XX X " " X XXX " " ") }, { /* [à] */ CHAFA_SYMBOL_TAG_LATIN, 0xe0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [á] */ CHAFA_SYMBOL_TAG_LATIN, 0xe1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [â] */ CHAFA_SYMBOL_TAG_LATIN, 0xe2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [ã] */ CHAFA_SYMBOL_TAG_LATIN, 0xe3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX X " " X XX " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [ä] */ CHAFA_SYMBOL_TAG_LATIN, 0xe4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [å] */ CHAFA_SYMBOL_TAG_LATIN, 0xe5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [æ] */ CHAFA_SYMBOL_TAG_LATIN, 0xe6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXX X " "X XXXX " " XX XX " " ") }, { /* [ç] */ CHAFA_SYMBOL_TAG_LATIN, 0xe7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X X " " XXXX " " X ") }, { /* [è] */ CHAFA_SYMBOL_TAG_LATIN, 0xe8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [é] */ CHAFA_SYMBOL_TAG_LATIN, 0xe9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [ê] */ CHAFA_SYMBOL_TAG_LATIN, 0xea, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [ë] */ CHAFA_SYMBOL_TAG_LATIN, 0xeb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [ì] */ CHAFA_SYMBOL_TAG_LATIN, 0xec, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " X " " X " " XXX " " ") }, { /* [í] */ CHAFA_SYMBOL_TAG_LATIN, 0xed, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " X " " X " " XXX " " ") }, { /* [î] */ CHAFA_SYMBOL_TAG_LATIN, 0xee, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XX " " X " " X " " XXX " " ") }, { /* [ï] */ CHAFA_SYMBOL_TAG_LATIN, 0xef, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XX " " X " " X " " XXX " " ") }, { /* [ð] */ CHAFA_SYMBOL_TAG_LATIN, 0xf0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X XX " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ñ] */ CHAFA_SYMBOL_TAG_LATIN, 0xf1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX X " " X XX " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [ò] */ CHAFA_SYMBOL_TAG_LATIN, 0xf2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ó] */ CHAFA_SYMBOL_TAG_LATIN, 0xf3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ô] */ CHAFA_SYMBOL_TAG_LATIN, 0xf4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [õ] */ CHAFA_SYMBOL_TAG_LATIN, 0xf5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX X " " X XX " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ö] */ CHAFA_SYMBOL_TAG_LATIN, 0xf6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [÷] */ CHAFA_SYMBOL_TAG_LATIN, 0xf7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " X " " X " " XXXXX " " X " " " " ") }, { /* [ø] */ CHAFA_SYMBOL_TAG_LATIN, 0xf8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " X " " XXXXXX " " X XX X " " XX X " "X XXXX " " ") }, { /* [ù] */ CHAFA_SYMBOL_TAG_LATIN, 0xf9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [ú] */ CHAFA_SYMBOL_TAG_LATIN, 0xfa, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [û] */ CHAFA_SYMBOL_TAG_LATIN, 0xfb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [ü] */ CHAFA_SYMBOL_TAG_LATIN, 0xfc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " X X " " X X " " X X " " XXXXX " " ") }, { /* [ý] */ CHAFA_SYMBOL_TAG_LATIN, 0xfd, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " X X " " X X " " XXXXX " " XXXX ") }, { /* [þ] */ CHAFA_SYMBOL_TAG_LATIN, 0xfe, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXXX " " X ") }, { /* [ÿ] */ CHAFA_SYMBOL_TAG_LATIN, 0xff, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " X X " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ā] */ CHAFA_SYMBOL_TAG_LATIN, 0x100, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [ā] */ CHAFA_SYMBOL_TAG_LATIN, 0x101, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [Ă] */ CHAFA_SYMBOL_TAG_LATIN, 0x102, CHAFA_SYMBOL_OUTLINE_8X8 ( " X XX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [ă] */ CHAFA_SYMBOL_TAG_LATIN, 0x103, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [Ą] */ CHAFA_SYMBOL_TAG_LATIN, 0x104, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " XXX") }, { /* [ą] */ CHAFA_SYMBOL_TAG_LATIN, 0x105, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX " " XXXXXX " " X X " " XXXXX " " XX") }, { /* [Ć] */ CHAFA_SYMBOL_TAG_LATIN, 0x106, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [ć] */ CHAFA_SYMBOL_TAG_LATIN, 0x107, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [Ĉ] */ CHAFA_SYMBOL_TAG_LATIN, 0x108, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [ĉ] */ CHAFA_SYMBOL_TAG_LATIN, 0x109, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [Ċ] */ CHAFA_SYMBOL_TAG_LATIN, 0x10a, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [ċ] */ CHAFA_SYMBOL_TAG_LATIN, 0x10b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [Č] */ CHAFA_SYMBOL_TAG_LATIN, 0x10c, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [č] */ CHAFA_SYMBOL_TAG_LATIN, 0x10d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [Ď] */ CHAFA_SYMBOL_TAG_LATIN, 0x10e, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXXXX " " X X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [ď] */ CHAFA_SYMBOL_TAG_LATIN, 0x10f, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XX X " " X " " XXXXXX " " X X " " X X " " XXXXX " " ") }, { /* [Đ] */ CHAFA_SYMBOL_TAG_LATIN, 0x110, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " "XXXX X " " X X " " X X " " XXXXX " " ") }, { /* [đ] */ CHAFA_SYMBOL_TAG_LATIN, 0x111, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " XXX " " XXXXXX " " X X " " X X " " XXXXX " " ") }, { /* [Ē] */ CHAFA_SYMBOL_TAG_LATIN, 0x112, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [ē] */ CHAFA_SYMBOL_TAG_LATIN, 0x113, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXX " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [Ĕ] */ CHAFA_SYMBOL_TAG_LATIN, 0x114, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [ĕ] */ CHAFA_SYMBOL_TAG_LATIN, 0x115, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [Ė] */ CHAFA_SYMBOL_TAG_LATIN, 0x116, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [ė] */ CHAFA_SYMBOL_TAG_LATIN, 0x117, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [Ę] */ CHAFA_SYMBOL_TAG_LATIN, 0x118, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " XX") }, { /* [ę] */ CHAFA_SYMBOL_TAG_LATIN, 0x119, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X XX X " " X " " XXXX " " XX ") }, { /* [Ě] */ CHAFA_SYMBOL_TAG_LATIN, 0x11a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [ě] */ CHAFA_SYMBOL_TAG_LATIN, 0x11b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [Ĝ] */ CHAFA_SYMBOL_TAG_LATIN, 0x11c, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [ĝ] */ CHAFA_SYMBOL_TAG_LATIN, 0x11d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ğ] */ CHAFA_SYMBOL_TAG_LATIN, 0x11e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [ğ] */ CHAFA_SYMBOL_TAG_LATIN, 0x11f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ġ] */ CHAFA_SYMBOL_TAG_LATIN, 0x120, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [ġ] */ CHAFA_SYMBOL_TAG_LATIN, 0x121, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ģ] */ CHAFA_SYMBOL_TAG_LATIN, 0x122, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " X ") }, { /* [ģ] */ CHAFA_SYMBOL_TAG_LATIN, 0x123, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ĥ] */ CHAFA_SYMBOL_TAG_LATIN, 0x124, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [ĥ] */ CHAFA_SYMBOL_TAG_LATIN, 0x125, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X " " X " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Ħ] */ CHAFA_SYMBOL_TAG_LATIN, 0x126, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " "XXX XXX" " XXXXXX " " X X " " X X " " X X " " ") }, { /* [ħ] */ CHAFA_SYMBOL_TAG_LATIN, 0x127, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " XXX " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Ĩ] */ CHAFA_SYMBOL_TAG_LATIN, 0x128, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXX " " " " X " " X " " X " " X " " XXX " " ") }, { /* [ĩ] */ CHAFA_SYMBOL_TAG_LATIN, 0x129, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX XX " " X X " " XX " " X " " X " " XXX " " ") }, { /* [Ī] */ CHAFA_SYMBOL_TAG_LATIN, 0x12a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [ī] */ CHAFA_SYMBOL_TAG_LATIN, 0x12b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXX " " XX " " X " " X " " XXX " " ") }, { /* [Ĭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x12c, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [ĭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x12d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XX " " X " " X " " XXX " " ") }, { /* [Į] */ CHAFA_SYMBOL_TAG_LATIN, 0x12e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " XXX " " XX ") }, { /* [į] */ CHAFA_SYMBOL_TAG_LATIN, 0x12f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XX " " X " " X " " XXX " " XX ") }, { /* [İ] */ CHAFA_SYMBOL_TAG_LATIN, 0x130, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [ı] */ CHAFA_SYMBOL_TAG_LATIN, 0x131, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " X " " X " " XXX " " ") }, { /* [IJ] */ CHAFA_SYMBOL_TAG_LATIN, 0x132, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXX XXX" " X X " " X X " " X X " " X X X " "XXX XX " " ") }, { /* [ij] */ CHAFA_SYMBOL_TAG_LATIN, 0x133, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " "XX XX " " X X " " X X " "XXXX X " " XX ") }, { /* [Ĵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x134, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X " " X " " X " " X X " " XXX " " ") }, { /* [ĵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x135, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XX " " X " " X " " X X " " XXX ") }, { /* [Ķ] */ CHAFA_SYMBOL_TAG_LATIN, 0x136, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X XX " " XXX " " XXX " " X XX " " X X X " " X ") }, { /* [ķ] */ CHAFA_SYMBOL_TAG_LATIN, 0x137, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X XX " " XXXX " " X XX " " X X X " " X ") }, { /* [ĸ] */ CHAFA_SYMBOL_TAG_LATIN, 0x138, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X XX " " XXXX " " X XX " " X X " " ") }, { /* [Ĺ] */ CHAFA_SYMBOL_TAG_LATIN, 0x139, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " X " " X " " X " " X " " XXXXXX " " ") }, { /* [ĺ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ļ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X " " X " " X " " XXXXXX " " X ") }, { /* [ļ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X " " X " " X " " XXX " " X ") }, { /* [Ľ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13d, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " X XX " " X " " X " " X " " X " " XXXXXX " " ") }, { /* [ľ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ŀ] */ CHAFA_SYMBOL_TAG_LATIN, 0x13f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " X X " " X " " XXXXXX " " ") }, { /* [ŀ] */ CHAFA_SYMBOL_TAG_LATIN, 0x140, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " X X" " X X" " X " " XXX " " ") }, { /* [Ł] */ CHAFA_SYMBOL_TAG_LATIN, 0x141, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " "XX " " X " " XXXXXX " " ") }, { /* [ł] */ CHAFA_SYMBOL_TAG_LATIN, 0x142, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X " " XX " " XX " " X " " XXX " " ") }, { /* [Ń] */ CHAFA_SYMBOL_TAG_LATIN, 0x143, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " X X " " X X " " XXX X " " X XXX " " X X " " X X " " ") }, { /* [ń] */ CHAFA_SYMBOL_TAG_LATIN, 0x144, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Ņ] */ CHAFA_SYMBOL_TAG_LATIN, 0x145, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXX X " " X XXX " " X X " " X X X " " X ") }, { /* [ņ] */ CHAFA_SYMBOL_TAG_LATIN, 0x146, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X X " " X ") }, { /* [Ň] */ CHAFA_SYMBOL_TAG_LATIN, 0x147, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " X XX X " " X X " " XXX X " " X XXX " " X X " " X X " " ") }, { /* [ň] */ CHAFA_SYMBOL_TAG_LATIN, 0x148, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [ʼn] */ CHAFA_SYMBOL_TAG_LATIN, 0x149, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Ŋ] */ CHAFA_SYMBOL_TAG_LATIN, 0x14a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXX X " " X XXX " " X X " " X X " " XX ") }, { /* [ŋ] */ CHAFA_SYMBOL_TAG_LATIN, 0x14b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " " XX ") }, { /* [Ō] */ CHAFA_SYMBOL_TAG_LATIN, 0x14c, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ō] */ CHAFA_SYMBOL_TAG_LATIN, 0x14d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [Ŏ] */ CHAFA_SYMBOL_TAG_LATIN, 0x14e, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ŏ] */ CHAFA_SYMBOL_TAG_LATIN, 0x14f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [Ő] */ CHAFA_SYMBOL_TAG_LATIN, 0x150, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX " " " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ő] */ CHAFA_SYMBOL_TAG_LATIN, 0x151, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [Œ] */ CHAFA_SYMBOL_TAG_LATIN, 0x152, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " "X X " "X XXX " "X X " "X X " " XXXXXX " " ") }, { /* [œ] */ CHAFA_SYMBOL_TAG_LATIN, 0x153, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXX " "X XXXX " "X X " " XXXXXX " " ") }, { /* [Ŕ] */ CHAFA_SYMBOL_TAG_LATIN, 0x154, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXXX " " X X " " X X " " XXXXX " " X X " " X XX " " ") }, { /* [ŕ] */ CHAFA_SYMBOL_TAG_LATIN, 0x155, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X " " X " " X " " ") }, { /* [Ŗ] */ CHAFA_SYMBOL_TAG_LATIN, 0x156, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " X X " " XXXXX " " X X " " X X XX " " X ") }, { /* [ŗ] */ CHAFA_SYMBOL_TAG_LATIN, 0x157, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X " " XX " " X ") }, { /* [Ř] */ CHAFA_SYMBOL_TAG_LATIN, 0x158, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXX " " X X " " X X " " XXXXX " " X X " " X XX " " ") }, { /* [ř] */ CHAFA_SYMBOL_TAG_LATIN, 0x159, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X " " X " " X " " ") }, { /* [Ś] */ CHAFA_SYMBOL_TAG_LATIN, 0x15a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXX " " X X " " X " " XXXXX " " X X " " XXXX " " ") }, { /* [ś] */ CHAFA_SYMBOL_TAG_LATIN, 0x15b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " XXXXX " " X " " XXXXX " " ") }, { /* [Ŝ] */ CHAFA_SYMBOL_TAG_LATIN, 0x15c, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " XXXXX " " X X " " XXXX " " ") }, { /* [ŝ] */ CHAFA_SYMBOL_TAG_LATIN, 0x15d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XXXXXX " " XXXXX " " X " " XXXXX " " ") }, { /* [Ş] */ CHAFA_SYMBOL_TAG_LATIN, 0x15e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXXXX " " X " " X X " " XXXX " " X ") }, { /* [ş] */ CHAFA_SYMBOL_TAG_LATIN, 0x15f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXXX " " X " " XXXXX " " X ") }, { /* [Š] */ CHAFA_SYMBOL_TAG_LATIN, 0x160, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " XXXXX " " X X " " XXXX " " ") }, { /* [š] */ CHAFA_SYMBOL_TAG_LATIN, 0x161, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " XXXXX " " X " " XXXXX " " ") }, { /* [Ţ] */ CHAFA_SYMBOL_TAG_LATIN, 0x162, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXX " " X " " X " " X " " X " " XX " " X ") }, { /* [ţ] */ CHAFA_SYMBOL_TAG_LATIN, 0x163, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " X " " X " " XXX " " X ") }, { /* [Ť] */ CHAFA_SYMBOL_TAG_LATIN, 0x164, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " " X " " X " " X " " X " " X " " ") }, { /* [ť] */ CHAFA_SYMBOL_TAG_LATIN, 0x165, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " " X " " XXXXX " " X " " X " " XXX " " ") }, { /* [Ŧ] */ CHAFA_SYMBOL_TAG_LATIN, 0x166, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXX " " X " " XXXXX " " X " " X " " X " " ") }, { /* [ŧ] */ CHAFA_SYMBOL_TAG_LATIN, 0x167, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " XXX " " X " " XXX " " ") }, { /* [Ũ] */ CHAFA_SYMBOL_TAG_LATIN, 0x168, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX X " " X XX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ũ] */ CHAFA_SYMBOL_TAG_LATIN, 0x169, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX X " " XX " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ū] */ CHAFA_SYMBOL_TAG_LATIN, 0x16a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ū] */ CHAFA_SYMBOL_TAG_LATIN, 0x16b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ŭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x16c, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " X XX X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ŭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x16d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ů] */ CHAFA_SYMBOL_TAG_LATIN, 0x16e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ů] */ CHAFA_SYMBOL_TAG_LATIN, 0x16f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ű] */ CHAFA_SYMBOL_TAG_LATIN, 0x170, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX " " " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ű] */ CHAFA_SYMBOL_TAG_LATIN, 0x171, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ų] */ CHAFA_SYMBOL_TAG_LATIN, 0x172, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " X X " " X X " " X X " " XXXX " " XX ") }, { /* [ų] */ CHAFA_SYMBOL_TAG_LATIN, 0x173, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X X " " XXXXX " " XX") }, { /* [Ŵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x174, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " "X X " "X X " "X X X " "X X X X " "XX XX " " ") }, { /* [ŵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x175, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " "X X " "X X X " "X X X " " XXXXX " " ") }, { /* [Ŷ] */ CHAFA_SYMBOL_TAG_LATIN, 0x176, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " " "X X " " X X " " X X " " X " " X " " ") }, { /* [ŷ] */ CHAFA_SYMBOL_TAG_LATIN, 0x177, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " X X " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ÿ] */ CHAFA_SYMBOL_TAG_LATIN, 0x178, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " " "X X " " X X " " X X " " X " " X " " ") }, { /* [Ź] */ CHAFA_SYMBOL_TAG_LATIN, 0x179, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [ź] */ CHAFA_SYMBOL_TAG_LATIN, 0x17a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " XX " " X " " XXXXXX " " ") }, { /* [Ż] */ CHAFA_SYMBOL_TAG_LATIN, 0x17b, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [ż] */ CHAFA_SYMBOL_TAG_LATIN, 0x17c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " XX " " X " " XXXXXX " " ") }, { /* [Ž] */ CHAFA_SYMBOL_TAG_LATIN, 0x17d, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [ž] */ CHAFA_SYMBOL_TAG_LATIN, 0x17e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " XX " " X " " XXXXXX " " ") }, { /* [ſ] */ CHAFA_SYMBOL_TAG_LATIN, 0x17f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " X " " ") }, { /* [Ɔ] */ CHAFA_SYMBOL_TAG_LATIN, 0x186, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X " " X X " " XXXX " " ") }, { /* [Ǝ] */ CHAFA_SYMBOL_TAG_LATIN, 0x18e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [Ə] */ CHAFA_SYMBOL_TAG_LATIN, 0x18f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [Ɛ] */ CHAFA_SYMBOL_TAG_LATIN, 0x190, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXX " " X " " X X " " XXXX " " ") }, { /* [ƒ] */ CHAFA_SYMBOL_TAG_LATIN, 0x192, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " X X " " XXXXX " " X " " X " " X " "XXX ") }, { /* [Ɲ] */ CHAFA_SYMBOL_TAG_LATIN, 0x19d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXX X " " X XXX " " X X " " X X " "X ") }, { /* [ƞ] */ CHAFA_SYMBOL_TAG_LATIN, 0x19e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " " X ") }, { /* [Ƶ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1b5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXXXX " " XX " " X " " XXXXXX " " ") }, { /* [ƶ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1b6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXX " " X " " XXXXXX " " ") }, { /* [Ʒ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1b7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXX " " X " " X X " " XXXX " " ") }, { /* [Ǎ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1cd, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [ǎ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ce, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXX " " XXXXXX " " X X " " XXXXX " " ") }, { /* [Ǐ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1cf, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [ǐ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1d0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XX " " X " " X " " XXX " " ") }, { /* [Ǒ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1d1, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ǒ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1d2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [Ǔ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1d3, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ǔ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1d4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " X X " " X X " " X X " " XXXXX " " ") }, { /* [Ǣ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e2, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXX " " XXXXXX " "X X " "XXXXXX " "X X " "X X " "X XXXX " " ") }, { /* [ǣ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XXXXXX " " XXXXXX " "X X " " XX XX " " ") }, { /* [Ǥ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X " " X XXX " " X XXXX" " XXXX " " ") }, { /* [ǥ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X XXXX" " X X " " XXXXX " " XXXX ") }, { /* [Ǧ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e6, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [ǧ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ǩ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e8, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X X " " X XX " " XXX " " XXX " " X XX " " X X " " ") }, { /* [ǩ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1e9, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " X " " X " " X XX " " XXXX " " X XX " " X X " " ") }, { /* [Ǫ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ea, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " XX ") }, { /* [ǫ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1eb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXX " " XX ") }, { /* [Ǭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ec, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " XX ") }, { /* [ǭ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ed, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXX " " XXXXXX " " X X " " X X " " XXXX " " XX ") }, { /* [Ǯ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ee, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXXX " " X " " XXX " " X " " X X " " XXXX " " ") }, { /* [ǯ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ef, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " XX " " XXXXXX " " X " " XXX " " X " " XXXXX ") }, { /* [ǰ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1f0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XX " " X " " X " " X X " " XXX ") }, { /* [Ǵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1f4, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXX " " X X " " X " " X XXX " " X X " " XXXX " " ") }, { /* [ǵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1f5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [Ǽ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1fc, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXXXX " "X X " "X X " "XXXXXX " "X X " "X XXXX " " ") }, { /* [ǽ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1fd, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " XXXXXX " "X X " " XX XX " " ") }, { /* [Ǿ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1fe, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXX " " X XXX" " X X X " " XXX X " "XX X " " XXXX " " ") }, { /* [ǿ] */ CHAFA_SYMBOL_TAG_LATIN, 0x1ff, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X X " " XXXXXX " " X XX X " " XX X " "X XXXX " " ") }, { /* [Ș] */ CHAFA_SYMBOL_TAG_LATIN, 0x218, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXXXX " " X " " X X " " XXXX " " X ") }, { /* [ș] */ CHAFA_SYMBOL_TAG_LATIN, 0x219, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXXX " " X " " XXXXX " " X ") }, { /* [Ț] */ CHAFA_SYMBOL_TAG_LATIN, 0x21a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXX " " X " " X " " X " " X " " XX " " X ") }, { /* [ț] */ CHAFA_SYMBOL_TAG_LATIN, 0x21b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXX " " X " " X " " XXX " " X ") }, { /* [Ȳ] */ CHAFA_SYMBOL_TAG_LATIN, 0x232, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXX " "X X " " X X " " XX XX " " X " " X " " X " " ") }, { /* [ȳ] */ CHAFA_SYMBOL_TAG_LATIN, 0x233, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " " X X " " X X " " X X " " XXXXX " " XXXX ") }, { /* [ȷ] */ CHAFA_SYMBOL_TAG_LATIN, 0x237, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " X " " X " " X X " " XXX ") }, { /* [ɔ] */ CHAFA_SYMBOL_TAG_LATIN, 0x254, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [ɘ] */ CHAFA_SYMBOL_TAG_LATIN, 0x258, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X XX X " " X " " XXXX " " ") }, { /* [ə] */ CHAFA_SYMBOL_TAG_LATIN, 0x259, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX " " XXXX X " " X X " " XXXX " " ") }, { /* [ɛ] */ CHAFA_SYMBOL_TAG_LATIN, 0x25b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXX " " X X " " XXXX " " ") }, { /* [ɲ] */ CHAFA_SYMBOL_TAG_LATIN, 0x272, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " "X ") }, { /* [ʒ] */ CHAFA_SYMBOL_TAG_LATIN, 0x292, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " XXX " " X " " XXXXX ") }, { /* [ʻ] */ CHAFA_SYMBOL_TAG_LATIN, 0x2bb, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " " " " " " " " " ") }, { /* [ʼ] */ CHAFA_SYMBOL_TAG_LATIN, 0x2bc, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " " " " " " " " " ") }, { /* [ʽ] */ CHAFA_SYMBOL_TAG_LATIN, 0x2bd, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " " " " " " " " " ") }, { /* [ˆ] */ CHAFA_SYMBOL_TAG_LATIN, 0x2c6, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " " " " " " " " " " " ") }, { /* [ˇ] */ CHAFA_SYMBOL_TAG_LATIN, 0x2c7, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XX " " " " " " " " " " " " ") }, { /* [˘] */ CHAFA_SYMBOL_TAG_LATIN, 0x2d8, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XX " " " " " " " " " " " " ") }, { /* [˙] */ CHAFA_SYMBOL_TAG_LATIN, 0x2d9, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " " " " " " " " " " " " " ") }, { /* [˛] */ CHAFA_SYMBOL_TAG_LATIN, 0x2db, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " X " " XXX ") }, { /* [˜] */ CHAFA_SYMBOL_TAG_LATIN, 0x2dc, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX X " " X XX " " " " " " " " " " " " ") }, { /* [˝] */ CHAFA_SYMBOL_TAG_LATIN, 0x2dd, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX " " " " " " " " " " " " " " ") }, { /* [΄] */ CHAFA_SYMBOL_TAG_LATIN, 0x384, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " " " " " " " " " " " " " ") }, { /* [΅] */ CHAFA_SYMBOL_TAG_LATIN, 0x385, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XX X " " X X " " " " " " " " " " ") }, { /* [Ά] */ CHAFA_SYMBOL_TAG_LATIN, 0x386, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [·] */ CHAFA_SYMBOL_TAG_LATIN, 0x387, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X " " " " " " " " ") }, { /* [Έ] */ CHAFA_SYMBOL_TAG_LATIN, 0x388, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [Ή] */ CHAFA_SYMBOL_TAG_LATIN, 0x389, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " X X " " X X " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Ί] */ CHAFA_SYMBOL_TAG_LATIN, 0x38a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ό] */ CHAFA_SYMBOL_TAG_LATIN, 0x38c, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Ύ] */ CHAFA_SYMBOL_TAG_LATIN, 0x38e, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " " "X X " " X X " " X X " " X " " X " " ") }, { /* [Ώ] */ CHAFA_SYMBOL_TAG_LATIN, 0x38f, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XXXX " " X X " " X X " " X X " " X X " " XX XX " " ") }, { /* [ΐ] */ CHAFA_SYMBOL_TAG_LATIN, 0x390, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XX X " " " " XX " " X " " X " " XX " " ") }, { /* [Α] */ CHAFA_SYMBOL_TAG_LATIN, 0x391, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " XXXXXX " " X X " " X X " " ") }, { /* [Β] */ CHAFA_SYMBOL_TAG_LATIN, 0x392, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " XXXXX " " X X " " X X " " XXXXX " " ") }, { /* [Γ] */ CHAFA_SYMBOL_TAG_LATIN, 0x393, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " X " " X " " X " " X " " ") }, { /* [Δ] */ CHAFA_SYMBOL_TAG_LATIN, 0x394, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X X " " XX XX " " X X " "X X " "XXXXXXX " " ") }, { /* [Ε] */ CHAFA_SYMBOL_TAG_LATIN, 0x395, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XXXX " " X " " X " " XXXXXX " " ") }, { /* [Ζ] */ CHAFA_SYMBOL_TAG_LATIN, 0x396, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [Η] */ CHAFA_SYMBOL_TAG_LATIN, 0x397, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [Θ] */ CHAFA_SYMBOL_TAG_LATIN, 0x398, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X XX X " " X X " " X X " " XXXX " " ") }, { /* [Ι] */ CHAFA_SYMBOL_TAG_LATIN, 0x399, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Κ] */ CHAFA_SYMBOL_TAG_LATIN, 0x39a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X XX " " XXX " " XXX " " X XX " " X X " " ") }, { /* [Λ] */ CHAFA_SYMBOL_TAG_LATIN, 0x39b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X X " " XX XX " " X X " "X X " "X X " " ") }, { /* [Μ] */ CHAFA_SYMBOL_TAG_LATIN, 0x39c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XX XX " "X X X X " "X X X " "X X " "X X " "X X " " ") }, { /* [Ν] */ CHAFA_SYMBOL_TAG_LATIN, 0x39d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XXX X " " X XXX " " X X " " X X " " ") }, { /* [Ξ] */ CHAFA_SYMBOL_TAG_LATIN | CHAFA_SYMBOL_TAG_EXTRA, 0x39e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " " " XXXX " " " " " " XXXXXX " " ") }, { /* [Ο] */ CHAFA_SYMBOL_TAG_LATIN, 0x39f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [Π] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X X " " X X " " X X " " X X " " X X " " ") }, { /* [Ρ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X X " " X X " " XXXXX " " X " " X " " ") }, { /* [Σ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XX " " XX " " X " " XXXXXX " " ") }, { /* [Τ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXX " " X " " X " " X " " X " " X " " ") }, { /* [Υ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X " " X X " " XX XX " " X " " X " " X " " ") }, { /* [Φ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " "X X X " "X X X " "X X X " "X X X " " XXXXX " " ") }, { /* [Χ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " X X " " XX " " XX " " X X " " X X " " ") }, { /* [Ψ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X X " "X X X " "X X X " "X X X " " XXXXX " " X " " ") }, { /* [Ω] */ CHAFA_SYMBOL_TAG_LATIN, 0x3a9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " X X " " X X " " X X " " XX XX " " ") }, { /* [Ϊ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3aa, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " XXX " " X " " X " " X " " X " " XXX " " ") }, { /* [Ϋ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ab, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X " " " "X X " " X X " " X X " " X " " X " " ") }, { /* [ά] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ac, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " XXX X " " ") }, { /* [έ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ad, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " XXXX " " X X " " XXXX " " ") }, { /* [ή] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ae, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XXXXXX " " X X " " X X " " X X " " X ") }, { /* [ί] */ CHAFA_SYMBOL_TAG_LATIN, 0x3af, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " XX " " X " " X " " XX " " ") }, { /* [ΰ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b0, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XX X " " X X " " X X " " X X " " X X " " XXXX " " ") }, { /* [α] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXX X " " ") }, { /* [β] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X X " " XXXXX " " X X " " X X " " XXXXX " " X ") }, { /* [γ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "X X " " X X " " X X " " X " " X ") }, { /* [δ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ε] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " XXXX " " X X " " XXXX " " ") }, { /* [ζ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " X " " XX " " X " " X " " XXXX " " XX ") }, { /* [η] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " " X ") }, { /* [θ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X X " " XXXXX " " X X " " X X " " XXX " " ") }, { /* [ι] */ CHAFA_SYMBOL_TAG_LATIN, 0x3b9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " X " " X " " XX " " ") }, { /* [κ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ba, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X XX " " XXXX " " X X " " X X " " ") }, { /* [λ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3bb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " XX XX " " X X " "X X " " ") }, { /* [μ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3bc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X XX " " XXXX X " " X ") }, { /* [ν] */ CHAFA_SYMBOL_TAG_LATIN, 0x3bd, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " XX XX " " X X " " XX " " ") }, { /* [ξ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3be, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXX " " X " " XXXX " " X " " X " " XXXX " " XX ") }, { /* [ο] */ CHAFA_SYMBOL_TAG_LATIN, 0x3bf, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [π] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " X X " " ") }, { /* [ρ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXXX " " X ") }, { /* [ς] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X " " XXXX " " XX ") }, { /* [σ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXX " " ") }, { /* [τ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXX " " X " " X " " XX " " ") }, { /* [υ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " X X " " XXXX " " ") }, { /* [φ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XX XXXX " "X X X " "X X X " " XXXXX " " X ") }, { /* [χ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c7, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X X " " X X " " XX " " X X " " X X ") }, { /* [ψ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c8, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "X X X " "X X X " "X X X " " XXXXX " " X ") }, { /* [ω] */ CHAFA_SYMBOL_TAG_LATIN, 0x3c9, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XX XX " "X X X " "X X X " " XX XX " " ") }, { /* [ϊ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ca, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " XX " " X " " X " " XX " " ") }, { /* [ϋ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3cb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X X " " " " X X " " X X " " X X " " XXXX " " ") }, { /* [ό] */ CHAFA_SYMBOL_TAG_LATIN, 0x3cc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ύ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3cd, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " " X X " " X X " " X X " " XXXX " " ") }, { /* [ώ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3ce, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " X " "XX XX " "X X X " "X X X " " XX XX " " ") }, { /* [ϑ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3d1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX " " X X " " XXXXX " "X X " " X X " " XXX " " ") }, { /* [ϕ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3d5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " X " "XXXXXXX " "X X X " "X X X " " XXXXX " " X ") }, { /* [ϰ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f0, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXX XX " " XX " " XX " "X XX " " ") }, { /* [ϱ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f1, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X X " " X X " " XXXXX " " XXXX ") }, { /* [ϲ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXXX " " X " " X X " " XXXX " " ") }, { /* [ϳ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X " " " " XX " " X " " X " " X X " " XXX ") }, { /* [ϴ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " X X " " XXXXXX " " X X " " X X " " XXXX " " ") }, { /* [ϵ] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX " " XXXXX " " X " " XXXXX " " ") }, { /* [϶] */ CHAFA_SYMBOL_TAG_LATIN, 0x3f6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX " " XXXXX " " X " " XXXXX " " ") }, chafa-1.14.5/chafa/internal/chafa-symbols-misc-narrow.h000066400000000000000000000171531471154763100227020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 " " " " " " ") }, chafa-1.14.5/chafa/internal/chafa-symbols.c000066400000000000000000000347031471154763100204360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #ifdef 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; #ifdef 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 const 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-latin.h" #include "chafa-symbols-block.h" #include "chafa-symbols-kana.h" #include "chafa-symbols-misc-narrow.h" { #ifdef 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 gboolean is_private_use (gunichar c) { return !!((c >= 0xe000 && c <= 0xf8ff) || (c >= 0xf0000 && c <= 0xfffff) || (c >= 0x100000 && c <= 0x10ffff)); } 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) && !is_private_use (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.14.5/chafa/internal/chafa-work-cell.c000066400000000000000000000250471471154763100206460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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" 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_AVX2_INTRINSICS if (chafa_have_avx2 ()) calc_colors_avx2 (wcell->pixels, accums, sym->mask_u32); else #elif defined(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 [0], &color_pair_out->colors [CHAFA_COLOR_PAIR_BG]); accum_to_color (&accums [1], &color_pair_out->colors [CHAFA_COLOR_PAIR_FG]); } 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); } 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; index = &wcell->pixels_sorted_index [ch] [0]; if (wcell->have_pixels_sorted_by_channel [ch]) return index; 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]; } 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 [CHAFA_COLOR_PAIR_BG] = wcell->pixels [sorted_pixels [0]].col; color_pair_out->colors [CHAFA_COLOR_PAIR_FG] = wcell->pixels [sorted_pixels [CHAFA_SYMBOL_N_PIXELS - 1]].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.14.5/chafa/internal/chafa-work-cell.h000066400000000000000000000041701471154763100206450ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/chafa/internal/smolscale/000077500000000000000000000000001471154763100175155ustar00rootroot00000000000000chafa-1.14.5/chafa/internal/smolscale/COPYING000066400000000000000000000013301471154763100205450ustar00rootroot00000000000000This 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.14.5/chafa/internal/smolscale/COPYING.BSD-4000066400000000000000000000032751471154763100213270ustar00rootroot00000000000000Copyright © 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.14.5/chafa/internal/smolscale/COPYING.LGPLv3000066400000000000000000000167441471154763100215720ustar00rootroot00000000000000 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.14.5/chafa/internal/smolscale/Makefile.am000066400000000000000000000012531471154763100215520ustar00rootroot00000000000000noinst_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-generic.c \ 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.14.5/chafa/internal/smolscale/README000066400000000000000000000025041471154763100203760ustar00rootroot00000000000000Smolscale ========= 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.14.5/chafa/internal/smolscale/smolscale-avx2.c000066400000000000000000004111171471154763100225260ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2024 Hans Petter Jansson. See COPYING for details. */ #include /* assert */ #include /* malloc, free, alloca */ #include /* ptrdiff_t */ #include /* memset */ #include #include #include "smolscale-private.h" /* ---------------------- * * Context initialization * * ---------------------- */ /* Number of horizontal pixels to process in a single batch. The define exists for * clarity and cannot be changed without significant changes to the code elsewhere. */ #define BILIN_HORIZ_BATCH_PIXELS 16 /* Batched precalc array layout: * * 16 offsets followed by 16 factors, repeating until epilogue. The epilogue * has offsets and factors alternating one by one, and will always have fewer * than 16 o/f pairs: * * ooooooooooooooooffffffffffffffffooooooooooooooooffffffffffffffffofofofofof... * * 16 offsets layout: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 * 16 factors layout: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 */ static uint32_t array_offset_offset (uint32_t elem_i, int max_index, int do_batches) { if (do_batches && (max_index - ((elem_i / BILIN_HORIZ_BATCH_PIXELS) * BILIN_HORIZ_BATCH_PIXELS) >= BILIN_HORIZ_BATCH_PIXELS)) { return (elem_i / (BILIN_HORIZ_BATCH_PIXELS)) * (BILIN_HORIZ_BATCH_PIXELS * 2) + (elem_i % BILIN_HORIZ_BATCH_PIXELS); } else { return elem_i * 2; } } static uint32_t array_offset_factor (uint32_t elem_i, int max_index, int do_batches) { const uint8_t o [BILIN_HORIZ_BATCH_PIXELS] = { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; if (do_batches && (max_index - ((elem_i / BILIN_HORIZ_BATCH_PIXELS) * BILIN_HORIZ_BATCH_PIXELS) >= BILIN_HORIZ_BATCH_PIXELS)) { return (elem_i / (BILIN_HORIZ_BATCH_PIXELS)) * (BILIN_HORIZ_BATCH_PIXELS * 2) + BILIN_HORIZ_BATCH_PIXELS + o [elem_i % BILIN_HORIZ_BATCH_PIXELS]; } else { return elem_i * 2 + 1; } } static void precalc_linear_range (uint16_t *array_out, int first_index, int last_index, int max_index, uint64_t first_sample_ofs, uint64_t sample_step, int sample_ofs_px_max, int32_t dest_clip_before_px, int do_batches, int *array_i_inout) { uint64_t sample_ofs; int i; sample_ofs = first_sample_ofs; for (i = first_index; i < last_index; i++) { uint16_t sample_ofs_px = sample_ofs / SMOL_BILIN_MULTIPLIER; if (sample_ofs_px >= sample_ofs_px_max - 1) { if (i >= dest_clip_before_px) { array_out [array_offset_offset ((*array_i_inout), max_index, do_batches)] = sample_ofs_px_max - 2; array_out [array_offset_factor ((*array_i_inout), max_index, do_batches)] = 0; (*array_i_inout)++; } continue; } if (i >= dest_clip_before_px) { array_out [array_offset_offset ((*array_i_inout), max_index, do_batches)] = sample_ofs_px; array_out [array_offset_factor ((*array_i_inout), max_index, do_batches)] = SMOL_SMALL_MUL - ((sample_ofs / (SMOL_BILIN_MULTIPLIER / SMOL_SMALL_MUL)) % SMOL_SMALL_MUL); (*array_i_inout)++; } sample_ofs += sample_step; } } static void precalc_bilinear_array (uint16_t *array, uint64_t src_dim_spx, uint64_t dest_ofs_spx, uint64_t dest_dim_spx, uint32_t dest_dim_prehalving_px, unsigned int n_halvings, int32_t dest_clip_before_px, int32_t dest_clip_after_px, unsigned int do_batches) { uint32_t src_dim_px = SMOL_SPX_TO_PX (src_dim_spx); uint64_t first_sample_ofs [3]; uint64_t sample_step; int i = 0; assert (src_dim_px > 1); dest_ofs_spx %= SMOL_SUBPIXEL_MUL; if (src_dim_spx > dest_dim_spx) { /* Minification */ sample_step = ((uint64_t) src_dim_spx * SMOL_BILIN_MULTIPLIER) / dest_dim_spx; first_sample_ofs [0] = (sample_step - SMOL_BILIN_MULTIPLIER) / 2; first_sample_ofs [1] = ((sample_step - SMOL_BILIN_MULTIPLIER) / 2) + ((sample_step * (SMOL_SUBPIXEL_MUL - dest_ofs_spx) * (1 << n_halvings)) / SMOL_SUBPIXEL_MUL); } else { /* Magnification */ sample_step = ((src_dim_spx - SMOL_SUBPIXEL_MUL) * SMOL_BILIN_MULTIPLIER) / (dest_dim_spx > SMOL_SUBPIXEL_MUL ? (dest_dim_spx - SMOL_SUBPIXEL_MUL) : 1); first_sample_ofs [0] = 0; first_sample_ofs [1] = (sample_step * (SMOL_SUBPIXEL_MUL - dest_ofs_spx)) / SMOL_SUBPIXEL_MUL; } first_sample_ofs [2] = (((uint64_t) src_dim_spx * SMOL_BILIN_MULTIPLIER) / SMOL_SUBPIXEL_MUL) + ((sample_step - SMOL_BILIN_MULTIPLIER) / 2) - sample_step * (1U << n_halvings); /* Left fringe */ precalc_linear_range (array, 0, 1 << n_halvings, dest_dim_prehalving_px - dest_clip_after_px, first_sample_ofs [0], sample_step, src_dim_px, dest_clip_before_px, do_batches, &i); /* Main range */ precalc_linear_range (array, 1 << n_halvings, dest_dim_prehalving_px - (1 << n_halvings), dest_dim_prehalving_px - dest_clip_after_px, first_sample_ofs [1], sample_step, src_dim_px, dest_clip_before_px, do_batches, &i); /* Right fringe */ precalc_linear_range (array, dest_dim_prehalving_px - (1 << n_halvings), dest_dim_prehalving_px, dest_dim_prehalving_px - dest_clip_after_px, first_sample_ofs [2], sample_step, src_dim_px, dest_clip_before_px, do_batches, &i); } static void precalc_boxes_array (uint32_t *array, uint32_t *span_step, uint32_t *span_mul, uint32_t src_dim_spx, int32_t dest_dim, uint32_t dest_ofs_spx, uint32_t dest_dim_spx, int32_t dest_clip_before_px) { uint64_t fracF, frac_stepF; uint64_t f; uint64_t stride; uint64_t a, b; int i, dest_i; dest_ofs_spx %= SMOL_SUBPIXEL_MUL; /* Output sample can't be less than a pixel. Fringe opacity is applied in * a separate step. FIXME: May cause wrong subpixel distribution -- revisit. */ if (dest_dim_spx < 256) dest_dim_spx = 256; frac_stepF = ((uint64_t) src_dim_spx * SMOL_BIG_MUL) / (uint64_t) dest_dim_spx; fracF = 0; stride = frac_stepF / (uint64_t) SMOL_BIG_MUL; f = (frac_stepF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; /* We divide by (b + 1) instead of just (b) to avoid overflows in * scale_128bpp_half(), which would affect horizontal box scaling. The * fudge factor counters limited precision in the inverted division * operation. It causes 16-bit values to undershoot by less than 127/65535 * (<.2%). Since the final output is 8-bit, and rounding neutralizes the * error, this doesn't matter. */ a = (SMOL_BOXES_MULTIPLIER * 255); b = ((stride * 255) + ((f * 255) / 256)); *span_step = frac_stepF / SMOL_SMALL_MUL; *span_mul = (a + (b / 2)) / (b + 1); /* Left fringe */ i = 0; dest_i = 0; if (dest_i >= dest_clip_before_px) array [i++] = 0; /* Main range */ fracF = ((frac_stepF * (SMOL_SUBPIXEL_MUL - dest_ofs_spx)) / SMOL_SUBPIXEL_MUL); for (dest_i = 1; dest_i < dest_dim - 1; dest_i++) { if (dest_i >= dest_clip_before_px) array [i++] = fracF / SMOL_SMALL_MUL; fracF += frac_stepF; } /* Right fringe */ if (dest_dim > 1 && dest_i >= dest_clip_before_px) array [i++] = (((uint64_t) src_dim_spx * SMOL_SMALL_MUL - frac_stepF) / SMOL_SMALL_MUL); } static void init_dim (SmolDim *dim, int do_batches) { if (dim->filter_type == SMOL_FILTER_ONE || dim->filter_type == SMOL_FILTER_COPY) { } else if (dim->filter_type == SMOL_FILTER_BOX) { precalc_boxes_array (dim->precalc, &dim->span_step, &dim->span_mul, dim->src_size_spx, dim->placement_size_px, dim->placement_ofs_spx, dim->placement_size_spx, dim->clip_before_px); } else /* SMOL_FILTER_BILINEAR_?H */ { precalc_bilinear_array (dim->precalc, dim->src_size_spx, dim->placement_ofs_spx, dim->placement_size_prehalving_spx, dim->placement_size_prehalving_px, dim->n_halvings, dim->clip_before_px, dim->clip_after_px, do_batches); } } static void init_horizontal (SmolScaleCtx *scale_ctx) { init_dim (&scale_ctx->hdim, scale_ctx->storage_type == SMOL_STORAGE_64BPP ? TRUE : FALSE); } static void init_vertical (SmolScaleCtx *scale_ctx) { init_dim (&scale_ctx->vdim, FALSE); } /* ----------------- * * Premultiplication * * ----------------- */ static SMOL_INLINE void premul_u_to_p8_128bpp (uint64_t * SMOL_RESTRICT inout, uint8_t alpha) { inout [0] = (((inout [0] + 0x0000000100000001) * ((uint16_t) alpha + 1) - 0x0000000100000001) >> 8) & 0x000000ff000000ff; inout [1] = (((inout [1] + 0x0000000100000001) * ((uint16_t) alpha + 1) - 0x0000000100000001) >> 8) & 0x000000ff000000ff; } static SMOL_INLINE void unpremul_p8_to_u_128bpp (const uint64_t *in, uint64_t *out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p8_lut [alpha]) >> INVERTED_DIV_SHIFT_P8) & 0x000000ff000000ff; out [1] = ((in [1] * _smol_inv_div_p8_lut [alpha]) >> INVERTED_DIV_SHIFT_P8) & 0x000000ff000000ff; } static SMOL_INLINE uint64_t premul_u_to_p8_64bpp (const uint64_t in, uint8_t alpha) { return (((in + 0x0001000100010001) * ((uint16_t) alpha + 1) - 0x0001000100010001) >> 8) & 0x00ff00ff00ff00ff; } static SMOL_INLINE uint64_t unpremul_p8_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_p8_to_u_128bpp (in_128bpp, out_128bpp, alpha); return out_128bpp [0] | (out_128bpp [1] << 16); } static SMOL_INLINE void premul_u_to_p16_128bpp (uint64_t *inout, uint8_t alpha) { inout [0] = inout [0] * alpha; inout [1] = inout [1] * alpha; } static SMOL_INLINE void unpremul_p16_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p16_lut [alpha]) >> INVERTED_DIV_SHIFT_P16) & 0x000000ff000000ffULL; out [1] = ((in [1] * _smol_inv_div_p16_lut [alpha]) >> INVERTED_DIV_SHIFT_P16) & 0x000000ff000000ffULL; } /* --------- * * Repacking * * --------- */ /* PACK_SHUF_MM256_EPI8_32_TO_128() * * 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_32_TO_128 0x01000302U #define SHUF_CH_32_TO_128(n) ((char) (SHUF_ORDER_32_TO_128 >> ((4 - (n)) * 8))) #define SHUF_QUAD_CH_32_TO_128(q, n) (4 * (q) + SHUF_CH_32_TO_128 (n)) #define SHUF_QUAD_32_TO_128(q, a, b, c, d) \ SHUF_QUAD_CH_32_TO_128 ((q), (a)), \ SHUF_QUAD_CH_32_TO_128 ((q), (b)), \ SHUF_QUAD_CH_32_TO_128 ((q), (c)), \ SHUF_QUAD_CH_32_TO_128 ((q), (d)) #define PACK_SHUF_EPI8_LANE_32_TO_128(a, b, c, d) \ SHUF_QUAD_32_TO_128 (3, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_128 (2, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_128 (1, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_128 (0, (a), (b), (c), (d)) #define PACK_SHUF_MM256_EPI8_32_TO_128(a, b, c, d) _mm256_set_epi8 ( \ PACK_SHUF_EPI8_LANE_32_TO_128 ((a), (b), (c), (d)), \ PACK_SHUF_EPI8_LANE_32_TO_128 ((a), (b), (c), (d))) /* PACK_SHUF_MM256_EPI8_32_TO_64() * * 64bpp version. Packs only once, so fewer contortions required. */ #define SHUF_CH_32_TO_64(n) ((char) (4 - (n))) #define SHUF_QUAD_CH_32_TO_64(q, n) (4 * (q) + SHUF_CH_32_TO_64 (n)) #define SHUF_QUAD_32_TO_64(q, a, b, c, d) \ SHUF_QUAD_CH_32_TO_64 ((q), (a)), \ SHUF_QUAD_CH_32_TO_64 ((q), (b)), \ SHUF_QUAD_CH_32_TO_64 ((q), (c)), \ SHUF_QUAD_CH_32_TO_64 ((q), (d)) #define PACK_SHUF_EPI8_LANE_32_TO_64(a, b, c, d) \ SHUF_QUAD_32_TO_64 (3, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_64 (2, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_64 (1, (a), (b), (c), (d)), \ SHUF_QUAD_32_TO_64 (0, (a), (b), (c), (d)) #define PACK_SHUF_MM256_EPI8_32_TO_64(a, b, c, d) _mm256_set_epi8 ( \ PACK_SHUF_EPI8_LANE_32_TO_64 ((a), (b), (c), (d)), \ PACK_SHUF_EPI8_LANE_32_TO_64 ((a), (b), (c), (d))) /* 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)) /* ---------------------- * * Repacking: 24/32 -> 64 * * ---------------------- */ static void unpack_8x_1234_p8_to_xxxx_p8_64bpp (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 * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; __m256i m0, m1, m2; SMOL_ASSUME_ALIGNED (my_out, __m256i * SMOL_RESTRICT); while ((ptrdiff_t) (my_out + 2) <= (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); _mm256_store_si256 (my_out, m1); my_out++; _mm256_store_si256 (my_out, m2); my_out++; } *out = (uint64_t * SMOL_RESTRICT) my_out; *in = (const uint32_t * SMOL_RESTRICT) my_in; } static SMOL_INLINE uint64_t unpack_pixel_123_p8_to_132a_p8_64bpp (const uint8_t *p) { return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) | ((uint64_t) p [2] << 32) | 0xff; } SMOL_REPACK_ROW_DEF (123, 24, 8, PREMUL8, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_123_p8_to_132a_p8_64bpp (src_row); src_row += 3; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_1324_p8_64bpp (uint32_t p) { return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_64 (1, 3, 2, 4); unpack_8x_1234_p8_to_xxxx_p8_64bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_3241_p8_64bpp (uint32_t p) { return (((uint64_t) p & 0x0000ff00) << 40) | (((uint64_t) p & 0x00ff00ff) << 16) | (p >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 3241, 64, 64, PREMUL8, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_64 (3, 2, 4, 1); unpack_8x_1234_p8_to_xxxx_p8_64bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_3241_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_2431_p8_64bpp (uint32_t p) { uint64_t p64 = p; return ((p64 & 0x00ff00ff) << 32) | ((p64 & 0x0000ff00) << 8) | ((p64 & 0xff000000) >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 2431, 64, 64, PREMUL8, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_64 (2, 4, 3, 1); unpack_8x_1234_p8_to_xxxx_p8_64bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_2431_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_a234_u_to_324a_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 40) | (((uint64_t) p & 0x00ff00ff) << 16); uint8_t alpha = p >> 24; return premul_u_to_p8_64bpp (p64, alpha) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 3241, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_a234_u_to_324a_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_u_to_2431_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x00ff00ff) << 32) | (((uint64_t) p & 0x0000ff00) << 8); uint8_t alpha = p >> 24; return premul_u_to_p8_64bpp (p64, alpha) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2431, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_u_to_2431_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_123a_u_to_132a_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; return premul_u_to_p8_64bpp (p64, alpha) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_123a_u_to_132a_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END /* ----------------------- * * Repacking: 24/32 -> 128 * * ----------------------- */ static void unpack_8x_xxxx_u_to_123a_p16_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); const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; __m256i m0, m1, m2, m3, m4, m5, m6; __m256i fact1, fact2; 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 (my_out, m3); my_out++; _mm256_store_si256 (my_out, m4); my_out++; _mm256_store_si256 (my_out, m5); my_out++; _mm256_store_si256 (my_out, m6); my_out++; } *out = (uint64_t * SMOL_RESTRICT) my_out; *in = (const uint32_t * SMOL_RESTRICT) my_in; } static SMOL_INLINE void unpack_pixel_123_p8_to_123a_p8_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; } SMOL_REPACK_ROW_DEF (123, 24, 8, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123_p8_to_123a_p8_128bpp (src_row, dest_row); src_row += 3; dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_p8_to_123a_p8_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); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123a_p8_to_123a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_p8_to_234a_p8_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; out [0] = ((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8); out [1] = ((p64 & 0x000000ff) << 32) | ((p64 & 0xff000000) >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 2341, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_a234_p8_to_234a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_p8_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0x00ff00ff) << 32) | (((uint64_t) p & 0x0000ff00) << 8); uint8_t alpha = p >> 24; p64 = premul_u_to_p8_64bpp (p64, alpha) | alpha; out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_p16_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; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL16, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_128 (2, 3, 4, 1); unpack_8x_xxxx_u_to_123a_p16_128bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_p16_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p8_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_p8_64bpp (p64, alpha) | ((uint64_t) alpha); out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p16_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; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL16, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_128 (1, 2, 3, 4); unpack_8x_xxxx_u_to_123a_p16_128bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_p16_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END /* ---------------------- * * Repacking: 64 -> 24/32 * * ---------------------- */ static void pack_8x_1234_p8_to_xxxx_p8_64bpp (const uint64_t * SMOL_RESTRICT *in, uint32_t * SMOL_RESTRICT *out, uint32_t * out_max, const __m256i channel_shuf) { const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; __m256i m0, m1; SMOL_ASSUME_ALIGNED (my_in, __m256i * SMOL_RESTRICT); while ((ptrdiff_t) (my_out + 1) <= (ptrdiff_t) out_max) { /* Load inputs */ m0 = _mm256_stream_load_si256 (my_in); my_in++; m1 = _mm256_stream_load_si256 (my_in); my_in++; /* Pack and store */ m0 = _mm256_packus_epi16 (m0, m1); m0 = _mm256_shuffle_epi8 (m0, channel_shuf); m0 = _mm256_permute4x64_epi64 (m0, SMOL_4X2BIT (3, 1, 2, 0)); _mm256_storeu_si256 (my_out, m0); my_out++; } *out = (uint32_t * SMOL_RESTRICT) my_out; *in = (const uint64_t * SMOL_RESTRICT) my_in; } static SMOL_INLINE uint32_t pack_pixel_1234_p8_to_1324_p8_64bpp (uint64_t in) { return in | (in >> 24); } SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 132, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 24; *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 132, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 24; *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 231, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; *(dest_row++) = p >> 24; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 231, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; *(dest_row++) = p >> 24; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 324, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; *(dest_row++) = p; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 324, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row >> 24; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; *(dest_row++) = p; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 423, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p; *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 423, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row >> 24; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p; *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 1324, 32, 32, PREMUL8, COMPRESSED) { const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_64 (1, 3, 2, 4); pack_8x_1234_p8_to_xxxx_p8_64bpp (&src_row, &dest_row, dest_row_max, channel_shuf); while (dest_row != dest_row_max) { *(dest_row++) = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 1324, 32, 32, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; *(dest_row++) = pack_pixel_1234_p8_to_1324_p8_64bpp (t); src_row++; } } SMOL_REPACK_ROW_DEF_END #define DEF_REPACK_FROM_1234_64BPP_TO_32BPP(a, b, c, d) \ SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, PREMUL8, COMPRESSED) { \ const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_64 ((a), (b), (c), (d)); \ pack_8x_1234_p8_to_xxxx_p8_64bpp (&src_row, &dest_row, dest_row_max, \ channel_shuf); \ while (dest_row != dest_row_max) \ { \ *(dest_row++) = PACK_FROM_1234_64BPP (*src_row, a, b, c, d); \ src_row++; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint8_t alpha = *src_row; \ uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_64BPP (t, a, b, c, d); \ src_row++; \ } \ } SMOL_REPACK_ROW_DEF_END DEF_REPACK_FROM_1234_64BPP_TO_32BPP (1, 4, 2, 3) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (2, 3, 1, 4) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (4, 1, 3, 2) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (4, 2, 3, 1) /* ----------------------- * * Repacking: 128 -> 24/32 * * ----------------------- */ static void pack_8x_123a_p16_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_P16 - 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 * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; __m256i m0, m1, m2, m3, m4, m5, m6, m7, m8; SMOL_ASSUME_ALIGNED (my_in, __m256i * SMOL_RESTRICT); while ((ptrdiff_t) (my_out + 1) <= (ptrdiff_t) out_max) { /* Load inputs */ m0 = _mm256_stream_load_si256 (my_in); my_in++; m1 = _mm256_stream_load_si256 (my_in); my_in++; m2 = _mm256_stream_load_si256 (my_in); my_in++; m3 = _mm256_stream_load_si256 (my_in); my_in++; /* Load alpha factors */ m4 = _mm256_slli_si256 (m0, 4); m6 = _mm256_srli_si256 (m3, 4); m5 = _mm256_blend_epi32 (m4, m1, ALPHA_MASK); m7 = _mm256_blend_epi32 (m6, m2, ALPHA_MASK); m7 = _mm256_srli_si256 (m7, 4); m4 = _mm256_blend_epi32 (m5, m7, SMOL_8X1BIT (0, 0, 1, 1, 0, 0, 1, 1)); m4 = _mm256_srli_epi32 (m4, 8); m4 = _mm256_and_si256 (m4, alpha_clean_mask); m4 = _mm256_i32gather_epi32 ((const void *) _smol_inv_div_p16_lut, m4, 4); /* 2 pixels times 4 */ m5 = _mm256_shuffle_epi32 (m4, SMOL_4X2BIT (3, 3, 3, 3)); m6 = _mm256_shuffle_epi32 (m4, SMOL_4X2BIT (2, 2, 2, 2)); m7 = _mm256_shuffle_epi32 (m4, SMOL_4X2BIT (1, 1, 1, 1)); m8 = _mm256_shuffle_epi32 (m4, SMOL_4X2BIT (0, 0, 0, 0)); m5 = _mm256_blend_epi32 (m5, ones, ALPHA_MASK); m6 = _mm256_blend_epi32 (m6, ones, ALPHA_MASK); m7 = _mm256_blend_epi32 (m7, ones, ALPHA_MASK); m8 = _mm256_blend_epi32 (m8, ones, ALPHA_MASK); m5 = _mm256_mullo_epi32 (m5, m0); m6 = _mm256_mullo_epi32 (m6, m1); m7 = _mm256_mullo_epi32 (m7, m2); m8 = _mm256_mullo_epi32 (m8, m3); m5 = _mm256_srli_epi32 (m5, INVERTED_DIV_SHIFT_P16); m6 = _mm256_srli_epi32 (m6, INVERTED_DIV_SHIFT_P16); m7 = _mm256_srli_epi32 (m7, INVERTED_DIV_SHIFT_P16); m8 = _mm256_srli_epi32 (m8, INVERTED_DIV_SHIFT_P16); /* Pack and store */ m0 = _mm256_packus_epi32 (m5, m6); m1 = _mm256_packus_epi32 (m7, m8); m0 = _mm256_packus_epi16 (m0, m1); m0 = _mm256_shuffle_epi8 (m0, channel_shuf); m0 = _mm256_permute4x64_epi64 (m0, SMOL_4X2BIT (3, 1, 2, 0)); m0 = _mm256_shuffle_epi32 (m0, SMOL_4X2BIT (3, 1, 2, 0)); _mm256_storeu_si256 (my_out, m0); my_out += 1; } *out = (uint32_t * SMOL_RESTRICT) my_out; *in = (const uint64_t * SMOL_RESTRICT) my_in; #undef ALPHA_MUL #undef ALPHA_MASK } SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 123, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = *src_row >> 32; *(dest_row++) = *(src_row++); *(dest_row++) = *(src_row++) >> 32; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1]; unpremul_p8_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1]; unpremul_p16_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 321, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = src_row [1] >> 32; *(dest_row++) = src_row [0]; *(dest_row++) = src_row [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1]; unpremul_p8_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1] >> 8; unpremul_p16_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END #define DEF_REPACK_FROM_1234_128BPP_TO_32BPP(a, b, c, d) \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, PREMUL8, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ *(dest_row++) = PACK_FROM_1234_128BPP (src_row, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = src_row [1]; \ unpremul_p8_to_u_128bpp (src_row, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ const __m256i channel_shuf = PACK_SHUF_MM256_EPI8_32_TO_128 ((a), (b), (c), (d)); \ pack_8x_123a_p16_to_xxxx_u_128bpp (&src_row, &dest_row, dest_row_max, \ channel_shuf); \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = src_row [1] >> 8; \ unpremul_p16_to_u_128bpp (src_row, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END DEF_REPACK_FROM_1234_128BPP_TO_32BPP (1, 2, 3, 4) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (3, 2, 1, 4) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (4, 1, 2, 3) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (4, 3, 2, 1) /* -------------- * * Filter helpers * * -------------- */ #define LERP_SIMD256_EPI32(a, b, f) \ _mm256_add_epi32 ( \ _mm256_srli_epi32 ( \ _mm256_mullo_epi32 ( \ _mm256_sub_epi32 ((a), (b)), (f)), 8), (b)) #define LERP_SIMD128_EPI32(a, b, f) \ _mm_add_epi32 ( \ _mm_srli_epi32 ( \ _mm_mullo_epi32 ( \ _mm_sub_epi32 ((a), (b)), (f)), 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)) static SMOL_INLINE const char * src_row_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t src_row_ofs) { return scale_ctx->src_pixels + scale_ctx->src_rowstride * src_row_ofs; } static SMOL_INLINE uint64_t weight_pixel_64bpp (uint64_t p, uint16_t w) { return ((p * w) >> 8) & 0x00ff00ff00ff00ffULL; } /* p and out may be the same address */ static SMOL_INLINE void weight_pixel_128bpp (const 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 | (b << 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++); } static void copy_weighted_parts_64bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { 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++) = weight_pixel_64bpp (*(parts_in++), w); } } static void copy_weighted_parts_128bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { const uint64_t *parts_in_max = parts_in + n * 2; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in < parts_in_max) { weight_pixel_128bpp (parts_in, parts_acc_out, w); parts_in += 2; parts_acc_out += 2; } } static void add_weighted_parts_64bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { 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++) += weight_pixel_64bpp (*(parts_in++), w); } } static void add_weighted_parts_128bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { const uint64_t *parts_in_max = parts_in + n * 2; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in < parts_in_max) { uint64_t t [2]; weight_pixel_128bpp (parts_in, t, w); parts_acc_out [0] += t [0]; parts_acc_out [1] += t [1]; parts_in += 2; parts_acc_out += 2; } } static SMOL_INLINE void apply_subpixel_opacity_64bpp (uint64_t * SMOL_RESTRICT u64_inout, uint16_t opacity) { *u64_inout = ((*u64_inout * opacity) >> SMOL_SUBPIXEL_SHIFT) & 0x00ff00ff00ff00ffULL; } static SMOL_INLINE void apply_subpixel_opacity_128bpp_half (uint64_t * SMOL_RESTRICT u64_inout, uint16_t opacity) { *u64_inout = ((*u64_inout * opacity) >> SMOL_SUBPIXEL_SHIFT) & 0x00ffffff00ffffffULL; } static SMOL_INLINE void apply_subpixel_opacity_128bpp (uint64_t *u64_inout, uint16_t opacity) { apply_subpixel_opacity_128bpp_half (u64_inout, opacity); apply_subpixel_opacity_128bpp_half (u64_inout + 1, opacity); } static void apply_subpixel_opacity_row_copy_64bpp (uint64_t * SMOL_RESTRICT u64_in, uint64_t * SMOL_RESTRICT u64_out, int n_pixels, uint16_t opacity) { uint64_t *u64_out_max = u64_out + n_pixels; while (u64_out != u64_out_max) { *u64_out = *u64_in++; apply_subpixel_opacity_64bpp (u64_out, opacity); u64_out++; } } static void apply_subpixel_opacity_row_copy_128bpp (uint64_t * SMOL_RESTRICT u64_in, uint64_t * SMOL_RESTRICT u64_out, int n_pixels, uint16_t opacity) { uint64_t *u64_out_max = u64_out + (n_pixels * 2); while (u64_out != u64_out_max) { u64_out [0] = u64_in [0]; u64_out [1] = u64_in [1]; apply_subpixel_opacity_128bpp_half (u64_out, opacity); apply_subpixel_opacity_128bpp_half (u64_out + 1, opacity); u64_in += 2; u64_out += 2; } } static void apply_horiz_edge_opacity (const SmolScaleCtx *scale_ctx, uint64_t *row_parts) { if (scale_ctx->storage_type == SMOL_STORAGE_64BPP) { apply_subpixel_opacity_64bpp (&row_parts [0], scale_ctx->hdim.first_opacity); apply_subpixel_opacity_64bpp (&row_parts [scale_ctx->hdim.placement_size_px - 1], scale_ctx->hdim.last_opacity); } else { apply_subpixel_opacity_128bpp (&row_parts [0], scale_ctx->hdim.first_opacity); apply_subpixel_opacity_128bpp (&row_parts [(scale_ctx->hdim.placement_size_px - 1) * 2], scale_ctx->hdim.last_opacity); } } /* ------------------ * * Horizontal scaling * * ------------------ */ #define CONTROL_4X2BIT_1_0_3_2 (SMOL_4X2BIT (1, 0, 3, 2)) #define CONTROL_4X2BIT_3_1_2_0 (SMOL_4X2BIT (3, 1, 2, 0)) #define CONTROL_8X1BIT_1_1_0_0_1_1_0_0 (SMOL_8X1BIT (1, 1, 0, 0, 1, 1, 0, 0)) static SMOL_INLINE void hadd_pixels_16x_to_8x_64bpp (__m256i i0, __m256i i1, __m256i i2, __m256i i3, __m256i * SMOL_RESTRICT o0, __m256i * SMOL_RESTRICT o1) { __m256i t0, t1, t2, t3; t0 = _mm256_shuffle_epi32 (i0, CONTROL_4X2BIT_1_0_3_2); t1 = _mm256_shuffle_epi32 (i1, CONTROL_4X2BIT_1_0_3_2); t2 = _mm256_shuffle_epi32 (i2, CONTROL_4X2BIT_1_0_3_2); t3 = _mm256_shuffle_epi32 (i3, CONTROL_4X2BIT_1_0_3_2); t0 = _mm256_add_epi16 (t0, i0); t1 = _mm256_add_epi16 (t1, i1); t2 = _mm256_add_epi16 (t2, i2); t3 = _mm256_add_epi16 (t3, i3); t0 = _mm256_blend_epi32 (t0, t1, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); t1 = _mm256_blend_epi32 (t2, t3, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); t0 = _mm256_permute4x64_epi64 (t0, CONTROL_4X2BIT_3_1_2_0); t1 = _mm256_permute4x64_epi64 (t1, CONTROL_4X2BIT_3_1_2_0); *o0 = t0; *o1 = t1; } static SMOL_INLINE void hadd_pixels_8x_to_4x_64bpp (__m256i i0, __m256i i1, __m256i * SMOL_RESTRICT o0) { __m256i t0, t1; t0 = _mm256_shuffle_epi32 (i0, CONTROL_4X2BIT_1_0_3_2); t1 = _mm256_shuffle_epi32 (i1, CONTROL_4X2BIT_1_0_3_2); t0 = _mm256_add_epi16 (t0, i0); t1 = _mm256_add_epi16 (t1, i1); t0 = _mm256_blend_epi32 (t0, t1, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); t0 = _mm256_permute4x64_epi64 (t0, CONTROL_4X2BIT_3_1_2_0); *o0 = t0; } static SMOL_INLINE void interp_horizontal_bilinear_batch_64bpp (const uint64_t * SMOL_RESTRICT row_parts_in, const uint16_t * SMOL_RESTRICT precalc_x, __m256i * SMOL_RESTRICT o0, __m256i * SMOL_RESTRICT o1, __m256i * SMOL_RESTRICT o2, __m256i * SMOL_RESTRICT o3) { const __m256i mask = _mm256_set_epi16 (0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff); const __m256i shuf_0 = _mm256_set_epi8 (3, 2, 3, 2, 3, 2, 3, 2, 1, 0, 1, 0, 1, 0, 1, 0, 3, 2, 3, 2, 3, 2, 3, 2, 1, 0, 1, 0, 1, 0, 1, 0); const __m256i shuf_1 = _mm256_set_epi8 (7, 6, 7, 6, 7, 6, 7, 6, 5, 4, 5, 4, 5, 4, 5, 4, 7, 6, 7, 6, 7, 6, 7, 6, 5, 4, 5, 4, 5, 4, 5, 4); const __m256i shuf_2 = _mm256_set_epi8 (11, 10, 11, 10, 11, 10, 11, 10, 9, 8, 9, 8, 9, 8, 9, 8, 11, 10, 11, 10, 11, 10, 11, 10, 9, 8, 9, 8, 9, 8, 9, 8); const __m256i shuf_3 = _mm256_set_epi8 (15, 14, 15, 14, 15, 14, 15, 14, 13, 12, 13, 12, 13, 12, 13, 12, 15, 14, 15, 14, 15, 14, 15, 14, 13, 12, 13, 12, 13, 12, 13, 12); __m256i m0, m1, m2, m3; __m256i f0, f1, f2, f3; __m256i q00, q10, q20, q30, q40, q50, q60, q70; __m256i q01, q11, q21, q31, q41, q51, q61, q71; __m256i p00, p01, p10, p11, p20, p21, p30, p31; __m256i f; /* Fetch pixel pairs to interpolate between, two pairs per ymm register. * This looks clumsy, but it's a lot faster than using _mm256_i32gather_epi64(), * as benchmarked on both Haswell and Tiger Lake. */ q00 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [0]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [1])), 1); q10 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [2]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [3])), 1); q20 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [4]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [5])), 1); q30 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [6]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [7])), 1); q40 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [8]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [9])), 1); q50 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [10]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [11])), 1); q60 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [12]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [13])), 1); q70 = _mm256_inserti128_si256 (_mm256_castsi128_si256 ( _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [14]))), _mm_loadu_si128 ((const __m128i *) (row_parts_in + precalc_x [15])), 1); f = _mm256_load_si256 ((const __m256i *) (precalc_x + 16)); /* Factors */ /* 0123 -> 0x2x, 1x3x. 4567 -> x4x6, x5x7. Etc. */ q01 = _mm256_shuffle_epi32 (q00, CONTROL_4X2BIT_1_0_3_2); q11 = _mm256_shuffle_epi32 (q10, CONTROL_4X2BIT_1_0_3_2); q21 = _mm256_shuffle_epi32 (q20, CONTROL_4X2BIT_1_0_3_2); q31 = _mm256_shuffle_epi32 (q30, CONTROL_4X2BIT_1_0_3_2); q41 = _mm256_shuffle_epi32 (q40, CONTROL_4X2BIT_1_0_3_2); q51 = _mm256_shuffle_epi32 (q50, CONTROL_4X2BIT_1_0_3_2); q61 = _mm256_shuffle_epi32 (q60, CONTROL_4X2BIT_1_0_3_2); q71 = _mm256_shuffle_epi32 (q70, CONTROL_4X2BIT_1_0_3_2); /* 0x2x, x4x6 -> 0426. 1x3x, x5x7 -> 1537. Etc. */ p00 = _mm256_blend_epi32 (q00, q11, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p10 = _mm256_blend_epi32 (q20, q31, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p20 = _mm256_blend_epi32 (q40, q51, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p30 = _mm256_blend_epi32 (q60, q71, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p01 = _mm256_blend_epi32 (q01, q10, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p11 = _mm256_blend_epi32 (q21, q30, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p21 = _mm256_blend_epi32 (q41, q50, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); p31 = _mm256_blend_epi32 (q61, q70, CONTROL_8X1BIT_1_1_0_0_1_1_0_0); /* Interpolation. 0426 vs 1537. Etc. */ m0 = _mm256_sub_epi16 (p00, p01); m1 = _mm256_sub_epi16 (p10, p11); m2 = _mm256_sub_epi16 (p20, p21); m3 = _mm256_sub_epi16 (p30, p31); f0 = _mm256_shuffle_epi8 (f, shuf_0); f1 = _mm256_shuffle_epi8 (f, shuf_1); f2 = _mm256_shuffle_epi8 (f, shuf_2); f3 = _mm256_shuffle_epi8 (f, shuf_3); m0 = _mm256_mullo_epi16 (m0, f0); m1 = _mm256_mullo_epi16 (m1, f1); m2 = _mm256_mullo_epi16 (m2, f2); m3 = _mm256_mullo_epi16 (m3, f3); m0 = _mm256_srli_epi16 (m0, 8); m1 = _mm256_srli_epi16 (m1, 8); m2 = _mm256_srli_epi16 (m2, 8); m3 = _mm256_srli_epi16 (m3, 8); m0 = _mm256_add_epi16 (m0, p01); m1 = _mm256_add_epi16 (m1, p11); m2 = _mm256_add_epi16 (m2, p21); m3 = _mm256_add_epi16 (m3, p31); m0 = _mm256_and_si256 (m0, mask); m1 = _mm256_and_si256 (m1, mask); m2 = _mm256_and_si256 (m2, mask); m3 = _mm256_and_si256 (m3, mask); /* [0426/1537] -> [0246/1357]. Etc. */ *o0 = _mm256_permute4x64_epi64 (m0, CONTROL_4X2BIT_3_1_2_0); *o1 = _mm256_permute4x64_epi64 (m1, CONTROL_4X2BIT_3_1_2_0); *o2 = _mm256_permute4x64_epi64 (m2, CONTROL_4X2BIT_3_1_2_0); *o3 = _mm256_permute4x64_epi64 (m3, CONTROL_4X2BIT_3_1_2_0); } static void interp_horizontal_bilinear_batch_to_4x_64bpp (const uint64_t * SMOL_RESTRICT row_parts_in, const uint16_t * SMOL_RESTRICT precalc_x, __m256i * SMOL_RESTRICT o0) { __m256i m0, m1, m2, m3, s0, s1; interp_horizontal_bilinear_batch_64bpp (row_parts_in, precalc_x, &m0, &m1, &m2, &m3); hadd_pixels_16x_to_8x_64bpp (m0, m1, m2, m3, &s0, &s1); hadd_pixels_8x_to_4x_64bpp (s0, s1, o0); } static void interp_horizontal_bilinear_4x_batch_to_4x_64bpp (const uint64_t * SMOL_RESTRICT row_parts_in, const uint16_t * SMOL_RESTRICT precalc_x, __m256i * SMOL_RESTRICT o0) { __m256i t0, t1, t2, t3; interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x, &t0); interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x + 32, &t1); interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x + 64, &t2); interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x + 96, &t3); hadd_pixels_16x_to_8x_64bpp (t0, t1, t2, t3, &t0, &t1); hadd_pixels_8x_to_4x_64bpp (t0, t1, o0); } /* Note that precalc_x must point to offsets and factors interleaved one by one, i.e. * offset - factor - offset - factor, and not 16x as with the batch function. */ static SMOL_INLINE void interp_horizontal_bilinear_epilogue_64bpp (const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out, uint64_t * SMOL_RESTRICT row_parts_out_max, const uint16_t * SMOL_RESTRICT precalc_x, int n_halvings) { while (row_parts_out != row_parts_out_max) { uint64_t accum = 0; int i; for (i = 0; i < (1 << (n_halvings)); i++) { uint64_t p, q; uint64_t F; p = *(row_parts_in + (*precalc_x)); q = *(row_parts_in + (*precalc_x) + 1); precalc_x++; F = *(precalc_x++); accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; } } 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) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 16 <= row_parts_out_max) { __m256i m0, m1, m2, m3; interp_horizontal_bilinear_batch_64bpp (row_parts_in, precalc_x, &m0, &m1, &m2, &m3); _mm256_store_si256 ((__m256i *) row_parts_out + 0, m0); _mm256_store_si256 ((__m256i *) row_parts_out + 1, m1); _mm256_store_si256 ((__m256i *) row_parts_out + 2, m2); _mm256_store_si256 ((__m256i *) row_parts_out + 3, m3); row_parts_out += 16; precalc_x += 32; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 0); } static void interp_horizontal_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 8 <= row_parts_out_max) { __m256i m0, m1, m2, m3, s0, s1; interp_horizontal_bilinear_batch_64bpp (row_parts_in, precalc_x, &m0, &m1, &m2, &m3); hadd_pixels_16x_to_8x_64bpp (m0, m1, m2, m3, &s0, &s1); s0 = _mm256_srli_epi16 (s0, 1); s1 = _mm256_srli_epi16 (s1, 1); _mm256_store_si256 ((__m256i *) row_parts_out, s0); _mm256_store_si256 ((__m256i *) row_parts_out + 1, s1); row_parts_out += 8; precalc_x += 32; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 1); } static void interp_horizontal_bilinear_2h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 4 <= row_parts_out_max) { __m256i t; interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x, &t); t = _mm256_srli_epi16 (t, 2); _mm256_store_si256 ((__m256i *) row_parts_out, t); row_parts_out += 4; precalc_x += 32; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 2); } static void interp_horizontal_bilinear_3h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 4 <= row_parts_out_max) { __m256i s0, s1; interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x, &s0); interp_horizontal_bilinear_batch_to_4x_64bpp (row_parts_in, precalc_x + 32, &s1); hadd_pixels_8x_to_4x_64bpp (s0, s1, &s0); s0 = _mm256_srli_epi16 (s0, 3); _mm256_store_si256 ((__m256i *) row_parts_out, s0); row_parts_out += 4; precalc_x += 64; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 3); } static void interp_horizontal_bilinear_4h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 4 <= row_parts_out_max) { __m256i t0; interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x, &t0); t0 = _mm256_srli_epi16 (t0, 4); _mm256_store_si256 ((__m256i *) row_parts_out, t0); row_parts_out += 4; precalc_x += 128; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 4); } static void interp_horizontal_bilinear_5h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 4 <= row_parts_out_max) { __m256i t0, t1; interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x, &t0); interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x + 128, &t1); hadd_pixels_8x_to_4x_64bpp (t0, t1, &t0); t0 = _mm256_srli_epi16 (t0, 5); _mm256_store_si256 ((__m256i *) row_parts_out, t0); row_parts_out += 4; precalc_x += 256; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 5); } static void interp_horizontal_bilinear_6h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t * SMOL_RESTRICT); SMOL_ASSUME_ALIGNED (precalc_x, const uint16_t * SMOL_RESTRICT); while (row_parts_out + 4 <= row_parts_out_max) { __m256i t0, t1, t2, t3; interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x, &t0); interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x + 128, &t1); interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x + 256, &t2); interp_horizontal_bilinear_4x_batch_to_4x_64bpp (row_parts_in, precalc_x + 384, &t3); hadd_pixels_16x_to_8x_64bpp (t0, t1, t2, t3, &t0, &t1); hadd_pixels_8x_to_4x_64bpp (t0, t1, &t0); t0 = _mm256_srli_epi16 (t0, 6); _mm256_store_si256 ((__m256i *) row_parts_out, t0); row_parts_out += 4; precalc_x += 512; } interp_horizontal_bilinear_epilogue_64bpp (row_parts_in, row_parts_out, row_parts_out_max, precalc_x, 6); } 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 precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px * 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 factors; __m256i m0, m1; __m128i n0, n1, n2, n3, n4, n5; const uint64_t * SMOL_RESTRICT p0; p0 = row_parts_in + *(precalc_x++) * 2; n4 = _mm_set1_epi16 (*(precalc_x++)); n0 = _mm_load_si128 ((__m128i *) p0); n1 = _mm_load_si128 ((__m128i *) p0 + 1); p0 = row_parts_in + *(precalc_x++) * 2; n5 = _mm_set1_epi16 (*(precalc_x++)); n2 = _mm_load_si128 ((__m128i *) p0); n3 = _mm_load_si128 ((__m128i *) p0 + 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 factors; __m128i m0, m1; uint32_t f; const uint64_t * SMOL_RESTRICT p0; p0 = row_parts_in + *(precalc_x++) * 2; f = *(precalc_x++); factors = _mm_set1_epi32 ((uint32_t) f); m0 = _mm_stream_load_si128 ((__m128i *) p0); m1 = _mm_stream_load_si128 ((__m128i *) p0 + 1); m0 = LERP_SIMD128_EPI32_AND_MASK (m0, m1, factors, mask128); _mm_store_si128 ((__m128i *) row_parts_out, m0); row_parts_out += 2; } } #define DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(n_halvings) \ 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 precalc_x = scale_ctx->hdim.precalc; \ uint64_t *row_parts_out_max = row_parts_out + scale_ctx->hdim.placement_size_px * 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 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; \ const uint64_t * SMOL_RESTRICT p0; \ \ p0 = row_parts_in + *(precalc_x++) * 2; \ n4 = _mm_set1_epi16 (*(precalc_x++)); \ n0 = _mm_load_si128 ((__m128i *) p0); \ n1 = _mm_load_si128 ((__m128i *) p0 + 1); \ \ p0 = row_parts_in + *(precalc_x++) * 2; \ n5 = _mm_set1_epi16 (*(precalc_x++)); \ n2 = _mm_load_si128 ((__m128i *) p0); \ n3 = _mm_load_si128 ((__m128i *) p0 + 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_AND_MASK (m0, m1, factors, mask256); \ 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; \ } \ } DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(1) DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(2) DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(3) DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(4) DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(5) DEF_INTERP_HORIZONTAL_BILINEAR_128BPP(6) static SMOL_INLINE void unpack_box_precalc (const uint32_t precalc, uint32_t step, uint32_t *ofs0, uint32_t *ofs1, uint32_t *f0, uint32_t *f1, uint32_t *n) { *ofs0 = precalc; *ofs1 = *ofs0 + step; *f0 = 256 - (*ofs0 % SMOL_SUBPIXEL_MUL); *f1 = *ofs1 % SMOL_SUBPIXEL_MUL; *ofs0 /= SMOL_SUBPIXEL_MUL; *ofs1 /= SMOL_SUBPIXEL_MUL; *n = *ofs1 - *ofs0 - 1; } static void interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t *src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint64_t * SMOL_RESTRICT pp; const uint32_t *precalc_x = scale_ctx->hdim.precalc; uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px; uint64_t accum; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); while (dest_row_parts < dest_row_parts_max) { uint32_t ofs0, ofs1; uint32_t f0, f1; uint32_t n; unpack_box_precalc (*(precalc_x++), scale_ctx->hdim.span_step, &ofs0, &ofs1, &f0, &f1, &n); pp = src_row_parts + ofs0; accum = weight_pixel_64bpp (*(pp++), f0); sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); accum += weight_pixel_64bpp (*pp, f1); *(dest_row_parts++) = scale_64bpp (accum, scale_ctx->hdim.span_mul); } } static void interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t *src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint64_t * SMOL_RESTRICT pp; const uint32_t *precalc_x = scale_ctx->hdim.precalc; uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px * 2; uint64_t accum [2]; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); while (dest_row_parts < dest_row_parts_max) { uint32_t ofs0, ofs1; uint32_t f0, f1; uint32_t n; uint64_t t [2]; unpack_box_precalc (*(precalc_x++), scale_ctx->hdim.span_step, &ofs0, &ofs1, &f0, &f1, &n); pp = src_row_parts + (ofs0 * 2); weight_pixel_128bpp (pp, accum, f0); pp += 2; sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); weight_pixel_128bpp (pp, t, f1); accum [0] += t [0]; accum [1] += t [1]; scale_and_store_128bpp (accum, scale_ctx->hdim.span_mul, (uint64_t ** SMOL_RESTRICT) &dest_row_parts); } } 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->hdim.placement_size_px; 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->hdim.placement_size_px * 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->hdim.placement_size_px * 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->hdim.placement_size_px * 2 * sizeof (uint64_t)); } static void scale_horizontal (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, const char *src_row, uint64_t *dest_row_parts) { uint64_t * SMOL_RESTRICT src_row_unpacked; src_row_unpacked = local_ctx->parts_row [3]; /* 32-bit unpackers need 32-bit alignment */ if ((((uintptr_t) src_row) & 3) && scale_ctx->src_pixel_type != SMOL_PIXEL_RGB8 && scale_ctx->src_pixel_type != SMOL_PIXEL_BGR8) { if (!local_ctx->src_aligned) local_ctx->src_aligned = smol_alloc_aligned (scale_ctx->hdim.src_size_px * sizeof (uint32_t), &local_ctx->src_aligned_storage); memcpy (local_ctx->src_aligned, src_row, scale_ctx->hdim.src_size_px * sizeof (uint32_t)); src_row = (const char *) local_ctx->src_aligned; } scale_ctx->src_unpack_row_func (src_row, src_row_unpacked, scale_ctx->hdim.src_size_px); scale_ctx->hfilter_func (scale_ctx, src_row_unpacked, dest_row_parts); apply_horiz_edge_opacity (scale_ctx, dest_row_parts); } /* ---------------- * * Vertical scaling * * ---------------- */ static void update_local_ctx_bilinear (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t new_src_ofs = precalc_y [dest_row_index * 2]; if (new_src_ofs == local_ctx->src_ofs) return; if (new_src_ofs == local_ctx->src_ofs + 1) { uint64_t *t = local_ctx->parts_row [0]; local_ctx->parts_row [0] = local_ctx->parts_row [1]; local_ctx->parts_row [1] = t; scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs + 1), local_ctx->parts_row [1]); } else { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs), local_ctx->parts_row [0]); scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs + 1), local_ctx->parts_row [1]); } local_ctx->src_ofs = new_src_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) { const __m256i mask = _mm256_set_epi16 (0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff); uint64_t *parts_out_last = parts_out + width; __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_epi16 ((uint16_t) F); while (parts_out + 4 <= parts_out_last) { __m256i m0, m1; m0 = _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; m0 = _mm256_sub_epi16 (m0, m1); m0 = _mm256_mullo_epi16 (m0, F256); m0 = _mm256_srli_epi16 (m0, 8); m0 = _mm256_add_epi16 (m0, m1); m0 = _mm256_and_si256 (m0, mask); _mm256_store_si256 ((__m256i *) parts_out, m0); 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) & 0x00ff00ff00ff00ffULL; } } static void interp_vertical_bilinear_store_with_opacity_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width, uint16_t opacity) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *dest_parts = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; apply_subpixel_opacity_64bpp (dest_parts, opacity); dest_parts++; } while (dest_parts != parts_dest_last); } static void interp_vertical_bilinear_add_64bpp (uint16_t F, const uint64_t *top_row_parts_in, const uint64_t *bottom_row_parts_in, uint64_t *accum_out, uint32_t width) { const __m256i mask = _mm256_set_epi16 (0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff); uint64_t *accum_out_last = accum_out + width; __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_epi16 ((uint16_t) F); while (accum_out + 4 <= accum_out_last) { __m256i m0, m1, o0; m0 = _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; o0 = _mm256_load_si256 ((const __m256i *) accum_out); m0 = _mm256_sub_epi16 (m0, m1); m0 = _mm256_mullo_epi16 (m0, F256); m0 = _mm256_srli_epi16 (m0, 8); m0 = _mm256_add_epi16 (m0, m1); m0 = _mm256_and_si256 (m0, mask); o0 = _mm256_add_epi16 (o0, m0); _mm256_store_si256 ((__m256i *) accum_out, o0); 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) & 0x00ff00ff00ff00ffULL; } } 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_store_with_opacity_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width, uint16_t opacity) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *dest_parts = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; apply_subpixel_opacity_128bpp_half (dest_parts, opacity); dest_parts++; } while (dest_parts != parts_dest_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; 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) \ { \ const __m256i mask = _mm256_set_epi16 (0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, \ 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff, 0x00ff); \ uint64_t *accum_inout_last = accum_inout + width; \ __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_inout, uint64_t *); \ \ F256 = _mm256_set1_epi16 ((uint16_t) F); \ \ while (accum_inout + 4 <= accum_inout_last) \ { \ __m256i m0, m1, o0; \ \ m0 = _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; \ o0 = _mm256_load_si256 ((const __m256i *) accum_inout); \ \ m0 = _mm256_sub_epi16 (m0, m1); \ m0 = _mm256_mullo_epi16 (m0, F256); \ m0 = _mm256_srli_epi16 (m0, 8); \ m0 = _mm256_add_epi16 (m0, m1); \ m0 = _mm256_and_si256 (m0, mask); \ \ o0 = _mm256_add_epi16 (o0, m0); \ o0 = _mm256_srli_epi16 (o0, n_halvings); \ \ _mm256_store_si256 ((__m256i *) accum_inout, o0); \ accum_inout += 4; \ } \ \ while (accum_inout != accum_inout_last) \ { \ 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; \ } \ } \ static void \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_64bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width, \ uint16_t opacity) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ \ apply_subpixel_opacity_64bpp (&p, opacity); \ *(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_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } \ \ static void \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width, \ uint16_t opacity) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ apply_subpixel_opacity_128bpp_half (&p, opacity); \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } #define DEF_SCALE_DEST_ROW_BILINEAR(n_halvings) \ static int \ scale_dest_row_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ SmolLocalCtx *local_ctx, \ uint32_t dest_row_index) \ { \ uint16_t *precalc_y = scale_ctx->vdim.precalc; \ uint32_t bilin_index = dest_row_index << (n_halvings); \ unsigned int i; \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_store_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_add_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ bilin_index++; \ } \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ \ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px, \ scale_ctx->vdim.first_opacity); \ else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px, \ scale_ctx->vdim.last_opacity); \ else \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ \ return 2; \ } \ \ static int \ scale_dest_row_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ SmolLocalCtx *local_ctx, \ uint32_t dest_row_index) \ { \ uint16_t *precalc_y = scale_ctx->vdim.precalc; \ uint32_t bilin_index = dest_row_index << (n_halvings); \ unsigned int i; \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_store_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_add_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ bilin_index++; \ } \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ \ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2, \ scale_ctx->vdim.first_opacity); \ else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2, \ scale_ctx->vdim.last_opacity); \ else \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ \ return 2; \ } static int scale_dest_row_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; update_local_ctx_bilinear (scale_ctx, local_ctx, dest_row_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_store_with_opacity_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_store_with_opacity_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_store_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); return 2; } static int scale_dest_row_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; update_local_ctx_bilinear (scale_ctx, local_ctx, dest_row_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_store_with_opacity_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_store_with_opacity_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_store_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); return 2; } DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) static int scale_dest_row_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t bilin_index = dest_row_index << 1; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); interp_vertical_bilinear_store_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); bilin_index++; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_final_1h_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); return 2; } static int scale_dest_row_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t bilin_index = dest_row_index << 1; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); interp_vertical_bilinear_store_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); bilin_index++; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_final_1h_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); return 2; } DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) DEF_SCALE_DEST_ROW_BILINEAR(2) DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) DEF_SCALE_DEST_ROW_BILINEAR(3) DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) DEF_SCALE_DEST_ROW_BILINEAR(4) DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) DEF_SCALE_DEST_ROW_BILINEAR(5) DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) DEF_SCALE_DEST_ROW_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 finalize_vertical_with_opacity_64bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n, uint16_t opacity) { uint64_t *parts_dest_max = dest_parts + n; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { *dest_parts = scale_64bpp (*(accums++), multiplier); apply_subpixel_opacity_64bpp (dest_parts, opacity); dest_parts++; } } static int scale_dest_row_box_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint32_t *precalc_y = scale_ctx->vdim.precalc; uint32_t ofs_y, ofs_y_max; uint32_t w1, w2; uint32_t n, i; unpack_box_precalc (precalc_y [dest_row_index], scale_ctx->vdim.span_step, &ofs_y, &ofs_y_max, &w1, &w2, &n); /* First input row */ scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); copy_weighted_parts_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w1); ofs_y++; /* Add up whole input rows */ for (i = 0; i < n; i++) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_parts (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px); ofs_y++; } /* Last input row */ if (ofs_y < scale_ctx->vdim.src_size_px) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_weighted_parts_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w2); } /* Finalize */ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) { finalize_vertical_with_opacity_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (dest_row_index == scale_ctx->vdim.placement_size_px - 1 && scale_ctx->vdim.last_opacity < 256) { finalize_vertical_with_opacity_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { finalize_vertical_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px); } return 0; } static void finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n) { uint64_t *parts_dest_max = dest_parts + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { *(dest_parts++) = scale_128bpp_half (*(accums++), multiplier); *(dest_parts++) = scale_128bpp_half (*(accums++), multiplier); } } static void finalize_vertical_with_opacity_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n, uint16_t opacity) { uint64_t *parts_dest_max = dest_parts + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { dest_parts [0] = scale_128bpp_half (*(accums++), multiplier); dest_parts [1] = scale_128bpp_half (*(accums++), multiplier); apply_subpixel_opacity_128bpp (dest_parts, opacity); dest_parts += 2; } } static int scale_dest_row_box_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint32_t *precalc_y = scale_ctx->vdim.precalc; uint32_t ofs_y, ofs_y_max; uint32_t w1, w2; uint32_t n, i; unpack_box_precalc (precalc_y [dest_row_index], scale_ctx->vdim.span_step, &ofs_y, &ofs_y_max, &w1, &w2, &n); /* First input row */ scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); copy_weighted_parts_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w1); ofs_y++; /* Add up whole input rows */ for (i = 0; i < n; i++) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_parts (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px * 2); ofs_y++; } /* Last input row */ if (ofs_y < scale_ctx->vdim.src_size_px) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_weighted_parts_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w2); } if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) { finalize_vertical_with_opacity_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (dest_row_index == scale_ctx->vdim.placement_size_px - 1 && scale_ctx->vdim.last_opacity < 256) { finalize_vertical_with_opacity_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { finalize_vertical_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px); } return 0; } static int scale_dest_row_one_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { /* Scale the row and store it */ if (local_ctx->src_ofs != 0) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, 0), local_ctx->parts_row [0]); local_ctx->src_ofs = 0; } if (row_index == 0 && scale_ctx->vdim.first_opacity < 256) { apply_subpixel_opacity_row_copy_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) { apply_subpixel_opacity_row_copy_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { memcpy (local_ctx->parts_row [1], local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px * sizeof (uint64_t)); } return 1; } static int scale_dest_row_one_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { /* Scale the row and store it */ if (local_ctx->src_ofs != 0) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, 0), local_ctx->parts_row [0]); local_ctx->src_ofs = 0; } if (row_index == 0 && scale_ctx->vdim.first_opacity < 256) { apply_subpixel_opacity_row_copy_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) { apply_subpixel_opacity_row_copy_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { memcpy (local_ctx->parts_row [1], local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px * sizeof (uint64_t) * 2); } return 1; } static int scale_dest_row_copy (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, row_index), local_ctx->parts_row [0]); return 0; } /* --------------- * * Function tables * * --------------- */ #define R SMOL_REPACK_META static const SmolRepackMeta repack_meta [] = { R (123, 24, PREMUL8, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (123, 24, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 2431, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 3241, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2431, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 3241, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 2341, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL16, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL16, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 132, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 231, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 324, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 423, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 132, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 231, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 324, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 423, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1324, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1423, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 2314, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4132, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4231, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1324, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1423, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 2314, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4132, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4231, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 123, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 321, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 1234, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 3214, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4123, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4321, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4321, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 4321, 32, UNASSOCIATED, COMPRESSED), SMOL_REPACK_META_LAST }; #undef R static const SmolImplementation implementation = { /* Horizontal init */ init_horizontal, /* Vertical init */ init_vertical, { /* Horizontal filters */ { /* 24bpp */ }, { /* 32bpp */ }, { /* 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 */ { /* 24bpp */ }, { /* 32bpp */ }, { /* 64bpp */ scale_dest_row_copy, scale_dest_row_one_64bpp, scale_dest_row_bilinear_0h_64bpp, scale_dest_row_bilinear_1h_64bpp, scale_dest_row_bilinear_2h_64bpp, scale_dest_row_bilinear_3h_64bpp, scale_dest_row_bilinear_4h_64bpp, scale_dest_row_bilinear_5h_64bpp, scale_dest_row_bilinear_6h_64bpp, scale_dest_row_box_64bpp }, { /* 128bpp */ scale_dest_row_copy, scale_dest_row_one_128bpp, scale_dest_row_bilinear_0h_128bpp, scale_dest_row_bilinear_1h_128bpp, scale_dest_row_bilinear_2h_128bpp, scale_dest_row_bilinear_3h_128bpp, scale_dest_row_bilinear_4h_128bpp, scale_dest_row_bilinear_5h_128bpp, scale_dest_row_bilinear_6h_128bpp, scale_dest_row_box_128bpp } }, { /* Composite over color */ NULL, NULL, NULL, NULL }, { /* Composite over dest */ NULL, NULL, NULL, NULL }, { /* Clear dest */ NULL, NULL, NULL, NULL }, repack_meta }; const SmolImplementation * _smol_get_avx2_implementation (void) { return &implementation; } chafa-1.14.5/chafa/internal/smolscale/smolscale-generic.c000066400000000000000000003362611471154763100232700ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2024 Hans Petter Jansson. See COPYING for details. */ #include #include /* malloc, free, alloca */ #include /* memset */ #include #include "smolscale-private.h" /* ---------------------- * * Context initialization * * ---------------------- */ /* Linear precalc array: * * Each sample is extracted from a pair of adjacent pixels. The sample precalc * consists of the first pixel's index, followed by its sample fraction [0..256]. * The second sample is implicitly taken at index+1 and weighted as 256-fraction. * _ _ _ * In |_| |_| |_| * \_/ \_/ <- two samples per output pixel * Out |_| |_| * * When halving, * _ _ _ * In |_| |_| |_| * \_/ \_/ <- four samples per output pixel * |_| |_| * \_/ <- halving * Out |_| */ static void precalc_linear_range (uint16_t *array_out, int first_index, int last_index, uint64_t first_sample_ofs, uint64_t sample_step, int sample_ofs_px_max, int32_t dest_clip_before_px, int *array_i_inout) { uint64_t sample_ofs; int i; sample_ofs = first_sample_ofs; for (i = first_index; i < last_index; i++) { uint16_t sample_ofs_px = sample_ofs / SMOL_BILIN_MULTIPLIER; if (sample_ofs_px >= sample_ofs_px_max - 1) { if (i >= dest_clip_before_px) { array_out [(*array_i_inout) * 2] = sample_ofs_px_max - 2; array_out [(*array_i_inout) * 2 + 1] = 0; (*array_i_inout)++; } continue; } if (i >= dest_clip_before_px) { array_out [(*array_i_inout) * 2] = sample_ofs_px; array_out [(*array_i_inout) * 2 + 1] = SMOL_SMALL_MUL - ((sample_ofs / (SMOL_BILIN_MULTIPLIER / SMOL_SMALL_MUL)) % SMOL_SMALL_MUL); (*array_i_inout)++; } sample_ofs += sample_step; } } static void precalc_bilinear_array (uint16_t *array, uint64_t src_dim_spx, uint64_t dest_ofs_spx, uint64_t dest_dim_spx, uint32_t dest_dim_prehalving_px, unsigned int n_halvings, int32_t dest_clip_before_px) { uint32_t src_dim_px = SMOL_SPX_TO_PX (src_dim_spx); uint64_t first_sample_ofs [3]; uint64_t sample_step; int i = 0; assert (src_dim_px > 1); dest_ofs_spx %= SMOL_SUBPIXEL_MUL; if (src_dim_spx > dest_dim_spx) { /* Minification */ sample_step = ((uint64_t) src_dim_spx * SMOL_BILIN_MULTIPLIER) / dest_dim_spx; first_sample_ofs [0] = (sample_step - SMOL_BILIN_MULTIPLIER) / 2; first_sample_ofs [1] = ((sample_step - SMOL_BILIN_MULTIPLIER) / 2) + ((sample_step * (SMOL_SUBPIXEL_MUL - dest_ofs_spx) * (1 << n_halvings)) / SMOL_SUBPIXEL_MUL); } else { /* Magnification */ sample_step = ((src_dim_spx - SMOL_SUBPIXEL_MUL) * SMOL_BILIN_MULTIPLIER) / (dest_dim_spx > SMOL_SUBPIXEL_MUL ? (dest_dim_spx - SMOL_SUBPIXEL_MUL) : 1); first_sample_ofs [0] = 0; first_sample_ofs [1] = (sample_step * (SMOL_SUBPIXEL_MUL - dest_ofs_spx)) / SMOL_SUBPIXEL_MUL; } first_sample_ofs [2] = (((uint64_t) src_dim_spx * SMOL_BILIN_MULTIPLIER) / SMOL_SUBPIXEL_MUL) + ((sample_step - SMOL_BILIN_MULTIPLIER) / 2) - sample_step * (1U << n_halvings); /* Left fringe */ precalc_linear_range (array, 0, 1 << n_halvings, first_sample_ofs [0], sample_step, src_dim_px, dest_clip_before_px, &i); /* Main range */ precalc_linear_range (array, 1 << n_halvings, dest_dim_prehalving_px - (1 << n_halvings), first_sample_ofs [1], sample_step, src_dim_px, dest_clip_before_px, &i); /* Right fringe */ precalc_linear_range (array, dest_dim_prehalving_px - (1 << n_halvings), dest_dim_prehalving_px, first_sample_ofs [2], sample_step, src_dim_px, dest_clip_before_px, &i); } static void precalc_boxes_array (uint32_t *array, uint32_t *span_step, uint32_t *span_mul, uint32_t src_dim_spx, int32_t dest_dim, uint32_t dest_ofs_spx, uint32_t dest_dim_spx, int32_t dest_clip_before_px) { uint64_t fracF, frac_stepF; uint64_t f; uint64_t stride; uint64_t a, b; int i, dest_i; dest_ofs_spx %= SMOL_SUBPIXEL_MUL; /* Output sample can't be less than a pixel. Fringe opacity is applied in * a separate step. FIXME: May cause wrong subpixel distribution -- revisit. */ if (dest_dim_spx < 256) dest_dim_spx = 256; frac_stepF = ((uint64_t) src_dim_spx * SMOL_BIG_MUL) / (uint64_t) dest_dim_spx; fracF = 0; stride = frac_stepF / (uint64_t) SMOL_BIG_MUL; f = (frac_stepF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; /* We divide by (b + 1) instead of just (b) to avoid overflows in * scale_128bpp_half(), which would affect horizontal box scaling. The * fudge factor counters limited precision in the inverted division * operation. It causes 16-bit values to undershoot by less than 127/65535 * (<.2%). Since the final output is 8-bit, and rounding neutralizes the * error, this doesn't matter. */ a = (SMOL_BOXES_MULTIPLIER * 255); b = ((stride * 255) + ((f * 255) / 256)); *span_step = frac_stepF / SMOL_SMALL_MUL; *span_mul = (a + (b / 2)) / (b + 1); /* Left fringe */ i = 0; dest_i = 0; if (dest_i >= dest_clip_before_px) array [i++] = 0; /* Main range */ fracF = ((frac_stepF * (SMOL_SUBPIXEL_MUL - dest_ofs_spx)) / SMOL_SUBPIXEL_MUL); for (dest_i = 1; dest_i < dest_dim - 1; dest_i++) { if (dest_i >= dest_clip_before_px) array [i++] = fracF / SMOL_SMALL_MUL; fracF += frac_stepF; } /* Right fringe */ if (dest_dim > 1 && dest_i >= dest_clip_before_px) array [i++] = (((uint64_t) src_dim_spx * SMOL_SMALL_MUL - frac_stepF) / SMOL_SMALL_MUL); } static void init_dim (SmolDim *dim) { if (dim->filter_type == SMOL_FILTER_ONE || dim->filter_type == SMOL_FILTER_COPY) { } else if (dim->filter_type == SMOL_FILTER_BOX) { precalc_boxes_array (dim->precalc, &dim->span_step, &dim->span_mul, dim->src_size_spx, dim->placement_size_px, dim->placement_ofs_spx, dim->placement_size_spx, dim->clip_before_px); } else /* SMOL_FILTER_BILINEAR_?H */ { precalc_bilinear_array (dim->precalc, dim->src_size_spx, dim->placement_ofs_spx, dim->placement_size_prehalving_spx, dim->placement_size_prehalving_px, dim->n_halvings, dim->clip_before_px); } } static void init_horizontal (SmolScaleCtx *scale_ctx) { init_dim (&scale_ctx->hdim); } static void init_vertical (SmolScaleCtx *scale_ctx) { init_dim (&scale_ctx->vdim); } /* ---------------------- * * sRGB/linear conversion * * ---------------------- */ static void from_srgb_pixel_xxxa_128bpp (uint64_t * SMOL_RESTRICT pixel_inout) { uint64_t part; part = pixel_inout [0]; pixel_inout [0] = ((uint64_t) _smol_from_srgb_lut [part >> 32] << 32) | _smol_from_srgb_lut [part & 0xff]; part = pixel_inout [1]; pixel_inout [1] = ((uint64_t) _smol_from_srgb_lut [part >> 32] << 32) | ((part & 0xffffffff) << 3) | 7; } static void to_srgb_pixel_xxxa_128bpp (const uint64_t *pixel_in, uint64_t *pixel_out) { pixel_out [0] = (((uint64_t) _smol_to_srgb_lut [pixel_in [0] >> 32]) << 32) | _smol_to_srgb_lut [pixel_in [0] & 0xffff]; pixel_out [1] = (((uint64_t) _smol_to_srgb_lut [pixel_in [1] >> 32]) << 32) | (pixel_in [1] & 0xffffffff); /* FIXME: No need to preserve alpha? */ } /* Fetches alpha from linear pixel. Input alpha is in the range [0x000..0x7ff]. * Returned alpha is in the range [0x00..0xff], rounded towards 0xff. */ static SMOL_INLINE uint8_t get_alpha_from_linear_xxxa_128bpp (const uint64_t * SMOL_RESTRICT pixel_in) { uint16_t alpha = (pixel_in [1] + 7) >> 3; return (uint8_t) (alpha - (alpha >> 8)); /* Turn 0x100 into 0xff */ } /* ----------------- * * Premultiplication * * ----------------- */ static SMOL_INLINE void premul_u_to_p8_128bpp (uint64_t * SMOL_RESTRICT inout, uint16_t alpha) { inout [0] = ((inout [0] * (alpha + 1)) >> 8) & 0x000000ff000000ff; inout [1] = ((inout [1] * (alpha + 1)) >> 8) & 0x000000ff000000ff; } static SMOL_INLINE void unpremul_p8_to_u_128bpp (const uint64_t *in, uint64_t *out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p8_lut [alpha]) >> INVERTED_DIV_SHIFT_P8) & 0x000000ff000000ff; out [1] = ((in [1] * _smol_inv_div_p8_lut [alpha]) >> INVERTED_DIV_SHIFT_P8) & 0x000000ff000000ff; } static SMOL_INLINE uint64_t premul_u_to_p8_64bpp (const uint64_t in, uint16_t alpha) { return ((in * (alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; } static SMOL_INLINE uint64_t unpremul_p8_to_u_64bpp (const uint64_t in, uint8_t alpha) { uint64_t in_128bpp [2]; uint64_t dest_128bpp [2]; in_128bpp [0] = (in & 0x000000ff000000ff); in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; unpremul_p8_to_u_128bpp (in_128bpp, dest_128bpp, alpha); return dest_128bpp [0] | (dest_128bpp [1] << 16); } static SMOL_INLINE void premul_ul_to_p8l_128bpp (uint64_t * SMOL_RESTRICT inout, uint16_t alpha) { inout [0] = ((inout [0] * (alpha + 1)) >> 8) & 0x000007ff000007ff; inout [1] = (((inout [1] * (alpha + 1)) >> 8) & 0x000007ff00000000) | (inout [1] & 0x000007ff); } static SMOL_INLINE void unpremul_p8l_to_ul_128bpp (const uint64_t *in, uint64_t *out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p8l_lut [alpha]) >> INVERTED_DIV_SHIFT_P8L) & 0x000007ff000007ff; out [1] = ((in [1] * _smol_inv_div_p8l_lut [alpha]) >> INVERTED_DIV_SHIFT_P8L) & 0x000007ff000007ff; } static SMOL_INLINE void premul_u_to_p16_128bpp (uint64_t *inout, uint8_t alpha) { inout [0] = inout [0] * ((uint16_t) alpha + 2); inout [1] = inout [1] * ((uint16_t) alpha + 2); } static SMOL_INLINE void unpremul_p16_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p16_lut [alpha]) >> INVERTED_DIV_SHIFT_P16) & 0x000000ff000000ffULL; out [1] = ((in [1] * _smol_inv_div_p16_lut [alpha]) >> INVERTED_DIV_SHIFT_P16) & 0x000000ff000000ffULL; } static SMOL_INLINE void premul_ul_to_p16l_128bpp (uint64_t *inout, uint8_t alpha) { inout [0] = inout [0] * ((uint16_t) alpha + 2); inout [1] = inout [1] * ((uint16_t) alpha + 2); } static SMOL_INLINE void unpremul_p16l_to_ul_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = ((in [0] * _smol_inv_div_p16l_lut [alpha]) >> INVERTED_DIV_SHIFT_P16L) & 0x000007ff000007ffULL; out [1] = ((in [1] * _smol_inv_div_p16l_lut [alpha]) >> INVERTED_DIV_SHIFT_P16L) & 0x000007ff000007ffULL; } /* --------- * * Repacking * * --------- */ /* 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)) /* ---------------------- * * Repacking: 24/32 -> 64 * * ---------------------- */ static SMOL_INLINE uint64_t unpack_pixel_123_p8_to_132a_p8_64bpp (const uint8_t *p) { return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) | ((uint64_t) p [2] << 32) | 0xff; } SMOL_REPACK_ROW_DEF (123, 24, 8, PREMUL8, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_123_p8_to_132a_p8_64bpp (src_row); src_row += 3; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_1324_p8_64bpp (uint32_t p) { return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_3241_p8_64bpp (uint32_t p) { return (((uint64_t) p & 0x0000ff00) << 40) | (((uint64_t) p & 0x00ff00ff) << 16) | (p >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 3241, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_3241_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_p8_to_2431_p8_64bpp (uint32_t p) { uint64_t p64 = p; return ((p64 & 0x00ff00ff) << 32) | ((p64 & 0x0000ff00) << 8) | ((p64 & 0xff000000) >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 2431, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_p8_to_2431_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_a234_u_to_324a_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 40) | (((uint64_t) p & 0x00ff00ff) << 16); uint8_t alpha = p >> 24; return (premul_u_to_p8_64bpp (p64, alpha) & 0xffffffffffffff00ULL) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 3241, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_a234_u_to_324a_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_1234_u_to_2431_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x00ff00ff) << 32) | (((uint64_t) p & 0x0000ff00) << 8); uint8_t alpha = p >> 24; return (premul_u_to_p8_64bpp (p64, alpha) & 0xffffffffffffff00ULL) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2431, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_1234_u_to_2431_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE uint64_t unpack_pixel_123a_u_to_132a_p8_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; return (premul_u_to_p8_64bpp (p64, alpha) & 0xffffffffffffff00ULL) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1324, 64, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = unpack_pixel_123a_u_to_132a_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END /* ----------------------- * * Repacking: 24/32 -> 128 * * ----------------------- */ static SMOL_INLINE void unpack_pixel_123_p8_to_123a_p8_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; } SMOL_REPACK_ROW_DEF (123, 24, 8, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123_p8_to_123a_p8_128bpp (src_row, dest_row); src_row += 3; dest_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (123, 24, 8, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, LINEAR) { while (dest_row != dest_row_max) { uint8_t alpha; unpack_pixel_123_p8_to_123a_p8_128bpp (src_row, dest_row); alpha = dest_row [1]; from_srgb_pixel_xxxa_128bpp (dest_row); dest_row [1] = (dest_row [1] & 0xffffffff00000000) | (alpha << 3) | 7; src_row += 3; dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_p8_to_123a_p8_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); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123a_p8_to_123a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 1234, 128, 64, PREMUL8, LINEAR) { while (dest_row != dest_row_max) { uint8_t alpha; unpack_pixel_123a_p8_to_123a_p8_128bpp (*(src_row++), dest_row); alpha = dest_row [1]; from_srgb_pixel_xxxa_128bpp (dest_row); dest_row [1] = (dest_row [1] & 0xffffffff00000000) | (alpha << 3) | 7; dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_p8_to_234a_p8_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; out [0] = ((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8); out [1] = ((p64 & 0x000000ff) << 32) | ((p64 & 0xff000000) >> 24); } SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 2341, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_a234_p8_to_234a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 32, 32, PREMUL8, COMPRESSED, 2341, 128, 64, PREMUL8, LINEAR) { while (dest_row != dest_row_max) { uint8_t alpha; unpack_pixel_a234_p8_to_234a_p8_128bpp (*(src_row++), dest_row); alpha = dest_row [1]; from_srgb_pixel_xxxa_128bpp (dest_row); dest_row [1] = (dest_row [1] & 0xffffffff00000000) | (alpha << 3) | 7; dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_p8_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0x00ff00ff) << 32) | (((uint64_t) p & 0x0000ff00) << 8); uint8_t alpha = p >> 24; p64 = (premul_u_to_p8_64bpp (p64, alpha) & 0xffffffffffffff00) | alpha; out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_pl_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p >> 24; out [0] = ((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8); out [1] = ((p64 & 0x000000ff) << 32) | alpha; from_srgb_pixel_xxxa_128bpp (out); premul_ul_to_p8l_128bpp (out, alpha); } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL8, LINEAR) { while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_pl_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_p16_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p >> 24; out [0] = ((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8); out [1] = ((p64 & 0x000000ff) << 32); premul_u_to_p16_128bpp (out, alpha); out [1] |= (((uint16_t) alpha) << 8) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL16, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_p16_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_a234_u_to_234a_p16l_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p >> 24; out [0] = ((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8); out [1] = ((p64 & 0x000000ff) << 32); from_srgb_pixel_xxxa_128bpp (out); out [0] *= alpha; out [1] *= alpha; out [1] = (out [1] & 0xffffffff00000000ULL) | (alpha << 8) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 2341, 128, 64, PREMUL16, LINEAR) { while (dest_row != dest_row_max) { unpack_pixel_a234_u_to_234a_p16l_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p8_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p; p64 = (premul_u_to_p8_64bpp (p64, alpha) & 0xffffffffffffff00ULL) | alpha; out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_p8_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_pl_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p; out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); out [1] = ((p64 & 0x0000ff00) << 24) | alpha; from_srgb_pixel_xxxa_128bpp (out); premul_ul_to_p8l_128bpp (out, alpha); } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL8, LINEAR) { while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_pl_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p16_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p; out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); out [1] = ((p64 & 0x0000ff00) << 24); premul_u_to_p16_128bpp (out, alpha); out [1] |= (((uint16_t) alpha) << 8) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL16, COMPRESSED) { while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_p16_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p16l_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint8_t alpha = p; out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); out [1] = ((p64 & 0x0000ff00) << 24); from_srgb_pixel_xxxa_128bpp (out); premul_ul_to_p16l_128bpp (out, alpha); out [1] = (out [1] & 0xffffffff00000000ULL) | ((uint16_t) alpha << 8) | alpha; } SMOL_REPACK_ROW_DEF (1234, 32, 32, UNASSOCIATED, COMPRESSED, 1234, 128, 64, PREMUL16, LINEAR) { while (dest_row != dest_row_max) { unpack_pixel_123a_u_to_123a_p16l_128bpp (*(src_row++), dest_row); dest_row += 2; } } SMOL_REPACK_ROW_DEF_END /* ---------------------- * * Repacking: 64 -> 24/32 * * ---------------------- */ static SMOL_INLINE uint32_t pack_pixel_1234_p8_to_1324_p8_64bpp (uint64_t in) { return in | (in >> 24); } SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 132, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 24; *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 132, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 24; *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 231, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; *(dest_row++) = p >> 24; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 231, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; *(dest_row++) = p >> 24; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 324, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; *(dest_row++) = p; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 324, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row >> 24; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p >> 16; *(dest_row++) = p >> 8; *(dest_row++) = p; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 423, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); *(dest_row++) = p; *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 423, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row >> 24; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; uint32_t p = pack_pixel_1234_p8_to_1324_p8_64bpp (t); *(dest_row++) = p; *(dest_row++) = p >> 8; *(dest_row++) = p >> 16; src_row++; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 1324, 32, 32, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = pack_pixel_1234_p8_to_1324_p8_64bpp (*(src_row++)); } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, 1324, 32, 32, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint8_t alpha = *src_row; uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; *(dest_row++) = pack_pixel_1234_p8_to_1324_p8_64bpp (t); src_row++; } } SMOL_REPACK_ROW_DEF_END #define DEF_REPACK_FROM_1234_64BPP_TO_32BPP(a, b, c, d) \ SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, PREMUL8, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ *(dest_row++) = PACK_FROM_1234_64BPP (*src_row, a, b, c, d); \ src_row++; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 64, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint8_t alpha = *src_row; \ uint64_t t = (unpremul_p8_to_u_64bpp (*src_row, alpha) & 0xffffffffffffff00ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_64BPP (t, a, b, c, d); \ src_row++; \ } \ } SMOL_REPACK_ROW_DEF_END DEF_REPACK_FROM_1234_64BPP_TO_32BPP (1, 4, 2, 3) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (2, 3, 1, 4) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (4, 1, 3, 2) DEF_REPACK_FROM_1234_64BPP_TO_32BPP (4, 2, 3, 1) /* ----------------------- * * Repacking: 128 -> 24/32 * * ----------------------- */ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 123, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = *src_row >> 32; *(dest_row++) = *(src_row++); *(dest_row++) = *(src_row++) >> 32; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, 123, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); unpremul_p8l_to_ul_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (src_row, t); *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1]; unpremul_p8_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); unpremul_p8l_to_ul_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (t, t); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1] >> 8; unpremul_p16_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } \ } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, LINEAR, 123, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1] >> 8; unpremul_p16_to_u_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (t, t); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [0] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [1] >> 32; src_row += 2; } \ } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 321, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { *(dest_row++) = src_row [1] >> 32; *(dest_row++) = src_row [0]; *(dest_row++) = src_row [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, 321, 24, 8, PREMUL8, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); unpremul_p8l_to_ul_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (t, t); *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1]; unpremul_p8_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); unpremul_p8l_to_ul_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (t, t); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1] >> 8; unpremul_p16_to_u_128bpp (src_row, t, alpha); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, LINEAR, 321, 24, 8, UNASSOCIATED, COMPRESSED) { while (dest_row != dest_row_max) { uint64_t t [2]; uint8_t alpha = src_row [1] >> 8; unpremul_p16_to_u_128bpp (src_row, t, alpha); to_srgb_pixel_xxxa_128bpp (t, t); t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; *(dest_row++) = t [1] >> 32; *(dest_row++) = t [0]; *(dest_row++) = t [0] >> 32; src_row += 2; } } SMOL_REPACK_ROW_DEF_END #define DEF_REPACK_FROM_1234_128BPP_TO_32BPP(a, b, c, d) \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, PREMUL8, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ *(dest_row++) = PACK_FROM_1234_128BPP (src_row, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, \ a##b##c##d, 32, 32, PREMUL8, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); \ to_srgb_pixel_xxxa_128bpp (src_row, t); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = src_row [1]; \ unpremul_p8_to_u_128bpp (src_row, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL8, LINEAR, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = get_alpha_from_linear_xxxa_128bpp (src_row); \ unpremul_p8l_to_ul_128bpp (src_row, t, alpha); \ to_srgb_pixel_xxxa_128bpp (t, t); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, COMPRESSED, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = src_row [1] >> 8; \ unpremul_p16_to_u_128bpp (src_row, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END \ SMOL_REPACK_ROW_DEF (1234, 128, 64, PREMUL16, LINEAR, \ a##b##c##d, 32, 32, UNASSOCIATED, COMPRESSED) { \ while (dest_row != dest_row_max) \ { \ uint64_t t [2]; \ uint8_t alpha = src_row [1] >> 8; \ unpremul_p16l_to_ul_128bpp (src_row, t, alpha); \ to_srgb_pixel_xxxa_128bpp (t, t); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ *(dest_row++) = PACK_FROM_1234_128BPP (t, a, b, c, d); \ src_row += 2; \ } \ } SMOL_REPACK_ROW_DEF_END DEF_REPACK_FROM_1234_128BPP_TO_32BPP (1, 2, 3, 4) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (3, 2, 1, 4) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (4, 1, 2, 3) DEF_REPACK_FROM_1234_128BPP_TO_32BPP (4, 3, 2, 1) /* -------------- * * Filter helpers * * -------------- */ static SMOL_INLINE const char * src_row_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t src_row_ofs) { return scale_ctx->src_pixels + scale_ctx->src_rowstride * src_row_ofs; } static SMOL_INLINE uint64_t weight_pixel_64bpp (uint64_t p, uint16_t w) { return ((p * w) >> 8) & 0x00ff00ff00ff00ffULL; } /* p and out may be the same address */ static SMOL_INLINE void weight_pixel_128bpp (const 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 * SMOL_RESTRICT pp = *parts_in; const uint64_t *pp_end; 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 * SMOL_RESTRICT pp = *parts_in; const uint64_t *pp_end; 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; 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 (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 | (b << 32); } static SMOL_INLINE void scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, uint64_t multiplier, uint64_t ** SMOL_RESTRICT dest_row_parts) { *(*dest_row_parts)++ = scale_128bpp_half (accum [0], multiplier); *(*dest_row_parts)++ = 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++); } static void copy_weighted_parts_64bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { 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++) = weight_pixel_64bpp (*(parts_in++), w); } } static void copy_weighted_parts_128bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { const uint64_t *parts_in_max = parts_in + n * 2; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in < parts_in_max) { weight_pixel_128bpp (parts_in, parts_acc_out, w); parts_in += 2; parts_acc_out += 2; } } static void add_weighted_parts_64bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { 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++) += weight_pixel_64bpp (*(parts_in++), w); } } static void add_weighted_parts_128bpp (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n, uint16_t w) { const uint64_t *parts_in_max = parts_in + n * 2; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in < parts_in_max) { uint64_t t [2]; weight_pixel_128bpp (parts_in, t, w); parts_acc_out [0] += t [0]; parts_acc_out [1] += t [1]; parts_in += 2; parts_acc_out += 2; } } static SMOL_INLINE void apply_subpixel_opacity_64bpp (uint64_t * SMOL_RESTRICT u64_inout, uint16_t opacity) { *u64_inout = ((*u64_inout * opacity) >> SMOL_SUBPIXEL_SHIFT) & 0x00ff00ff00ff00ffULL; } static SMOL_INLINE void apply_subpixel_opacity_128bpp_half (uint64_t * SMOL_RESTRICT u64_inout, uint16_t opacity) { *u64_inout = ((*u64_inout * opacity) >> SMOL_SUBPIXEL_SHIFT) & 0x00ffffff00ffffffULL; } static SMOL_INLINE void apply_subpixel_opacity_128bpp (uint64_t *u64_inout, uint16_t opacity) { apply_subpixel_opacity_128bpp_half (u64_inout, opacity); apply_subpixel_opacity_128bpp_half (u64_inout + 1, opacity); } static void apply_subpixel_opacity_row_copy_64bpp (uint64_t * SMOL_RESTRICT u64_in, uint64_t * SMOL_RESTRICT u64_out, int n_pixels, uint16_t opacity) { uint64_t *u64_out_max = u64_out + n_pixels; while (u64_out != u64_out_max) { *u64_out = *u64_in++; apply_subpixel_opacity_64bpp (u64_out, opacity); u64_out++; } } static void apply_subpixel_opacity_row_copy_128bpp (uint64_t * SMOL_RESTRICT u64_in, uint64_t * SMOL_RESTRICT u64_out, int n_pixels, uint16_t opacity) { uint64_t *u64_out_max = u64_out + (n_pixels * 2); while (u64_out != u64_out_max) { u64_out [0] = u64_in [0]; u64_out [1] = u64_in [1]; apply_subpixel_opacity_128bpp_half (u64_out, opacity); apply_subpixel_opacity_128bpp_half (u64_out + 1, opacity); u64_in += 2; u64_out += 2; } } static void apply_horiz_edge_opacity (const SmolScaleCtx *scale_ctx, uint64_t *row_parts) { if (scale_ctx->storage_type == SMOL_STORAGE_64BPP) { apply_subpixel_opacity_64bpp (&row_parts [0], scale_ctx->hdim.first_opacity); apply_subpixel_opacity_64bpp (&row_parts [scale_ctx->hdim.placement_size_px - 1], scale_ctx->hdim.last_opacity); } else { apply_subpixel_opacity_128bpp (&row_parts [0], scale_ctx->hdim.first_opacity); apply_subpixel_opacity_128bpp (&row_parts [(scale_ctx->hdim.placement_size_px - 1) * 2], scale_ctx->hdim.last_opacity); } } /* ------------------ * * 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 src_row_parts, \ uint64_t * SMOL_RESTRICT dest_row_parts) \ { \ const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; \ uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px; \ uint64_t p, q; \ uint64_t F; \ int i; \ \ SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); \ \ do \ { \ uint64_t accum = 0; \ \ for (i = 0; i < (1 << (n_halvings)); i++) \ { \ uint64_t pixel_ofs = *(precalc_x++); \ F = *(precalc_x++); \ \ p = src_row_parts [pixel_ofs]; \ q = src_row_parts [pixel_ofs + 1]; \ \ accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ } \ *(dest_row_parts++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ } \ while (dest_row_parts != dest_row_parts_max); \ } \ \ static void \ interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ const uint64_t * SMOL_RESTRICT src_row_parts, \ uint64_t * SMOL_RESTRICT dest_row_parts) \ { \ const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; \ uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px * 2; \ uint64_t p, q; \ uint64_t F; \ int i; \ \ SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); \ \ do \ { \ uint64_t accum [2] = { 0 }; \ \ for (i = 0; i < (1 << (n_halvings)); i++) \ { \ uint32_t pixel_ofs = *(precalc_x++) * 2; \ F = *(precalc_x++); \ \ p = src_row_parts [pixel_ofs]; \ q = src_row_parts [pixel_ofs + 2]; \ \ accum [0] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ \ p = src_row_parts [pixel_ofs + 1]; \ q = src_row_parts [pixel_ofs + 3]; \ \ accum [1] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ } \ *(dest_row_parts++) = ((accum [0]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ *(dest_row_parts++) = ((accum [1]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ } \ while (dest_row_parts != dest_row_parts_max); \ } static void interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px; uint64_t p, q; uint64_t F; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); do { uint32_t pixel_ofs = *(precalc_x++); F = *(precalc_x++); p = src_row_parts [pixel_ofs]; q = src_row_parts [pixel_ofs + 1]; *(dest_row_parts++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (dest_row_parts != dest_row_parts_max); } static void interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint16_t * SMOL_RESTRICT precalc_x = scale_ctx->hdim.precalc; uint64_t * SMOL_RESTRICT dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px * 2; uint64_t p, q; uint64_t F; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); do { uint32_t pixel_ofs = *(precalc_x++) * 2; F = *(precalc_x++); p = src_row_parts [pixel_ofs]; q = src_row_parts [pixel_ofs + 2]; *(dest_row_parts++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; p = src_row_parts [pixel_ofs + 1]; q = src_row_parts [pixel_ofs + 3]; *(dest_row_parts++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (dest_row_parts != dest_row_parts_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 SMOL_INLINE void unpack_box_precalc (const uint32_t precalc, uint32_t step, uint32_t *ofs0, uint32_t *ofs1, uint32_t *f0, uint32_t *f1, uint32_t *n) { *ofs0 = precalc; *ofs1 = *ofs0 + step; *f0 = 256 - (*ofs0 % SMOL_SUBPIXEL_MUL); *f1 = *ofs1 % SMOL_SUBPIXEL_MUL; *ofs0 /= SMOL_SUBPIXEL_MUL; *ofs1 /= SMOL_SUBPIXEL_MUL; *n = *ofs1 - *ofs0 - 1; } static void interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t *src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint64_t * SMOL_RESTRICT pp; const uint32_t *precalc_x = scale_ctx->hdim.precalc; uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px; uint64_t accum; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); while (dest_row_parts < dest_row_parts_max) { uint32_t ofs0, ofs1; uint32_t f0, f1; uint32_t n; unpack_box_precalc (*(precalc_x++), scale_ctx->hdim.span_step, &ofs0, &ofs1, &f0, &f1, &n); pp = src_row_parts + ofs0; accum = weight_pixel_64bpp (*(pp++), f0); sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); accum += weight_pixel_64bpp (*pp, f1); *(dest_row_parts++) = scale_64bpp (accum, scale_ctx->hdim.span_mul); } } static void interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t *src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { const uint64_t * SMOL_RESTRICT pp; const uint32_t *precalc_x = scale_ctx->hdim.precalc; uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px * 2; uint64_t accum [2]; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); while (dest_row_parts < dest_row_parts_max) { uint32_t ofs0, ofs1; uint32_t f0, f1; uint32_t n; uint64_t t [2]; unpack_box_precalc (*(precalc_x++), scale_ctx->hdim.span_step, &ofs0, &ofs1, &f0, &f1, &n); pp = src_row_parts + (ofs0 * 2); weight_pixel_128bpp (pp, accum, f0); pp += 2; sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); weight_pixel_128bpp (pp, t, f1); accum [0] += t [0]; accum [1] += t [1]; scale_and_store_128bpp (accum, scale_ctx->hdim.span_mul, (uint64_t ** SMOL_RESTRICT) &dest_row_parts); } } static void interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px; uint64_t part; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); part = *src_row_parts; while (dest_row_parts != dest_row_parts_max) *(dest_row_parts++) = part; } static void interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { uint64_t *dest_row_parts_max = dest_row_parts + scale_ctx->hdim.placement_size_px * 2; SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); while (dest_row_parts != dest_row_parts_max) { *(dest_row_parts++) = src_row_parts [0]; *(dest_row_parts++) = src_row_parts [1]; } } static void interp_horizontal_copy_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); memcpy (dest_row_parts, src_row_parts, scale_ctx->hdim.placement_size_px * sizeof (uint64_t)); } static void interp_horizontal_copy_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT src_row_parts, uint64_t * SMOL_RESTRICT dest_row_parts) { SMOL_ASSUME_ALIGNED (src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_row_parts, uint64_t *); memcpy (dest_row_parts, src_row_parts, scale_ctx->hdim.placement_size_px * 2 * sizeof (uint64_t)); } static void scale_horizontal (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, const char *src_row, uint64_t *dest_row_parts) { uint64_t * SMOL_RESTRICT src_row_unpacked; src_row_unpacked = local_ctx->parts_row [3]; /* 32-bit unpackers need 32-bit alignment */ if ((((uintptr_t) src_row) & 3) && scale_ctx->src_pixel_type != SMOL_PIXEL_RGB8 && scale_ctx->src_pixel_type != SMOL_PIXEL_BGR8) { if (!local_ctx->src_aligned) local_ctx->src_aligned = smol_alloc_aligned (scale_ctx->hdim.src_size_px * sizeof (uint32_t), &local_ctx->src_aligned_storage); memcpy (local_ctx->src_aligned, src_row, scale_ctx->hdim.src_size_px * sizeof (uint32_t)); src_row = (const char *) local_ctx->src_aligned; } scale_ctx->src_unpack_row_func (src_row, src_row_unpacked, scale_ctx->hdim.src_size_px); scale_ctx->hfilter_func (scale_ctx, src_row_unpacked, dest_row_parts); apply_horiz_edge_opacity (scale_ctx, dest_row_parts); } /* ---------------- * * Vertical scaling * * ---------------- */ static void update_local_ctx_bilinear (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t new_src_ofs = precalc_y [dest_row_index * 2]; if (new_src_ofs == local_ctx->src_ofs) return; if (new_src_ofs == local_ctx->src_ofs + 1) { uint64_t *t = local_ctx->parts_row [0]; local_ctx->parts_row [0] = local_ctx->parts_row [1]; local_ctx->parts_row [1] = t; scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs + 1), local_ctx->parts_row [1]); } else { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs), local_ctx->parts_row [0]); scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, new_src_ofs + 1), local_ctx->parts_row [1]); } local_ctx->src_ofs = new_src_ofs; } static void interp_vertical_bilinear_store_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *(dest_parts++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (dest_parts != parts_dest_last); } static void interp_vertical_bilinear_store_with_opacity_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width, uint16_t opacity) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *dest_parts = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; apply_subpixel_opacity_64bpp (dest_parts, opacity); dest_parts++; } while (dest_parts != parts_dest_last); } static void interp_vertical_bilinear_add_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_dest_last = accum_out + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (accum_out != accum_dest_last); } static void interp_vertical_bilinear_store_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *(dest_parts++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (dest_parts != parts_dest_last); } static void interp_vertical_bilinear_store_with_opacity_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT dest_parts, uint32_t width, uint16_t opacity) { uint64_t *parts_dest_last = dest_parts + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *dest_parts = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; apply_subpixel_opacity_128bpp_half (dest_parts, opacity); dest_parts++; } while (dest_parts != parts_dest_last); } static void interp_vertical_bilinear_add_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_src_row_parts, const uint64_t * SMOL_RESTRICT bottom_src_row_parts, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_dest_last = accum_out + width; SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); do { uint64_t p, q; p = *(top_src_row_parts++); q = *(bottom_src_row_parts++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (accum_out != accum_dest_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_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ 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_with_opacity_64bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width, \ uint16_t opacity) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ \ apply_subpixel_opacity_64bpp (&p, opacity); \ *(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_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } \ \ static void \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_src_row_parts, \ const uint64_t * SMOL_RESTRICT bottom_src_row_parts, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width, \ uint16_t opacity) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_src_row_parts, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_src_row_parts++); \ q = *(bottom_src_row_parts++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ apply_subpixel_opacity_128bpp_half (&p, opacity); \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } #define DEF_SCALE_DEST_ROW_BILINEAR(n_halvings) \ static int \ scale_dest_row_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ SmolLocalCtx *local_ctx, \ uint32_t dest_row_index) \ { \ uint16_t *precalc_y = scale_ctx->vdim.precalc; \ uint32_t bilin_index = dest_row_index << (n_halvings); \ unsigned int i; \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_store_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_add_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ bilin_index++; \ } \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ \ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px, \ scale_ctx->vdim.first_opacity); \ else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px, \ scale_ctx->vdim.last_opacity); \ else \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px); \ \ return 2; \ } \ \ static int \ scale_dest_row_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ SmolLocalCtx *local_ctx, \ uint32_t dest_row_index) \ { \ uint16_t *precalc_y = scale_ctx->vdim.precalc; \ uint32_t bilin_index = dest_row_index << (n_halvings); \ unsigned int i; \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_store_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ interp_vertical_bilinear_add_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ bilin_index++; \ } \ \ update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); \ \ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2, \ scale_ctx->vdim.first_opacity); \ else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) \ interp_vertical_bilinear_final_##n_halvings##h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2, \ scale_ctx->vdim.last_opacity); \ else \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (precalc_y [bilin_index * 2 + 1], \ local_ctx->parts_row [0], \ local_ctx->parts_row [1], \ local_ctx->parts_row [2], \ scale_ctx->hdim.placement_size_px * 2); \ \ return 2; \ } static int scale_dest_row_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; update_local_ctx_bilinear (scale_ctx, local_ctx, dest_row_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_store_with_opacity_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_store_with_opacity_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_store_64bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); return 2; } static int scale_dest_row_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; update_local_ctx_bilinear (scale_ctx, local_ctx, dest_row_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_store_with_opacity_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_store_with_opacity_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_store_128bpp (precalc_y [dest_row_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); return 2; } DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) static int scale_dest_row_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t bilin_index = dest_row_index << 1; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); interp_vertical_bilinear_store_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); bilin_index++; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_final_1h_64bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px); return 2; } static int scale_dest_row_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint16_t *precalc_y = scale_ctx->vdim.precalc; uint32_t bilin_index = dest_row_index << 1; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); interp_vertical_bilinear_store_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); bilin_index++; update_local_ctx_bilinear (scale_ctx, local_ctx, bilin_index); if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.first_opacity); else if (dest_row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) interp_vertical_bilinear_final_1h_with_opacity_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2, scale_ctx->vdim.last_opacity); else interp_vertical_bilinear_final_1h_128bpp (precalc_y [bilin_index * 2 + 1], local_ctx->parts_row [0], local_ctx->parts_row [1], local_ctx->parts_row [2], scale_ctx->hdim.placement_size_px * 2); return 2; } DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) DEF_SCALE_DEST_ROW_BILINEAR(2) DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) DEF_SCALE_DEST_ROW_BILINEAR(3) DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) DEF_SCALE_DEST_ROW_BILINEAR(4) DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) DEF_SCALE_DEST_ROW_BILINEAR(5) DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) DEF_SCALE_DEST_ROW_BILINEAR(6) static void finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n) { uint64_t *parts_dest_max = dest_parts + n; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { *(dest_parts++) = scale_64bpp (*(accums++), multiplier); } } static void finalize_vertical_with_opacity_64bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n, uint16_t opacity) { uint64_t *parts_dest_max = dest_parts + n; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { *dest_parts = scale_64bpp (*(accums++), multiplier); apply_subpixel_opacity_64bpp (dest_parts, opacity); dest_parts++; } } static int scale_dest_row_box_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint32_t *precalc_y = scale_ctx->vdim.precalc; uint32_t ofs_y, ofs_y_max; uint32_t w1, w2; uint32_t n, i; unpack_box_precalc (precalc_y [dest_row_index], scale_ctx->vdim.span_step, &ofs_y, &ofs_y_max, &w1, &w2, &n); /* First input row */ scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); copy_weighted_parts_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w1); ofs_y++; /* Add up whole input rows */ for (i = 0; i < n; i++) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_parts (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px); ofs_y++; } /* Last input row */ if (ofs_y < scale_ctx->vdim.src_size_px) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_weighted_parts_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w2); } /* Finalize */ if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) { finalize_vertical_with_opacity_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (dest_row_index == scale_ctx->vdim.placement_size_px - 1 && scale_ctx->vdim.last_opacity < 256) { finalize_vertical_with_opacity_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { finalize_vertical_64bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px); } return 0; } static void finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n) { uint64_t *parts_dest_max = dest_parts + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { *(dest_parts++) = scale_128bpp_half (*(accums++), multiplier); *(dest_parts++) = scale_128bpp_half (*(accums++), multiplier); } } static void finalize_vertical_with_opacity_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT dest_parts, uint32_t n, uint16_t opacity) { uint64_t *parts_dest_max = dest_parts + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (dest_parts, uint64_t *); while (dest_parts != parts_dest_max) { dest_parts [0] = scale_128bpp_half (*(accums++), multiplier); dest_parts [1] = scale_128bpp_half (*(accums++), multiplier); apply_subpixel_opacity_128bpp (dest_parts, opacity); dest_parts += 2; } } static int scale_dest_row_box_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index) { uint32_t *precalc_y = scale_ctx->vdim.precalc; uint32_t ofs_y, ofs_y_max; uint32_t w1, w2; uint32_t n, i; unpack_box_precalc (precalc_y [dest_row_index], scale_ctx->vdim.span_step, &ofs_y, &ofs_y_max, &w1, &w2, &n); /* First input row */ scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); copy_weighted_parts_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w1); ofs_y++; /* Add up whole input rows */ for (i = 0; i < n; i++) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_parts (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px * 2); ofs_y++; } /* Last input row */ if (ofs_y < scale_ctx->vdim.src_size_px) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, ofs_y), local_ctx->parts_row [0]); add_weighted_parts_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, w2); } if (dest_row_index == 0 && scale_ctx->vdim.first_opacity < 256) { finalize_vertical_with_opacity_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (dest_row_index == scale_ctx->vdim.placement_size_px - 1 && scale_ctx->vdim.last_opacity < 256) { finalize_vertical_with_opacity_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { finalize_vertical_128bpp (local_ctx->parts_row [1], scale_ctx->vdim.span_mul, local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px); } return 0; } static int scale_dest_row_one_64bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { /* Scale the row and store it */ if (local_ctx->src_ofs != 0) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, 0), local_ctx->parts_row [0]); local_ctx->src_ofs = 0; } if (row_index == 0 && scale_ctx->vdim.first_opacity < 256) { apply_subpixel_opacity_row_copy_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) { apply_subpixel_opacity_row_copy_64bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { memcpy (local_ctx->parts_row [1], local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px * sizeof (uint64_t)); } return 1; } static int scale_dest_row_one_128bpp (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { /* Scale the row and store it */ if (local_ctx->src_ofs != 0) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, 0), local_ctx->parts_row [0]); local_ctx->src_ofs = 0; } if (row_index == 0 && scale_ctx->vdim.first_opacity < 256) { apply_subpixel_opacity_row_copy_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.first_opacity); } else if (row_index == (scale_ctx->vdim.placement_size_px - 1) && scale_ctx->vdim.last_opacity < 256) { apply_subpixel_opacity_row_copy_128bpp (local_ctx->parts_row [0], local_ctx->parts_row [1], scale_ctx->hdim.placement_size_px, scale_ctx->vdim.last_opacity); } else { memcpy (local_ctx->parts_row [1], local_ctx->parts_row [0], scale_ctx->hdim.placement_size_px * sizeof (uint64_t) * 2); } return 1; } static int scale_dest_row_copy (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t row_index) { scale_horizontal (scale_ctx, local_ctx, src_row_ofs_to_pointer (scale_ctx, row_index), local_ctx->parts_row [0]); return 0; } /* ----------- * * Compositing * * ----------- */ static void composite_over_color_64bpp (uint64_t * SMOL_RESTRICT srcdest_row, const uint64_t * SMOL_RESTRICT color_pixel, uint32_t n_pixels) { uint32_t i; SMOL_ASSUME_ALIGNED_TO (srcdest_row, uint64_t *, sizeof (uint64_t)); SMOL_ASSUME_ALIGNED_TO (color_pixel, const uint64_t *, sizeof (uint64_t)); for (i = 0; i < n_pixels; i++) { uint64_t a = srcdest_row [i] & 0xff; srcdest_row [i] += (((*color_pixel) * (0xff - a)) >> 8) & 0x00ff00ff00ff00ff; } } static void composite_over_color_128bpp (uint64_t * SMOL_RESTRICT srcdest_row, const uint64_t * SMOL_RESTRICT color_pixel, uint32_t n_pixels) { uint32_t i; SMOL_ASSUME_ALIGNED_TO (srcdest_row, uint64_t *, sizeof (uint64_t) * 2); SMOL_ASSUME_ALIGNED_TO (color_pixel, const uint64_t *, sizeof (uint64_t)); for (i = 0; i < n_pixels * 2; i += 2) { uint64_t a = (srcdest_row [i + 1] >> 4) & 0xfff; srcdest_row [i] += ((color_pixel [0] * (0xfff - a)) >> 12) & 0x000fffff000fffff; srcdest_row [i + 1] += ((color_pixel [1] * (0xfff - a)) >> 12) & 0x000fffff000fffff; } } static void composite_over_dest_64bpp (const uint64_t * SMOL_RESTRICT src_row, uint64_t * SMOL_RESTRICT dest_row, uint32_t n_pixels) { uint32_t i; SMOL_ASSUME_ALIGNED_TO (src_row, const uint64_t *, sizeof (uint64_t)); SMOL_ASSUME_ALIGNED_TO (dest_row, uint64_t *, sizeof (uint64_t)); for (i = 0; i < n_pixels; i++) { dest_row [i] = ((src_row [i] + dest_row [i]) >> 1) & 0x7fff7fff7fff7fff; } } static void composite_over_dest_128bpp (const uint64_t * SMOL_RESTRICT src_row, uint64_t * SMOL_RESTRICT dest_row, uint32_t n_pixels) { uint32_t i; SMOL_ASSUME_ALIGNED_TO (src_row, const uint64_t *, sizeof (uint64_t) * 2); SMOL_ASSUME_ALIGNED_TO (dest_row, uint64_t *, sizeof (uint64_t) * 2); for (i = 0; i < n_pixels * 2; i += 2) { dest_row [i] = ((src_row [i] + dest_row [i]) >> 1) & 0x7fffffff7fffffff; dest_row [i + 1] = ((src_row [i + 1] + dest_row [i + 1]) >> 1) & 0x7fffffff7fffffff; } } /* -------- * * Clearing * * -------- */ static void clear_24bpp (const void *src_pixel_batch, void *dest_row, uint32_t n_pixels) { const uint8_t *src_pixel_batch_u8 = src_pixel_batch; const uint32_t *src_pixel_batch_u32 = src_pixel_batch; uint8_t *dest_row_u8 = dest_row; uint32_t *dest_row_u32 = dest_row; uint32_t i; SMOL_ASSUME_ALIGNED_TO (src_pixel_batch_u32, const uint32_t *, sizeof (uint32_t)); for (i = 0; n_pixels - i >= 4; i += 4) { *(dest_row_u32++) = src_pixel_batch_u32 [0]; *(dest_row_u32++) = src_pixel_batch_u32 [1]; *(dest_row_u32++) = src_pixel_batch_u32 [2]; } for ( ; i < n_pixels; i++) { dest_row_u8 [i * 3] = src_pixel_batch_u8 [0]; dest_row_u8 [i * 3 + 1] = src_pixel_batch_u8 [1]; dest_row_u8 [i * 3 + 2] = src_pixel_batch_u8 [2]; } } static void clear_32bpp (const void *src_pixel_batch, void *dest_row, uint32_t n_pixels) { const uint32_t *src_pixel_batch_u32 = src_pixel_batch; uint32_t *dest_row_u32 = dest_row; uint32_t i; SMOL_ASSUME_ALIGNED_TO (src_pixel_batch_u32, const uint32_t *, sizeof (uint32_t)); for (i = 0; i < n_pixels; i++) dest_row_u32 [i] = src_pixel_batch_u32 [0]; } /* --------------- * * Function tables * * --------------- */ #define R SMOL_REPACK_META static const SmolRepackMeta repack_meta [] = { R (123, 24, PREMUL8, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (123, 24, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (123, 24, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, LINEAR), R (1234, 32, PREMUL8, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 2431, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 3241, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1324, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2431, 64, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 3241, 64, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 2341, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL8, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL16, COMPRESSED), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL16, COMPRESSED), R (1234, 32, PREMUL8, COMPRESSED, 1234, 128, PREMUL8, LINEAR), R (1234, 32, PREMUL8, COMPRESSED, 2341, 128, PREMUL8, LINEAR), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL8, LINEAR), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL8, LINEAR), R (1234, 32, UNASSOCIATED, COMPRESSED, 1234, 128, PREMUL16, LINEAR), R (1234, 32, UNASSOCIATED, COMPRESSED, 2341, 128, PREMUL16, LINEAR), R (1234, 64, PREMUL8, COMPRESSED, 132, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 231, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 324, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 423, 24, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 132, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 231, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 324, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 423, 24, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1324, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1423, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 2314, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4132, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4231, 32, PREMUL8, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1324, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 1423, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 2314, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4132, 32, UNASSOCIATED, COMPRESSED), R (1234, 64, PREMUL8, COMPRESSED, 4231, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 123, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 321, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 123, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 321, 24, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 123, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 321, 24, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 1234, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 3214, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4123, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4321, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, COMPRESSED, 4321, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, COMPRESSED, 4321, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 1234, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 3214, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 4123, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 4321, 32, PREMUL8, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL8, LINEAR, 4321, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 1234, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 3214, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 4123, 32, UNASSOCIATED, COMPRESSED), R (1234, 128, PREMUL16, LINEAR, 4321, 32, UNASSOCIATED, COMPRESSED), SMOL_REPACK_META_LAST }; #undef R static const SmolImplementation implementation = { /* Horizontal init */ init_horizontal, /* Vertical init */ init_vertical, { /* Horizontal filters */ { /* 24bpp */ }, { /* 32bpp */ }, { /* 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 */ { /* 24bpp */ }, { /* 32bpp */ }, { /* 64bpp */ scale_dest_row_copy, scale_dest_row_one_64bpp, scale_dest_row_bilinear_0h_64bpp, scale_dest_row_bilinear_1h_64bpp, scale_dest_row_bilinear_2h_64bpp, scale_dest_row_bilinear_3h_64bpp, scale_dest_row_bilinear_4h_64bpp, scale_dest_row_bilinear_5h_64bpp, scale_dest_row_bilinear_6h_64bpp, scale_dest_row_box_64bpp }, { /* 128bpp */ scale_dest_row_copy, scale_dest_row_one_128bpp, scale_dest_row_bilinear_0h_128bpp, scale_dest_row_bilinear_1h_128bpp, scale_dest_row_bilinear_2h_128bpp, scale_dest_row_bilinear_3h_128bpp, scale_dest_row_bilinear_4h_128bpp, scale_dest_row_bilinear_5h_128bpp, scale_dest_row_bilinear_6h_128bpp, scale_dest_row_box_128bpp } }, { /* Composite over color */ NULL, NULL, composite_over_color_64bpp, composite_over_color_128bpp }, { /* Composite over dest */ NULL, NULL, composite_over_dest_64bpp, composite_over_dest_128bpp }, { /* Clear dest */ clear_24bpp, clear_32bpp, NULL, NULL }, repack_meta }; const SmolImplementation * _smol_get_generic_implementation (void) { return &implementation; } chafa-1.14.5/chafa/internal/smolscale/smolscale-private.h000066400000000000000000000323221471154763100233220ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2024 Hans Petter Jansson. See COPYING for details. */ /* If you're just going to use Smolscale in your project, you don't have to * worry about anything in here. The public API and documentation, such as * it is, lives in smolscale.h. * * If, on the other hand, you're here to hack on Smolscale itself, this file * contains all the internal shared declarations. */ #undef SMOL_ENABLE_ASSERTS #include #include "smolscale.h" #ifndef _SMOLSCALE_PRIVATE_H_ #define _SMOLSCALE_PRIVATE_H_ #ifdef __cplusplus extern "C" { #endif #ifdef SMOL_ENABLE_ASSERTS # include # define SMOL_ASSERT(x) assert (x) #else # define SMOL_ASSERT(x) #endif /* We'll use at most ~4MB of scratch space. That won't fit on the stack * everywhere, so we default to malloc(). If you know better, you can define * SMOL_USE_ALLOCA. */ #ifdef SMOL_USE_ALLOCA # define _SMOL_ALLOC(n) alloca (n) # define _SMOL_FREE(p) #else # define _SMOL_ALLOC(n) malloc (n) # define _SMOL_FREE(p) free (p) #endif /* 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_ASSIGN_ALIGNED_TO(x, t, n) (t) __builtin_assume_aligned ((x), (n)) #define SMOL_ASSIGN_ALIGNED(x, t) SMOL_ASSIGN_ALIGNED_TO ((x), t, SMOL_ALIGNMENT) #define SMOL_ASSUME_ALIGNED_TO(x, t, n) (x) = SMOL_ASSIGN_ALIGNED_TO ((x), t, (n)) #define SMOL_ASSUME_ALIGNED(x, t) SMOL_ASSUME_ALIGNED_TO ((x), t, SMOL_ALIGNMENT) /* Pointer to beginning of storage is stored in *r. This must be passed to smol_free() later. */ #define smol_alloc_aligned_to(s, a, r) \ ({ void *p; *(r) = _SMOL_ALLOC ((s) + (a)); p = (void *) (((uintptr_t) (*(r)) + (a)) & ~((a) - 1)); (p); }) #define smol_alloc_aligned(s, r) smol_alloc_aligned_to ((s), SMOL_ALIGNMENT, (r)) #define smol_free(p) _SMOL_FREE(p) typedef enum { SMOL_STORAGE_24BPP, SMOL_STORAGE_32BPP, 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; typedef enum { SMOL_REORDER_1234_TO_1234, SMOL_REORDER_1234_TO_2341, SMOL_REORDER_1234_TO_3214, SMOL_REORDER_1234_TO_4123, SMOL_REORDER_1234_TO_4321, SMOL_REORDER_1234_TO_123, SMOL_REORDER_1234_TO_321, SMOL_REORDER_123_TO_1234, SMOL_REORDER_1234_TO_1324, SMOL_REORDER_1234_TO_2314, SMOL_REORDER_1234_TO_2431, SMOL_REORDER_1234_TO_4132, SMOL_REORDER_1234_TO_4231, SMOL_REORDER_1234_TO_132, SMOL_REORDER_1234_TO_231, SMOL_REORDER_123_TO_1324, SMOL_REORDER_1234_TO_324, SMOL_REORDER_1234_TO_423, SMOL_REORDER_1234_TO_1423, SMOL_REORDER_1234_TO_3241, SMOL_REORDER_MAX } SmolReorderType; typedef enum { SMOL_ALPHA_UNASSOCIATED, SMOL_ALPHA_PREMUL8, SMOL_ALPHA_PREMUL16, SMOL_ALPHA_MAX } SmolAlphaType; typedef enum { SMOL_GAMMA_SRGB_COMPRESSED, SMOL_GAMMA_SRGB_LINEAR, SMOL_GAMMA_MAX } SmolGammaType; typedef struct { unsigned char src [4]; unsigned char dest [4]; } SmolReorderMeta; typedef struct { unsigned char storage; unsigned char pixel_stride; unsigned char alpha; unsigned char order [4]; } SmolPixelTypeMeta; /* For reusing rows that have already undergone horizontal scaling */ typedef struct { uint32_t src_ofs; uint64_t *parts_row [4]; uint64_t *row_storage [4]; uint32_t *src_aligned; uint32_t *src_aligned_storage; } SmolLocalCtx; typedef void (SmolInitFunc) (SmolScaleCtx *scale_ctx); typedef void (SmolRepackRowFunc) (const void *src_row, void *dest_row, uint32_t n_pixels); typedef void (SmolHFilterFunc) (const SmolScaleCtx *scale_ctx, const uint64_t *src_row_limbs, uint64_t *dest_row_limbs); typedef int (SmolVFilterFunc) (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index); typedef void (SmolCompositeOverColorFunc) (uint64_t *srcdest_row, const uint64_t *color_pixel, uint32_t n_pixels); typedef void (SmolCompositeOverDestFunc) (const uint64_t *src_row, uint64_t *dest_row, uint32_t n_pixels); typedef void (SmolClearFunc) (const void *src_pixel_batch, void *dest_row, uint32_t n_pixels); #define SMOL_REPACK_SIGNATURE_GET_REORDER(sig) ((sig) >> (2 * (SMOL_GAMMA_BITS + SMOL_ALPHA_BITS + SMOL_STORAGE_BITS))) #define SMOL_REORDER_BITS 6 #define SMOL_STORAGE_BITS 2 #define SMOL_ALPHA_BITS 2 #define SMOL_GAMMA_BITS 1 #define SMOL_MAKE_REPACK_SIGNATURE_ANY_ORDER(src_storage, src_alpha, src_gamma, \ dest_storage, dest_alpha, dest_gamma) \ (((src_storage) << (SMOL_GAMMA_BITS + SMOL_ALPHA_BITS + SMOL_STORAGE_BITS + SMOL_GAMMA_BITS + SMOL_ALPHA_BITS)) \ | ((src_alpha) << (SMOL_GAMMA_BITS + SMOL_ALPHA_BITS + SMOL_STORAGE_BITS + SMOL_GAMMA_BITS)) \ | ((src_gamma) << (SMOL_GAMMA_BITS + SMOL_ALPHA_BITS + SMOL_STORAGE_BITS)) \ | ((dest_storage) << (SMOL_GAMMA_BITS + SMOL_ALPHA_BITS)) \ | ((dest_alpha) << (SMOL_GAMMA_BITS)) \ | ((dest_gamma) << 0)) \ #define MASK_ITEM(m, n_bits) ((m) ? (1 << (n_bits)) - 1 : 0) #define SMOL_REPACK_SIGNATURE_ANY_ORDER_MASK(src_storage, src_alpha, src_gamma, \ dest_storage, dest_alpha, dest_gamma) \ SMOL_MAKE_REPACK_SIGNATURE_ANY_ORDER(MASK_ITEM (src_storage, SMOL_STORAGE_BITS), \ MASK_ITEM (src_alpha, SMOL_ALPHA_BITS), \ MASK_ITEM (src_gamma, SMOL_GAMMA_BITS), \ MASK_ITEM (dest_storage, SMOL_STORAGE_BITS), \ MASK_ITEM (dest_alpha, SMOL_ALPHA_BITS), \ MASK_ITEM (dest_gamma, SMOL_GAMMA_BITS)) #define SMOL_REPACK_META(src_order, src_storage, src_alpha, src_gamma, \ dest_order, dest_storage, dest_alpha, dest_gamma) \ { (((SMOL_REORDER_##src_order##_TO_##dest_order) << 10) \ | ((SMOL_STORAGE_##src_storage##BPP) << 8) | ((SMOL_ALPHA_##src_alpha) << 6) \ | ((SMOL_GAMMA_SRGB_##src_gamma) << 5) \ | ((SMOL_STORAGE_##dest_storage##BPP) << 3) | ((SMOL_ALPHA_##dest_alpha) << 1) \ | ((SMOL_GAMMA_SRGB_##dest_gamma) << 0)), \ (SmolRepackRowFunc *) repack_row_##src_order##_##src_storage##_##src_alpha##_##src_gamma##_to_##dest_order##_##dest_storage##_##dest_alpha##_##dest_gamma } #define SMOL_REPACK_META_LAST { 0xffff, NULL } typedef struct { uint16_t signature; SmolRepackRowFunc *repack_row_func; } SmolRepackMeta; #define SMOL_REPACK_ROW_DEF(src_order, src_storage, src_limb_bits, src_alpha, src_gamma, \ dest_order, dest_storage, dest_limb_bits, dest_alpha, dest_gamma) \ static void repack_row_##src_order##_##src_storage##_##src_alpha##_##src_gamma##_to_##dest_order##_##dest_storage##_##dest_alpha##_##dest_gamma \ (const uint##src_limb_bits##_t * SMOL_RESTRICT src_row, \ uint##dest_limb_bits##_t * SMOL_RESTRICT dest_row, \ uint32_t n_pixels) \ { \ uint##dest_limb_bits##_t *dest_row_max = dest_row + n_pixels * (dest_storage / dest_limb_bits); \ SMOL_ASSUME_ALIGNED_TO (src_row, uint##src_limb_bits##_t *, src_limb_bits / 8); \ SMOL_ASSUME_ALIGNED_TO (dest_row, uint##dest_limb_bits##_t *, dest_limb_bits / 8); #define SMOL_REPACK_ROW_DEF_END } typedef struct { SmolInitFunc *init_h_func; SmolInitFunc *init_v_func; SmolHFilterFunc *hfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; SmolVFilterFunc *vfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; SmolCompositeOverColorFunc *composite_over_color_funcs [SMOL_STORAGE_MAX]; SmolCompositeOverDestFunc *composite_over_dest_funcs [SMOL_STORAGE_MAX]; SmolClearFunc *clear_funcs [SMOL_STORAGE_MAX]; const SmolRepackMeta *repack_meta; } SmolImplementation; typedef struct { void *precalc; SmolFilterType filter_type; uint32_t src_size_px, src_size_spx; uint32_t dest_size_px, dest_size_spx; unsigned int n_halvings; int32_t placement_ofs_px, placement_ofs_spx; uint32_t placement_size_px, placement_size_spx; uint32_t placement_size_prehalving_px, placement_size_prehalving_spx; uint32_t span_step; /* For box filter, in spx */ uint32_t span_mul; /* For box filter */ /* Opacity of first and last column or row. Used for subpixel placement * and applied after each scaling step. */ uint16_t first_opacity, last_opacity; /* Rows or cols to add consisting of unbroken pixel_color. This is done * after scaling but before conversion to output pixel format. */ uint16_t clear_before_px, clear_after_px; uint16_t clip_before_px, clip_after_px; } SmolDim; #define SMOL_CLEAR_BATCH_SIZE 96 struct SmolScaleCtx { /* */ const char *src_pixels; char *dest_pixels; uint32_t src_rowstride; uint32_t dest_rowstride; SmolPixelType src_pixel_type, dest_pixel_type; SmolStorageType storage_type; SmolGammaType gamma_type; SmolCompositeOp composite_op; /* Raw flags passed in by user */ SmolFlags flags; SmolRepackRowFunc *src_unpack_row_func; SmolRepackRowFunc *dest_unpack_row_func; SmolRepackRowFunc *pack_row_func; SmolHFilterFunc *hfilter_func; SmolVFilterFunc *vfilter_func; SmolCompositeOverColorFunc *composite_over_color_func; SmolCompositeOverDestFunc *composite_over_dest_func; SmolClearFunc *clear_dest_func; /* User specified, can be NULL */ SmolPostRowFunc *post_row_func; void *user_data; /* Storage for dimensions' precalc arrays. Single allocation. */ void *precalc_storage; /* Specifics for each dimension */ SmolDim hdim, vdim; /* TRUE if input rows can be copied directly to output. */ unsigned int is_noop : 1; /* TRUE if we have a color_pixel to composite on. */ unsigned int have_composite_color : 1; /* Unpacked color to composite on */ uint64_t color_pixel [2]; /* A batch of color pixels in dest storage format. The batch size * is in bytes, and chosen as an even multiple of 3, allowing 32 bytes wide * operations (e.g. AVX2) to be used to clear packed RGB pixels. */ unsigned char color_pixels_clear_batch [SMOL_CLEAR_BATCH_SIZE]; }; /* Number of pixels to convert per batch. For some conversions, we perform * an alpha test per batch to avoid the expensive premul path when the image * data is opaque. * * FIXME: Unimplemented. */ #define PIXEL_BATCH_SIZE 32 #define SRGB_LINEAR_BITS 11 #define SRGB_LINEAR_MAX (1 << (SRGB_LINEAR_BITS)) extern const uint16_t _smol_from_srgb_lut [256]; extern const uint8_t _smol_to_srgb_lut [SRGB_LINEAR_MAX]; #define INVERTED_DIV_SHIFT_P8 (21 - 8) #define INVERTED_DIV_SHIFT_P8L (22 - SRGB_LINEAR_BITS) #define INVERTED_DIV_SHIFT_P16 (24 - 8) #define INVERTED_DIV_SHIFT_P16L (30 - SRGB_LINEAR_BITS) extern const uint32_t _smol_inv_div_p8_lut [256]; extern const uint32_t _smol_inv_div_p8l_lut [256]; extern const uint32_t _smol_inv_div_p16_lut [256]; extern const uint32_t _smol_inv_div_p16l_lut [256]; const SmolImplementation *_smol_get_generic_implementation (void); #ifdef SMOL_WITH_AVX2 const SmolImplementation *_smol_get_avx2_implementation (void); #endif #ifdef __cplusplus } #endif #endif chafa-1.14.5/chafa/internal/smolscale/smolscale.c000066400000000000000000001731351471154763100216550ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2024 Hans Petter Jansson. See COPYING for details. */ #include /* assert */ #include /* malloc, free, alloca */ #include /* memset */ #include #include "smolscale-private.h" /* ----------------------- * * Misc. conversion tables * * ----------------------- */ /* Table of channel reorderings. Each entry describes an available shuffle * implementation indexed by its SmolReorderType. Channel indexes are 1-based. * A zero index denotes that the channel is not present (e.g. 3-channel RGB). * * Keep in sync with the private SmolReorderType enum. */ static const SmolReorderMeta reorder_meta [SMOL_REORDER_MAX] = { { { 1, 2, 3, 4 }, { 1, 2, 3, 4 } }, { { 1, 2, 3, 4 }, { 2, 3, 4, 1 } }, { { 1, 2, 3, 4 }, { 3, 2, 1, 4 } }, { { 1, 2, 3, 4 }, { 4, 1, 2, 3 } }, { { 1, 2, 3, 4 }, { 4, 3, 2, 1 } }, { { 1, 2, 3, 4 }, { 1, 2, 3, 0 } }, { { 1, 2, 3, 4 }, { 3, 2, 1, 0 } }, { { 1, 2, 3, 0 }, { 1, 2, 3, 4 } }, { { 1, 2, 3, 4 }, { 1, 3, 2, 4 } }, { { 1, 2, 3, 4 }, { 2, 3, 1, 4 } }, { { 1, 2, 3, 4 }, { 2, 4, 3, 1 } }, { { 1, 2, 3, 4 }, { 4, 1, 3, 2 } }, { { 1, 2, 3, 4 }, { 4, 2, 3, 1 } }, { { 1, 2, 3, 4 }, { 1, 3, 2, 0 } }, { { 1, 2, 3, 4 }, { 2, 3, 1, 0 } }, { { 1, 2, 3, 0 }, { 1, 3, 2, 4 } }, { { 1, 2, 3, 4 }, { 3, 2, 4, 0 } }, { { 1, 2, 3, 4 }, { 4, 2, 3, 0 } }, { { 1, 2, 3, 4 }, { 1, 4, 2, 3 } }, { { 1, 2, 3, 4 }, { 3, 2, 4, 1 } } }; /* Metadata for each pixel type. Storage type, number of channels, alpha type, * channel ordering. Channel indexes are 1-based, and 4 is always alpha. A * zero index denotes that the channel is not present. * * RGBA = 1, 2, 3, 4. * * Keep in sync with the public SmolPixelType enum. */ static const SmolPixelTypeMeta pixel_type_meta [SMOL_PIXEL_MAX] = { { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_PREMUL8, { 1, 2, 3, 4 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_PREMUL8, { 3, 2, 1, 4 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_PREMUL8, { 4, 1, 2, 3 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_PREMUL8, { 4, 3, 2, 1 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_UNASSOCIATED, { 1, 2, 3, 4 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_UNASSOCIATED, { 3, 2, 1, 4 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_UNASSOCIATED, { 4, 1, 2, 3 } }, { SMOL_STORAGE_32BPP, 4, SMOL_ALPHA_UNASSOCIATED, { 4, 3, 2, 1 } }, { SMOL_STORAGE_24BPP, 3, SMOL_ALPHA_PREMUL8, { 1, 2, 3, 0 } }, { SMOL_STORAGE_24BPP, 3, SMOL_ALPHA_PREMUL8, { 3, 2, 1, 0 } } }; /* Channel ordering corrected for little endian. Only applies when fetching * entire pixels as dwords (i.e. u32), so 3-byte variants don't require any * correction. * * Keep in sync with the public SmolPixelType enum. */ static const SmolPixelType pixel_type_u32_le [SMOL_PIXEL_MAX] = { SMOL_PIXEL_ABGR8_PREMULTIPLIED, SMOL_PIXEL_ARGB8_PREMULTIPLIED, SMOL_PIXEL_BGRA8_PREMULTIPLIED, SMOL_PIXEL_RGBA8_PREMULTIPLIED, SMOL_PIXEL_ABGR8_UNASSOCIATED, SMOL_PIXEL_ARGB8_UNASSOCIATED, SMOL_PIXEL_BGRA8_UNASSOCIATED, SMOL_PIXEL_RGBA8_UNASSOCIATED, SMOL_PIXEL_RGB8, SMOL_PIXEL_BGR8 }; /* ----------------------------------- * * sRGB/linear conversion: Shared code * * ----------------------------------- */ /* These tables are manually tweaked to be reversible without information * loss; _smol_to_srgb_lut [_smol_from_srgb_lut [i]] == i. * * As a side effect, the values in the lower range (first 35 indexes) are * off by < 2%. */ const uint16_t _smol_from_srgb_lut [256] = { 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, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 62, 64, 67, 69, 72, 74, 77, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 113, 116, 119, 123, 126, 130, 134, 137, 141, 145, 149, 153, 157, 161, 165, 169, 174, 178, 182, 187, 191, 196, 201, 205, 210, 215, 220, 225, 230, 235, 240, 246, 251, 256, 262, 267, 273, 279, 284, 290, 296, 302, 308, 314, 320, 326, 333, 339, 345, 352, 359, 365, 372, 379, 385, 392, 399, 406, 414, 421, 428, 435, 443, 450, 458, 466, 473, 481, 489, 497, 505, 513, 521, 530, 538, 546, 555, 563, 572, 581, 589, 598, 607, 616, 625, 634, 644, 653, 662, 672, 682, 691, 701, 711, 721, 731, 741, 751, 761, 771, 782, 792, 803, 813, 824, 835, 845, 856, 867, 879, 890, 901, 912, 924, 935, 947, 959, 970, 982, 994, 1006, 1018, 1030, 1043, 1055, 1067, 1080, 1093, 1105, 1118, 1131, 1144, 1157, 1170, 1183, 1197, 1210, 1223, 1237, 1251, 1264, 1278, 1292, 1306, 1320, 1334, 1349, 1363, 1377, 1392, 1407, 1421, 1436, 1451, 1466, 1481, 1496, 1512, 1527, 1542, 1558, 1573, 1589, 1605, 1621, 1637, 1653, 1669, 1685, 1702, 1718, 1735, 1751, 1768, 1785, 1802, 1819, 1836, 1853, 1870, 1887, 1905, 1922, 1940, 1958, 1976, 1994, 2012, 2030, 2047 }; const uint8_t _smol_to_srgb_lut [SRGB_LINEAR_MAX] = { 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, 36, 37, 37, 38, 38, 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 47, 48, 48, 49, 49, 49, 50, 50, 51, 51, 51, 52, 52, 53, 53, 53, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 110, 110, 110, 110, 110, 110, 110, 111, 111, 111, 111, 111, 111, 112, 112, 112, 112, 112, 112, 113, 113, 113, 113, 113, 113, 113, 114, 114, 114, 114, 114, 114, 115, 115, 115, 115, 115, 115, 115, 116, 116, 116, 116, 116, 116, 117, 117, 117, 117, 117, 117, 117, 118, 118, 118, 118, 118, 118, 118, 119, 119, 119, 119, 119, 119, 120, 120, 120, 120, 120, 120, 120, 121, 121, 121, 121, 121, 121, 121, 122, 122, 122, 122, 122, 122, 122, 123, 123, 123, 123, 123, 123, 123, 124, 124, 124, 124, 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 130, 130, 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, 132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134, 135, 135, 135, 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, 137, 137, 137, 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144, 145, 145, 145, 145, 145, 145, 145, 145, 146, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 147, 148, 148, 148, 148, 148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149, 149, 150, 150, 150, 150, 150, 150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 153, 153, 153, 153, 153, 153, 153, 153, 153, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 155, 155, 155, 155, 155, 155, 155, 155, 155, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 157, 157, 157, 157, 157, 157, 157, 157, 157, 158, 158, 158, 158, 158, 158, 158, 158, 158, 158, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 195, 195, 195, 195, 195, 195, 195, 195, 195, 195, 195, 195, 195, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 235, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 241, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 249, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; /* ------------------------------ * * Premultiplication: Shared code * * ------------------------------ */ /* These tables are 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. * * The values are tuned to minimize the error and overhead when turning * premultiplied (8-bit, 11-bit, 16-bit, 19-bit) into 8-bit unassociated alpha. */ /* Lossy premultiplication: 8-bit * alpha -> 8-bit. Not perfectly reversible. */ const uint32_t _smol_inv_div_p8_lut [256] = { 0x00000000, 0x00181fff, 0x000e2fff, 0x0009f555, 0x0007a7ff, 0x00063333, 0x00052555, 0x00047999, 0x0003ebff, 0x0003838e, 0x00032333, 0x0002e2e8, 0x0002a2aa, 0x0002713b, 0x00024249, 0x00021ccc, 0x0001f924, 0x0001dd17, 0x0001c1c7, 0x0001ab4b, 0x000195e5, 0x0001830c, 0x000170c3, 0x00016164, 0x0001537a, 0x0001450d, 0x0001390b, 0x00012de9, 0x00012249, 0x00011846, 0x00010eaa, 0x0001069e, 0x0000fd70, 0x0000f6aa, 0x0000eedb, 0x0000e8f5, 0x0000e1c7, 0x0000db8e, 0x0000d638, 0x0000d069, 0x0000cb7c, 0x0000c690, 0x0000c186, 0x0000bd2d, 0x0000b8f9, 0x0000b4f7, 0x0000b0ff, 0x0000ad65, 0x0000a9ac, 0x0000a687, 0x0000a286, 0x00009f33, 0x00009c98, 0x000099b9, 0x000096f1, 0x00009414, 0x00009147, 0x00008efa, 0x00008c59, 0x00008a0a, 0x000087b4, 0x0000856c, 0x00008341, 0x0000818c, 0x00007f55, 0x00007d60, 0x00007b7f, 0x000079b2, 0x000077b9, 0x00007608, 0x0000743c, 0x000072b5, 0x0000711a, 0x00006fac, 0x00006e1a, 0x00006cad, 0x00006b17, 0x000069e1, 0x00006864, 0x00006736, 0x000065db, 0x000064b1, 0x00006357, 0x00006250, 0x000060c5, 0x00006060, 0x00005ec0, 0x00005da5, 0x00005c9b, 0x00005b8b, 0x00005a93, 0x000059ab, 0x00005884, 0x00005799, 0x000056ae, 0x000055d5, 0x000054e2, 0x0000540b, 0x00005343, 0x00005255, 0x0000517c, 0x000050a7, 0x00004fff, 0x00004f2c, 0x00004e5e, 0x00004d9f, 0x00004cec, 0x00004c34, 0x00004b78, 0x00004adc, 0x00004a23, 0x00004981, 0x000048ce, 0x00004836, 0x0000478c, 0x000046eb, 0x00004656, 0x000045b6, 0x00004524, 0x0000449c, 0x000043ff, 0x00004370, 0x000042e2, 0x00004257, 0x000041ce, 0x00004147, 0x000040c3, 0x00004081, 0x00003fff, 0x00003f57, 0x00003ed3, 0x00003e54, 0x00003dd9, 0x00003d60, 0x00003ced, 0x00003c78, 0x00003c07, 0x00003b9a, 0x00003b26, 0x00003abf, 0x00003a4f, 0x000039e1, 0x0000397e, 0x00003917, 0x000038af, 0x00003848, 0x000037ee, 0x00003787, 0x00003726, 0x000036c9, 0x0000366b, 0x0000360d, 0x000035b0, 0x00003567, 0x00003503, 0x000034aa, 0x00003453, 0x000033ff, 0x000033a8, 0x0000335c, 0x00003305, 0x000032b3, 0x00003266, 0x00003213, 0x000031c7, 0x00003178, 0x0000312b, 0x000030df, 0x00003094, 0x00003049, 0x00003018, 0x00002fc0, 0x00002f76, 0x00002f2d, 0x00002ee8, 0x00002ea6, 0x00002e5f, 0x00002e1c, 0x00002dd9, 0x00002d99, 0x00002d59, 0x00002d17, 0x00002cdf, 0x00002c9b, 0x00002c5d, 0x00002c1c, 0x00002be1, 0x00002ba6, 0x00002b6a, 0x00002b2e, 0x00002af3, 0x00002ac7, 0x00002a85, 0x00002a4a, 0x00002a11, 0x000029dc, 0x000029a6, 0x0000296e, 0x00002936, 0x00002904, 0x000028cd, 0x0000289a, 0x00002866, 0x00002833, 0x0000280a, 0x000027d0, 0x0000279e, 0x0000276f, 0x0000273c, 0x0000270d, 0x000026de, 0x000026ad, 0x0000267e, 0x00002652, 0x00002622, 0x000025f5, 0x000025c9, 0x0000259b, 0x0000256f, 0x00002545, 0x00002518, 0x000024ef, 0x000024c3, 0x0000249c, 0x0000246f, 0x00002446, 0x0000241c, 0x000023f4, 0x000023ca, 0x000023a2, 0x0000237b, 0x00002354, 0x0000232e, 0x00002306, 0x000022e0, 0x000022b9, 0x00002294, 0x0000226f, 0x0000224b, 0x00002226, 0x00002202, 0x000021dc, 0x000021b8, 0x00002195, 0x00002172, 0x0000214f, 0x0000212c, 0x0000210a, 0x000020e7, 0x000020c5, 0x000020a4, 0x00002083, 0x00002061, 0x00002041, 0x00002020, 0x00002020 }; /* Lossy premultiplication: 11-bit * alpha -> 11-bit. Not perfectly reversible. */ const uint32_t _smol_inv_div_p8l_lut [256] = { 0x00000000, 0x0007ffff, 0x0003ffff, 0x0002aaaa, 0x0001ffff, 0x00019999, 0x00015555, 0x00012492, 0x0000ffff, 0x0000e38e, 0x0000cccc, 0x0000ba2e, 0x0000aaaa, 0x00009d89, 0x00009249, 0x00008888, 0x00007fff, 0x00007878, 0x000071c7, 0x00006bca, 0x00006666, 0x00006186, 0x00005d17, 0x0000590b, 0x00005555, 0x000051eb, 0x00004ec4, 0x00004bda, 0x00004924, 0x0000469e, 0x00004444, 0x00004210, 0x00003fff, 0x00003e0f, 0x00003c3c, 0x00003a83, 0x000038e3, 0x0000372a, 0x000035b7, 0x00003458, 0x0000330a, 0x000031cc, 0x0000309e, 0x00002f7d, 0x00002e69, 0x00002d62, 0x00002c66, 0x00002b75, 0x00002a8e, 0x000029b0, 0x000028db, 0x0000280f, 0x0000274a, 0x0000268c, 0x000025d6, 0x00002526, 0x0000247d, 0x000023d9, 0x0000233c, 0x000022a3, 0x0000220f, 0x00002181, 0x000020f7, 0x00002071, 0x00001ff0, 0x00001f72, 0x00001ef8, 0x00001e82, 0x00001e0f, 0x00001da0, 0x00001d34, 0x00001ccb, 0x00001c65, 0x00001bf5, 0x00001b95, 0x00001b37, 0x00001adb, 0x00001a82, 0x00001a2c, 0x000019d7, 0x00001985, 0x00001934, 0x000018e6, 0x00001899, 0x0000184f, 0x00001806, 0x000017be, 0x00001779, 0x00001734, 0x000016f2, 0x000016b1, 0x00001671, 0x00001633, 0x000015f6, 0x000015ba, 0x00001580, 0x00001547, 0x0000150f, 0x000014d8, 0x000014a2, 0x0000146d, 0x0000143a, 0x00001407, 0x000013d5, 0x000013a5, 0x00001375, 0x00001346, 0x00001318, 0x000012eb, 0x000012be, 0x0000128e, 0x00001263, 0x00001239, 0x00001210, 0x000011e7, 0x000011c0, 0x00001199, 0x00001172, 0x0000114d, 0x00001127, 0x00001103, 0x000010df, 0x000010bc, 0x00001099, 0x00001077, 0x00001055, 0x00001034, 0x00001014, 0x00000ff4, 0x00000fd4, 0x00000fb5, 0x00000f96, 0x00000f78, 0x00000f5a, 0x00000f3d, 0x00000f20, 0x00000f04, 0x00000ee8, 0x00000ecc, 0x00000eb1, 0x00000e96, 0x00000e7c, 0x00000e62, 0x00000e48, 0x00000e2f, 0x00000e16, 0x00000dfa, 0x00000de2, 0x00000dca, 0x00000db2, 0x00000d9b, 0x00000d84, 0x00000d6d, 0x00000d57, 0x00000d41, 0x00000d2b, 0x00000d16, 0x00000d00, 0x00000ceb, 0x00000cd7, 0x00000cc2, 0x00000cae, 0x00000c9a, 0x00000c86, 0x00000c73, 0x00000c5f, 0x00000c4c, 0x00000c3a, 0x00000c27, 0x00000c15, 0x00000c03, 0x00000bf1, 0x00000bdf, 0x00000bcd, 0x00000bbc, 0x00000bab, 0x00000b9a, 0x00000b89, 0x00000b79, 0x00000b68, 0x00000b58, 0x00000b48, 0x00000b38, 0x00000b27, 0x00000b17, 0x00000b08, 0x00000af9, 0x00000aea, 0x00000adb, 0x00000acc, 0x00000abe, 0x00000ab0, 0x00000aa1, 0x00000a93, 0x00000a85, 0x00000a78, 0x00000a6a, 0x00000a5c, 0x00000a4f, 0x00000a42, 0x00000a35, 0x00000a28, 0x00000a1b, 0x00000a0e, 0x00000a02, 0x000009f5, 0x000009e9, 0x000009dd, 0x000009d1, 0x000009c5, 0x000009b9, 0x000009ad, 0x000009a1, 0x00000996, 0x0000098a, 0x0000097f, 0x00000974, 0x00000969, 0x0000095e, 0x00000951, 0x00000947, 0x0000093c, 0x00000931, 0x00000927, 0x0000091c, 0x00000912, 0x00000908, 0x000008fe, 0x000008f3, 0x000008e9, 0x000008e0, 0x000008d6, 0x000008cc, 0x000008c2, 0x000008b9, 0x000008af, 0x000008a6, 0x0000089d, 0x00000893, 0x0000088a, 0x00000881, 0x00000878, 0x0000086f, 0x00000866, 0x0000085e, 0x00000855, 0x0000084c, 0x00000844, 0x0000083b, 0x00000833, 0x0000082a, 0x00000822, 0x0000081a, 0x00000812, 0x0000080a, 0x00000801 }; /* Lossless premultiplication: 8-bit * alpha -> 16-bit. Reversible with this table. */ const uint32_t _smol_inv_div_p16_lut [256] = { 0x00000000, 0x00005556, 0x00004000, 0x00003334, 0x00002aab, 0x00002493, 0x00002000, 0x00001c72, 0x0000199a, 0x00001746, 0x00001556, 0x000013b2, 0x0000124a, 0x00001112, 0x00001000, 0x00000f10, 0x00000e39, 0x00000d7a, 0x00000ccd, 0x00000c31, 0x00000ba3, 0x00000b22, 0x00000aab, 0x00000a3e, 0x000009d9, 0x0000097c, 0x00000925, 0x000008d4, 0x00000889, 0x00000843, 0x00000800, 0x000007c2, 0x00000788, 0x00000751, 0x0000071d, 0x000006ec, 0x000006bd, 0x00000691, 0x00000667, 0x0000063f, 0x00000619, 0x000005f5, 0x000005d2, 0x000005b1, 0x00000591, 0x00000573, 0x00000556, 0x0000053a, 0x0000051f, 0x00000506, 0x000004ed, 0x000004d5, 0x000004be, 0x000004a8, 0x00000493, 0x0000047e, 0x0000046a, 0x00000457, 0x00000445, 0x00000433, 0x00000422, 0x00000411, 0x00000400, 0x000003f1, 0x000003e1, 0x000003d3, 0x000003c4, 0x000003b6, 0x000003a9, 0x0000039c, 0x0000038f, 0x00000382, 0x00000376, 0x0000036a, 0x0000035f, 0x00000354, 0x00000349, 0x0000033e, 0x00000334, 0x0000032a, 0x00000320, 0x00000316, 0x0000030d, 0x00000304, 0x000002fb, 0x000002f2, 0x000002e9, 0x000002e1, 0x000002d9, 0x000002d1, 0x000002c9, 0x000002c1, 0x000002ba, 0x000002b2, 0x000002ab, 0x000002a4, 0x0000029d, 0x00000296, 0x00000290, 0x00000289, 0x00000283, 0x0000027d, 0x00000277, 0x00000271, 0x0000026b, 0x00000265, 0x0000025f, 0x0000025a, 0x00000254, 0x0000024f, 0x0000024a, 0x00000244, 0x0000023f, 0x0000023a, 0x00000235, 0x00000231, 0x0000022c, 0x00000227, 0x00000223, 0x0000021e, 0x0000021a, 0x00000215, 0x00000211, 0x0000020d, 0x00000209, 0x00000205, 0x00000200, 0x000001fd, 0x000001f9, 0x000001f5, 0x000001f1, 0x000001ed, 0x000001ea, 0x000001e6, 0x000001e2, 0x000001df, 0x000001db, 0x000001d8, 0x000001d5, 0x000001d1, 0x000001ce, 0x000001cb, 0x000001c8, 0x000001c4, 0x000001c1, 0x000001be, 0x000001bb, 0x000001b8, 0x000001b5, 0x000001b3, 0x000001b0, 0x000001ad, 0x000001aa, 0x000001a7, 0x000001a5, 0x000001a2, 0x0000019f, 0x0000019d, 0x0000019a, 0x00000198, 0x00000195, 0x00000193, 0x00000190, 0x0000018e, 0x0000018b, 0x00000189, 0x00000187, 0x00000184, 0x00000182, 0x00000180, 0x0000017e, 0x0000017b, 0x00000179, 0x00000177, 0x00000175, 0x00000173, 0x00000171, 0x0000016f, 0x0000016d, 0x0000016b, 0x00000169, 0x00000167, 0x00000165, 0x00000163, 0x00000161, 0x0000015f, 0x0000015d, 0x0000015b, 0x00000159, 0x00000158, 0x00000156, 0x00000154, 0x00000152, 0x00000151, 0x0000014f, 0x0000014d, 0x0000014b, 0x0000014a, 0x00000148, 0x00000147, 0x00000145, 0x00000143, 0x00000142, 0x00000140, 0x0000013f, 0x0000013d, 0x0000013c, 0x0000013a, 0x00000139, 0x00000137, 0x00000136, 0x00000134, 0x00000133, 0x00000131, 0x00000130, 0x0000012f, 0x0000012d, 0x0000012c, 0x0000012a, 0x00000129, 0x00000128, 0x00000126, 0x00000125, 0x00000124, 0x00000122, 0x00000121, 0x00000120, 0x0000011f, 0x0000011d, 0x0000011c, 0x0000011b, 0x0000011a, 0x00000119, 0x00000117, 0x00000116, 0x00000115, 0x00000114, 0x00000113, 0x00000112, 0x00000110, 0x0000010f, 0x0000010e, 0x0000010d, 0x0000010c, 0x0000010b, 0x0000010a, 0x00000109, 0x00000108, 0x00000107, 0x00000106, 0x00000105, 0x00000104, 0x00000103, 0x00000102, 0x00000100, 0x00000100 }; /* Lossless premultiplication: 11-bit * alpha -> 19-bit. Reversible with this table. */ const uint32_t _smol_inv_div_p16l_lut [256] = { 0x00000000, 0x0002aaab, 0x00020000, 0x0001999a, 0x00015556, 0x00012493, 0x00010000, 0x0000e38f, 0x0000cccd, 0x0000ba2f, 0x0000aaab, 0x00009d8a, 0x0000924a, 0x00008889, 0x00008000, 0x00007879, 0x000071c8, 0x00006bcb, 0x00006667, 0x00006187, 0x00005d18, 0x0000590c, 0x00005556, 0x000051ec, 0x00004ec5, 0x00004bdb, 0x00004925, 0x0000469f, 0x00004445, 0x00004211, 0x00004000, 0x00003e10, 0x00003c3d, 0x00003a84, 0x000038e4, 0x0000375a, 0x000035e6, 0x00003484, 0x00003334, 0x000031f4, 0x000030c4, 0x00002fa1, 0x00002e8c, 0x00002d83, 0x00002c86, 0x00002b94, 0x00002aab, 0x000029cc, 0x000028f6, 0x00002829, 0x00002763, 0x000026a5, 0x000025ee, 0x0000253d, 0x00002493, 0x000023ef, 0x00002350, 0x000022b7, 0x00002223, 0x00002193, 0x00002109, 0x00002083, 0x00002000, 0x00001f82, 0x00001f08, 0x00001e92, 0x00001e1f, 0x00001daf, 0x00001d42, 0x00001cd9, 0x00001c72, 0x00001c0f, 0x00001bad, 0x00001b4f, 0x00001af3, 0x00001a99, 0x00001a42, 0x000019ed, 0x0000199a, 0x00001949, 0x000018fa, 0x000018ad, 0x00001862, 0x00001819, 0x000017d1, 0x0000178b, 0x00001746, 0x00001703, 0x000016c2, 0x00001682, 0x00001643, 0x00001606, 0x000015ca, 0x0000158f, 0x00001556, 0x0000151e, 0x000014e6, 0x000014b0, 0x0000147b, 0x00001447, 0x00001415, 0x000013e3, 0x000013b2, 0x00001382, 0x00001353, 0x00001324, 0x000012f7, 0x000012ca, 0x0000129f, 0x00001274, 0x0000124a, 0x00001220, 0x000011f8, 0x000011d0, 0x000011a8, 0x00001182, 0x0000115c, 0x00001136, 0x00001112, 0x000010ed, 0x000010ca, 0x000010a7, 0x00001085, 0x00001063, 0x00001042, 0x00001021, 0x00001000, 0x00000fe1, 0x00000fc1, 0x00000fa3, 0x00000f84, 0x00000f67, 0x00000f49, 0x00000f2c, 0x00000f10, 0x00000ef3, 0x00000ed8, 0x00000ebc, 0x00000ea1, 0x00000e87, 0x00000e6d, 0x00000e53, 0x00000e39, 0x00000e20, 0x00000e08, 0x00000def, 0x00000dd7, 0x00000dbf, 0x00000da8, 0x00000d91, 0x00000d7a, 0x00000d63, 0x00000d4d, 0x00000d37, 0x00000d21, 0x00000d0c, 0x00000cf7, 0x00000ce2, 0x00000ccd, 0x00000cb9, 0x00000ca5, 0x00000c91, 0x00000c7d, 0x00000c6a, 0x00000c57, 0x00000c44, 0x00000c31, 0x00000c1f, 0x00000c0d, 0x00000bfb, 0x00000be9, 0x00000bd7, 0x00000bc6, 0x00000bb4, 0x00000ba3, 0x00000b93, 0x00000b82, 0x00000b71, 0x00000b61, 0x00000b51, 0x00000b41, 0x00000b31, 0x00000b22, 0x00000b12, 0x00000b03, 0x00000af4, 0x00000ae5, 0x00000ad7, 0x00000ac8, 0x00000ab9, 0x00000aab, 0x00000a9d, 0x00000a8f, 0x00000a81, 0x00000a73, 0x00000a66, 0x00000a58, 0x00000a4b, 0x00000a3e, 0x00000a31, 0x00000a24, 0x00000a17, 0x00000a0b, 0x000009fe, 0x000009f2, 0x000009e5, 0x000009d9, 0x000009cd, 0x000009c1, 0x000009b5, 0x000009aa, 0x0000099e, 0x00000992, 0x00000987, 0x0000097c, 0x00000971, 0x00000965, 0x0000095b, 0x00000950, 0x00000945, 0x0000093a, 0x00000930, 0x00000925, 0x0000091b, 0x00000910, 0x00000906, 0x000008fc, 0x000008f2, 0x000008e8, 0x000008de, 0x000008d4, 0x000008cb, 0x000008c1, 0x000008b8, 0x000008ae, 0x000008a5, 0x0000089b, 0x00000892, 0x00000889, 0x00000880, 0x00000877, 0x0000086e, 0x00000865, 0x0000085c, 0x00000854, 0x0000084b, 0x00000843, 0x0000083a, 0x00000832, 0x00000829, 0x00000821, 0x00000819, 0x00000811, 0x00000809, 0x00000800, 0x000007f9 }; /* ------- * * Helpers * * ------- */ static SMOL_INLINE int check_row_range (const SmolScaleCtx *scale_ctx, int32_t *first_dest_row, int32_t *n_dest_rows) { if (*first_dest_row < 0) { *n_dest_rows += *first_dest_row; *first_dest_row = 0; } else if (*first_dest_row >= (int32_t) scale_ctx->vdim.dest_size_px) { return 0; } if (*n_dest_rows < 0 || *first_dest_row + *n_dest_rows > (int32_t) scale_ctx->vdim.dest_size_px) { *n_dest_rows = scale_ctx->vdim.dest_size_px - *first_dest_row; } else if (*n_dest_rows == 0) { return 0; } return 1; } /* ------------------- * * Scaling: Outer loop * * ------------------- */ static SMOL_INLINE const char * src_row_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t src_row_ofs) { return scale_ctx->src_pixels + scale_ctx->src_rowstride * src_row_ofs; } static SMOL_INLINE char * dest_row_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t dest_row_ofs) { return scale_ctx->dest_pixels + scale_ctx->dest_rowstride * dest_row_ofs; } static SMOL_INLINE void * dest_hofs_to_pointer (const SmolScaleCtx *scale_ctx, void *dest_row_ptr, uint32_t dest_hofs) { uint8_t *dest_row_ptr_u8 = dest_row_ptr; return dest_row_ptr_u8 + dest_hofs * pixel_type_meta [scale_ctx->dest_pixel_type].pixel_stride; } static void copy_row (const SmolScaleCtx *scale_ctx, uint32_t dest_row_index, uint32_t *row_out) { memcpy (row_out, src_row_ofs_to_pointer (scale_ctx, dest_row_index), scale_ctx->hdim.dest_size_px * pixel_type_meta [scale_ctx->dest_pixel_type].pixel_stride); } static void scale_dest_row (const SmolScaleCtx *scale_ctx, SmolLocalCtx *local_ctx, uint32_t dest_row_index, void *row_out) { if (dest_row_index < scale_ctx->vdim.clear_before_px || dest_row_index >= scale_ctx->vdim.dest_size_px - scale_ctx->vdim.clear_after_px) { /* Row doesn't intersect placement */ if (scale_ctx->composite_op == SMOL_COMPOSITE_SRC_CLEAR_DEST) { /* Clear entire row */ scale_ctx->clear_dest_func (scale_ctx->color_pixels_clear_batch, row_out, scale_ctx->hdim.dest_size_px); } } else { if (scale_ctx->composite_op == SMOL_COMPOSITE_SRC_CLEAR_DEST) { /* Clear left */ scale_ctx->clear_dest_func (scale_ctx->color_pixels_clear_batch, row_out, scale_ctx->hdim.clear_before_px); } if (scale_ctx->is_noop) { copy_row (scale_ctx, dest_row_index, row_out); } else { int scaled_row_index; scaled_row_index = scale_ctx->vfilter_func (scale_ctx, local_ctx, dest_row_index - scale_ctx->vdim.clear_before_px); if ((scale_ctx->composite_op == SMOL_COMPOSITE_SRC || scale_ctx->composite_op == SMOL_COMPOSITE_SRC_CLEAR_DEST) && scale_ctx->have_composite_color) { scale_ctx->composite_over_color_func (local_ctx->parts_row [scaled_row_index], scale_ctx->color_pixel, scale_ctx->hdim.placement_size_px); } scale_ctx->pack_row_func (local_ctx->parts_row [scaled_row_index], dest_hofs_to_pointer (scale_ctx, row_out, scale_ctx->hdim.placement_ofs_px), scale_ctx->hdim.placement_size_px); } if (scale_ctx->composite_op == SMOL_COMPOSITE_SRC_CLEAR_DEST) { /* Clear right */ scale_ctx->clear_dest_func (scale_ctx->color_pixels_clear_batch, dest_hofs_to_pointer (scale_ctx, row_out, scale_ctx->hdim.placement_ofs_px + scale_ctx->hdim.placement_size_px), scale_ctx->hdim.clear_after_px); } } if (scale_ctx->post_row_func) scale_ctx->post_row_func (row_out, scale_ctx->hdim.dest_size_px, scale_ctx->user_data); } static void do_rows (const SmolScaleCtx *scale_ctx, void *dest, uint32_t row_dest_index, uint32_t n_rows) { SmolLocalCtx local_ctx = { 0 }; uint32_t n_parts_per_pixel = 1; uint32_t n_stored_rows = 4; uint32_t i; if (scale_ctx->storage_type == SMOL_STORAGE_128BPP) n_parts_per_pixel = 2; /* Must be one less, or this test in update_local_ctx() will wrap around: * if (new_src_ofs == local_ctx->src_ofs + 1) { ... } */ local_ctx.src_ofs = UINT_MAX - 1; for (i = 0; i < n_stored_rows; i++) { /* Allocate space for an extra pixel at the rightmost edge. This pixel * allows bilinear horizontal sampling to exceed the input width and * produce transparency when the output is smaller than its whole-pixel * count. This is especially noticeable with halving, which can * produce 2^n such samples (the extra pixel is sampled repeatedly in * those cases). * * FIXME: This is no longer true, and the extra storage is probably not * needed. The edge transparency is now handled by applying a precalculated * opacity directly. We should verify that the extra storage can be * eliminated without overruns. */ local_ctx.parts_row [i] = smol_alloc_aligned (MAX (scale_ctx->hdim.src_size_px + 1, scale_ctx->hdim.placement_size_px) * n_parts_per_pixel * sizeof (uint64_t), &local_ctx.row_storage [i]); local_ctx.parts_row [i] [scale_ctx->hdim.src_size_px * n_parts_per_pixel] = 0; if (n_parts_per_pixel == 2) local_ctx.parts_row [i] [scale_ctx->hdim.src_size_px * n_parts_per_pixel + 1] = 0; } for (i = row_dest_index; i < row_dest_index + n_rows; i++) { scale_dest_row (scale_ctx, &local_ctx, i, dest); dest = (char *) dest + scale_ctx->dest_rowstride; } for (i = 0; i < n_stored_rows; i++) { smol_free (local_ctx.row_storage [i]); } /* Used to align row data if needed. May be allocated in scale_horizontal(). */ if (local_ctx.src_aligned) smol_free (local_ctx.src_aligned_storage); } /* -------------------- * * Architecture support * * -------------------- */ #ifdef SMOL_WITH_AVX2 static SmolBool have_avx2 (void) { __builtin_cpu_init (); if (__builtin_cpu_supports ("avx2")) return TRUE; return FALSE; } #endif 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) { if (host_is_little_endian ()) return pixel_type_u32_le [pixel_type]; return pixel_type; } /* ---------------------- * * Context initialization * * ---------------------- */ static void pick_filter_params (uint32_t src_dim, uint32_t src_dim_spx, int32_t dest_ofs_spx, uint32_t dest_dim, uint32_t dest_dim_spx, uint32_t *dest_halvings, uint32_t *dest_dim_prehalving, uint32_t *dest_dim_prehalving_spx, SmolFilterType *dest_filter, SmolStorageType *dest_storage, uint16_t *first_opacity, uint16_t *last_opacity, SmolFlags flags) { *dest_dim_prehalving = dest_dim; *dest_storage = (flags & SMOL_DISABLE_SRGB_LINEARIZATION) ? SMOL_STORAGE_64BPP : SMOL_STORAGE_128BPP; *first_opacity = SMOL_SUBPIXEL_MOD (-dest_ofs_spx - 1) + 1; *last_opacity = SMOL_SUBPIXEL_MOD (dest_ofs_spx + dest_dim_spx - 1) + 1; /* Special handling when the output is a single pixel */ if (dest_dim == 1) { *first_opacity = dest_dim_spx; *last_opacity = 256; } /* The box algorithms are only sufficiently precise when * src_dim > dest_dim * 5. box_64bpp typically starts outperforming * bilinear+halving at src_dim > dest_dim * 8. */ if (src_dim > dest_dim * 255) { *dest_storage = SMOL_STORAGE_128BPP; *dest_filter = SMOL_FILTER_BOX; } else if (src_dim > dest_dim * 8) { *dest_filter = SMOL_FILTER_BOX; } else if (src_dim <= 1) { *dest_filter = SMOL_FILTER_ONE; *last_opacity = ((dest_ofs_spx + dest_dim_spx - 1) % SMOL_SUBPIXEL_MUL) + 1; } else if ((dest_ofs_spx & 0xff) == 0 && src_dim_spx == dest_dim_spx) { *dest_filter = SMOL_FILTER_COPY; *first_opacity = 256; *last_opacity = 256; } else { uint32_t n_halvings = 0; uint32_t d = dest_dim_spx; for (;;) { d *= 2; if (d >= src_dim_spx) break; n_halvings++; } *dest_dim_prehalving = dest_dim << n_halvings; *dest_dim_prehalving_spx = dest_dim_spx << n_halvings; *dest_filter = SMOL_FILTER_BILINEAR_0H + n_halvings; *dest_halvings = n_halvings; } } static const SmolRepackMeta * find_repack_match (const SmolRepackMeta *meta, uint16_t sig, uint16_t mask) { sig &= mask; for (;; meta++) { if (!meta->repack_row_func) { meta = NULL; break; } if (sig == (meta->signature & mask)) break; } return meta; } static void do_reorder (const uint8_t *order_in, uint8_t *order_out, const uint8_t *reorder) { int i; for (i = 0; i < 4; i++) { uint8_t r = reorder [i]; uint8_t o; if (r == 0) { o = 0; } else { o = order_in [r - 1]; if (o == 0) o = i + 1; } order_out [i] = o; } } static void find_repacks (const SmolImplementation **implementations, SmolStorageType src_storage, SmolStorageType mid_storage, SmolStorageType dest_storage, SmolAlphaType src_alpha, SmolAlphaType mid_alpha, SmolAlphaType dest_alpha, SmolGammaType src_gamma, SmolGammaType mid_gamma, SmolGammaType dest_gamma, const SmolPixelTypeMeta *src_pmeta, const SmolPixelTypeMeta *dest_pmeta, const SmolRepackMeta **src_repack, const SmolRepackMeta **dest_repack) { int src_impl, dest_impl; const SmolRepackMeta *src_meta, *dest_meta = NULL; uint16_t src_to_mid_sig, mid_to_dest_sig; uint16_t sig_mask; int reorder_dest_alpha_ch; sig_mask = SMOL_REPACK_SIGNATURE_ANY_ORDER_MASK (1, 1, 1, 1, 1, 1); src_to_mid_sig = SMOL_MAKE_REPACK_SIGNATURE_ANY_ORDER (src_storage, src_alpha, src_gamma, mid_storage, mid_alpha, mid_gamma); mid_to_dest_sig = SMOL_MAKE_REPACK_SIGNATURE_ANY_ORDER (mid_storage, mid_alpha, mid_gamma, dest_storage, dest_alpha, dest_gamma); /* The initial conversion must always leave alpha in position #4, so further * processing knows where to find it. The order of the other channels * doesn't matter, as long as there's a repack chain that ultimately * produces the desired result. */ reorder_dest_alpha_ch = src_pmeta->order [0] == 4 ? 1 : 4; for (src_impl = 0; implementations [src_impl]; src_impl++) { src_meta = &implementations [src_impl]->repack_meta [0]; for (;; src_meta++) { uint8_t mid_order [4]; src_meta = find_repack_match (src_meta, src_to_mid_sig, sig_mask); if (!src_meta) break; if (reorder_meta [SMOL_REPACK_SIGNATURE_GET_REORDER (src_meta->signature)].dest [3] != reorder_dest_alpha_ch) continue; do_reorder (src_pmeta->order, mid_order, reorder_meta [SMOL_REPACK_SIGNATURE_GET_REORDER (src_meta->signature)].dest); for (dest_impl = 0; implementations [dest_impl]; dest_impl++) { dest_meta = &implementations [dest_impl]->repack_meta [0]; for (;; dest_meta++) { uint8_t dest_order [4]; dest_meta = find_repack_match (dest_meta, mid_to_dest_sig, sig_mask); if (!dest_meta) break; do_reorder (mid_order, dest_order, reorder_meta [SMOL_REPACK_SIGNATURE_GET_REORDER (dest_meta->signature)].dest); if (*((uint32_t *) dest_order) == *((uint32_t *) dest_pmeta->order)) { /* Success */ goto out; } } } } } out: if (src_repack) *src_repack = src_meta; if (dest_repack) *dest_repack = dest_meta; } static void populate_clear_batch (SmolScaleCtx *scale_ctx) { uint8_t dest_color [16]; int pixel_stride; int i; scale_ctx->pack_row_func (scale_ctx->color_pixel, dest_color, 1); pixel_stride = pixel_type_meta [scale_ctx->dest_pixel_type].pixel_stride; for (i = 0; i != SMOL_CLEAR_BATCH_SIZE; i += pixel_stride) { /* Must be an exact fit */ SMOL_ASSERT (i + pixel_stride <= SMOL_CLEAR_BATCH_SIZE); memcpy (scale_ctx->color_pixels_clear_batch + i, dest_color, pixel_stride); } } #define IMPLEMENTATION_MAX 8 /* scale_ctx->storage_type must be initialized first by pick_filter_params() */ static void get_implementations (SmolScaleCtx *scale_ctx, const void *color_pixel, SmolPixelType color_pixel_type) { SmolPixelType src_ptype, dest_ptype; const SmolPixelTypeMeta *src_pmeta, *dest_pmeta; const SmolRepackMeta *src_rmeta, *dest_rmeta; SmolAlphaType internal_alpha = SMOL_ALPHA_PREMUL8; const SmolImplementation *implementations [IMPLEMENTATION_MAX]; int i = 0; if (color_pixel) scale_ctx->have_composite_color = TRUE; /* Check for noop (direct copy) */ if (scale_ctx->hdim.src_size_spx == scale_ctx->hdim.dest_size_spx && scale_ctx->vdim.src_size_spx == scale_ctx->vdim.dest_size_spx && scale_ctx->src_pixel_type == scale_ctx->dest_pixel_type && scale_ctx->composite_op != SMOL_COMPOSITE_SRC_OVER_DEST) { /* The scaling and packing is a no-op, but we may still need to * clear dest, so allow the rest of the function to run so we get * the clear functions etc. */ scale_ctx->is_noop = TRUE; } /* Enumerate implementations, preferred first */ if (!(scale_ctx->flags & SMOL_DISABLE_ACCELERATION)) { #ifdef SMOL_WITH_AVX2 if (have_avx2 ()) implementations [i++] = _smol_get_avx2_implementation (); #endif } implementations [i++] = _smol_get_generic_implementation (); implementations [i] = NULL; /* Install repackers */ src_ptype = get_host_pixel_type (scale_ctx->src_pixel_type); dest_ptype = get_host_pixel_type (scale_ctx->dest_pixel_type); src_pmeta = &pixel_type_meta [src_ptype]; dest_pmeta = &pixel_type_meta [dest_ptype]; if (src_pmeta->alpha == SMOL_ALPHA_UNASSOCIATED && dest_pmeta->alpha == SMOL_ALPHA_UNASSOCIATED) { /* In order to preserve the color range in transparent pixels when going * from unassociated to unassociated, we use 16 bits per channel internally. */ internal_alpha = SMOL_ALPHA_PREMUL16; scale_ctx->storage_type = SMOL_STORAGE_128BPP; } if (scale_ctx->hdim.src_size_px > scale_ctx->hdim.dest_size_px * 8191 || scale_ctx->vdim.src_size_px > scale_ctx->vdim.dest_size_px * 8191) { /* Even with 128bpp, there's only enough bits to store 11-bit linearized * times 13 bits of summed pixels plus 8 bits of scratch space for * multiplying with an 8-bit weight -> 32 bits total per channel. * * For now, just turn off sRGB linearization if the input is bigger * than the output by a factor of 2^13 or more. */ scale_ctx->gamma_type = SMOL_GAMMA_SRGB_COMPRESSED; } find_repacks (implementations, src_pmeta->storage, scale_ctx->storage_type, dest_pmeta->storage, src_pmeta->alpha, internal_alpha, dest_pmeta->alpha, SMOL_GAMMA_SRGB_COMPRESSED, scale_ctx->gamma_type, SMOL_GAMMA_SRGB_COMPRESSED, src_pmeta, dest_pmeta, &src_rmeta, &dest_rmeta); SMOL_ASSERT (src_rmeta != NULL); SMOL_ASSERT (dest_rmeta != NULL); scale_ctx->src_unpack_row_func = src_rmeta->repack_row_func; scale_ctx->pack_row_func = dest_rmeta->repack_row_func; if (scale_ctx->composite_op == SMOL_COMPOSITE_SRC_OVER_DEST) { const SmolRepackMeta *dest_unpack_rmeta; /* Need to unpack destination rows and composite on them */ find_repacks (implementations, dest_pmeta->storage, scale_ctx->storage_type, dest_pmeta->storage, dest_pmeta->alpha, internal_alpha, dest_pmeta->alpha, SMOL_GAMMA_SRGB_COMPRESSED, scale_ctx->gamma_type, SMOL_GAMMA_SRGB_COMPRESSED, dest_pmeta, dest_pmeta, &dest_unpack_rmeta, NULL); SMOL_ASSERT (dest_unpack_rmeta != NULL); scale_ctx->dest_unpack_row_func = dest_unpack_rmeta->repack_row_func; } else { /* Compositing on solid color */ if (color_pixel) { SmolPixelType color_ptype; const SmolPixelTypeMeta *color_pmeta; const SmolRepackMeta *color_rmeta; color_ptype = get_host_pixel_type (color_pixel_type); color_pmeta = &pixel_type_meta [color_ptype]; find_repacks (implementations, color_pmeta->storage, scale_ctx->storage_type, dest_pmeta->storage, color_pmeta->alpha, internal_alpha, dest_pmeta->alpha, SMOL_GAMMA_SRGB_COMPRESSED, scale_ctx->gamma_type, SMOL_GAMMA_SRGB_COMPRESSED, color_pmeta, dest_pmeta, &color_rmeta, NULL); SMOL_ASSERT (color_rmeta != NULL); color_rmeta->repack_row_func (color_pixel, scale_ctx->color_pixel, 1); } else { /* No color provided; use fully transparent black */ memset (scale_ctx->color_pixel, 0, sizeof (scale_ctx->color_pixel)); } populate_clear_batch (scale_ctx); } /* Install filters and compositors */ scale_ctx->hfilter_func = NULL; scale_ctx->vfilter_func = NULL; scale_ctx->composite_over_color_func = NULL; scale_ctx->composite_over_dest_func = NULL; scale_ctx->clear_dest_func = NULL; for (i = 0; implementations [i]; i++) { SmolHFilterFunc *hfilter_func = implementations [i]->hfilter_funcs [scale_ctx->storage_type] [scale_ctx->hdim.filter_type]; SmolVFilterFunc *vfilter_func = implementations [i]->vfilter_funcs [scale_ctx->storage_type] [scale_ctx->vdim.filter_type]; SmolCompositeOverColorFunc *composite_over_color_func = implementations [i]->composite_over_color_funcs [scale_ctx->storage_type]; SmolCompositeOverDestFunc *composite_over_dest_func = implementations [i]->composite_over_dest_funcs [scale_ctx->storage_type]; SmolClearFunc *clear_dest_func = implementations [i]->clear_funcs [dest_pmeta->storage]; if (!scale_ctx->hfilter_func && hfilter_func) { scale_ctx->hfilter_func = hfilter_func; if (implementations [i]->init_h_func) implementations [i]->init_h_func (scale_ctx); } if (!scale_ctx->vfilter_func && vfilter_func) { scale_ctx->vfilter_func = vfilter_func; if (implementations [i]->init_v_func) implementations [i]->init_v_func (scale_ctx); } if (!scale_ctx->composite_over_color_func && composite_over_color_func) scale_ctx->composite_over_color_func = composite_over_color_func; if (!scale_ctx->composite_over_dest_func && composite_over_dest_func) scale_ctx->composite_over_dest_func = composite_over_dest_func; if (!scale_ctx->clear_dest_func && clear_dest_func) scale_ctx->clear_dest_func = clear_dest_func; } SMOL_ASSERT (scale_ctx->hfilter_func != NULL); SMOL_ASSERT (scale_ctx->vfilter_func != NULL); } static void init_dim (SmolDim *dim, uint32_t src_size_spx, uint32_t dest_size_spx, int32_t placement_ofs_spx, int32_t placement_size_spx, SmolFlags flags, SmolStorageType *storage_type_out) { dim->src_size_spx = src_size_spx; dim->src_size_px = SMOL_SPX_TO_PX (src_size_spx); dim->dest_size_spx = dest_size_spx; dim->dest_size_px = SMOL_SPX_TO_PX (dest_size_spx); dim->placement_ofs_spx = placement_ofs_spx; if (placement_ofs_spx < 0) dim->placement_ofs_px = (placement_ofs_spx - 255) / SMOL_SUBPIXEL_MUL; else dim->placement_ofs_px = placement_ofs_spx / SMOL_SUBPIXEL_MUL; dim->placement_size_spx = placement_size_spx; dim->placement_size_px = SMOL_SPX_TO_PX (placement_size_spx + SMOL_SUBPIXEL_MOD (placement_ofs_spx)); pick_filter_params (dim->src_size_px, dim->src_size_spx, dim->placement_ofs_spx, dim->placement_size_px, dim->placement_size_spx, &dim->n_halvings, &dim->placement_size_prehalving_px, &dim->placement_size_prehalving_spx, &dim->filter_type, storage_type_out, &dim->first_opacity, &dim->last_opacity, flags); /* Calculate clip and clear intervals */ if (dim->placement_ofs_px > 0) { dim->clear_before_px = dim->placement_ofs_px; dim->clip_before_px = 0; } else if (dim->placement_ofs_px < 0) { dim->clear_before_px = 0; dim->clip_before_px = -dim->placement_ofs_px; dim->first_opacity = 256; } if (dim->placement_ofs_px + dim->placement_size_px < dim->dest_size_px) { dim->clear_after_px = dim->dest_size_px - dim->placement_ofs_px - dim->placement_size_px; dim->clip_after_px = 0; } else if (dim->placement_ofs_px + dim->placement_size_px > dim->dest_size_px) { dim->clear_after_px = 0; dim->clip_after_px = dim->placement_ofs_px + dim->placement_size_px - dim->dest_size_px; dim->last_opacity = 256; } /* Clamp placement */ if (dim->placement_ofs_px < 0) { dim->placement_size_px += dim->placement_ofs_px; dim->placement_ofs_px = 0; } if (dim->placement_ofs_px + dim->placement_size_px > dim->dest_size_px) { dim->placement_size_px = dim->dest_size_px - dim->placement_ofs_px; } } static void smol_scale_init (SmolScaleCtx *scale_ctx, const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width_spx, uint32_t src_height_spx, uint32_t src_rowstride, const void *color_pixel, SmolPixelType color_pixel_type, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width_spx, uint32_t dest_height_spx, uint32_t dest_rowstride, int32_t placement_x_spx, int32_t placement_y_spx, int32_t placement_width_spx, int32_t placement_height_spx, SmolCompositeOp composite_op, SmolFlags flags, SmolPostRowFunc post_row_func, void *user_data) { SmolStorageType storage_type [2]; if (placement_width_spx <= 0 || placement_height_spx <= 0) { placement_width_spx = 0; placement_height_spx = 0; placement_x_spx = 0; placement_y_spx = 0; } scale_ctx->src_pixels = src_pixels; scale_ctx->src_pixel_type = src_pixel_type; scale_ctx->src_rowstride = src_rowstride; scale_ctx->dest_pixels = dest_pixels; scale_ctx->dest_pixel_type = dest_pixel_type; scale_ctx->dest_rowstride = dest_rowstride; scale_ctx->composite_op = composite_op; scale_ctx->flags = flags; scale_ctx->gamma_type = (flags & SMOL_DISABLE_SRGB_LINEARIZATION) ? SMOL_GAMMA_SRGB_COMPRESSED : SMOL_GAMMA_SRGB_LINEAR; scale_ctx->post_row_func = post_row_func; scale_ctx->user_data = user_data; init_dim (&scale_ctx->hdim, src_width_spx, dest_width_spx, placement_x_spx, placement_width_spx, flags, &storage_type [0]); init_dim (&scale_ctx->vdim, src_height_spx, dest_height_spx, placement_y_spx, placement_height_spx, flags, &storage_type [1]); scale_ctx->storage_type = MAX (storage_type [0], storage_type [1]); scale_ctx->hdim.precalc = smol_alloc_aligned (((scale_ctx->hdim.placement_size_prehalving_px + 1) * 2 + (scale_ctx->vdim.placement_size_prehalving_px + 1) * 2) * sizeof (uint16_t), &scale_ctx->precalc_storage); scale_ctx->vdim.precalc = ((uint16_t *) scale_ctx->hdim.precalc) + (scale_ctx->hdim.placement_size_prehalving_px + 1) * 2; get_implementations (scale_ctx, color_pixel, color_pixel_type); } static void smol_scale_finalize (SmolScaleCtx *scale_ctx) { free (scale_ctx->precalc_storage); } /* ---------- * * Public API * * ---------- */ SmolScaleCtx * smol_scale_new_simple (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, SmolFlags flags) { SmolScaleCtx *scale_ctx; scale_ctx = calloc (sizeof (SmolScaleCtx), 1); smol_scale_init (scale_ctx, src_pixels, src_pixel_type, SMOL_PX_TO_SPX (src_width), SMOL_PX_TO_SPX (src_height), src_rowstride, NULL, 0, dest_pixels, dest_pixel_type, SMOL_PX_TO_SPX (dest_width), SMOL_PX_TO_SPX (dest_height), dest_rowstride, 0, 0, SMOL_PX_TO_SPX (dest_width), SMOL_PX_TO_SPX (dest_height), SMOL_COMPOSITE_SRC, flags, NULL, NULL); return scale_ctx; } void smol_scale_simple (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, SmolFlags flags) { SmolScaleCtx scale_ctx = { 0 }; int first_row, n_rows; smol_scale_init (&scale_ctx, src_pixels, src_pixel_type, SMOL_PX_TO_SPX (src_width), SMOL_PX_TO_SPX (src_height), src_rowstride, NULL, 0, dest_pixels, dest_pixel_type, SMOL_PX_TO_SPX (dest_width), SMOL_PX_TO_SPX (dest_height), dest_rowstride, 0, 0, SMOL_PX_TO_SPX (dest_width), SMOL_PX_TO_SPX (dest_height), SMOL_COMPOSITE_SRC, flags, NULL, NULL); first_row = 0; n_rows = scale_ctx.vdim.dest_size_px; if (check_row_range (&scale_ctx, &first_row, &n_rows)) { do_rows (&scale_ctx, dest_row_ofs_to_pointer (&scale_ctx, 0), first_row, n_rows); } smol_scale_finalize (&scale_ctx); } SmolScaleCtx * smol_scale_new_full (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, const void *color_pixel, SmolPixelType color_pixel_type, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, int32_t placement_x, int32_t placement_y, uint32_t placement_width, uint32_t placement_height, SmolCompositeOp composite_op, SmolFlags flags, SmolPostRowFunc post_row_func, void *user_data) { SmolScaleCtx *scale_ctx; scale_ctx = calloc (sizeof (SmolScaleCtx), 1); smol_scale_init (scale_ctx, src_pixels, src_pixel_type, SMOL_PX_TO_SPX (src_width), SMOL_PX_TO_SPX (src_height), src_rowstride, color_pixel, color_pixel_type, dest_pixels, dest_pixel_type, SMOL_PX_TO_SPX (dest_width), SMOL_PX_TO_SPX (dest_height), dest_rowstride, placement_x, placement_y, placement_width, placement_height, composite_op, flags, 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_batch (const SmolScaleCtx *scale_ctx, int32_t first_dest_row, int32_t n_dest_rows) { if (!check_row_range (scale_ctx, &first_dest_row, &n_dest_rows)) return; do_rows (scale_ctx, dest_row_ofs_to_pointer (scale_ctx, first_dest_row), first_dest_row, n_dest_rows); } void smol_scale_batch_full (const SmolScaleCtx *scale_ctx, void *dest, int32_t first_dest_row, int32_t n_dest_rows) { if (!check_row_range (scale_ctx, &first_dest_row, &n_dest_rows)) return; do_rows (scale_ctx, dest, first_dest_row, n_dest_rows); } chafa-1.14.5/chafa/internal/smolscale/smolscale.h000066400000000000000000000117011471154763100216500ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2024 Hans Petter Jansson. See COPYING for details. */ #include #ifndef _SMOLSCALE_H_ #define _SMOLSCALE_H_ #ifdef __cplusplus extern "C" { #endif #define SMOL_SUBPIXEL_SHIFT 8 #define SMOL_SUBPIXEL_MUL (1 << (SMOL_SUBPIXEL_SHIFT)) /* Applies modulo twice, yielding a positive fraction for negative offsets */ #define SMOL_SUBPIXEL_MOD(n) ((((n) % SMOL_SUBPIXEL_MUL) + SMOL_SUBPIXEL_MUL) % SMOL_SUBPIXEL_MUL) #define SMOL_PX_TO_SPX(px) ((px) * (SMOL_SUBPIXEL_MUL)) #define SMOL_SPX_TO_PX(spx) (((spx) + (SMOL_SUBPIXEL_MUL) - 1) / (SMOL_SUBPIXEL_MUL)) typedef enum { SMOL_NO_FLAGS = 0, SMOL_DISABLE_ACCELERATION = (1 << 0), SMOL_DISABLE_SRGB_LINEARIZATION = (1 << 1) } SmolFlags; 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 enum { SMOL_COMPOSITE_SRC, SMOL_COMPOSITE_SRC_CLEAR_DEST, SMOL_COMPOSITE_SRC_OVER_DEST } SmolCompositeOp; typedef void (SmolPostRowFunc) (void *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 (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, SmolFlags flags); /* Batch API: Allows scaling a few rows at a time. Suitable for multithreading. */ SmolScaleCtx *smol_scale_new_simple (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, SmolFlags flags); SmolScaleCtx *smol_scale_new_full (const void *src_pixels, SmolPixelType src_pixel_type, uint32_t src_width, uint32_t src_height, uint32_t src_rowstride, const void *color_pixel, SmolPixelType color_pixel_type, void *dest_pixels, SmolPixelType dest_pixel_type, uint32_t dest_width, uint32_t dest_height, uint32_t dest_rowstride, int32_t placement_x, int32_t placement_y, uint32_t placement_width, uint32_t placement_height, SmolCompositeOp composite_op, SmolFlags flags, 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, int32_t first_outrow, int32_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, int32_t first_outrow, int32_t n_outrows); #ifdef __cplusplus } #endif #endif chafa-1.14.5/configure.ac000066400000000000000000000501701471154763100151460ustar00rootroot00000000000000dnl ---------------------------- dnl Automake/autoconf input file dnl ---------------------------- dnl --- Package configuration --- m4_define([chafa_major_version], [1]) m4_define([chafa_minor_version], [14]) m4_define([chafa_micro_version], [5]) 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]) AC_CONFIG_HEADERS(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="-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26 \ -Wall -Wextra -Wmissing-prototypes -Wwrite-strings -Wunused-macros -Wundef \ -Wpointer-arith -Werror=format-security" # May want to look into -Wconversion sometime. For now, it's just too much noise. AC_USE_SYSTEM_EXTENSIONS AM_SANITY_CHECK AM_MAINTAINER_MODE AC_C_CONST AC_PROG_CC AC_PROG_CPP AC_PROG_INSTALL AM_PROG_AR # We keep this obsolete macro around to allow configuration on older systems # that require -std=c99, cf. CentOS 7. See github#113. AC_PROG_CC_STDC LT_INIT([win32-dll]) dnl --- Check for extra compiler warnings --- AX_CHECK_COMPILE_FLAG([-Wstack-usage=131072],[BASE_CFLAGS="$BASE_CFLAGS -Wstack-usage=131072"],,[-Werror]) AX_CHECK_COMPILE_FLAG([-Wfor-loop-analysis],[BASE_CFLAGS="$BASE_CFLAGS -Wfor-loop-analysis"],,[-Werror]) AX_CHECK_COMPILE_FLAG([-Wlogical-op],[BASE_CFLAGS="$BASE_CFLAGS -Wlogical-op"],,[-Werror]) AX_CHECK_COMPILE_FLAG([-Wlogical-op-parentheses],[BASE_CFLAGS="$BASE_CFLAGS -Wlogical-op-parentheses"],,[-Werror]) 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.26) AC_ARG_WITH(tools, [AS_HELP_STRING([--without-tools], [don't build command-line tools [default=on]])], , with_tools=yes) AS_IF([test "$with_tools" != no], [ dnl FreeType (required) 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.])]) dnl libavif (optional) AC_ARG_WITH(avif, [AS_HELP_STRING([--without-avif], [don't build AVIF loader [default=on]])], , with_avif=yes) AS_IF([test "$with_avif" != no], [ PKG_CHECK_MODULES(AVIF, [libavif],, missing_rpms="$missing_rpms libavif-devel" missing_debs="$missing_debs libavif-dev" with_avif=no)]) AS_IF([test "$with_avif" != no], [AC_DEFINE([HAVE_AVIF], [1], [Define if we have AVIF support.])]) dnl libjpeg (optional) AC_ARG_WITH(jpeg, [AS_HELP_STRING([--without-jpeg], [don't build JPEG loader [default=on]])], , with_jpeg=yes) AS_IF([test "$with_jpeg" != no], [PKG_CHECK_MODULES(JPEG, [libjpeg],, missing_rpms="$missing_rpms libjpeg-devel" missing_debs="$missing_debs libjpeg-dev" with_jpeg=no)]) AS_IF([test "$with_jpeg" != no], [AC_DEFINE([HAVE_JPEG], [1], [Define if we have JPEG support.])]) dnl librsvg (optional) AC_ARG_WITH(svg, [AS_HELP_STRING([--without-svg], [don't build SVG loader [default=on]])], , with_svg=yes) AS_IF([test "$with_svg" != no], [PKG_CHECK_MODULES(SVG, [librsvg-2.0],, missing_rpms="$missing_rpms librsvg-devel" missing_debs="$missing_debs librsvg2-dev" with_svg=no)]) AS_IF([test "$with_svg" != no], [AC_DEFINE([HAVE_SVG], [1], [Define if we have SVG support.])]) dnl libtiff (optional) AC_ARG_WITH(tiff, [AS_HELP_STRING([--without-tiff], [don't build TIFF loader [default=on]])], , with_tiff=yes) AS_IF([test "$with_tiff" != no], [PKG_CHECK_MODULES(TIFF, [libtiff-4],, missing_rpms="$missing_rpms libtiff-devel" missing_debs="$missing_debs libtiff-dev" with_tiff=no)]) AS_IF([test "$with_tiff" != no], [AC_DEFINE([HAVE_TIFF], [1], [Define if we have TIFF support.])]) dnl libwebp (optional) AC_ARG_WITH(webp, [AS_HELP_STRING([--without-webp], [don't build WebP loader [default=on]])], , with_webp=yes) AS_IF([test "$with_webp" != no], [ PKG_CHECK_MODULES(WEBP, [libwebp libwebpdemux],, missing_rpms="$missing_rpms libwebp-devel" missing_debs="$missing_debs libwebp-dev" with_webp=no)]) AS_IF([test "$with_webp" != no], [AC_DEFINE([HAVE_WEBP], [1], [Define if we have WebP support.])]) dnl libjxl (optional) AC_ARG_WITH(jxl, [AS_HELP_STRING([--without-jxl], [don't build JXL loader [default=on]])], , with_jxl=yes) AS_IF([test "$with_jxl" != no], [ PKG_CHECK_MODULES(JXL, [libjxl libjxl_threads],, missing_rpms="$missing_rpms libjxl-devel" missing_debs="$missing_debs libjxl-dev" with_jxl=no)]) AS_IF([test "$with_jxl" != no], [AC_DEFINE([HAVE_JXL], [1], [Define if we have JXL support.])]) ]) AM_CONDITIONAL([WANT_TOOLS], [test "$with_tools" != no]) AM_CONDITIONAL([HAVE_JPEG], [test "$with_tools" != no -a "$with_jpeg" != no]) AM_CONDITIONAL([HAVE_SVG], [test "$with_tools" != no -a "$with_svg" != no]) AM_CONDITIONAL([HAVE_TIFF], [test "$with_tools" != no -a "$with_tiff" != no]) AM_CONDITIONAL([HAVE_WEBP], [test "$with_tools" != no -a "$with_webp" != no]) AM_CONDITIONAL([HAVE_AVIF], [test "$with_tools" != no -a "$with_avif" != no]) AM_CONDITIONAL([HAVE_JXL], [test "$with_tools" != no -a "$with_jxl" != no]) # 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(ctermid getrandom mmap sigaction) AC_CHECK_HEADERS(sys/ioctl.h termios.h windows.h) dnl dnl Define IS_WIN32_BUILD if we're building for Microsoft Windows. In order to dnl get UTF-8 support in command-line arguments and environment vars, we need dnl to build a resource file with windres and link it in. dnl is_windows_build=no case "${host_os}" in cygwin*|mingw*) is_windows_build=yes ;; esac AM_CONDITIONAL([IS_WIN32_BUILD], [test "x$is_windows_build" = "xyes"]) AC_CHECK_TOOL([WINDRES], [windres], [:]) 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 ]], [[__m64 t [2] = { 0 }; t [0] = _mm_setzero_si64 ();]])], [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]) CHAFA_VISIBILITY_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 --- Set compiler flags --- dnl Disable some LodePNG features. In particular, the CRC feature, which would dnl sometimes cause valid images to not load because of issues in the dnl encoding software. LODEPNG_FEATURES="\ -DLODEPNG_NO_COMPILE_ENCODER \ -DLODEPNG_NO_COMPILE_DISK \ -DLODEPNG_NO_COMPILE_CPP \ -DLODEPNG_NO_COMPILE_CRC" LODEPNG_CFLAGS="$BASE_CFLAGS $LODEPNG_FEATURES" LODEPNG_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" LIBNSGIF_CFLAGS="$BASE_CFLAGS" LIBNSGIF_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" LIBCHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS" LIBCHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" CHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS $LODEPNG_FEATURES" CHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" AC_SUBST(LODEPNG_CFLAGS) AC_SUBST(LODEPNG_LDFLAGS) AC_SUBST(LIBNSGIF_CFLAGS) AC_SUBST(LIBNSGIF_LDFLAGS) 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 lodepng/Makefile chafa.pc docs/Makefile docs/version.xml tests/Makefile tests/data/Makefile tests/data/bad/Makefile tests/data/good/Makefile tools/Makefile tools/chafa/Makefile tools/completions/Makefile tools/fontgen/Makefile]) AC_OUTPUT dnl --- Print a neatly colorized summary --- colorize_vars=" enable_man ac_cv_mmx_intrinsics ac_cv_sse41_intrinsics ac_cv_avx2_intrinsics ac_cv_popcnt32_intrinsics ac_cv_popcnt64_intrinsics with_tools with_jpeg with_svg with_tiff with_webp with_jxl with_avif " dnl Only use colors if the terminal supports the aixterm-style bright ones (16 total). cols=$(tput colors 2>/dev/null) if test ${cols:--1} -ge 16; then normal=$(tput sgr0) red=$(tput setaf 9) green=$(tput setaf 10) yellow=$(tput setaf 11) blue=$(tput setaf 12) pyes=${green}yes${normal} pno=${red}no${normal} pyno=${yellow}no${normal} else normal= red= green= yellow= blue= pyes=yes pno=no pyno=no fi dnl Gross. At least make sure eval arguments are sanitized. for i in $colorize_vars; do eval state=\$$i if test x$state != xno; then state=yes; fi eval p$i=\$p$state done dnl gtk-doc needs special handling; since docs come pregenerated in the tarball, "no" dnl here is less critical, so use a different color. Also, enable_gtk_doc can be empty. if test x$enable_gtk_doc = xyes; then penable_gtk_doc=$pyes else penable_gtk_doc=$pyno fi echo >&AS_MESSAGE_FD $normal echo >&AS_MESSAGE_FD "Build man page .............. $penable_man" echo >&AS_MESSAGE_FD "Rebuild API documentation ... $penable_gtk_doc (--enable-gtk-doc)" echo >&AS_MESSAGE_FD "Support MMX ................. $pac_cv_mmx_intrinsics" echo >&AS_MESSAGE_FD "Support SSE 4.1 ............. $pac_cv_sse41_intrinsics" echo >&AS_MESSAGE_FD "Support AVX2 ................ $pac_cv_avx2_intrinsics" echo >&AS_MESSAGE_FD "Support popcount32 .......... $pac_cv_popcnt32_intrinsics" echo >&AS_MESSAGE_FD "Support popcount64 .......... $pac_cv_popcnt64_intrinsics" echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "Build command-line tool ..... $pwith_tools" if test "x$with_tools" != xno; then echo >&AS_MESSAGE_FD "With AVIF loader ............ $pwith_avif" echo >&AS_MESSAGE_FD "With GIF loader ............. $pyes (internal)" echo >&AS_MESSAGE_FD "With JPEG loader ............ $pwith_jpeg" echo >&AS_MESSAGE_FD "With PNG loader ............. $pyes (internal)" echo >&AS_MESSAGE_FD "With QOI loader ............. $pyes (internal)" echo >&AS_MESSAGE_FD "With SVG loader ............. $pwith_svg" echo >&AS_MESSAGE_FD "With TIFF loader ............ $pwith_tiff" echo >&AS_MESSAGE_FD "With WebP loader ............ $pwith_webp" echo >&AS_MESSAGE_FD "With JXL loader ............. $pwith_jxl" echo >&AS_MESSAGE_FD "With XWD loader ............. $pyes (internal)" fi echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "Install prefix .............. $blue$prefix$normal" echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "You can now type ${blue}gmake${normal} or ${blue}make${normal} to build the project." dnl --- Warn about missing dependencies --- dnl Remove leading spaces. missing_rpms=$(echo $missing_rpms | sed 's/^ *//') missing_debs=$(echo $missing_debs | sed 's/^ *//') if test "x$missing_rpms" != x; then echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "Some optional libraries were not found. You may want to install these and" echo >&AS_MESSAGE_FD "run configure again (package names may be different on your system)." echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "On Fedora, openSUSE or similar:" echo >&AS_MESSAGE_FD "${blue}${missing_rpms}${normal}" echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "On Debian, Ubuntu or similar:" echo >&AS_MESSAGE_FD "${blue}${missing_debs}${normal}" echo >&AS_MESSAGE_FD fi chafa-1.14.5/docs/000077500000000000000000000000001471154763100136055ustar00rootroot00000000000000chafa-1.14.5/docs/Makefile.am000066400000000000000000000050511471154763100156420ustar00rootroot00000000000000## 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.14.5/docs/building.xml000066400000000000000000000111311471154763100161210ustar00rootroot00000000000000 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 the FreeType library and its development files. chafa has built-in support for the GIF, PNG, QOI and XWD formats, and can be built with optional support for many others, including AVIF, JPEG, SVG, TIFF and WebP. ./configure will summarize the build features. 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.14.5/docs/chafa-docs.xml000066400000000000000000000055651471154763100163320ustar00rootroot00000000000000 %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 Index of new API in version 1.2 Index of new API in version 1.4 Index of new API in version 1.6 Index of new API in version 1.8 Index of new API in version 1.10 Index of new API in version 1.12 Index of new API in version 1.14 Index of deprecated API chafa-1.14.5/docs/chafa-logo.gif000066400000000000000000037150611471154763100163110ustar00rootroot00000000000000GIF89a6TwDq 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_set_placement chafa_canvas_draw_all_pixels chafa_canvas_print chafa_canvas_print_rows chafa_canvas_print_rows_strv 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 ChafaPassthrough 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_canvas_config_get_passthrough chafa_canvas_config_set_passthrough
chafa-features ChafaFeatures chafa_get_builtin_features chafa_get_supported_features chafa_describe_features chafa_get_n_threads chafa_set_n_threads chafa_get_n_actual_threads
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_get_glyph 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 CHAFA_VERSION_1_10 CHAFA_VERSION_1_12 CHAFA_VERSION_1_14 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_emit_seq chafa_term_info_parse_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_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_enable_bold chafa_term_info_emit_invert_colors chafa_term_info_emit_set_color_bg_8 chafa_term_info_emit_set_color_fg_8 chafa_term_info_emit_set_color_fgbg_8 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_reset_color_fg chafa_term_info_emit_reset_color_bg chafa_term_info_emit_reset_color_fgbg chafa_term_info_emit_set_default_fg chafa_term_info_emit_set_default_bg chafa_term_info_emit_reset_default_fg chafa_term_info_emit_reset_default_bg chafa_term_info_emit_query_default_fg chafa_term_info_emit_query_default_bg chafa_term_info_emit_repeat_char chafa_term_info_emit_set_scrolling_rows chafa_term_info_emit_reset_scrolling_rows chafa_term_info_emit_save_cursor_pos chafa_term_info_emit_restore_cursor_pos chafa_term_info_emit_begin_sixels chafa_term_info_emit_end_sixels chafa_term_info_emit_enable_sixel_scrolling chafa_term_info_emit_disable_sixel_scrolling chafa_term_info_emit_set_sixel_advance_down chafa_term_info_emit_set_sixel_advance_right chafa_term_info_emit_begin_kitty_immediate_image_v1 chafa_term_info_emit_begin_kitty_immediate_virt_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_info_emit_begin_screen_passthrough chafa_term_info_emit_end_screen_passthrough chafa_term_info_emit_enable_alt_screen chafa_term_info_emit_disable_alt_screen chafa_term_info_emit_begin_tmux_passthrough chafa_term_info_emit_end_tmux_passthrough chafa_term_info_emit_return_key chafa_term_info_emit_backspace_key chafa_term_info_emit_delete_key chafa_term_info_emit_delete_ctrl_key chafa_term_info_emit_delete_shift_key chafa_term_info_emit_insert_key chafa_term_info_emit_insert_ctrl_key chafa_term_info_emit_insert_shift_key chafa_term_info_emit_home_key chafa_term_info_emit_home_ctrl_key chafa_term_info_emit_home_shift_key chafa_term_info_emit_end_key chafa_term_info_emit_end_ctrl_key chafa_term_info_emit_end_shift_key chafa_term_info_emit_up_key chafa_term_info_emit_up_ctrl_key chafa_term_info_emit_up_shift_key chafa_term_info_emit_down_key chafa_term_info_emit_down_ctrl_key chafa_term_info_emit_down_shift_key chafa_term_info_emit_left_key chafa_term_info_emit_left_ctrl_key chafa_term_info_emit_left_shift_key chafa_term_info_emit_right_key chafa_term_info_emit_right_ctrl_key chafa_term_info_emit_right_shift_key chafa_term_info_emit_page_up_key chafa_term_info_emit_page_up_ctrl_key chafa_term_info_emit_page_up_shift_key chafa_term_info_emit_page_down_key chafa_term_info_emit_page_down_ctrl_key chafa_term_info_emit_page_down_shift_key chafa_term_info_emit_tab_key chafa_term_info_emit_tab_shift_key chafa_term_info_emit_f1_key chafa_term_info_emit_f1_ctrl_key chafa_term_info_emit_f1_shift_key chafa_term_info_emit_f2_key chafa_term_info_emit_f2_ctrl_key chafa_term_info_emit_f2_shift_key chafa_term_info_emit_f3_key chafa_term_info_emit_f3_ctrl_key chafa_term_info_emit_f3_shift_key chafa_term_info_emit_f4_key chafa_term_info_emit_f4_ctrl_key chafa_term_info_emit_f4_shift_key chafa_term_info_emit_f5_key chafa_term_info_emit_f5_ctrl_key chafa_term_info_emit_f5_shift_key chafa_term_info_emit_f6_key chafa_term_info_emit_f6_ctrl_key chafa_term_info_emit_f6_shift_key chafa_term_info_emit_f7_key chafa_term_info_emit_f7_ctrl_key chafa_term_info_emit_f7_shift_key chafa_term_info_emit_f8_key chafa_term_info_emit_f8_ctrl_key chafa_term_info_emit_f8_shift_key chafa_term_info_emit_f9_key chafa_term_info_emit_f9_ctrl_key chafa_term_info_emit_f9_shift_key chafa_term_info_emit_f10_key chafa_term_info_emit_f10_ctrl_key chafa_term_info_emit_f10_shift_key chafa_term_info_emit_f11_key chafa_term_info_emit_f11_ctrl_key chafa_term_info_emit_f11_shift_key chafa_term_info_emit_f12_key chafa_term_info_emit_f12_ctrl_key chafa_term_info_emit_f12_shift_key
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-placement ChafaTuck ChafaAlign ChafaPlacement chafa_placement_new chafa_placement_ref chafa_placement_unref chafa_placement_get_tuck chafa_placement_set_tuck chafa_placement_get_halign chafa_placement_set_halign chafa_placement_get_valign chafa_placement_set_valign
chafa-image ChafaImage chafa_image_new chafa_image_ref chafa_image_unref chafa_image_set_frame
chafa-frame ChafaFrame chafa_frame_new chafa_frame_new_borrow chafa_frame_new_steal chafa_frame_ref chafa_frame_unref
chafa-1.14.5/docs/chafa.xml000066400000000000000000000467051471154763100154050ustar00rootroot00000000000000 chafa chafa Developer Hans Petter Jansson chafa 1 User Commands chafa Character art facsimile generator chafaOPTIONIMAGE Description chafa is a command-line utility that converts image data, including animated GIFs, into graphics formats or ANSI/Unicode character art suitable for display in a terminal. It has broad feature support, allowing it to be used on devices ranging from historical teleprinters to modern terminal emulators and everything in between. 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. General options Show a brief help text. Show version, feature and copyright information. Output encoding 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. 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. Use relative cursor positioning [on, off]. When on, control sequences will be used to position images relative to the cursor. When off, newlines will be used to separate rows instead for e.g. 'less -R' interop. Defaults to off. Graphics protocol passthrough [auto, none, screen, tmux]. Used to show pixel graphics from within multiplexers. Defaults to auto, which will enable passthrough if the Kitty terminal is detected along with one of the supported multiplexers. Other combinations must be enabled manually; use with the -f option to select the appropriate graphics protocol. Polite mode [on, off]. Inhibits escape sequences that on rare occasions may confuse the terminal or other programs. Defaults to off. Size and layout Align images in viewport. The following alignments are understood: left, right, top, bottom, hcenter, vcenter, center. Two orthogonal alignments can be separated by a comma, e.g. "center,right". The meaning of "center" depends on context, and defaults to "hcenter" if ambiguous. "center,center" will center along both axes. Centering vertically makes sense when used together with "--clear", or possibly as part of a scheme where the cursor is pre-positioned at the top-left corner of the view, or a subview when used with "--relative on". Center images horizontally in the view [on, off]. Defaults to off. This option is deprecated; use "--align center" instead. Clear screen before processing each file. Try to match the input's size exactly [auto, on, off]. When on, this will override other sizing options and produce output images at the exact pixel size of the inputs. In auto mode, scaling will be avoided (in exchange for padding) if the output size is equal to or slightly bigger than the input. When off, padding will never be added, and the image is scaled to fit the containing cell extent. Defaults to auto. Fit images to the view's width, potentially exceeding its height. Target font's width/height ratio. Can be specified as a real number or a fraction. Defaults to 1/2. This will only be applied in symbol mode. When terminal size is detected, reserve at least this many rows at the bottom as a safety margin. Can be used to prevent images from scrolling out. Defaults to 1. When terminal size is detected, reserve at least this many columns on the right-hand side as a safety margin. Defaults to 0. Scale image, respecting terminal's maximum dimensions. 1.0 approximates original pixel dimensions. Specify "max" to use all available space. Defaults to 1.0 for pixel graphics and 4.0 for symbols. Set maximum output image dimensions in columns and rows. By default this will be equal to the view size (see --view-size). Stretch image to fit output dimensions; ignore aspect. Implies --scale max. Set the view size in columns and rows. By default this will be the size of your terminal, or 80x25 if size detection fails. If one dimension is omitted (by providing a size of e.g. 80x or x25), it will be set to a reasonable approximation of infinity. Animation and timing Whether to allow animation [on, off]. Defaults to on. When off, will show a still frame from each animation. 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, or "inf". Animations will always be played through at least once, even if duration is e.g. zero. See the "Duration" section for more. 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. Watch a single input file, redisplaying it whenever its contents change. Will run until manually interrupted or, if --duration is set, until it expires. Colors and processing 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. Set output color mode; one of [none, 2, 8, 16/8 16, 240, 256, full]. 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. The 16/8 mode allows for 8 colors plus another "bright" 8 colors in the foreground implemented with the "bold" escape sequence. 2-color mode will only emit the ANSI codes for reverse color and attribute reset, while "none" will emit no escape sequences at all. 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). If left unspecified, an optimal default will be chosen based on the current environment. 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. 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. Invert video. For display with bright backgrounds in color modes 2 and none. Swaps --fg and --bg. 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. 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). Resource allocation Maximum number of CPU threads to use. If left unspecified or negative, this will equal available CPU cores. How hard to work in terms of CPU and memory [1-9]. 1 is the cheapest, 9 is the most accurate. Defaults to 5. Extra options for symbol encoding 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. 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. Specify character symbols to employ in final output. See below for full usage and a list of symbol classes. Exit Status chafa will return 0 on success, 1 on partial failure or 2 on complete failure (including when invoked with no arguments). Status Meaning 0Success 1Some files failed to display 2All files failed to display Symbols Accepted classes for --symbols and --fill 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 add specific characters with the letter "u" followed by a hexadecimal code point, e.g. "ue080", or a range of code points by separating the first and last index by "..", e.g. "u100..u200". Symbol sets can also be specified as a string of UTF-8 characters in square brackets, e.g. [abcd]. To include a closing bracket in the set, escape it with a backslash. 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). Duration In order to accommodate both interactive use and batch processing, an animation's duration is determined according to a few simple rules: If one or more --duration arguments are present, the final instance is respected and applied to every file. Otherwise, if there's a controlling terminal attached (indicating there's an interactive session), and only a single file argument is provided, and that file is an animation, it will have infinite duration. Otherwise (no controlling terminal, multiple files, file is a still image), duration will be zero, causing animations to play once and then stop. 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.14.5/docs/manpage.css000066400000000000000000000132741471154763100157360ustar00rootroot00000000000000/* ===== * * 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.14.5/docs/style.css000066400000000000000000000340461471154763100154660ustar00rootroot00000000000000body { 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.14.5/docs/using.xml000066400000000000000000000100571471154763100154570ustar00rootroot00000000000000 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.14.5/docs/version.xml.in000066400000000000000000000000201471154763100164110ustar00rootroot00000000000000@CHAFA_VERSION@ chafa-1.14.5/libnsgif/000077500000000000000000000000001471154763100144525ustar00rootroot00000000000000chafa-1.14.5/libnsgif/COPYING000066400000000000000000000021071471154763100155050ustar00rootroot00000000000000Copyright (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.14.5/libnsgif/Makefile.am000066400000000000000000000003541471154763100165100ustar00rootroot00000000000000noinst_LTLIBRARIES = libnsgif.la libnsgif_la_SOURCES = libnsgif.c libnsgif.h lzw.c lzw.h log.h libnsgif_la_CFLAGS = ${LIBNSGIF_CFLAGS} -I${top_srcdir} libnsgif_la_LDFLAGS = ${LIBNSGIF_LDFLAGS} EXTRA_DIST = COPYING README README-chafa chafa-1.14.5/libnsgif/README000066400000000000000000000035621471154763100153400ustar00rootroot00000000000000libnsgif - 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.14.5/libnsgif/README-chafa000066400000000000000000000007671471154763100164040ustar00rootroot00000000000000Notes 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.14.5/libnsgif/libnsgif.c000066400000000000000000001356221471154763100164240ustar00rootroot00000000000000/* * 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 */ /* Unused macro */ #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 */ /* Unused macro */ #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; /* Clear all frame fields */ memset(&gif->frames[frame], 0, sizeof(gif_frame)); } /* 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; /* If the GIF has no frame data, frame holders will not be allocated in * gif_initialise() */ if (gif->frames == NULL) { return GIF_INSUFFICIENT_DATA; } /* 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; /* Clear all frame fields */ memset(gif->frames, 0, sizeof(gif_frame)); /* 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.14.5/libnsgif/libnsgif.h000066400000000000000000000147771471154763100164400ustar00rootroot00000000000000/* * 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.14.5/libnsgif/log.h000066400000000000000000000010321471154763100154000ustar00rootroot00000000000000/* * 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.14.5/libnsgif/lzw.c000066400000000000000000000244211471154763100154350ustar00rootroot00000000000000/* * 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 >= 1 << LZW_CODE_MAX) { /* Don't access out of bound */ 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.14.5/libnsgif/lzw.h000066400000000000000000000063511471154763100154440ustar00rootroot00000000000000/* * 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.14.5/lodepng/000077500000000000000000000000001471154763100143055ustar00rootroot00000000000000chafa-1.14.5/lodepng/LICENSE000066400000000000000000000015661471154763100153220ustar00rootroot00000000000000Copyright (c) 2005-2018 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. chafa-1.14.5/lodepng/Makefile.am000066400000000000000000000003241471154763100163400ustar00rootroot00000000000000noinst_LTLIBRARIES = liblodepng.la liblodepng_la_SOURCES = lodepng.c lodepng.h liblodepng_la_CFLAGS = ${LODEPNG_CFLAGS} -I${top_srcdir} liblodepng_la_LDFLAGS = ${LODEPNG_LDFLAGS} EXTRA_DIST = LICENSE README.md chafa-1.14.5/lodepng/README.md000066400000000000000000000042761471154763100155750ustar00rootroot00000000000000LodePNG ------- PNG encoder and decoder in C and C++, without dependencies Home page: http://lodev.org/lodepng/ ### Documentation Detailed documentation is included in a large comment in the second half of the header file `lodepng.h`. Source code examples using LodePNG can be found in the examples directory. An FAQ can be found on http://lodev.org/lodepng/ ### Building Only two files are needed to encode and decode PNGs: * `lodepng.cpp` (or renamed to `lodepng.c`) * `lodepng.h` All other files are just source code examples, tests, misc utilities, etc..., which are normally not needed in projects using this. You can include the files directly in your project's source tree and its makefile, IDE project file, or other build system. No library is necessary. In addition to C++, LodePNG also supports ANSI C (C89), with all the same functionality: C++ only adds extra convenience API. For C, rename `lodepng.cpp` to `lodepng.c`. Consider using git submodules to include LodePNG in your project. ### Compiling in C++ If you have a hypothetical `your_program.cpp` that #includes and uses `lodepng.h`, you can build as follows: `g++ your_program.cpp lodepng.cpp -Wall -Wextra -pedantic -ansi -O3` or: `clang++ your_program.cpp lodepng.cpp -Wall -Wextra -pedantic -ansi -O3` This shows compiler flags it was designed for, but normally one would use the compiler or build system of their project instead of those commands, and other C++ compilers are supported. ### Compiling in C Rename `lodepng.cpp` to `lodepng.c` for this. If you have a hypothetical your_program.c that #includes and uses lodepng.h, you can build as follows: `gcc your_program.c lodepng.c -ansi -pedantic -Wall -Wextra -O3` or `clang your_program.c lodepng.c -ansi -pedantic -Wall -Wextra -O3` This shows compiler flags it was designed for, but normally one would use the compiler or build system of their project instead of those commands, and other C compilers are supported. ### Makefile There is a Makefile, but this is not intended for using LodePNG itself since the way to use that one is to include its source files in your program. The Makefile only builds development and testing utilities. It can be used as follows: `make -j` chafa-1.14.5/lodepng/lodepng.c000066400000000000000000010066211471154763100161070ustar00rootroot00000000000000/* LodePNG version 20220109 Copyright (c) 2005-2022 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* The manual and changelog are in the header file "lodepng.h" Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. */ #include "lodepng.h" #ifdef LODEPNG_COMPILE_DISK #include /* LONG_MAX */ #include /* file handling */ #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ALLOCATORS #include /* allocations */ #endif /* LODEPNG_COMPILE_ALLOCATORS */ #if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ #pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ #endif /*_MSC_VER */ const char* LODEPNG_VERSION_STRING = "20220109"; /* This source file is built up in the following large parts. The code sections with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. -Tools for C and common code for PNG and Zlib -C Code for Zlib (huffman, deflate, ...) -C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) -The C++ wrapper around all of the above */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // Tools for C, and common code for PNG and Zlib. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /*The malloc, realloc and free functions defined here with "lodepng_" in front of the name, so that you can easily change them to others related to your platform if needed. Everything else in the code calls these. Pass -DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out #define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and define them in your own project's source files without needing to change lodepng source code. Don't forget to remove "static" if you copypaste them from here.*/ #ifdef LODEPNG_COMPILE_ALLOCATORS static void* lodepng_malloc(size_t size) { #ifdef LODEPNG_MAX_ALLOC if(size > LODEPNG_MAX_ALLOC) return 0; #endif return malloc(size); } /* NOTE: when realloc returns NULL, it leaves the original memory untouched */ static void* lodepng_realloc(void* ptr, size_t new_size) { #ifdef LODEPNG_MAX_ALLOC if(new_size > LODEPNG_MAX_ALLOC) return 0; #endif return realloc(ptr, new_size); } static void lodepng_free(void* ptr) { free(ptr); } #else /*LODEPNG_COMPILE_ALLOCATORS*/ /* TODO: support giving additional void* payload to the custom allocators */ void* lodepng_malloc(size_t size); void* lodepng_realloc(void* ptr, size_t new_size); void lodepng_free(void* ptr); #endif /*LODEPNG_COMPILE_ALLOCATORS*/ /* convince the compiler to inline a function, for use when this measurably improves performance */ /* inline is not available in C90, but use it when supported by the compiler */ #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) #define LODEPNG_INLINE inline #else #define LODEPNG_INLINE /* not available */ #endif /* restrict is not available in C90, but use it when supported by the compiler */ #if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) #define LODEPNG_RESTRICT __restrict #else #define LODEPNG_RESTRICT /* not available */ #endif /* Replacements for C library functions such as memcpy and strlen, to support platforms where a full C library is not available. The compiler can recognize them and compile to something as fast. */ static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, const void* LODEPNG_RESTRICT src, size_t size) { size_t i; for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; } static void lodepng_memset(void* LODEPNG_RESTRICT dst, int value, size_t num) { size_t i; for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; } /* does not check memory out of bounds, do not use on untrusted data */ static size_t lodepng_strlen(const char* a) { const char* orig = a; /* avoid warning about unused function in case of disabled COMPILE... macros */ (void)(&lodepng_strlen); while(*a) a++; return (size_t)(a - orig); } #define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) #ifdef LODEPNG_COMPILE_ENCODER # define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) #if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) /* Safely check if adding two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ static int lodepng_addofl(size_t a, size_t b, size_t* result) { *result = a + b; /* Unsigned addition is well defined and safe in C90 */ return *result < a; } #endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ #ifdef LODEPNG_COMPILE_DECODER /* Safely check if multiplying two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ static int lodepng_mulofl(size_t a, size_t b, size_t* result) { *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ return (a != 0 && *result / a != b); } #ifdef LODEPNG_COMPILE_ZLIB /* Safely check if a + b > c, even if overflow could happen. */ static int lodepng_gtofl(size_t a, size_t b, size_t c) { size_t d; if(lodepng_addofl(a, b, &d)) return 1; return d > c; } #endif /*LODEPNG_COMPILE_ZLIB*/ #endif /*LODEPNG_COMPILE_DECODER*/ /* Often in case of an error a value is assigned to a variable and then it breaks out of a loop (to go to the cleanup phase of a function). This macro does that. It makes the error handling code shorter and more readable. Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); */ #define CERROR_BREAK(errorvar, code){\ errorvar = code;\ break;\ } /*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ #define ERROR_BREAK(code) CERROR_BREAK(error, code) /*Set error var to the error code, and return it.*/ #define CERROR_RETURN_ERROR(errorvar, code){\ errorvar = code;\ return code;\ } /*Try the code, if it returns error, also return the error.*/ #define CERROR_TRY_RETURN(call){\ unsigned error = call;\ if(error) return error;\ } /*Set error var to the error code, and return from the void function.*/ #define CERROR_RETURN(errorvar, code){\ errorvar = code;\ return;\ } /* About uivector, ucvector and string: -All of them wrap dynamic arrays or text strings in a similar way. -LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. -The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. -They're not used in the interface, only internally in this file as static functions. -As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_ENCODER /*dynamic vector of unsigned ints*/ typedef struct uivector { unsigned* data; size_t size; /*size in number of unsigned longs*/ size_t allocsize; /*allocated size in bytes*/ } uivector; static void uivector_cleanup(void* p) { ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; lodepng_free(((uivector*)p)->data); ((uivector*)p)->data = NULL; } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned uivector_resize(uivector* p, size_t size) { size_t allocsize = size * sizeof(unsigned); if(allocsize > p->allocsize) { size_t newsize = allocsize + (p->allocsize >> 1u); void* data = lodepng_realloc(p->data, newsize); if(data) { p->allocsize = newsize; p->data = (unsigned*)data; } else return 0; /*error: not enough memory*/ } p->size = size; return 1; /*success*/ } static void uivector_init(uivector* p) { p->data = NULL; p->size = p->allocsize = 0; } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned uivector_push_back(uivector* p, unsigned c) { if(!uivector_resize(p, p->size + 1)) return 0; p->data[p->size - 1] = c; return 1; } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ /* /////////////////////////////////////////////////////////////////////////// */ /*dynamic vector of unsigned chars*/ typedef struct ucvector { unsigned char* data; size_t size; /*used size*/ size_t allocsize; /*allocated size*/ } ucvector; /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned ucvector_reserve(ucvector* p, size_t size) { if(size > p->allocsize) { size_t newsize = size + (p->allocsize >> 1u); void* data = lodepng_realloc(p->data, newsize); if(data) { p->allocsize = newsize; p->data = (unsigned char*)data; } else return 0; /*error: not enough memory*/ } return 1; /*success*/ } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned ucvector_resize(ucvector* p, size_t size) { p->size = size; return ucvector_reserve(p, size); } static ucvector ucvector_init(unsigned char* buffer, size_t size) { ucvector v; v.data = buffer; v.allocsize = v.size = size; return v; } /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_PNG #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*free string pointer and set it to NULL*/ static void string_cleanup(char** out) { lodepng_free(*out); *out = NULL; } /*also appends null termination character*/ static char* alloc_string_sized(const char* in, size_t insize) { char* out = (char*)lodepng_malloc(insize + 1); if(out) { lodepng_memcpy(out, in, insize); out[insize] = 0; } return out; } /* dynamically allocates a new string with a copy of the null terminated input text */ static char* alloc_string(const char* in) { return alloc_string_sized(in, lodepng_strlen(in)); } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ #endif /*LODEPNG_COMPILE_PNG*/ /* ////////////////////////////////////////////////////////////////////////// */ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) static unsigned lodepng_read32bitInt(const unsigned char* buffer) { return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); } #endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ #if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) /*buffer must have at least 4 allocated bytes available*/ static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { buffer[0] = (unsigned char)((value >> 24) & 0xff); buffer[1] = (unsigned char)((value >> 16) & 0xff); buffer[2] = (unsigned char)((value >> 8) & 0xff); buffer[3] = (unsigned char)((value ) & 0xff); } #endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ /* ////////////////////////////////////////////////////////////////////////// */ /* / File IO / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_DISK /* returns negative value on error. This should be pure C compatible, so no fstat. */ static long lodepng_filesize(const char* filename) { FILE* file; long size; file = fopen(filename, "rb"); if(!file) return -1; if(fseek(file, 0, SEEK_END) != 0) { fclose(file); return -1; } size = ftell(file); /* It may give LONG_MAX as directory size, this is invalid for us. */ if(size == LONG_MAX) size = -1; fclose(file); return size; } /* load file into buffer that already has the correct allocated size. Returns error code.*/ static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { FILE* file; size_t readsize; file = fopen(filename, "rb"); if(!file) return 78; readsize = fread(out, 1, size, file); fclose(file); if(readsize != size) return 78; return 0; } unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { long size = lodepng_filesize(filename); if(size < 0) return 78; *outsize = (size_t)size; *out = (unsigned char*)lodepng_malloc((size_t)size); if(!(*out) && size > 0) return 83; /*the above malloc failed*/ return lodepng_buffer_file(*out, (size_t)size, filename); } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { FILE* file; file = fopen(filename, "wb" ); if(!file) return 79; fwrite(buffer, 1, buffersize, file); fclose(file); return 0; } #endif /*LODEPNG_COMPILE_DISK*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // End of common code and tools. Begin of Zlib related code. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_ENCODER typedef struct { ucvector* data; unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ } LodePNGBitWriter; static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { writer->data = data; writer->bp = 0; } /*TODO: this ignores potential out of memory errors*/ #define WRITEBIT(writer, bit){\ /* append new byte */\ if(((writer->bp) & 7u) == 0) {\ if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ writer->data->data[writer->data->size - 1] = 0;\ }\ (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ ++writer->bp;\ } /* LSB of value is written first, and LSB of bytes is used first */ static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ WRITEBIT(writer, value); } else { /* TODO: increase output size only once here rather than in each WRITEBIT */ size_t i; for(i = 0; i != nbits; ++i) { WRITEBIT(writer, (unsigned char)((value >> i) & 1)); } } } /* This one is to use for adding huffman symbol, the value bits are written MSB first */ static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { size_t i; for(i = 0; i != nbits; ++i) { /* TODO: increase output size only once here rather than in each WRITEBIT */ WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); } } #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DECODER typedef struct { const unsigned char* data; size_t size; /*size of data in bytes*/ size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ size_t bp; unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ } LodePNGBitReader; /* data size argument is in bytes. Returns error if size too large causing overflow */ static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { size_t temp; reader->data = data; reader->size = size; /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and trying to ensure 32 more bits*/ if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; reader->bp = 0; reader->buffer = 0; return 0; /*ok*/ } /* ensureBits functions: Ensures the reader can at least read nbits bits in one or more readBits calls, safely even if not enough bits are available. The nbits parameter is unused but is given for documentation purposes, error checking for amount of bits must be done beforehand. */ /*See ensureBits documentation above. This one ensures up to 9 bits */ static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 1u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer = reader->data[start + 0]; reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 17 bits */ static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 2u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 25 bits */ static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 3u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 32 bits */ static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 4u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { /* The shift allows nbits to be only up to 31. */ return reader->buffer & ((1u << nbits) - 1u); } /* Must have enough bits available with ensureBits */ static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { reader->buffer >>= nbits; reader->bp += nbits; } /* Must have enough bits available with ensureBits */ static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { unsigned result = peekBits(reader, nbits); advanceBits(reader, nbits); return result; } #endif /*LODEPNG_COMPILE_DECODER*/ static unsigned reverseBits(unsigned bits, unsigned num) { /*TODO: implement faster lookup table based version when needed*/ unsigned i, result = 0; for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; return result; } /* ////////////////////////////////////////////////////////////////////////// */ /* / Deflate - Huffman / */ /* ////////////////////////////////////////////////////////////////////////// */ #define FIRST_LENGTH_CODE_INDEX 257 #define LAST_LENGTH_CODE_INDEX 285 /*256 literals, the end code, some length codes, and 2 unused codes*/ #define NUM_DEFLATE_CODE_SYMBOLS 288 /*the distance codes have their own symbols, 30 used, 2 unused*/ #define NUM_DISTANCE_SYMBOLS 32 /*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ #define NUM_CODE_LENGTH_CODES 19 /*the base lengths represented by codes 257-285*/ static const unsigned LENGTHBASE[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; /*the extra bits used by codes 257-285 (added to base length)*/ static const unsigned LENGTHEXTRA[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; /*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ static const unsigned DISTANCEBASE[30] = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; /*the extra bits of backwards distances (added to base)*/ static const unsigned DISTANCEEXTRA[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; /*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman tree of the dynamic huffman tree lengths is generated*/ static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; /* ////////////////////////////////////////////////////////////////////////// */ /* Huffman tree struct, containing multiple representations of the tree */ typedef struct HuffmanTree { unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ unsigned* lengths; /*the lengths of the huffman codes*/ unsigned maxbitlen; /*maximum number of bits a single code can get*/ unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ /* for reading only */ unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ } HuffmanTree; static void HuffmanTree_init(HuffmanTree* tree) { tree->codes = 0; tree->lengths = 0; tree->table_len = 0; tree->table_value = 0; } static void HuffmanTree_cleanup(HuffmanTree* tree) { lodepng_free(tree->codes); lodepng_free(tree->lengths); lodepng_free(tree->table_len); lodepng_free(tree->table_value); } /* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ /* values 8u and 9u work the fastest */ #define FIRSTBITS 9u /* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, which is possible in case of only 0 or 1 present symbols. */ #define INVALIDSYMBOL 65535u /* make table for huffman decoding */ static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; size_t i, numpresent, pointer, size; /*total table size*/ unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); if(!maxlens) return 83; /*alloc fail*/ /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); for(i = 0; i < tree->numcodes; i++) { unsigned symbol = tree->codes[i]; unsigned l = tree->lengths[i]; unsigned index; if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); maxlens[index] = LODEPNG_MAX(maxlens[index], l); } /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ size = headsize; for(i = 0; i < headsize; ++i) { unsigned l = maxlens[i]; if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); } tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); if(!tree->table_len || !tree->table_value) { lodepng_free(maxlens); /* freeing tree->table values is done at a higher scope */ return 83; /*alloc fail*/ } /*initialize with an invalid length to indicate unused entries*/ for(i = 0; i < size; ++i) tree->table_len[i] = 16; /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ pointer = headsize; for(i = 0; i < headsize; ++i) { unsigned l = maxlens[i]; if(l <= FIRSTBITS) continue; tree->table_len[i] = l; tree->table_value[i] = pointer; pointer += (1u << (l - FIRSTBITS)); } lodepng_free(maxlens); /*fill in the first table for short symbols, or secondary table for long symbols*/ numpresent = 0; for(i = 0; i < tree->numcodes; ++i) { unsigned l = tree->lengths[i]; unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ unsigned reverse = reverseBits(symbol, l); if(l == 0) continue; numpresent++; if(l <= FIRSTBITS) { /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ unsigned num = 1u << (FIRSTBITS - l); unsigned j; for(j = 0; j < num; ++j) { /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ unsigned index = reverse | (j << l); if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ tree->table_len[index] = l; tree->table_value[index] = i; } } else { /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ /*the FIRSTBITS MSBs of the symbol are the first table index*/ unsigned index = reverse & mask; unsigned maxlen = tree->table_len[index]; /*log2 of secondary table length, should be >= l - FIRSTBITS*/ unsigned tablelen = maxlen - FIRSTBITS; unsigned start = tree->table_value[index]; /*starting index in secondary table*/ unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ unsigned j; if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ for(j = 0; j < num; ++j) { unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); tree->table_len[index2] = l; tree->table_value[index2] = i; } } } if(numpresent < 2) { /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, but deflate uses 1 bit instead. In case of 0 symbols, no symbols can appear at all, but such huffman tree could still exist (e.g. if distance codes are never used). In both cases, not all symbols of the table will be filled in. Fill them in with an invalid symbol value so returning them from huffmanDecodeSymbol will cause error. */ for(i = 0; i < size; ++i) { if(tree->table_len[i] == 16) { /* As length, use a value smaller than FIRSTBITS for the head table, and a value larger than FIRSTBITS for the secondary table, to ensure valid behavior for advanceBits when reading this symbol. */ tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); tree->table_value[i] = INVALIDSYMBOL; } } } else { /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. If that is not the case (due to too long length codes), the table will not have been fully used, and this is an error (not all bit combinations can be decoded): an oversubscribed huffman tree, indicated by error 55. */ for(i = 0; i < size; ++i) { if(tree->table_len[i] == 16) return 55; } } return 0; } /* Second step for the ...makeFromLengths and ...makeFromFrequencies functions. numcodes, lengths and maxbitlen must already be filled in correctly. return value is error. */ static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { unsigned* blcount; unsigned* nextcode; unsigned error = 0; unsigned bits, n; tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ if(!error) { for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; /*step 1: count number of instances of each code length*/ for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; /*step 2: generate the nextcode values*/ for(bits = 1; bits <= tree->maxbitlen; ++bits) { nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; } /*step 3: generate all the codes*/ for(n = 0; n != tree->numcodes; ++n) { if(tree->lengths[n] != 0) { tree->codes[n] = nextcode[tree->lengths[n]]++; /*remove superfluous bits from the code*/ tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); } } } lodepng_free(blcount); lodepng_free(nextcode); if(!error) error = HuffmanTree_makeTable(tree); return error; } /* given the code lengths (as stored in the PNG file), generate the tree as defined by Deflate. maxbitlen is the maximum bits that a code in the tree can have. return value is error. */ static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, size_t numcodes, unsigned maxbitlen) { unsigned i; tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); if(!tree->lengths) return 83; /*alloc fail*/ for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; tree->numcodes = (unsigned)numcodes; /*number of symbols*/ tree->maxbitlen = maxbitlen; return HuffmanTree_makeFromLengths2(tree); } #ifdef LODEPNG_COMPILE_ENCODER /*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ /*chain node for boundary package merge*/ typedef struct BPMNode { int weight; /*the sum of all weights in this chain*/ unsigned index; /*index of this leaf node (called "count" in the paper)*/ struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ int in_use; } BPMNode; /*lists of chains*/ typedef struct BPMLists { /*memory pool*/ unsigned memsize; BPMNode* memory; unsigned numfree; unsigned nextfree; BPMNode** freelist; /*two heads of lookahead chains per list*/ unsigned listsize; BPMNode** chains0; BPMNode** chains1; } BPMLists; /*creates a new chain node with the given parameters, from the memory in the lists */ static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { unsigned i; BPMNode* result; /*memory full, so garbage collect*/ if(lists->nextfree >= lists->numfree) { /*mark only those that are in use*/ for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; for(i = 0; i != lists->listsize; ++i) { BPMNode* node; for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; } /*collect those that are free*/ lists->numfree = 0; for(i = 0; i != lists->memsize; ++i) { if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; } lists->nextfree = 0; } result = lists->freelist[lists->nextfree++]; result->weight = weight; result->index = index; result->tail = tail; return result; } /*sort the leaves with stable mergesort*/ static void bpmnode_sort(BPMNode* leaves, size_t num) { BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); size_t width, counter = 0; for(width = 1; width < num; width *= 2) { BPMNode* a = (counter & 1) ? mem : leaves; BPMNode* b = (counter & 1) ? leaves : mem; size_t p; for(p = 0; p < num; p += 2 * width) { size_t q = (p + width > num) ? num : (p + width); size_t r = (p + 2 * width > num) ? num : (p + 2 * width); size_t i = p, j = q, k; for(k = p; k < r; k++) { if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; else b[k] = a[j++]; } } counter++; } if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); lodepng_free(mem); } /*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { unsigned lastindex = lists->chains1[c]->index; if(c == 0) { if(lastindex >= numpresent) return; lists->chains0[c] = lists->chains1[c]; lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); } else { /*sum of the weights of the head nodes of the previous lookahead chains.*/ int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; lists->chains0[c] = lists->chains1[c]; if(lastindex < numpresent && sum > leaves[lastindex].weight) { lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); return; } lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); /*in the end we are only interested in the chain of the last list, so no need to recurse if we're at the last one (this gives measurable speedup)*/ if(num + 1 < (int)(2 * numpresent - 2)) { boundaryPM(lists, leaves, numpresent, c - 1, num); boundaryPM(lists, leaves, numpresent, c - 1, num); } } } unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, size_t numcodes, unsigned maxbitlen) { unsigned error = 0; unsigned i; size_t numpresent = 0; /*number of symbols with non-zero frequency*/ BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); if(!leaves) return 83; /*alloc fail*/ for(i = 0; i != numcodes; ++i) { if(frequencies[i] > 0) { leaves[numpresent].weight = (int)frequencies[i]; leaves[numpresent].index = i; ++numpresent; } } lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); /*ensure at least two present symbols. There should be at least one symbol according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To make these work as well ensure there are at least two symbols. The Package-Merge code below also doesn't work correctly if there's only one symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ if(numpresent == 0) { lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ } else if(numpresent == 1) { lengths[leaves[0].index] = 1; lengths[leaves[0].index == 0 ? 1 : 0] = 1; } else { BPMLists lists; BPMNode* node; bpmnode_sort(leaves, numpresent); lists.listsize = maxbitlen; lists.memsize = 2 * maxbitlen * (maxbitlen + 1); lists.nextfree = 0; lists.numfree = lists.memsize; lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ if(!error) { for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; bpmnode_create(&lists, leaves[0].weight, 1, 0); bpmnode_create(&lists, leaves[1].weight, 2, 0); for(i = 0; i != lists.listsize; ++i) { lists.chains0[i] = &lists.memory[0]; lists.chains1[i] = &lists.memory[1]; } /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; } } lodepng_free(lists.memory); lodepng_free(lists.freelist); lodepng_free(lists.chains0); lodepng_free(lists.chains1); } lodepng_free(leaves); return error; } /*Create the Huffman tree given the symbol frequencies*/ static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, size_t mincodes, size_t numcodes, unsigned maxbitlen) { unsigned error = 0; while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); if(!tree->lengths) return 83; /*alloc fail*/ tree->maxbitlen = maxbitlen; tree->numcodes = (unsigned)numcodes; /*number of symbols*/ error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); if(!error) error = HuffmanTree_makeFromLengths2(tree); return error; } #endif /*LODEPNG_COMPILE_ENCODER*/ /*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ static unsigned generateFixedLitLenTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ for(i = 0; i <= 143; ++i) bitlen[i] = 8; for(i = 144; i <= 255; ++i) bitlen[i] = 9; for(i = 256; i <= 279; ++i) bitlen[i] = 7; for(i = 280; i <= 287; ++i) bitlen[i] = 8; error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); lodepng_free(bitlen); return error; } /*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ static unsigned generateFixedDistanceTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ /*there are 32 distance codes, but 30-31 are unused*/ for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); lodepng_free(bitlen); return error; } #ifdef LODEPNG_COMPILE_DECODER /* returns the code. The bit reader must already have been ensured at least 15 bits */ static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { unsigned short code = peekBits(reader, FIRSTBITS); unsigned short l = codetree->table_len[code]; unsigned short value = codetree->table_value[code]; if(l <= FIRSTBITS) { advanceBits(reader, l); return value; } else { advanceBits(reader, FIRSTBITS); value += peekBits(reader, l - FIRSTBITS); advanceBits(reader, codetree->table_len[value] - FIRSTBITS); return codetree->table_value[value]; } } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_DECODER /* ////////////////////////////////////////////////////////////////////////// */ /* / Inflator (Decompressor) / */ /* ////////////////////////////////////////////////////////////////////////// */ /*get the tree of a deflated block with fixed tree, as specified in the deflate specification Returns error code.*/ static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { unsigned error = generateFixedLitLenTree(tree_ll); if(error) return error; return generateFixedDistanceTree(tree_d); } /*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, LodePNGBitReader* reader) { /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ unsigned error = 0; unsigned n, HLIT, HDIST, HCLEN, i; /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ unsigned* bitlen_ll = 0; /*lit,len code lengths*/ unsigned* bitlen_d = 0; /*dist code lengths*/ /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ unsigned* bitlen_cl = 0; HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ ensureBits17(reader, 14); /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ HLIT = readBits(reader, 5) + 257; /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ HDIST = readBits(reader, 5) + 1; /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ HCLEN = readBits(reader, 4) + 4; bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); if(!bitlen_cl) return 83 /*alloc fail*/; HuffmanTree_init(&tree_cl); while(!error) { /*read the code length codes out of 3 * (amount of code length codes) bits*/ if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ } for(i = 0; i != HCLEN; ++i) { ensureBits9(reader, 3); /*out of bounds already checked above */ bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); } for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { bitlen_cl[CLCL_ORDER[i]] = 0; } error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); if(error) break; /*now we can use this tree to read the lengths for the tree that this function will return*/ bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ i = 0; while(i < HLIT + HDIST) { unsigned code; ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ code = huffmanDecodeSymbol(reader, &tree_cl); if(code <= 15) /*a length code*/ { if(i < HLIT) bitlen_ll[i] = code; else bitlen_d[i - HLIT] = code; ++i; } else if(code == 16) /*repeat previous*/ { unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ unsigned value; /*set value to the previous code*/ if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ replength += readBits(reader, 2); if(i < HLIT + 1) value = bitlen_ll[i - 1]; else value = bitlen_d[i - HLIT - 1]; /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = value; else bitlen_d[i - HLIT] = value; ++i; } } else if(code == 17) /*repeat "0" 3-10 times*/ { unsigned replength = 3; /*read in the bits that indicate repeat length*/ replength += readBits(reader, 3); /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } } else if(code == 18) /*repeat "0" 11-138 times*/ { unsigned replength = 11; /*read in the bits that indicate repeat length*/ replength += readBits(reader, 7); /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } } else /*if(code == INVALIDSYMBOL)*/ { ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } /*check if any of the ensureBits above went out of bounds*/ if(reader->bp > reader->bitsize) { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ } } if(error) break; if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); if(error) break; error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); break; /*end of error-while*/ } lodepng_free(bitlen_cl); lodepng_free(bitlen_ll); lodepng_free(bitlen_d); HuffmanTree_cleanup(&tree_cl); return error; } /*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, unsigned btype, size_t max_output_size) { unsigned error = 0; HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ HuffmanTree tree_d; /*the huffman tree for distance codes*/ const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ int done = 0; if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { /*code_ll is literal, length or end code*/ unsigned code_ll; /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ ensureBits32(reader, 30); code_ll = huffmanDecodeSymbol(reader, &tree_ll); if(code_ll <= 255) { /*slightly faster code path if multiple literals in a row*/ out->data[out->size++] = (unsigned char)code_ll; code_ll = huffmanDecodeSymbol(reader, &tree_ll); } if(code_ll <= 255) /*literal symbol*/ { out->data[out->size++] = (unsigned char)code_ll; } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { unsigned code_d, distance; unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ size_t start, backward, length; /*part 1: get length base*/ length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; /*part 2: get extra bits and add the value of that to length*/ numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; if(numextrabits_l != 0) { /* bits already ensured above */ ensureBits25(reader, 5); length += readBits(reader, numextrabits_l); } /*part 3: get distance code*/ ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ code_d = huffmanDecodeSymbol(reader, &tree_d); if(code_d > 29) { if(code_d <= 31) { ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ } else /* if(code_d == INVALIDSYMBOL) */{ ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } } distance = DISTANCEBASE[code_d]; /*part 4: get extra bits from distance*/ numextrabits_d = DISTANCEEXTRA[code_d]; if(numextrabits_d != 0) { /* bits already ensured above */ distance += readBits(reader, numextrabits_d); } /*part 5: fill in all the out[n] values based on the length and dist*/ start = out->size; if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ backward = start - distance; out->size += length; if(distance < length) { size_t forward; lodepng_memcpy(out->data + start, out->data + backward, distance); start += distance; for(forward = distance; forward < length; ++forward) { out->data[start++] = out->data[backward++]; } } else { lodepng_memcpy(out->data + start, out->data + backward, length); } } else if(code_ll == 256) { done = 1; /*end code, finish the loop*/ } else /*if(code_ll == INVALIDSYMBOL)*/ { ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } if(out->allocsize - out->size < reserved_size) { if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ } /*check if any of the ensureBits above went out of bounds*/ if(reader->bp > reader->bitsize) { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ } if(max_output_size && out->size > max_output_size) { ERROR_BREAK(109); /*error, larger than max size*/ } } HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); return error; } static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, const LodePNGDecompressSettings* settings) { size_t bytepos; size_t size = reader->size; unsigned LEN, NLEN, error = 0; /*go to first boundary of byte*/ bytepos = (reader->bp + 7u) >> 3u; /*read LEN (2 bytes) and NLEN (2 bytes)*/ if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; /*check if 16-bit NLEN is really the one's complement of LEN*/ if(!settings->ignore_nlen && LEN + NLEN != 65535) { return 21; /*error: NLEN is not one's complement of LEN*/ } if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ /*read the literal data: LEN bytes are now stored in the out buffer*/ if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ /*out->data can be NULL (when LEN is zero), and arithmetics on NULL ptr is undefined. so we check*/ if (out->data) { lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); bytepos += LEN; } reader->bp = bytepos << 3u; return error; } static unsigned lodepng_inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned BFINAL = 0; LodePNGBitReader reader; unsigned error = LodePNGBitReader_init(&reader, in, insize); if(error) return error; while(!BFINAL) { unsigned BTYPE; if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ ensureBits9(&reader, 3); BFINAL = readBits(&reader, 1); BTYPE = readBits(&reader, 2); if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; if(error) break; } return error; } unsigned lodepng_inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_inflatev(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { if(settings->custom_inflate) { unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); out->allocsize = out->size; if(error) { /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ error = 110; /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ if(settings->max_output_size && out->size > settings->max_output_size) error = 109; } return error; } else { return lodepng_inflatev(out, in, insize, settings); } } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* ////////////////////////////////////////////////////////////////////////// */ /* / Deflator (Compressor) / */ /* ////////////////////////////////////////////////////////////////////////// */ static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; /*search the index in the array, that has the largest value smaller than or equal to the given value, given array must be sorted (if no value is smaller, it returns the size of the given array)*/ static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ size_t left = 1; size_t right = array_size - 1; while(left <= right) { size_t mid = (left + right) >> 1; if(array[mid] >= value) right = mid - 1; else left = mid + 1; } if(left >= array_size || array[left] > value) left--; return left; } static void addLengthDistance(uivector* values, size_t length, size_t distance) { /*values in encoded vector are those used by deflate: 0-255: literal bytes 256: end 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) 286-287: invalid*/ unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); size_t pos = values->size; /*TODO: return error when this fails (out of memory)*/ unsigned ok = uivector_resize(values, values->size + 4); if(ok) { values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; values->data[pos + 1] = extra_length; values->data[pos + 2] = dist_code; values->data[pos + 3] = extra_distance; } } /*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 bytes as input because 3 is the minimum match length for deflate*/ static const unsigned HASH_NUM_VALUES = 65536; static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ typedef struct Hash { int* head; /*hash value to head circular pos - can be outdated if went around window*/ /*circular pos to prev circular pos*/ unsigned short* chain; int* val; /*circular pos to hash value*/ /*TODO: do this not only for zeros but for any repeated byte. However for PNG it's always going to be the zeros that dominate, so not important for PNG*/ int* headz; /*similar to head, but for chainz*/ unsigned short* chainz; /*those with same amount of zeros*/ unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ } Hash; static unsigned hash_init(Hash* hash, unsigned windowsize) { unsigned i; hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { return 83; /*alloc fail*/ } /*initialize hash table*/ for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; for(i = 0; i != windowsize; ++i) hash->val[i] = -1; for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ return 0; } static void hash_cleanup(Hash* hash) { lodepng_free(hash->head); lodepng_free(hash->val); lodepng_free(hash->chain); lodepng_free(hash->zeros); lodepng_free(hash->headz); lodepng_free(hash->chainz); } static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { unsigned result = 0; if(pos + 2 < size) { /*A simple shift and xor hash is used. Since the data of PNGs is dominated by zeroes due to the filters, a better hash does not have a significant effect on speed in traversing the chain, and causes more time spend on calculating the hash.*/ result ^= ((unsigned)data[pos + 0] << 0u); result ^= ((unsigned)data[pos + 1] << 4u); result ^= ((unsigned)data[pos + 2] << 8u); } else { size_t amount, i; if(pos >= size) return 0; amount = size - pos; for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); } return result & HASH_BIT_MASK; } static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { const unsigned char* start = data + pos; const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; if(end > data + size) end = data + size; data = start; while(data != end && *data == 0) ++data; /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ return (unsigned)(data - start); } /*wpos = pos & (windowsize - 1)*/ static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { hash->val[wpos] = (int)hashval; if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; hash->head[hashval] = (int)wpos; hash->zeros[wpos] = numzeros; if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; hash->headz[numzeros] = (int)wpos; } /* LZ77-encode the data. Return value is error code. The input are raw bytes, the output is in the form of unsigned integers with codes representing for example literal bytes, or length/distance pairs. It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a sliding window (of windowsize) is used, and all past bytes in that window can be used as the "dictionary". A brute force search through all possible distances would be slow, and this hash technique is one out of several ways to speed this up. */ static unsigned encodeLZ77(uivector* out, Hash* hash, const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, unsigned minmatch, unsigned nicematch, unsigned lazymatching) { size_t pos; unsigned i, error = 0; /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ unsigned numzeros = 0; unsigned offset; /*the offset represents the distance in LZ77 terminology*/ unsigned length; unsigned lazy = 0; unsigned lazylength = 0, lazyoffset = 0; unsigned hashval; unsigned current_offset, current_length; unsigned prev_offset; const unsigned char *lastptr, *foreptr, *backptr; unsigned hashpos; if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; for(pos = inpos; pos < insize; ++pos) { size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ unsigned chainlength = 0; hashval = getHash(in, insize, pos); if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; } else { numzeros = 0; } updateHashChain(hash, wpos, hashval, numzeros); /*the length and offset found for the current position*/ length = 0; offset = 0; hashpos = hash->chain[wpos]; lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; /*search for the longest string*/ prev_offset = 0; for(;;) { if(chainlength++ >= maxchainlength) break; current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ prev_offset = current_offset; if(current_offset > 0) { /*test the next characters*/ foreptr = &in[pos]; backptr = &in[pos - current_offset]; /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ if(numzeros >= 3) { unsigned skip = hash->zeros[hashpos]; if(skip > numzeros) skip = numzeros; backptr += skip; foreptr += skip; } while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { ++backptr; ++foreptr; } current_length = (unsigned)(foreptr - &in[pos]); if(current_length > length) { length = current_length; /*the longest length*/ offset = current_offset; /*the offset that is related to this longest length*/ /*jump out once a length of max length is found (speed gain). This also jumps out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ if(current_length >= nicematch) break; } } if(hashpos == hash->chain[hashpos]) break; if(numzeros >= 3 && length > numzeros) { hashpos = hash->chainz[hashpos]; if(hash->zeros[hashpos] != numzeros) break; } else { hashpos = hash->chain[hashpos]; /*outdated hash value, happens if particular value was not encountered in whole last window*/ if(hash->val[hashpos] != (int)hashval) break; } } if(lazymatching) { if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { lazy = 1; lazylength = length; lazyoffset = offset; continue; /*try the next byte*/ } if(lazy) { lazy = 0; if(pos == 0) ERROR_BREAK(81); if(length > lazylength + 1) { /*push the previous character as literal*/ if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); } else { length = lazylength; offset = lazyoffset; hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ hash->headz[numzeros] = -1; /*idem*/ --pos; } } } if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); /*encode it as length/distance pair or literal value*/ if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); } else if(length < minmatch || (length == 3 && offset > 4096)) { /*compensate for the fact that longer offsets have more extra bits, a length of only 3 may be not worth it then*/ if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); } else { addLengthDistance(out, length, offset); for(i = 1; i < length; ++i) { ++pos; wpos = pos & (windowsize - 1); hashval = getHash(in, insize, pos); if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; } else { numzeros = 0; } updateHashChain(hash, wpos, hashval, numzeros); } } } /*end of the loop through each character of input*/ return error; } /* /////////////////////////////////////////////////////////////////////////// */ static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; unsigned datapos = 0; for(i = 0; i != numdeflateblocks; ++i) { unsigned BFINAL, BTYPE, LEN, NLEN; unsigned char firstbyte; size_t pos = out->size; BFINAL = (i == numdeflateblocks - 1); BTYPE = 0; LEN = 65535; if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; NLEN = 65535 - LEN; if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); out->data[pos + 0] = firstbyte; out->data[pos + 1] = (unsigned char)(LEN & 255); out->data[pos + 2] = (unsigned char)(LEN >> 8u); out->data[pos + 3] = (unsigned char)(NLEN & 255); out->data[pos + 4] = (unsigned char)(NLEN >> 8u); lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); datapos += LEN; } return 0; } /* write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. tree_ll: the tree for lit and len codes. tree_d: the tree for distance codes. */ static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { size_t i = 0; for(i = 0; i != lz77_encoded->size; ++i) { unsigned val = lz77_encoded->data[i]; writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); if(val > 256) /*for a length code, 3 more things have to be added*/ { unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; unsigned length_extra_bits = lz77_encoded->data[++i]; unsigned distance_code = lz77_encoded->data[++i]; unsigned distance_index = distance_code; unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; unsigned distance_extra_bits = lz77_encoded->data[++i]; writeBits(writer, length_extra_bits, n_length_extra_bits); writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); writeBits(writer, distance_extra_bits, n_distance_extra_bits); } } } /*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, const LodePNGCompressSettings* settings, unsigned final) { unsigned error = 0; /* A block is compressed as follows: The PNG data is lz77 encoded, resulting in literal bytes and length/distance pairs. This is then huffman compressed with two huffman trees. One huffman tree is used for the lit and len values ("ll"), another huffman tree is used for the dist values ("d"). These two trees are stored using their code lengths, and to compress even more these code lengths are also run-length encoded and huffman compressed. This gives a huffman tree of code lengths "cl". The code lengths used to describe this third tree are the code length code lengths ("clcl"). */ /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ uivector lz77_encoded; HuffmanTree tree_ll; /*tree for lit,len values*/ HuffmanTree tree_d; /*tree for distance codes*/ HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ unsigned* frequencies_d = 0; /*frequency of dist codes*/ unsigned* frequencies_cl = 0; /*frequency of code length codes*/ unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ size_t datasize = dataend - datapos; /* If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are some analogies: bitlen_lld is to tree_cl what data is to tree_ll and tree_d. bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. */ unsigned BFINAL = final; size_t i; size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; unsigned HLIT, HDIST, HCLEN; uivector_init(&lz77_encoded); HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); HuffmanTree_init(&tree_cl); /* could fit on stack, but >1KB is on the larger side so allocate instead */ frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ /*This while loop never loops due to a break at the end, it is here to allow breaking out of it to the cleanup phase on error conditions.*/ while(!error) { lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); if(settings->use_lz77) { error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(error) break; } else { if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ } /*Count the frequencies of lit, len and dist codes*/ for(i = 0; i != lz77_encoded.size; ++i) { unsigned symbol = lz77_encoded.data[i]; ++frequencies_ll[symbol]; if(symbol > 256) { unsigned dist = lz77_encoded.data[i + 2]; ++frequencies_d[dist]; i += 3; } } frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); if(error) break; /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); if(error) break; numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); /*store the code lengths of both generated trees in bitlen_lld*/ numcodes_lld = numcodes_ll + numcodes_d; bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); /*numcodes_lld_e never needs more size than bitlen_lld*/ bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ numcodes_lld_e = 0; for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), 17 (3-10 zeroes), 18 (11-138 zeroes)*/ for(i = 0; i != numcodes_lld; ++i) { unsigned j = 0; /*amount of repetitions*/ while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { ++j; /*include the first zero*/ if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { bitlen_lld_e[numcodes_lld_e++] = 17; bitlen_lld_e[numcodes_lld_e++] = j - 3; } else /*repeat code 18 supports max 138 zeroes*/ { if(j > 138) j = 138; bitlen_lld_e[numcodes_lld_e++] = 18; bitlen_lld_e[numcodes_lld_e++] = j - 11; } i += (j - 1); } else if(j >= 3) /*repeat code for value other than zero*/ { size_t k; unsigned num = j / 6u, rest = j % 6u; bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; for(k = 0; k < num; ++k) { bitlen_lld_e[numcodes_lld_e++] = 16; bitlen_lld_e[numcodes_lld_e++] = 6 - 3; } if(rest >= 3) { bitlen_lld_e[numcodes_lld_e++] = 16; bitlen_lld_e[numcodes_lld_e++] = rest - 3; } else j -= rest; i += j; } else /*too short to benefit from repeat code*/ { bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; } } /*generate tree_cl, the huffmantree of huffmantrees*/ for(i = 0; i != numcodes_lld_e; ++i) { ++frequencies_cl[bitlen_lld_e[i]]; /*after a repeat code come the bits that specify the number of repetitions, those don't need to be in the frequencies_cl calculation*/ if(bitlen_lld_e[i] >= 16) ++i; } error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); if(error) break; /*compute amount of code-length-code-lengths to output*/ numcodes_cl = NUM_CODE_LENGTH_CODES; /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { numcodes_cl--; } /* Write everything into the output After the BFINAL and BTYPE, the dynamic block consists out of the following: - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN - (HCLEN+4)*3 bits code lengths of code length alphabet - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) - HDIST + 1 code lengths of distance alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) - compressed data - 256 (end code) */ /*Write block type*/ writeBits(writer, BFINAL, 1); writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ /*write the HLIT, HDIST and HCLEN values*/ /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies or in the loop for numcodes_cl above, which saves space. */ HLIT = (unsigned)(numcodes_ll - 257); HDIST = (unsigned)(numcodes_d - 1); HCLEN = (unsigned)(numcodes_cl - 4); writeBits(writer, HLIT, 5); writeBits(writer, HDIST, 5); writeBits(writer, HCLEN, 4); /*write the code lengths of the code length alphabet ("bitlen_cl")*/ for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); /*write the lengths of the lit/len AND the dist alphabet*/ for(i = 0; i != numcodes_lld_e; ++i) { writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); /*extra bits of repeat codes*/ if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); } /*write the compressed data symbols*/ writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); /*error: the length of the end code 256 must be larger than 0*/ if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); /*write the end code*/ writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); break; /*end of error-while*/ } /*cleanup*/ uivector_cleanup(&lz77_encoded); HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); HuffmanTree_cleanup(&tree_cl); lodepng_free(frequencies_ll); lodepng_free(frequencies_d); lodepng_free(frequencies_cl); lodepng_free(bitlen_lld); lodepng_free(bitlen_lld_e); return error; } static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, const LodePNGCompressSettings* settings, unsigned final) { HuffmanTree tree_ll; /*tree for literal values and length codes*/ HuffmanTree tree_d; /*tree for distance codes*/ unsigned BFINAL = final; unsigned error = 0; size_t i; HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); error = generateFixedLitLenTree(&tree_ll); if(!error) error = generateFixedDistanceTree(&tree_d); if(!error) { writeBits(writer, BFINAL, 1); writeBits(writer, 1, 1); /*first bit of BTYPE*/ writeBits(writer, 0, 1); /*second bit of BTYPE*/ if(settings->use_lz77) /*LZ77 encoded*/ { uivector lz77_encoded; uivector_init(&lz77_encoded); error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); uivector_cleanup(&lz77_encoded); } else /*no LZ77, but still will be Huffman compressed*/ { for(i = datapos; i < dataend; ++i) { writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); } } /*add END code*/ if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); } /*cleanup*/ HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); return error; } static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { unsigned error = 0; size_t i, blocksize, numdeflateblocks; Hash hash; LodePNGBitWriter writer; LodePNGBitWriter_init(&writer, out); if(settings->btype > 2) return 61; else if(settings->btype == 0) return deflateNoCompression(out, in, insize); else if(settings->btype == 1) blocksize = insize; else /*if(settings->btype == 2)*/ { /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ blocksize = insize / 8u + 8; if(blocksize < 65536) blocksize = 65536; if(blocksize > 262144) blocksize = 262144; } numdeflateblocks = (insize + blocksize - 1) / blocksize; if(numdeflateblocks == 0) numdeflateblocks = 1; error = hash_init(&hash, settings->windowsize); if(!error) { for(i = 0; i != numdeflateblocks && !error; ++i) { unsigned final = (i == numdeflateblocks - 1); size_t start = i * blocksize; size_t end = start + blocksize; if(end > insize) end = insize; if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); } } hash_cleanup(&hash); return error; } unsigned lodepng_deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_deflatev(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } static unsigned deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(settings->custom_deflate) { unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ return error ? 111 : 0; } else { return lodepng_deflate(out, outsize, in, insize, settings); } } #endif /*LODEPNG_COMPILE_DECODER*/ /* ////////////////////////////////////////////////////////////////////////// */ /* / Adler32 / */ /* ////////////////////////////////////////////////////////////////////////// */ static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { unsigned s1 = adler & 0xffffu; unsigned s2 = (adler >> 16u) & 0xffffu; while(len != 0u) { unsigned i; /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ unsigned amount = len > 5552u ? 5552u : len; len -= amount; for(i = 0; i != amount; ++i) { s1 += (*data++); s2 += s1; } s1 %= 65521u; s2 %= 65521u; } return (s2 << 16u) | s1; } /*Return the adler32 of the bytes data[0..len-1]*/ static unsigned adler32(const unsigned char* data, unsigned len) { return update_adler32(1u, data, len); } /* ////////////////////////////////////////////////////////////////////////// */ /* / Zlib / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_DECODER static unsigned lodepng_zlib_decompressv(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned error = 0; unsigned CM, CINFO, FDICT; if(insize < 2) return 53; /*error, size of zlib data too small*/ /*read information from zlib header*/ if((in[0] * 256 + in[1]) % 31 != 0) { /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ return 24; } CM = in[0] & 15; CINFO = (in[0] >> 4) & 15; /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ FDICT = (in[1] >> 5) & 1; /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ if(CM != 8 || CINFO > 7) { /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ return 25; } if(FDICT != 0) { /*error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary."*/ return 26; } error = inflatev(out, in + 2, insize - 2, settings); if(error) return error; if(!settings->ignore_adler32) { unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); unsigned checksum = adler32(out->data, (unsigned)(out->size)); if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ } return 0; /*no error*/ } unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } /*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned error; if(settings->custom_zlib) { error = settings->custom_zlib(out, outsize, in, insize, settings); if(error) { /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ error = 110; /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; } } else { ucvector v = ucvector_init(*out, *outsize); if(expected_size) { /*reserve the memory to avoid intermediate reallocations*/ ucvector_resize(&v, *outsize + expected_size); v.size = *outsize; } error = lodepng_zlib_decompressv(&v, in, insize, settings); *out = v.data; *outsize = v.size; } return error; } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { size_t i; unsigned error; unsigned char* deflatedata = 0; size_t deflatesize = 0; error = deflate(&deflatedata, &deflatesize, in, insize, settings); *out = NULL; *outsize = 0; if(!error) { *outsize = deflatesize + 6; *out = (unsigned char*)lodepng_malloc(*outsize); if(!*out) error = 83; /*alloc fail*/ } if(!error) { unsigned ADLER32 = adler32(in, (unsigned)insize); /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ unsigned FLEVEL = 0; unsigned FDICT = 0; unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; unsigned FCHECK = 31 - CMFFLG % 31; CMFFLG += FCHECK; (*out)[0] = (unsigned char)(CMFFLG >> 8); (*out)[1] = (unsigned char)(CMFFLG & 255); for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); } lodepng_free(deflatedata); return error; } /* compress using the default or custom zlib function */ static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(settings->custom_zlib) { unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ return error ? 111 : 0; } else { return lodepng_zlib_compress(out, outsize, in, insize, settings); } } #endif /*LODEPNG_COMPILE_ENCODER*/ #else /*no LODEPNG_COMPILE_ZLIB*/ #ifdef LODEPNG_COMPILE_DECODER static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ (void)expected_size; return settings->custom_zlib(out, outsize, in, insize, settings); } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ return settings->custom_zlib(out, outsize, in, insize, settings); } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_ENCODER /*this is a good tradeoff between speed and compression ratio*/ #define DEFAULT_WINDOWSIZE 2048 void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ settings->btype = 2; settings->use_lz77 = 1; settings->windowsize = DEFAULT_WINDOWSIZE; settings->minmatch = 3; settings->nicematch = 128; settings->lazymatching = 1; settings->custom_zlib = 0; settings->custom_deflate = 0; settings->custom_context = 0; } const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DECODER void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { settings->ignore_adler32 = 0; settings->ignore_nlen = 0; settings->max_output_size = 0; settings->custom_zlib = 0; settings->custom_inflate = 0; settings->custom_context = 0; } const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; #endif /*LODEPNG_COMPILE_DECODER*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // End of Zlib related code. Begin of PNG related code. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_PNG /* ////////////////////////////////////////////////////////////////////////// */ /* / CRC32 / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifndef LODEPNG_NO_COMPILE_CRC /* CRC polynomial: 0xedb88320 */ static unsigned lodepng_crc32_table[256] = { 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u }; /*Return the CRC of the bytes buf[0..len-1].*/ unsigned lodepng_crc32(const unsigned char* data, size_t length) { unsigned r = 0xffffffffu; size_t i; for(i = 0; i < length; ++i) { r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); } return r ^ 0xffffffffu; } #else /* !LODEPNG_NO_COMPILE_CRC */ unsigned lodepng_crc32(const unsigned char* data, size_t length); #endif /* !LODEPNG_NO_COMPILE_CRC */ /* ////////////////////////////////////////////////////////////////////////// */ /* / Reading and writing PNG color channel bits / */ /* ////////////////////////////////////////////////////////////////////////// */ /* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); ++(*bitpointer); return result; } /* TODO: make this faster */ static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { unsigned result = 0; size_t i; for(i = 0 ; i < nbits; ++i) { result <<= 1u; result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); } return result; } static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { /*the current bit in bitstream may be 0 or 1 for this to work*/ if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); ++(*bitpointer); } /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG chunks / */ /* ////////////////////////////////////////////////////////////////////////// */ unsigned lodepng_chunk_length(const unsigned char* chunk) { return lodepng_read32bitInt(&chunk[0]); } void lodepng_chunk_type(char type[5], const unsigned char* chunk) { unsigned i; for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; type[4] = 0; /*null termination char*/ } unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { if(lodepng_strlen(type) != 4) return 0; return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); } unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { return((chunk[4] & 32) != 0); } unsigned char lodepng_chunk_private(const unsigned char* chunk) { return((chunk[6] & 32) != 0); } unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { return((chunk[7] & 32) != 0); } unsigned char* lodepng_chunk_data(unsigned char* chunk) { return &chunk[8]; } const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { return &chunk[8]; } #ifndef LODEPNG_NO_COMPILE_CRC unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ unsigned checksum = lodepng_crc32(&chunk[4], length + 4); if(CRC != checksum) return 1; else return 0; } #endif void lodepng_chunk_generate_crc(unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); #ifndef LODEPNG_NO_COMPILE_CRC unsigned CRC = lodepng_crc32(&chunk[4], length + 4); #else unsigned CRC = 0; #endif lodepng_set32bitInt(chunk + 8 + length, CRC); } unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { size_t total_chunk_length; unsigned char* result; if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; result = chunk + total_chunk_length; if(result < chunk) return end; /*pointer overflow*/ return result; } } const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { size_t total_chunk_length; const unsigned char* result; if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; result = chunk + total_chunk_length; if(result < chunk) return end; /*pointer overflow*/ return result; } } unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { for(;;) { if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next(chunk, end); } } const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { for(;;) { if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next_const(chunk, end); } } unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { unsigned i; size_t total_chunk_length, new_length; unsigned char *chunk_start, *new_buffer; if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); if(!new_buffer) return 83; /*alloc fail*/ (*out) = new_buffer; (*outsize) = new_length; chunk_start = &(*out)[new_length - total_chunk_length]; for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; return 0; } /*Sets length and name and allocates the space for data and crc but does not set data or crc yet. Returns the start of the chunk in chunk. The start of the data is at chunk + 8. To finalize chunk, add the data, then use lodepng_chunk_generate_crc */ static unsigned lodepng_chunk_init(unsigned char** chunk, ucvector* out, unsigned length, const char* type) { size_t new_length = out->size; if(lodepng_addofl(new_length, length, &new_length)) return 77; if(lodepng_addofl(new_length, 12, &new_length)) return 77; if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ *chunk = out->data + new_length - length - 12u; /*1: length*/ lodepng_set32bitInt(*chunk, length); /*2: chunk name (4 letters)*/ lodepng_memcpy(*chunk + 4, type, 4); return 0; } /* like lodepng_chunk_create but with custom allocsize */ static unsigned lodepng_chunk_createv(ucvector* out, unsigned length, const char* type, const unsigned char* data) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); /*3: the data*/ lodepng_memcpy(chunk + 8, data, length); /*4: CRC (of the chunkname characters and the data)*/ lodepng_chunk_generate_crc(chunk); return 0; } unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, const char* type, const unsigned char* data) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_chunk_createv(&v, length, type, data); *out = v.data; *outsize = v.size; return error; } /* ////////////////////////////////////////////////////////////////////////// */ /* / Color types, channels, bits / */ /* ////////////////////////////////////////////////////////////////////////// */ /*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. Return value is a LodePNG error code.*/ static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { switch(colortype) { case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ default: return 31; /* invalid color type */ } return 0; /*allowed color type / bits combination*/ } static unsigned getNumColorChannels(LodePNGColorType colortype) { switch(colortype) { case LCT_GREY: return 1; case LCT_RGB: return 3; case LCT_PALETTE: return 1; case LCT_GREY_ALPHA: return 2; case LCT_RGBA: return 4; case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ default: return 0; /*invalid color type*/ } } static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { /*bits per pixel is amount of channels * bits per channel*/ return getNumColorChannels(colortype) * bitdepth; } /* ////////////////////////////////////////////////////////////////////////// */ void lodepng_color_mode_init(LodePNGColorMode* info) { info->key_defined = 0; info->key_r = info->key_g = info->key_b = 0; info->colortype = LCT_RGBA; info->bitdepth = 8; info->palette = 0; info->palettesize = 0; } /*allocates palette memory if needed, and initializes all colors to black*/ static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { size_t i; /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ /*the palette must have room for up to 256 colors with 4 bytes each.*/ if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); if(!info->palette) return; /*alloc fail*/ for(i = 0; i != 256; ++i) { /*Initialize all unused colors with black, the value used for invalid palette indices. This is an error according to the PNG spec, but common PNG decoders make it black instead. That makes color conversion slightly faster due to no error handling needed.*/ info->palette[i * 4 + 0] = 0; info->palette[i * 4 + 1] = 0; info->palette[i * 4 + 2] = 0; info->palette[i * 4 + 3] = 255; } } void lodepng_color_mode_cleanup(LodePNGColorMode* info) { lodepng_palette_clear(info); } unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { lodepng_color_mode_cleanup(dest); lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); if(source->palette) { dest->palette = (unsigned char*)lodepng_malloc(1024); if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); } return 0; } LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { LodePNGColorMode result; lodepng_color_mode_init(&result); result.colortype = colortype; result.bitdepth = bitdepth; return result; } static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { size_t i; if(a->colortype != b->colortype) return 0; if(a->bitdepth != b->bitdepth) return 0; if(a->key_defined != b->key_defined) return 0; if(a->key_defined) { if(a->key_r != b->key_r) return 0; if(a->key_g != b->key_g) return 0; if(a->key_b != b->key_b) return 0; } if(a->palettesize != b->palettesize) return 0; for(i = 0; i != a->palettesize * 4; ++i) { if(a->palette[i] != b->palette[i]) return 0; } return 1; } void lodepng_palette_clear(LodePNGColorMode* info) { if(info->palette) lodepng_free(info->palette); info->palette = 0; info->palettesize = 0; } unsigned lodepng_palette_add(LodePNGColorMode* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if(!info->palette) /*allocate palette if empty*/ { lodepng_color_mode_alloc_palette(info); if(!info->palette) return 83; /*alloc fail*/ } if(info->palettesize >= 256) { return 108; /*too many palette values*/ } info->palette[4 * info->palettesize + 0] = r; info->palette[4 * info->palettesize + 1] = g; info->palette[4 * info->palettesize + 2] = b; info->palette[4 * info->palettesize + 3] = a; ++info->palettesize; return 0; } /*calculate bits per pixel out of colortype and bitdepth*/ unsigned lodepng_get_bpp(const LodePNGColorMode* info) { return lodepng_get_bpp_lct(info->colortype, info->bitdepth); } unsigned lodepng_get_channels(const LodePNGColorMode* info) { return getNumColorChannels(info->colortype); } unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; } unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { return (info->colortype & 4) != 0; /*4 or 6*/ } unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { return info->colortype == LCT_PALETTE; } unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { size_t i; for(i = 0; i != info->palettesize; ++i) { if(info->palette[i * 4 + 3] < 255) return 1; } return 0; } unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { return info->key_defined || lodepng_is_alpha_type(info) || lodepng_has_palette_alpha(info); } static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); size_t n = (size_t)w * (size_t)h; return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; } size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); } #ifdef LODEPNG_COMPILE_PNG /*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, and in addition has one extra byte per line: the filter byte. So this gives a larger result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { /* + 1 for the filter byte, and possibly plus padding bits per line. */ /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; return (size_t)h * line; } #ifdef LODEPNG_COMPILE_DECODER /*Safely checks whether size_t overflow can be caused due to amount of pixels. This check is overcautious rather than precise. If this check indicates no overflow, you can safely compute in a size_t (but not an unsigned): -(size_t)w * (size_t)h * 8 -amount of bytes in IDAT (including filter, padding and Adam7 bytes) -amount of bytes in raw color model Returns 1 if overflow possible, 0 if not. */ static int lodepng_pixel_overflow(unsigned w, unsigned h, const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); size_t numpixels, total; size_t line; /* bytes per line in worst case */ if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ return 0; /* no overflow */ } #endif /*LODEPNG_COMPILE_DECODER*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static void LodePNGUnknownChunks_init(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; } static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); } static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { unsigned i; LodePNGUnknownChunks_cleanup(dest); for(i = 0; i != 3; ++i) { size_t j; dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ for(j = 0; j < src->unknown_chunks_size[i]; ++j) { dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; } } return 0; } /******************************************************************************/ static void LodePNGText_init(LodePNGInfo* info) { info->text_num = 0; info->text_keys = NULL; info->text_strings = NULL; } static void LodePNGText_cleanup(LodePNGInfo* info) { size_t i; for(i = 0; i != info->text_num; ++i) { string_cleanup(&info->text_keys[i]); string_cleanup(&info->text_strings[i]); } lodepng_free(info->text_keys); lodepng_free(info->text_strings); } static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->text_keys = NULL; dest->text_strings = NULL; dest->text_num = 0; for(i = 0; i != source->text_num; ++i) { CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); } return 0; } static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); if(new_keys) info->text_keys = new_keys; if(new_strings) info->text_strings = new_strings; if(!new_keys || !new_strings) return 83; /*alloc fail*/ ++info->text_num; info->text_keys[info->text_num - 1] = alloc_string(key); info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ return 0; } unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); } void lodepng_clear_text(LodePNGInfo* info) { LodePNGText_cleanup(info); } /******************************************************************************/ static void LodePNGIText_init(LodePNGInfo* info) { info->itext_num = 0; info->itext_keys = NULL; info->itext_langtags = NULL; info->itext_transkeys = NULL; info->itext_strings = NULL; } static void LodePNGIText_cleanup(LodePNGInfo* info) { size_t i; for(i = 0; i != info->itext_num; ++i) { string_cleanup(&info->itext_keys[i]); string_cleanup(&info->itext_langtags[i]); string_cleanup(&info->itext_transkeys[i]); string_cleanup(&info->itext_strings[i]); } lodepng_free(info->itext_keys); lodepng_free(info->itext_langtags); lodepng_free(info->itext_transkeys); lodepng_free(info->itext_strings); } static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->itext_keys = NULL; dest->itext_langtags = NULL; dest->itext_transkeys = NULL; dest->itext_strings = NULL; dest->itext_num = 0; for(i = 0; i != source->itext_num; ++i) { CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], source->itext_transkeys[i], source->itext_strings[i])); } return 0; } void lodepng_clear_itext(LodePNGInfo* info) { LodePNGIText_cleanup(info); } static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str, size_t size) { char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); if(new_keys) info->itext_keys = new_keys; if(new_langtags) info->itext_langtags = new_langtags; if(new_transkeys) info->itext_transkeys = new_transkeys; if(new_strings) info->itext_strings = new_strings; if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ ++info->itext_num; info->itext_keys[info->itext_num - 1] = alloc_string(key); info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); return 0; } unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str) { return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); } /* same as set but does not delete */ static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { if(profile_size == 0) return 100; /*invalid ICC profile size*/ info->iccp_name = alloc_string(name); info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ lodepng_memcpy(info->iccp_profile, profile, profile_size); info->iccp_profile_size = profile_size; return 0; /*ok*/ } unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { if(info->iccp_name) lodepng_clear_icc(info); info->iccp_defined = 1; return lodepng_assign_icc(info, name, profile, profile_size); } void lodepng_clear_icc(LodePNGInfo* info) { string_cleanup(&info->iccp_name); lodepng_free(info->iccp_profile); info->iccp_profile = NULL; info->iccp_profile_size = 0; info->iccp_defined = 0; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ void lodepng_info_init(LodePNGInfo* info) { lodepng_color_mode_init(&info->color); info->interlace_method = 0; info->compression_method = 0; info->filter_method = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS info->background_defined = 0; info->background_r = info->background_g = info->background_b = 0; LodePNGText_init(info); LodePNGIText_init(info); info->time_defined = 0; info->phys_defined = 0; info->gama_defined = 0; info->chrm_defined = 0; info->srgb_defined = 0; info->iccp_defined = 0; info->iccp_name = NULL; info->iccp_profile = NULL; LodePNGUnknownChunks_init(info); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } void lodepng_info_cleanup(LodePNGInfo* info) { lodepng_color_mode_cleanup(&info->color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS LodePNGText_cleanup(info); LodePNGIText_cleanup(info); lodepng_clear_icc(info); LodePNGUnknownChunks_cleanup(info); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { lodepng_info_cleanup(dest); lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); lodepng_color_mode_init(&dest->color); CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); if(source->iccp_defined) { CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); } LodePNGUnknownChunks_init(dest); CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ return 0; } /* ////////////////////////////////////////////////////////////////////////// */ /*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ unsigned p = index & m; in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ in = in << (bits * (m - p)); if(p == 0) out[index * bits / 8u] = in; else out[index * bits / 8u] |= in; } typedef struct ColorTree ColorTree; /* One node of a color tree This is the data structure used to count the number of unique colors and to get a palette index for a color. It's like an octree, but because the alpha channel is used too, each node has 16 instead of 8 children. */ struct ColorTree { ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ int index; /*the payload. Only has a meaningful value if this is in the last level*/ }; static void color_tree_init(ColorTree* tree) { lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); tree->index = -1; } static void color_tree_cleanup(ColorTree* tree) { int i; for(i = 0; i != 16; ++i) { if(tree->children[i]) { color_tree_cleanup(tree->children[i]); lodepng_free(tree->children[i]); } } } /*returns -1 if color not present, its index otherwise*/ static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { int bit = 0; for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); if(!tree->children[i]) return -1; else tree = tree->children[i]; } return tree ? tree->index : -1; } #ifdef LODEPNG_COMPILE_ENCODER static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { return color_tree_get(tree, r, g, b, a) >= 0; } #endif /*LODEPNG_COMPILE_ENCODER*/ /*color is not allowed to already exist. Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") Returns error code, or 0 if ok*/ static unsigned color_tree_add(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { int bit; for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); if(!tree->children[i]) { tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); if(!tree->children[i]) return 83; /*alloc fail*/ color_tree_init(tree->children[i]); } tree = tree->children[i]; } tree->index = (int)index; return 0; } /*put a pixel, given its RGBA color, into image of any color type*/ static unsigned rgba8ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if(mode->colortype == LCT_GREY) { unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ if(mode->bitdepth == 8) out[i] = gray; else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; else { /*take the most significant bits of gray*/ gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); addColorBits(out, i, mode->bitdepth, gray); } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { out[i * 3 + 0] = r; out[i * 3 + 1] = g; out[i * 3 + 2] = b; } else { out[i * 6 + 0] = out[i * 6 + 1] = r; out[i * 6 + 2] = out[i * 6 + 3] = g; out[i * 6 + 4] = out[i * 6 + 5] = b; } } else if(mode->colortype == LCT_PALETTE) { int index = color_tree_get(tree, r, g, b, a); if(index < 0) return 82; /*color not in palette*/ if(mode->bitdepth == 8) out[i] = index; else addColorBits(out, i, mode->bitdepth, (unsigned)index); } else if(mode->colortype == LCT_GREY_ALPHA) { unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ if(mode->bitdepth == 8) { out[i * 2 + 0] = gray; out[i * 2 + 1] = a; } else if(mode->bitdepth == 16) { out[i * 4 + 0] = out[i * 4 + 1] = gray; out[i * 4 + 2] = out[i * 4 + 3] = a; } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { out[i * 4 + 0] = r; out[i * 4 + 1] = g; out[i * 4 + 2] = b; out[i * 4 + 3] = a; } else { out[i * 8 + 0] = out[i * 8 + 1] = r; out[i * 8 + 2] = out[i * 8 + 3] = g; out[i * 8 + 4] = out[i * 8 + 5] = b; out[i * 8 + 6] = out[i * 8 + 7] = a; } } return 0; /*no error*/ } /*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ static void rgba16ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, unsigned short r, unsigned short g, unsigned short b, unsigned short a) { if(mode->colortype == LCT_GREY) { unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ out[i * 2 + 0] = (gray >> 8) & 255; out[i * 2 + 1] = gray & 255; } else if(mode->colortype == LCT_RGB) { out[i * 6 + 0] = (r >> 8) & 255; out[i * 6 + 1] = r & 255; out[i * 6 + 2] = (g >> 8) & 255; out[i * 6 + 3] = g & 255; out[i * 6 + 4] = (b >> 8) & 255; out[i * 6 + 5] = b & 255; } else if(mode->colortype == LCT_GREY_ALPHA) { unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ out[i * 4 + 0] = (gray >> 8) & 255; out[i * 4 + 1] = gray & 255; out[i * 4 + 2] = (a >> 8) & 255; out[i * 4 + 3] = a & 255; } else if(mode->colortype == LCT_RGBA) { out[i * 8 + 0] = (r >> 8) & 255; out[i * 8 + 1] = r & 255; out[i * 8 + 2] = (g >> 8) & 255; out[i * 8 + 3] = g & 255; out[i * 8 + 4] = (b >> 8) & 255; out[i * 8 + 5] = b & 255; out[i * 8 + 6] = (a >> 8) & 255; out[i * 8 + 7] = a & 255; } } /*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) { if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { *r = *g = *b = in[i]; if(mode->key_defined && *r == mode->key_r) *a = 0; else *a = 255; } else if(mode->bitdepth == 16) { *r = *g = *b = in[i * 2 + 0]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 255; } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = i * mode->bitdepth; unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); *r = *g = *b = (value * 255) / highest; if(mode->key_defined && value == mode->key_r) *a = 0; else *a = 255; } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; else *a = 255; } else { *r = in[i * 6 + 0]; *g = in[i * 6 + 2]; *b = in[i * 6 + 4]; if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 255; } } else if(mode->colortype == LCT_PALETTE) { unsigned index; if(mode->bitdepth == 8) index = in[i]; else { size_t j = i * mode->bitdepth; index = readBitsFromReversedStream(&j, in, mode->bitdepth); } /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ *r = mode->palette[index * 4 + 0]; *g = mode->palette[index * 4 + 1]; *b = mode->palette[index * 4 + 2]; *a = mode->palette[index * 4 + 3]; } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { *r = *g = *b = in[i * 2 + 0]; *a = in[i * 2 + 1]; } else { *r = *g = *b = in[i * 4 + 0]; *a = in[i * 4 + 2]; } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { *r = in[i * 4 + 0]; *g = in[i * 4 + 1]; *b = in[i * 4 + 2]; *a = in[i * 4 + 3]; } else { *r = in[i * 8 + 0]; *g = in[i * 8 + 2]; *b = in[i * 8 + 4]; *a = in[i * 8 + 6]; } } } /*Similar to getPixelColorRGBA8, but with all the for loops inside of the color mode test cases, optimized to convert the colors much faster, when converting to the common case of RGBA with 8 bit per channel. buffer must be RGBA with enough memory.*/ static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) { unsigned num_channels = 4; size_t i; if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i]; buffer[3] = 255; } if(mode->key_defined) { buffer -= numpixels * num_channels; for(i = 0; i != numpixels; ++i, buffer += num_channels) { if(buffer[0] == mode->key_r) buffer[3] = 0; } } } else if(mode->bitdepth == 16) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2]; buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; } } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; } } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { lodepng_memcpy(buffer, &in[i * 3], 3); buffer[3] = 255; } if(mode->key_defined) { buffer -= numpixels * num_channels; for(i = 0; i != numpixels; ++i, buffer += num_channels) { if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; } } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 6 + 0]; buffer[1] = in[i * 6 + 2]; buffer[2] = in[i * 6 + 4]; buffer[3] = mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; } } } else if(mode->colortype == LCT_PALETTE) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = in[i]; /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 4); } } else { size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 4); } } } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; buffer[3] = in[i * 2 + 1]; } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; buffer[3] = in[i * 4 + 2]; } } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { lodepng_memcpy(buffer, in, numpixels * 4); } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 8 + 0]; buffer[1] = in[i * 8 + 2]; buffer[2] = in[i * 8 + 4]; buffer[3] = in[i * 8 + 6]; } } } } /*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) { const unsigned num_channels = 3; size_t i; if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i]; } } else if(mode->bitdepth == 16) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2]; } } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; } } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { lodepng_memcpy(buffer, in, numpixels * 3); } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 6 + 0]; buffer[1] = in[i * 6 + 2]; buffer[2] = in[i * 6 + 4]; } } } else if(mode->colortype == LCT_PALETTE) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = in[i]; /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 3); } } else { size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 3); } } } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; } } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { lodepng_memcpy(buffer, &in[i * 4], 3); } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 8 + 0]; buffer[1] = in[i * 8 + 2]; buffer[2] = in[i * 8 + 4]; } } } } /*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with given color type, but the given color type must be 16-bit itself.*/ static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) { if(mode->colortype == LCT_GREY) { *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 65535; } else if(mode->colortype == LCT_RGB) { *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; if(mode->key_defined && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 65535; } else if(mode->colortype == LCT_GREY_ALPHA) { *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; } else if(mode->colortype == LCT_RGBA) { *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; } } unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h) { size_t i; ColorTree tree; size_t numpixels = (size_t)w * (size_t)h; unsigned error = 0; if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { return 107; /* error: must provide palette if input mode is palette */ } if(lodepng_color_mode_equal(mode_out, mode_in)) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); lodepng_memcpy(out, in, numbytes); return 0; } if(mode_out->colortype == LCT_PALETTE) { size_t palettesize = mode_out->palettesize; const unsigned char* palette = mode_out->palette; size_t palsize = (size_t)1u << mode_out->bitdepth; /*if the user specified output palette but did not give the values, assume they want the values of the input color type (assuming that one is palette). Note that we never create a new palette ourselves.*/ if(palettesize == 0) { palettesize = mode_in->palettesize; palette = mode_in->palette; /*if the input was also palette with same bitdepth, then the color types are also equal, so copy literally. This to preserve the exact indices that were in the PNG even in case there are duplicate colors in the palette.*/ if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); lodepng_memcpy(out, in, numbytes); return 0; } } if(palettesize < palsize) palsize = palettesize; color_tree_init(&tree); for(i = 0; i != palsize; ++i) { const unsigned char* p = &palette[i * 4]; error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); if(error) break; } } if(!error) { if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { for(i = 0; i != numpixels; ++i) { unsigned short r = 0, g = 0, b = 0, a = 0; getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); rgba16ToPixel(out, i, mode_out, r, g, b, a); } } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { getPixelColorsRGBA8(out, numpixels, in, mode_in); } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { getPixelColorsRGB8(out, numpixels, in, mode_in); } else { unsigned char r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); if(error) break; } } } if(mode_out->colortype == LCT_PALETTE) { color_tree_cleanup(&tree); } return error; } /* Converts a single rgb color without alpha from one type to another, color bits truncated to their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow function, do not use to process all pixels of an image. Alpha channel not supported on purpose: this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the specification it looks like bKGD should ignore the alpha values of the palette since it can use any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ unsigned lodepng_convert_rgb( unsigned* r_out, unsigned* g_out, unsigned* b_out, unsigned r_in, unsigned g_in, unsigned b_in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { unsigned r = 0, g = 0, b = 0; unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ unsigned shift = 16 - mode_out->bitdepth; if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { r = g = b = r_in * mul; } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { r = r_in * mul; g = g_in * mul; b = b_in * mul; } else if(mode_in->colortype == LCT_PALETTE) { if(r_in >= mode_in->palettesize) return 82; r = mode_in->palette[r_in * 4 + 0] * 257u; g = mode_in->palette[r_in * 4 + 1] * 257u; b = mode_in->palette[r_in * 4 + 2] * 257u; } else { return 31; } /* now convert to output format */ if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { *r_out = r >> shift ; } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { *r_out = r >> shift ; *g_out = g >> shift ; *b_out = b >> shift ; } else if(mode_out->colortype == LCT_PALETTE) { unsigned i; /* a 16-bit color cannot be in the palette */ if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; for(i = 0; i < mode_out->palettesize; i++) { unsigned j = i * 4; if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && (b >> 8) == mode_out->palette[j + 2]) { *r_out = i; return 0; } } return 82; } else { return 31; } return 0; } #ifdef LODEPNG_COMPILE_ENCODER void lodepng_color_stats_init(LodePNGColorStats* stats) { /*stats*/ stats->colored = 0; stats->key = 0; stats->key_r = stats->key_g = stats->key_b = 0; stats->alpha = 0; stats->numcolors = 0; stats->bits = 1; stats->numpixels = 0; /*settings*/ stats->allow_palette = 1; stats->allow_greyscale = 1; } /*function used for debug purposes with C++*/ /*void printColorStats(LodePNGColorStats* p) { std::cout << "colored: " << (int)p->colored << ", "; std::cout << "key: " << (int)p->key << ", "; std::cout << "key_r: " << (int)p->key_r << ", "; std::cout << "key_g: " << (int)p->key_g << ", "; std::cout << "key_b: " << (int)p->key_b << ", "; std::cout << "alpha: " << (int)p->alpha << ", "; std::cout << "numcolors: " << (int)p->numcolors << ", "; std::cout << "bits: " << (int)p->bits << std::endl; }*/ /*Returns how many bits needed to represent given value (max 8 bit)*/ static unsigned getValueRequiredBits(unsigned char value) { if(value == 0 || value == 255) return 1; /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; return 8; } /*stats must already have been inited. */ unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, const unsigned char* in, unsigned w, unsigned h, const LodePNGColorMode* mode_in) { size_t i; ColorTree tree; size_t numpixels = (size_t)w * (size_t)h; unsigned error = 0; /* mark things as done already if it would be impossible to have a more expensive case */ unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; unsigned numcolors_done = 0; unsigned bpp = lodepng_get_bpp(mode_in); unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; unsigned sixteen = 0; /* whether the input image is 16 bit */ unsigned maxnumcolors = 257; if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); stats->numpixels += numpixels; /*if palette not allowed, no need to compute numcolors*/ if(!stats->allow_palette) numcolors_done = 1; color_tree_init(&tree); /*If the stats was already filled in from previous data, fill its palette in tree and mark things as done already if we know they are the most expensive case already*/ if(stats->alpha) alpha_done = 1; if(stats->colored) colored_done = 1; if(stats->bits == 16) numcolors_done = 1; if(stats->bits >= bpp) bits_done = 1; if(stats->numcolors >= maxnumcolors) numcolors_done = 1; if(!numcolors_done) { for(i = 0; i < stats->numcolors; i++) { const unsigned char* color = &stats->palette[i * 4]; error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); if(error) goto cleanup; } } /*Check if the 16-bit input is truly 16-bit*/ if(mode_in->bitdepth == 16 && !sixteen) { unsigned short r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { stats->bits = 16; sixteen = 1; bits_done = 1; numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ break; } } } if(sixteen) { unsigned short r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if(!colored_done && (r != g || r != b)) { stats->colored = 1; colored_done = 1; } if(!alpha_done) { unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { stats->alpha = 1; stats->key = 0; alpha_done = 1; } else if(a == 0 && !stats->alpha && !stats->key) { stats->key = 1; stats->key_r = r; stats->key_g = g; stats->key_b = b; } else if(a == 65535 && stats->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; } } if(alpha_done && numcolors_done && colored_done && bits_done) break; } if(stats->key && !stats->alpha) { for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; } } } } else /* < 16-bit */ { unsigned char r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); if(!bits_done && stats->bits < 8) { /*only r is checked, < 8 bits is only relevant for grayscale*/ unsigned bits = getValueRequiredBits(r); if(bits > stats->bits) stats->bits = bits; } bits_done = (stats->bits >= bpp); if(!colored_done && (r != g || r != b)) { stats->colored = 1; colored_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ } if(!alpha_done) { unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); if(a != 255 && (a != 0 || (stats->key && !matchkey))) { stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } else if(a == 0 && !stats->alpha && !stats->key) { stats->key = 1; stats->key_r = r; stats->key_g = g; stats->key_b = b; } else if(a == 255 && stats->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } } if(!numcolors_done) { if(!color_tree_has(&tree, r, g, b, a)) { error = color_tree_add(&tree, r, g, b, a, stats->numcolors); if(error) goto cleanup; if(stats->numcolors < 256) { unsigned char* p = stats->palette; unsigned n = stats->numcolors; p[n * 4 + 0] = r; p[n * 4 + 1] = g; p[n * 4 + 2] = b; p[n * 4 + 3] = a; } ++stats->numcolors; numcolors_done = stats->numcolors >= maxnumcolors; } } if(alpha_done && numcolors_done && colored_done && bits_done) break; } if(stats->key && !stats->alpha) { for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } } } /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ stats->key_r += (stats->key_r << 8); stats->key_g += (stats->key_g << 8); stats->key_b += (stats->key_b << 8); } cleanup: color_tree_cleanup(&tree); return error; } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit (with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for all pixels of an image but only for a few additional values. */ static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, unsigned r, unsigned g, unsigned b, unsigned a) { unsigned error = 0; unsigned char image[8]; LodePNGColorMode mode; lodepng_color_mode_init(&mode); image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; mode.bitdepth = 16; mode.colortype = LCT_RGBA; error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); lodepng_color_mode_cleanup(&mode); return error; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*Computes a minimal PNG color model that can contain all colors as indicated by the stats. The stats should be computed with lodepng_compute_color_stats. mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... This is used if auto_convert is enabled (it is by default). */ static unsigned auto_choose_color(LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, const LodePNGColorStats* stats) { unsigned error = 0; unsigned palettebits; size_t i, n; size_t numpixels = stats->numpixels; unsigned palette_ok, gray_ok; unsigned alpha = stats->alpha; unsigned key = stats->key; unsigned bits = stats->bits; mode_out->key_defined = 0; if(key && numpixels <= 16) { alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ key = 0; if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } gray_ok = !stats->colored; if(!stats->allow_greyscale) gray_ok = 0; if(!gray_ok && bits < 8) bits = 8; n = stats->numcolors; palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ if(!stats->allow_palette) palette_ok = 0; if(palette_ok) { const unsigned char* p = stats->palette; lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ for(i = 0; i != stats->numcolors; ++i) { error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); if(error) break; } mode_out->colortype = LCT_PALETTE; mode_out->bitdepth = palettebits; if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize && mode_in->bitdepth == mode_out->bitdepth) { /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ lodepng_color_mode_cleanup(mode_out); lodepng_color_mode_copy(mode_out, mode_in); } } else /*8-bit or 16-bit per channel*/ { mode_out->bitdepth = bits; mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) : (gray_ok ? LCT_GREY : LCT_RGB); if(key) { unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ mode_out->key_r = stats->key_r & mask; mode_out->key_g = stats->key_g & mask; mode_out->key_b = stats->key_b & mask; mode_out->key_defined = 1; } } return error; } #endif /* #ifdef LODEPNG_COMPILE_ENCODER */ /* Paeth predictor, used by PNG filter type 4 The parameters are of type short, but should come from unsigned chars, the shorts are only needed to make the paeth calculation correct. */ static unsigned char paethPredictor(short a, short b, short c) { short pa = LODEPNG_ABS(b - c); short pb = LODEPNG_ABS(a - c); short pc = LODEPNG_ABS(a + b - c - c); /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ if(pb < pa) { a = b; pa = pb; } return (pc < pa) ? c : a; } /*shared values used by multiple Adam7 related functions*/ static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ /* Outputs various dimensions and positions in the image related to the Adam7 reduced images. passw: output containing the width of the 7 passes passh: output containing the height of the 7 passes filter_passstart: output containing the index of the start and end of each reduced image with filter bytes padded_passstart output containing the index of the start and end of each reduced image when without filter bytes but with padded scanlines passstart: output containing the index of the start and end of each reduced image without padding between scanlines, but still padding between the images w, h: width and height of non-interlaced image bpp: bits per pixel "padded" is only relevant if bpp is less than 8 and a scanline or image does not end at a full byte */ static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ unsigned i; /*calculate width and height in pixels of each pass*/ for(i = 0; i != 7; ++i) { passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; if(passw[i] == 0) passh[i] = 0; if(passh[i] == 0) passw[i] = 0; } filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; for(i = 0; i != 7; ++i) { /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ filter_passstart[i + 1] = filter_passstart[i] + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); /*bits padded if needed to fill full byte at end of each scanline*/ padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); /*only padded at end of reduced image*/ passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; } } #ifdef LODEPNG_COMPILE_DECODER /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG Decoder / */ /* ////////////////////////////////////////////////////////////////////////// */ /*read the information from the header and store it in the LodePNGInfo. return value is error*/ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { unsigned width, height; LodePNGInfo* info = &state->info_png; if(insize == 0 || in == 0) { CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ } if(insize < 33) { CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ } /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ /* TODO: remove this. One should use a new LodePNGState for new sessions */ lodepng_info_cleanup(info); lodepng_info_init(info); if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ } if(lodepng_chunk_length(in + 8) != 13) { CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ } if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ } /*read the values given in the header*/ width = lodepng_read32bitInt(&in[16]); height = lodepng_read32bitInt(&in[20]); /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ if(w) *w = width; if(h) *h = height; info->color.bitdepth = in[24]; info->color.colortype = (LodePNGColorType)in[25]; info->compression_method = in[26]; info->filter_method = in[27]; info->interlace_method = in[28]; /*errors returned only after the parsing so other values are still output*/ /*error: invalid image size*/ if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); /*error: invalid colortype or bitdepth combination*/ state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); if(state->error) return state->error; /*error: only compression method 0 is allowed in the specification*/ if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); /*error: only filter method 0 is allowed in the specification*/ if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); /*error: only interlace methods 0 and 1 exist in the specification*/ if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); #ifndef LODEPNG_NO_COMPILE_CRC if(!state->decoder.ignore_crc) { unsigned CRC = lodepng_read32bitInt(&in[29]); unsigned checksum = lodepng_crc32(&in[12], 17); if(CRC != checksum) { CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ } } #endif return state->error; } static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length) { /* For PNG filter method 0 unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works byte per byte (bytewidth = 1) precon is the previous unfiltered scanline, recon the result, scanline the current one the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead recon and scanline MAY be the same memory address! precon must be disjoint. */ size_t i; switch(filterType) { case 0: for(i = 0; i != length; ++i) recon[i] = scanline[i]; break; case 1: { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; break; } case 2: if(precon) { for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; } else { for(i = 0; i != length; ++i) recon[i] = scanline[i]; } break; case 3: if(precon) { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds too much code. Whether this speeds up anything depends on compiler and settings. */ if(bytewidth >= 4) { for(; i + 3 < length; i += 4, j += 4) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); recon[i + 2] = s2 + ((r2 + p2) >> 1u); recon[i + 3] = s3 + ((r3 + p3) >> 1u); } } else if(bytewidth >= 3) { for(; i + 2 < length; i += 3, j += 3) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); recon[i + 2] = s2 + ((r2 + p2) >> 1u); } } else if(bytewidth >= 2) { for(; i + 1 < length; i += 2, j += 2) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); } } for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); } else { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); } break; case 4: if(precon) { size_t j = 0; for(i = 0; i != bytewidth; ++i) { recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ } /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that adds too much code. Whether this speeds up anything depends on compiler and settings. */ if(bytewidth >= 4) { for(; i + 3 < length; i += 4, j += 4) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; recon[i + 0] = s0 + paethPredictor(r0, p0, q0); recon[i + 1] = s1 + paethPredictor(r1, p1, q1); recon[i + 2] = s2 + paethPredictor(r2, p2, q2); recon[i + 3] = s3 + paethPredictor(r3, p3, q3); } } else if(bytewidth >= 3) { for(; i + 2 < length; i += 3, j += 3) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; recon[i + 0] = s0 + paethPredictor(r0, p0, q0); recon[i + 1] = s1 + paethPredictor(r1, p1, q1); recon[i + 2] = s2 + paethPredictor(r2, p2, q2); } } else if(bytewidth >= 2) { for(; i + 1 < length; i += 2, j += 2) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; recon[i + 0] = s0 + paethPredictor(r0, p0, q0); recon[i + 1] = s1 + paethPredictor(r1, p1, q1); } } for(; i != length; ++i, ++j) { recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); } } else { size_t j = 0; for(i = 0; i != bytewidth; ++i) { recon[i] = scanline[i]; } for(i = bytewidth; i != length; ++i, ++j) { /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ recon[i] = (scanline[i] + recon[j]); } } break; default: return 36; /*error: invalid filter type given*/ } return 0; } static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { /* For PNG filter method 0 this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) */ unsigned y; unsigned char* prevline = 0; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ size_t bytewidth = (bpp + 7u) / 8u; /*the width of a scanline in bytes, not including the filter type*/ size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; for(y = 0; y < h; ++y) { size_t outindex = linebytes * y; size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ unsigned char filterType = in[inindex]; CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); prevline = &out[outindex]; } return 0; } /* in: Adam7 interlaced image, with no padding bits between scanlines, but between reduced images so that each reduced image starts at a byte. out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h bpp: bits per pixel out has the following size in bits: w * h * bpp. in is possibly bigger due to padding bits between reduced images. out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation (because that's likely a little bit faster) NOTE: comments about padding bits are only relevant if bpp < 8 */ static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); if(bpp >= 8) { for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8u; for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } } } } } static void removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) { /* After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers for the Adam7 code, the color convert code and the output to the user. in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 only useful if (ilinebits - olinebits) is a value in the range 1..7 */ unsigned y; size_t diff = ilinebits - olinebits; size_t ibp = 0, obp = 0; /*input and output bit pointers*/ for(y = 0; y < h; ++y) { size_t x; for(x = 0; x < olinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } ibp += diff; } } /*out must be buffer big enough to contain full image, and in must contain the full decompressed data from the IDAT chunks (with filter index bytes and possible padding bits) return value is error*/ static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png) { /* This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. Steps: *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace NOTE: the in buffer will be overwritten with intermediate data! */ unsigned bpp = lodepng_get_bpp(&info_png->color); if(bpp == 0) return 31; /*error: invalid colortype*/ if(info_png->interlace_method == 0) { if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); } /*we can immediately filter into the out buffer, no other steps needed*/ else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); for(i = 0; i != 7; ++i) { CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, move bytes instead of bits or move not at all*/ if(bpp < 8) { /*remove padding bits in scanlines; after this there still may be padding bits between the different reduced images: each reduced image still starts nicely at a byte*/ removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); } } Adam7_deinterlace(out, in, w, h, bpp); } return 0; } static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned pos = 0, i; color->palettesize = chunkLength / 3u; if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ lodepng_color_mode_alloc_palette(color); if(!color->palette && color->palettesize) { color->palettesize = 0; return 83; /*alloc fail*/ } for(i = 0; i != color->palettesize; ++i) { color->palette[4 * i + 0] = data[pos++]; /*R*/ color->palette[4 * i + 1] = data[pos++]; /*G*/ color->palette[4 * i + 2] = data[pos++]; /*B*/ color->palette[4 * i + 3] = 255; /*alpha*/ } return 0; /* OK */ } static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned i; if(color->colortype == LCT_PALETTE) { /*error: more alpha values given than there are palette entries*/ if(chunkLength > color->palettesize) return 39; for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; } else if(color->colortype == LCT_GREY) { /*error: this chunk must be 2 bytes for grayscale image*/ if(chunkLength != 2) return 30; color->key_defined = 1; color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; } else if(color->colortype == LCT_RGB) { /*error: this chunk must be 6 bytes for RGB image*/ if(chunkLength != 6) return 41; color->key_defined = 1; color->key_r = 256u * data[0] + data[1]; color->key_g = 256u * data[2] + data[3]; color->key_b = 256u * data[4] + data[5]; } else return 42; /*error: tRNS chunk not allowed for other color models*/ return 0; /* OK */ } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*background color chunk (bKGD)*/ static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(info->color.colortype == LCT_PALETTE) { /*error: this chunk must be 1 byte for indexed color image*/ if(chunkLength != 1) return 43; /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ if(data[0] >= info->color.palettesize) return 103; info->background_defined = 1; info->background_r = info->background_g = info->background_b = data[0]; } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { /*error: this chunk must be 2 bytes for grayscale image*/ if(chunkLength != 2) return 44; /*the values are truncated to bitdepth in the PNG file*/ info->background_defined = 1; info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { /*error: this chunk must be 6 bytes for grayscale image*/ if(chunkLength != 6) return 45; /*the values are truncated to bitdepth in the PNG file*/ info->background_defined = 1; info->background_r = 256u * data[0] + data[1]; info->background_g = 256u * data[2] + data[3]; info->background_b = 256u * data[4] + data[5]; } return 0; /* OK */ } /*text chunk (tEXt)*/ static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { unsigned error = 0; char *key = 0, *str = 0; while(!error) /*not really a while loop, only used to break on error*/ { unsigned length, string2_begin; length = 0; while(length < chunkLength && data[length] != 0) ++length; /*even though it's not allowed by the standard, no error is thrown if there's no null termination char, if the text is empty*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; string2_begin = length + 1; /*skip keyword null terminator*/ length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); str = (char*)lodepng_malloc(length + 1); if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(str, data + string2_begin, length); str[length] = 0; error = lodepng_add_text(info, key, str); break; } lodepng_free(key); lodepng_free(str); return error; } /*compressed text chunk (zTXt)*/ static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, string2_begin; char *key = 0; unsigned char* str = 0; size_t size = 0; while(!error) /*not really a while loop, only used to break on error*/ { for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ string2_begin = length + 2; if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ length = (unsigned)chunkLength - string2_begin; zlibsettings.max_output_size = decoder->max_text_size; /*will fail if zlib error, e.g. if length is too small*/ error = zlib_decompress(&str, &size, 0, &data[string2_begin], length, &zlibsettings); /*error: compressed text larger than decoder->max_text_size*/ if(error && size > zlibsettings.max_output_size) error = 112; if(error) break; error = lodepng_add_text_sized(info, key, (char*)str, size); break; } lodepng_free(key); lodepng_free(str); return error; } /*international text chunk (iTXt)*/ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, begin, compressed; char *key = 0, *langtag = 0, *transkey = 0; while(!error) /*not really a while loop, only used to break on error*/ { /*Quick check if the chunk length isn't too small. Even without check it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ /*read the key*/ for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; /*read the compression method*/ compressed = data[length + 1]; if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ /*even though it's not allowed by the standard, no error is thrown if there's no null termination char, if the text is empty for the next 3 texts*/ /*read the langtag*/ begin = length + 3; length = 0; for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; langtag = (char*)lodepng_malloc(length + 1); if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(langtag, data + begin, length); langtag[length] = 0; /*read the transkey*/ begin += length + 1; length = 0; for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; transkey = (char*)lodepng_malloc(length + 1); if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(transkey, data + begin, length); transkey[length] = 0; /*read the actual text*/ begin += length + 1; length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; if(compressed) { unsigned char* str = 0; size_t size = 0; zlibsettings.max_output_size = decoder->max_text_size; /*will fail if zlib error, e.g. if length is too small*/ error = zlib_decompress(&str, &size, 0, &data[begin], length, &zlibsettings); /*error: compressed text larger than decoder->max_text_size*/ if(error && size > zlibsettings.max_output_size) error = 112; if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); lodepng_free(str); } else { error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); } break; } lodepng_free(key); lodepng_free(langtag); lodepng_free(transkey); return error; } static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ info->time_defined = 1; info->time.year = 256u * data[0] + data[1]; info->time.month = data[2]; info->time.day = data[3]; info->time.hour = data[4]; info->time.minute = data[5]; info->time.second = data[6]; return 0; /* OK */ } static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ info->phys_defined = 1; info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; info->phys_unit = data[8]; return 0; /* OK */ } static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ info->gama_defined = 1; info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; return 0; /* OK */ } static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ info->chrm_defined = 1; info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; return 0; /* OK */ } static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ info->srgb_defined = 1; info->srgb_intent = data[0]; return 0; /* OK */ } static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; size_t size = 0; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, string2_begin; info->iccp_defined = 1; if(info->iccp_name) lodepng_clear_icc(info); for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ if(length < 1 || length > 79) return 89; /*keyword too short or long*/ info->iccp_name = (char*)lodepng_malloc(length + 1); if(!info->iccp_name) return 83; /*alloc fail*/ info->iccp_name[length] = 0; for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ string2_begin = length + 2; if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ length = (unsigned)chunkLength - string2_begin; zlibsettings.max_output_size = decoder->max_icc_size; error = zlib_decompress(&info->iccp_profile, &size, 0, &data[string2_begin], length, &zlibsettings); /*error: ICC profile larger than decoder->max_icc_size*/ if(error && size > zlibsettings.max_output_size) error = 113; info->iccp_profile_size = size; if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ return error; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, const unsigned char* in, size_t insize) { const unsigned char* chunk = in + pos; unsigned chunkLength; const unsigned char* data; unsigned unhandled = 0; unsigned error = 0; if(pos + 4 > insize) return 30; chunkLength = lodepng_chunk_length(chunk); if(chunkLength > 2147483647) return 63; data = lodepng_chunk_data_const(chunk); if(data + chunkLength + 4 > in + insize) return 30; if(lodepng_chunk_type_equals(chunk, "PLTE")) { error = readChunk_PLTE(&state->info_png.color, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { error = readChunk_tRNS(&state->info_png.color, data, chunkLength); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { error = readChunk_bKGD(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { error = readChunk_tEXt(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tIME")) { error = readChunk_tIME(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { error = readChunk_pHYs(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { error = readChunk_gAMA(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { error = readChunk_cHRM(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { error = readChunk_sRGB(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else { /* unhandled chunk is ok (is not an error) */ unhandled = 1; } if(!error && !unhandled && !state->decoder.ignore_crc) { #ifndef LODEPNG_NO_COMPILE_CRC if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ #endif } return error; } /*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { unsigned char IEND = 0; const unsigned char* chunk; unsigned char* idat; /*the data from idat chunks, zlib compressed*/ size_t idatsize = 0; unsigned char* scanlines = 0; size_t scanlines_size = 0, expected_size = 0; size_t outsize = 0; /*for unknown chunk order*/ unsigned unknown = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /* safe output values in case error happens */ *out = 0; *w = *h = 0; state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ if(state->error) return; if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ } /*the input filesize is a safe upper bound for the sum of idat chunks size*/ idat = (unsigned char*)lodepng_malloc(insize); if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ chunk = &in[33]; /*first byte of the first chunk after the header*/ /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer*/ while(!IEND && !state->error) { unsigned chunkLength; const unsigned char* data; /*the data in the chunk*/ /*error: size of the in buffer too small to contain next chunk*/ if((size_t)((chunk - in) + 12) > insize || chunk < in) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 30); } /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ chunkLength = lodepng_chunk_length(chunk); /*error: chunk length larger than the max PNG chunk size*/ if(chunkLength > 2147483647) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 63); } if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ } data = lodepng_chunk_data_const(chunk); unknown = 0; /*IDAT chunk, containing compressed image data*/ if(lodepng_chunk_type_equals(chunk, "IDAT")) { size_t newsize; if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); if(newsize > insize) CERROR_BREAK(state->error, 95); lodepng_memcpy(idat + idatsize, data, chunkLength); idatsize += chunkLength; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 3; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else if(lodepng_chunk_type_equals(chunk, "IEND")) { /*IEND chunk*/ IEND = 1; } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { /*palette chunk (PLTE)*/ state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); if(state->error) break; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 2; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that affects the alpha channel of pixels. */ state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); if(state->error) break; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*background color chunk (bKGD)*/ } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { state->error = readChunk_bKGD(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { /*text chunk (tEXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_tEXt(&state->info_png, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { /*compressed text chunk (zTXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { /*international text chunk (iTXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "tIME")) { state->error = readChunk_tIME(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { state->error = readChunk_pHYs(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { state->error = readChunk_gAMA(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { state->error = readChunk_cHRM(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { state->error = readChunk_sRGB(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { CERROR_BREAK(state->error, 69); } unknown = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(state->decoder.remember_unknown_chunks) { state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); if(state->error) break; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { #ifndef LODEPNG_NO_COMPILE_CRC if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ #endif } if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); } if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ } if(!state->error) { /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. If the decompressed size does not match the prediction, the image must be corrupt.*/ if(state->info_png.interlace_method == 0) { size_t bpp = lodepng_get_bpp(&state->info_png.color); expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); } else { size_t bpp = lodepng_get_bpp(&state->info_png.color); /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ expected_size = 0; expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); } if(expected_size > LODEPNG_IMAGE_DATA_SIZE_MAX) { state->error = 114; } } if (!state->error) { state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); } if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ lodepng_free(idat); if(!state->error) { outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); if (outsize > LODEPNG_IMAGE_DATA_SIZE_MAX) { state->error = 114; } } if(!state->error) { *out = (unsigned char*)lodepng_malloc(outsize); if(!*out) state->error = 83; /*alloc fail*/ } if(!state->error) { lodepng_memset(*out, 0, outsize); state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); } lodepng_free(scanlines); } unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { *out = 0; decodeGeneric(out, w, h, state, in, insize); if(state->error) return state->error; if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { /*same color type, no copying or converting of data needed*/ /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype the raw image has to the end user*/ if(!state->decoder.color_convert) { state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); if(state->error) return state->error; } } else { /*color conversion needed*/ unsigned char* data = *out; size_t outsize; /*TODO: check if this works according to the statement in the documentation: "The converter can convert from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) && !(state->info_raw.bitdepth == 8)) { return 56; /*unsupported color mode conversion*/ } outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); *out = (unsigned char*)lodepng_malloc(outsize); if(!(*out)) { state->error = 83; /*alloc fail*/ } else state->error = lodepng_convert(*out, data, &state->info_raw, &state->info_png.color, *w, *h); lodepng_free(data); } return state->error; } unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*disable reading things that this function doesn't output*/ state.decoder.read_text_chunks = 0; state.decoder.remember_unknown_chunks = 0; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ error = lodepng_decode(out, w, h, &state, in, insize); lodepng_state_cleanup(&state); return error; } unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); } unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer = 0; size_t buffersize; unsigned error; /* safe output values in case error happens */ *out = 0; *w = *h = 0; error = lodepng_load_file(&buffer, &buffersize, filename); if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); lodepng_free(buffer); return error; } unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); } unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { settings->color_convert = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS settings->read_text_chunks = 1; settings->remember_unknown_chunks = 0; settings->max_text_size = 16777216; settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ settings->ignore_crc = 0; settings->ignore_critical = 0; settings->ignore_end = 0; lodepng_decompress_settings_init(&settings->zlibsettings); } #endif /*LODEPNG_COMPILE_DECODER*/ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) void lodepng_state_init(LodePNGState* state) { #ifdef LODEPNG_COMPILE_DECODER lodepng_decoder_settings_init(&state->decoder); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER lodepng_encoder_settings_init(&state->encoder); #endif /*LODEPNG_COMPILE_ENCODER*/ lodepng_color_mode_init(&state->info_raw); lodepng_info_init(&state->info_png); state->error = 1; } void lodepng_state_cleanup(LodePNGState* state) { lodepng_color_mode_cleanup(&state->info_raw); lodepng_info_cleanup(&state->info_png); } void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { lodepng_state_cleanup(dest); *dest = *source; lodepng_color_mode_init(&dest->info_raw); lodepng_info_init(&dest->info_png); dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; } #endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ #ifdef LODEPNG_COMPILE_ENCODER /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG Encoder / */ /* ////////////////////////////////////////////////////////////////////////// */ static unsigned writeSignature(ucvector* out) { size_t pos = out->size; const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; /*8 bytes PNG signature, aka the magic bytes*/ if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ lodepng_memcpy(out->data + pos, signature, 8); return 0; } static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { unsigned char *chunk, *data; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); data = chunk + 8; lodepng_set32bitInt(data + 0, w); /*width*/ lodepng_set32bitInt(data + 4, h); /*height*/ data[8] = (unsigned char)bitdepth; /*bit depth*/ data[9] = (unsigned char)colortype; /*color type*/ data[10] = 0; /*compression method*/ data[11] = 0; /*filter method*/ data[12] = interlace_method; /*interlace method*/ lodepng_chunk_generate_crc(chunk); return 0; } /* only adds the chunk if needed (there is a key or palette with alpha) */ static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { unsigned char* chunk; size_t i, j = 8; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); for(i = 0; i != info->palettesize; ++i) { /*add all channels except alpha channel*/ chunk[j++] = info->palette[i * 4 + 0]; chunk[j++] = info->palette[i * 4 + 1]; chunk[j++] = info->palette[i * 4 + 2]; } lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { unsigned char* chunk = 0; if(info->colortype == LCT_PALETTE) { size_t i, amount = info->palettesize; /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ for(i = info->palettesize; i != 0; --i) { if(info->palette[4 * (i - 1) + 3] != 255) break; --amount; } if(amount) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); /*add the alpha channel values from the palette*/ for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; } } else if(info->colortype == LCT_GREY) { if(info->key_defined) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); chunk[8] = (unsigned char)(info->key_r >> 8); chunk[9] = (unsigned char)(info->key_r & 255); } } else if(info->colortype == LCT_RGB) { if(info->key_defined) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); chunk[8] = (unsigned char)(info->key_r >> 8); chunk[9] = (unsigned char)(info->key_r & 255); chunk[10] = (unsigned char)(info->key_g >> 8); chunk[11] = (unsigned char)(info->key_g & 255); chunk[12] = (unsigned char)(info->key_b >> 8); chunk[13] = (unsigned char)(info->key_b & 255); } } if(chunk) lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* zlib = 0; size_t zlibsize = 0; error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); if(!error) { error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); } lodepng_free(zlib); return error; } static unsigned addChunk_IEND(ucvector* out) { return lodepng_chunk_createv(out, 0, "IEND", 0); } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { unsigned char* chunk = 0; size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); size_t size = keysize + 1 + textsize; if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); lodepng_memcpy(chunk + 8, keyword, keysize); chunk[8 + keysize] = 0; /*null termination char*/ lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t textsize = lodepng_strlen(textstring); size_t keysize = lodepng_strlen(keyword); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ error = zlib_compress(&compressed, &compressedsize, (const unsigned char*)textstring, textsize, zlibsettings); if(!error) { size_t size = keysize + 2 + compressedsize; error = lodepng_chunk_init(&chunk, out, size, "zTXt"); } if(!error) { lodepng_memcpy(chunk + 8, keyword, keysize); chunk[8 + keysize] = 0; /*null termination char*/ chunk[9 + keysize] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t textsize = lodepng_strlen(textstring); size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ if(compress) { error = zlib_compress(&compressed, &compressedsize, (const unsigned char*)textstring, textsize, zlibsettings); } if(!error) { size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); error = lodepng_chunk_init(&chunk, out, size, "iTXt"); } if(!error) { size_t pos = 8; lodepng_memcpy(chunk + pos, keyword, keysize); pos += keysize; chunk[pos++] = 0; /*null termination char*/ chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ chunk[pos++] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + pos, langtag, langsize); pos += langsize; chunk[pos++] = 0; /*null termination char*/ lodepng_memcpy(chunk + pos, transkey, transsize); pos += transsize; chunk[pos++] = 0; /*null termination char*/ if(compress) { lodepng_memcpy(chunk + pos, compressed, compressedsize); } else { lodepng_memcpy(chunk + pos, textstring, textsize); } lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk = 0; if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); chunk[8] = (unsigned char)(info->background_r >> 8); chunk[9] = (unsigned char)(info->background_r & 255); } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); chunk[8] = (unsigned char)(info->background_r >> 8); chunk[9] = (unsigned char)(info->background_r & 255); chunk[10] = (unsigned char)(info->background_g >> 8); chunk[11] = (unsigned char)(info->background_g & 255); chunk[12] = (unsigned char)(info->background_b >> 8); chunk[13] = (unsigned char)(info->background_b & 255); } else if(info->color.colortype == LCT_PALETTE) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ } if(chunk) lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); chunk[8] = (unsigned char)(time->year >> 8); chunk[9] = (unsigned char)(time->year & 255); chunk[10] = (unsigned char)time->month; chunk[11] = (unsigned char)time->day; chunk[12] = (unsigned char)time->hour; chunk[13] = (unsigned char)time->minute; chunk[14] = (unsigned char)time->second; lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); lodepng_set32bitInt(chunk + 8, info->phys_x); lodepng_set32bitInt(chunk + 12, info->phys_y); chunk[16] = info->phys_unit; lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); lodepng_set32bitInt(chunk + 8, info->gama_gamma); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); lodepng_set32bitInt(chunk + 8, info->chrm_white_x); lodepng_set32bitInt(chunk + 12, info->chrm_white_y); lodepng_set32bitInt(chunk + 16, info->chrm_red_x); lodepng_set32bitInt(chunk + 20, info->chrm_red_y); lodepng_set32bitInt(chunk + 24, info->chrm_green_x); lodepng_set32bitInt(chunk + 28, info->chrm_green_y); lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { unsigned char data = info->srgb_intent; return lodepng_chunk_createv(out, 1, "sRGB", &data); } static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t keysize = lodepng_strlen(info->iccp_name); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ error = zlib_compress(&compressed, &compressedsize, info->iccp_profile, info->iccp_profile_size, zlibsettings); if(!error) { size_t size = keysize + 2 + compressedsize; error = lodepng_chunk_init(&chunk, out, size, "iCCP"); } if(!error) { lodepng_memcpy(chunk + 8, info->iccp_name, keysize); chunk[8 + keysize] = 0; /*null termination char*/ chunk[9 + keysize] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, size_t length, size_t bytewidth, unsigned char filterType) { size_t i; switch(filterType) { case 0: /*None*/ for(i = 0; i != length; ++i) out[i] = scanline[i]; break; case 1: /*Sub*/ for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; break; case 2: /*Up*/ if(prevline) { for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; } else { for(i = 0; i != length; ++i) out[i] = scanline[i]; } break; case 3: /*Average*/ if(prevline) { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); } break; case 4: /*Paeth*/ if(prevline) { /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); for(i = bytewidth; i < length; ++i) { out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); } } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); } break; default: return; /*invalid filter type given*/ } } /* integer binary logarithm, max return value is 31 */ static size_t ilog2(size_t i) { size_t result = 0; if(i >= 65536) { result += 16; i >>= 16; } if(i >= 256) { result += 8; i >>= 8; } if(i >= 16) { result += 4; i >>= 4; } if(i >= 4) { result += 2; i >>= 2; } if(i >= 2) { result += 1; /*i >>= 1;*/ } return result; } /* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ static size_t ilog2i(size_t i) { size_t l; if(i == 0) return 0; l = ilog2(i); /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) linearly approximates the missing fractional part multiplied by i */ return i * l + ((i - (1u << l)) << 1u); } static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { /* For PNG filter method 0 out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are the scanlines with 1 extra byte per scanline */ unsigned bpp = lodepng_get_bpp(color); /*the width of a scanline in bytes, not including the filter type*/ size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ size_t bytewidth = (bpp + 7u) / 8u; const unsigned char* prevline = 0; unsigned x, y; unsigned error = 0; LodePNGFilterStrategy strategy = settings->filter_strategy; /* There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. use fixed filtering, with the filter None). * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply all five filters and select the filter that produces the smallest sum of absolute values per row. This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum heuristic is used. */ if(settings->filter_palette_zero && (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; if(bpp == 0) return 31; /*error: invalid color type*/ if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { unsigned char type = (unsigned char)strategy; for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; out[outindex] = type; /*filter type byte*/ filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); prevline = &in[inindex]; } } else if(strategy == LFS_MINSUM) { /*adaptive filtering*/ unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t smallest = 0; unsigned char type, bestType = 0; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) { /*try the 5 filter types*/ for(type = 0; type != 5; ++type) { size_t sum = 0; filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); /*calculate the sum of the result*/ if(type == 0) { for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); } else { for(x = 0; x != linebytes; ++x) { /*For differences, each byte should be treated as signed, values above 127 are negative (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. This means filtertype 0 is almost never chosen, but that is justified.*/ unsigned char s = attempt[type][x]; sum += s < 128 ? s : (255U - s); } } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || sum < smallest) { bestType = type; smallest = sum; } } prevline = &in[y * linebytes]; /*now fill the out values*/ out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else if(strategy == LFS_ENTROPY) { unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t bestSum = 0; unsigned type, bestType = 0; unsigned count[256]; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) { /*try the 5 filter types*/ for(type = 0; type != 5; ++type) { size_t sum = 0; filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); lodepng_memset(count, 0, 256 * sizeof(*count)); for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; ++count[type]; /*the filter type itself is part of the scanline*/ for(x = 0; x != 256; ++x) { sum += ilog2i(count[x]); } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || sum > bestSum) { bestType = type; bestSum = sum; } } prevline = &in[y * linebytes]; /*now fill the out values*/ out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else if(strategy == LFS_PREDEFINED) { for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; unsigned char type = settings->predefined_filters[y]; out[outindex] = type; /*filter type byte*/ filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); prevline = &in[inindex]; } } else if(strategy == LFS_BRUTE_FORCE) { /*brute force filter chooser. deflate the scanline after every filter attempt to see which one deflates best. This is very slow and gives only slightly smaller, sometimes even larger, result*/ size_t size[5]; unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t smallest = 0; unsigned type = 0, bestType = 0; unsigned char* dummy; LodePNGCompressSettings zlibsettings; lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, to simulate the true case where the tree is the same for the whole image. Sometimes it gives better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare cases better compression. It does make this a bit less slow, so it's worth doing this.*/ zlibsettings.btype = 1; /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG images only, so disable it*/ zlibsettings.custom_zlib = 0; zlibsettings.custom_deflate = 0; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) /*try the 5 filter types*/ { for(type = 0; type != 5; ++type) { unsigned testsize = (unsigned)linebytes; /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); size[type] = 0; dummy = 0; zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); lodepng_free(dummy); /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || size[type] < smallest) { bestType = type; smallest = size[type]; } } prevline = &in[y * linebytes]; out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else return 88; /* unknown filter strategy */ return error; } static void addPaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) { /*The opposite of the removePaddingBits function olinebits must be >= ilinebits*/ unsigned y; size_t diff = olinebits - ilinebits; size_t obp = 0, ibp = 0; /*bit pointers*/ for(y = 0; y != h; ++y) { size_t x; for(x = 0; x < ilinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } /*obp += diff; --> no, fill in some value in the padding bits too, to avoid "Use of uninitialised value of size ###" warning from valgrind*/ for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); } } /* in: non-interlaced image with size w*h out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with no padding bits between scanlines, but between reduced images so that each reduced image starts at a byte. bpp: bits per pixel there are no padding bits, not between scanlines, not between reduced images in has the following size in bits: w * h * bpp. out is possibly bigger due to padding bits between reduced images NOTE: comments about padding bits are only relevant if bpp < 8 */ static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); if(bpp >= 8) { for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8u; for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } } } } } /*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. return value is error**/ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { /* This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter */ unsigned bpp = lodepng_get_bpp(&info_png->color); unsigned error = 0; if(info_png->interlace_method == 0) { *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ *out = (unsigned char*)lodepng_malloc(*outsize); if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ if(!error) { /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); if(!padded) error = 83; /*alloc fail*/ if(!error) { addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); error = filter(*out, padded, w, h, &info_png->color, settings); } lodepng_free(padded); } else { /*we can immediately filter into the out buffer, no other steps needed*/ error = filter(*out, in, w, h, &info_png->color, settings); } } } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned char* adam7; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ *out = (unsigned char*)lodepng_malloc(*outsize); if(!(*out)) error = 83; /*alloc fail*/ adam7 = (unsigned char*)lodepng_malloc(passstart[7]); if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ if(!error) { unsigned i; Adam7_interlace(adam7, in, w, h, bpp); for(i = 0; i != 7; ++i) { if(bpp < 8) { unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); if(!padded) ERROR_BREAK(83); /*alloc fail*/ addPaddingBits(padded, &adam7[passstart[i]], ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); error = filter(&(*out)[filter_passstart[i]], padded, passw[i], passh[i], &info_png->color, settings); lodepng_free(padded); } else { error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], passw[i], passh[i], &info_png->color, settings); } if(error) break; } } lodepng_free(adam7); } return error; } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { unsigned char* inchunk = data; while((size_t)(inchunk - data) < datasize) { CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); out->allocsize = out->size; /*fix the allocsize again*/ inchunk = lodepng_chunk_next(inchunk, data + datasize); } return 0; } static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { /* It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 are "RGB ". We do not perform any full parsing of the ICC profile here, other than check those 4 bytes to grayscale profile. Other than that, validity of the profile is not checked. This is needed only because the PNG specification requires using a non-gray color model if there is an ICC profile with "RGB " (sadly limiting compression opportunities if the input data is grayscale RGB data), and requires using a gray color model if it is "GRAY". */ if(size < 20) return 0; return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; } static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { /* See comment in isGrayICCProfile*/ if(size < 20) return 0; return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ unsigned lodepng_encode(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGState* state) { unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ size_t datasize = 0; ucvector outv = ucvector_init(NULL, 0); LodePNGInfo info; const LodePNGInfo* info_png = &state->info_png; lodepng_info_init(&info); /*provide some proper output values if error will happen*/ *out = 0; *outsize = 0; state->error = 0; /*check input values validity*/ if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ goto cleanup; } if(state->encoder.zlibsettings.btype > 2) { state->error = 61; /*error: invalid btype*/ goto cleanup; } if(info_png->interlace_method > 1) { state->error = 71; /*error: invalid interlace mode*/ goto cleanup; } state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); if(state->error) goto cleanup; /*error: invalid color type given*/ state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); if(state->error) goto cleanup; /*error: invalid color type given*/ /* color convert and compute scanline filter types */ lodepng_info_copy(&info, &state->info_png); if(state->encoder.auto_convert) { LodePNGColorStats stats; lodepng_color_stats_init(&stats); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->iccp_defined && isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { /*the PNG specification does not allow to use palette with a GRAY ICC profile, even if the palette has only gray colors, so disallow it.*/ stats.allow_palette = 0; } if(info_png->iccp_defined && isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ stats.allow_greyscale = 0; } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); if(state->error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->background_defined) { /*the background chunk's color must be taken into account as well*/ unsigned r = 0, g = 0, b = 0; LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); if(state->error) goto cleanup; } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ state->error = auto_choose_color(&info.color, &state->info_raw, &stats); if(state->error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*also convert the background chunk*/ if(info_png->background_defined) { if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { state->error = 104; goto cleanup; } } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->iccp_defined) { unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; if(!gray_icc && !rgb_icc) { state->error = 100; /* Disallowed profile color type for PNG */ goto cleanup; } if(gray_icc != gray_png) { /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, or in case of auto_convert, it wasn't possible to find appropriate model*/ state->error = state->encoder.auto_convert ? 102 : 101; goto cleanup; } } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { unsigned char* converted; size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; converted = (unsigned char*)lodepng_malloc(size); if(!converted && size) state->error = 83; /*alloc fail*/ if(!state->error) { state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); } if(!state->error) { state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); } lodepng_free(converted); if(state->error) goto cleanup; } else { state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); if(state->error) goto cleanup; } /* output all PNG chunks */ { #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS size_t i; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*write signature and chunks*/ state->error = writeSignature(&outv); if(state->error) goto cleanup; /*IHDR*/ state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); if(state->error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*unknown chunks between IHDR and PLTE*/ if(info.unknown_chunks_data[0]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); if(state->error) goto cleanup; } /*color profile chunks must come before PLTE */ if(info.iccp_defined) { state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); if(state->error) goto cleanup; } if(info.srgb_defined) { state->error = addChunk_sRGB(&outv, &info); if(state->error) goto cleanup; } if(info.gama_defined) { state->error = addChunk_gAMA(&outv, &info); if(state->error) goto cleanup; } if(info.chrm_defined) { state->error = addChunk_cHRM(&outv, &info); if(state->error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*PLTE*/ if(info.color.colortype == LCT_PALETTE) { state->error = addChunk_PLTE(&outv, &info.color); if(state->error) goto cleanup; } if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ state->error = addChunk_PLTE(&outv, &info.color); if(state->error) goto cleanup; } /*tRNS (this will only add if when necessary) */ state->error = addChunk_tRNS(&outv, &info.color); if(state->error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*bKGD (must come between PLTE and the IDAt chunks*/ if(info.background_defined) { state->error = addChunk_bKGD(&outv, &info); if(state->error) goto cleanup; } /*pHYs (must come before the IDAT chunks)*/ if(info.phys_defined) { state->error = addChunk_pHYs(&outv, &info); if(state->error) goto cleanup; } /*unknown chunks between PLTE and IDAT*/ if(info.unknown_chunks_data[1]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); if(state->error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*IDAT (multiple IDAT chunks must be consecutive)*/ state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); if(state->error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*tIME*/ if(info.time_defined) { state->error = addChunk_tIME(&outv, &info.time); if(state->error) goto cleanup; } /*tEXt and/or zTXt*/ for(i = 0; i != info.text_num; ++i) { if(lodepng_strlen(info.text_keys[i]) > 79) { state->error = 66; /*text chunk too large*/ goto cleanup; } if(lodepng_strlen(info.text_keys[i]) < 1) { state->error = 67; /*text chunk too small*/ goto cleanup; } if(state->encoder.text_compression) { state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); if(state->error) goto cleanup; } else { state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); if(state->error) goto cleanup; } } /*LodePNG version id in text chunk*/ if(state->encoder.add_id) { unsigned already_added_id_text = 0; for(i = 0; i != info.text_num; ++i) { const char* k = info.text_keys[i]; /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { already_added_id_text = 1; break; } } if(already_added_id_text == 0) { state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ if(state->error) goto cleanup; } } /*iTXt*/ for(i = 0; i != info.itext_num; ++i) { if(lodepng_strlen(info.itext_keys[i]) > 79) { state->error = 66; /*text chunk too large*/ goto cleanup; } if(lodepng_strlen(info.itext_keys[i]) < 1) { state->error = 67; /*text chunk too small*/ goto cleanup; } state->error = addChunk_iTXt( &outv, state->encoder.text_compression, info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], &state->encoder.zlibsettings); if(state->error) goto cleanup; } /*unknown chunks between IDAT and IEND*/ if(info.unknown_chunks_data[2]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); if(state->error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ state->error = addChunk_IEND(&outv); if(state->error) goto cleanup; } cleanup: lodepng_info_cleanup(&info); lodepng_free(data); /*instead of cleaning the vector up, give it to the output*/ *out = outv.data; *outsize = outv.size; return state->error; } unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; state.info_png.color.colortype = colortype; state.info_png.color.bitdepth = bitdepth; lodepng_encode(out, outsize, image, w, h, &state); error = state.error; lodepng_state_cleanup(&state); return error; } unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); } unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); if(!error) error = lodepng_save_file(buffer, buffersize, filename); lodepng_free(buffer); return error; } unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); } unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { lodepng_compress_settings_init(&settings->zlibsettings); settings->filter_palette_zero = 1; settings->filter_strategy = LFS_MINSUM; settings->auto_convert = 1; settings->force_palette = 0; settings->predefined_filters = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS settings->add_id = 0; settings->text_compression = 1; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ERROR_TEXT /* This returns the description of a numerical error code in English. This is also the documentation of all the error codes. */ const char* lodepng_error_text(unsigned code) { switch(code) { case 0: return "no error, everything went ok"; case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ case 13: return "problem while processing dynamic deflate block"; case 14: return "problem while processing dynamic deflate block"; case 15: return "problem while processing dynamic deflate block"; /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ case 16: return "invalid code while processing dynamic deflate block"; case 17: return "end of out buffer memory reached while inflating"; case 18: return "invalid distance code while inflating"; case 19: return "end of out buffer memory reached while inflating"; case 20: return "invalid deflate block BTYPE encountered while decoding"; case 21: return "NLEN is not ones complement of LEN in a deflate block"; /*end of out buffer memory reached while inflating: This can happen if the inflated deflate data is longer than the amount of bytes required to fill up all the pixels of the image, given the color depth and image dimensions. Something that doesn't happen in a normal, well encoded, PNG image.*/ case 22: return "end of out buffer memory reached while inflating"; case 23: return "end of in buffer memory reached while inflating"; case 24: return "invalid FCHECK in zlib header"; case 25: return "invalid compression method in zlib header"; case 26: return "FDICT encountered in zlib header while it's not used for PNG"; case 27: return "PNG file is smaller than a PNG header"; /*Checks the magic file header, the first 8 bytes of the PNG file*/ case 28: return "incorrect PNG signature, it's no PNG or corrupted"; case 29: return "first chunk is not the header chunk"; case 30: return "chunk length too large, chunk broken off at end of file"; case 31: return "illegal PNG color type or bpp"; case 32: return "illegal PNG compression method"; case 33: return "illegal PNG filter method"; case 34: return "illegal PNG interlace method"; case 35: return "chunk length of a chunk is too large or the chunk too small"; case 36: return "illegal PNG filter type encountered"; case 37: return "illegal bit depth for this color type given"; case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ case 39: return "tRNS chunk before PLTE or has more entries than palette size"; case 40: return "tRNS chunk has wrong size for grayscale image"; case 41: return "tRNS chunk has wrong size for RGB image"; case 42: return "tRNS chunk appeared while it was not allowed for this color type"; case 43: return "bKGD chunk has wrong size for palette image"; case 44: return "bKGD chunk has wrong size for grayscale image"; case 45: return "bKGD chunk has wrong size for RGB image"; case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; case 49: return "jumped past memory while generating dynamic huffman tree"; case 50: return "jumped past memory while generating dynamic huffman tree"; case 51: return "jumped past memory while inflating huffman block"; case 52: return "jumped past memory while inflating"; case 53: return "size of zlib data too small"; case 54: return "repeat symbol in tree while there was no value symbol yet"; /*jumped past tree while generating huffman tree, this could be when the tree will have more leaves than symbols after generating it out of the given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ case 55: return "jumped past tree while generating huffman tree"; case 56: return "given output image colortype or bitdepth not supported for color conversion"; case 57: return "invalid CRC encountered (checking CRC can be disabled)"; case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; case 59: return "requested color conversion not supported"; case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ case 62: return "conversion from color to grayscale not supported"; /*(2^31-1)*/ case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; case 73: return "invalid tIME chunk size"; case 74: return "invalid pHYs chunk size"; /*length could be wrong, or data chopped off*/ case 75: return "no null termination char found while decoding text chunk"; case 76: return "iTXt chunk too short to contain required bytes"; case 77: return "integer overflow in buffer size"; case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ case 79: return "failed to open file for writing"; case 80: return "tried creating a tree of 0 symbols"; case 81: return "lazy matching at pos 0 is impossible"; case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; case 83: return "memory allocation failed"; case 84: return "given image too small to contain all pixels to be encoded"; case 86: return "impossible offset in lz77 encoding (internal bug)"; case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; case 89: return "text chunk keyword too short or long: must have size 1-79"; /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ case 90: return "windowsize must be a power of two"; case 91: return "invalid decompressed idat size"; case 92: return "integer overflow due to too many pixels"; case 93: return "zero width or height is invalid"; case 94: return "header chunk must have a size of 13 bytes"; case 95: return "integer overflow with combined idat chunk size"; case 96: return "invalid gAMA chunk size"; case 97: return "invalid cHRM chunk size"; case 98: return "invalid sRGB chunk size"; case 99: return "invalid sRGB rendering intent"; case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; case 105: return "integer overflow of bitsize"; case 106: return "PNG file must have PLTE chunk if color type is palette"; case 107: return "color convert from palette mode requested without setting the palette data in it"; case 108: return "tried to add more than 256 values to a palette"; /*this limit can be configured in LodePNGDecompressSettings*/ case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; case 110: return "custom zlib or inflate decompression failed"; case 111: return "custom zlib or deflate compression failed"; /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents unreasonable memory consumption when decoding due to impossibly large text sizes.*/ case 112: return "compressed text unreasonably large"; /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents unreasonable memory consumption when decoding due to impossibly large ICC profile*/ case 113: return "ICC profile unreasonably large"; /*max size of an in-memory image buffer*/ case 114: return "image data unreasonably large"; } return "unknown error code"; } #endif /*LODEPNG_COMPILE_ERROR_TEXT*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // C++ Wrapper // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_CPP namespace lodepng { #ifdef LODEPNG_COMPILE_DISK unsigned load_file(std::vector& buffer, const std::string& filename) { long size = lodepng_filesize(filename.c_str()); if(size < 0) return 78; buffer.resize((size_t)size); return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ unsigned save_file(const std::vector& buffer, const std::string& filename) { return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); } #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_DECODER unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned decompress(std::vector& out, const std::vector& in, const LodePNGDecompressSettings& settings) { return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER unsigned compress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGCompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned compress(std::vector& out, const std::vector& in, const LodePNGCompressSettings& settings) { return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_ZLIB */ #ifdef LODEPNG_COMPILE_PNG State::State() { lodepng_state_init(this); } State::State(const State& other) { lodepng_state_init(this); lodepng_state_copy(this, &other); } State::~State() { lodepng_state_cleanup(this); } State& State::operator=(const State& other) { lodepng_state_copy(this, &other); return *this; } #ifdef LODEPNG_COMPILE_DECODER unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer = 0; unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); if(buffer && !error) { State state; state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); out.insert(out.end(), &buffer[0], &buffer[buffersize]); } lodepng_free(buffer); return error; } unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); } unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const unsigned char* in, size_t insize) { unsigned char* buffer = NULL; unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); if(buffer && !error) { size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); out.insert(out.end(), &buffer[0], &buffer[buffersize]); } lodepng_free(buffer); return error; } unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const std::vector& in) { return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); } #ifdef LODEPNG_COMPILE_DISK unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; /* safe output values in case error happens */ w = h = 0; unsigned error = load_file(buffer, filename); if(error) return error; return decode(out, w, h, buffer, colortype, bitdepth); } #endif /* LODEPNG_COMPILE_DECODER */ #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ENCODER unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, State& state) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, State& state) { if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, state); } #ifdef LODEPNG_COMPILE_DISK unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; unsigned error = encode(buffer, in, w, h, colortype, bitdepth); if(!error) error = save_file(buffer, filename); return error; } unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_PNG */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ chafa-1.14.5/lodepng/lodepng.h000066400000000000000000002772761471154763100161330ustar00rootroot00000000000000/* LodePNG version 20220109 Copyright (c) 2005-2022 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef LODEPNG_H #define LODEPNG_H #include /*for size_t*/ extern const char* LODEPNG_VERSION_STRING; /*Hard upper limit on size of an uncompressed in-memory image buffer. The total memory consumption may be higher, e.g. during postProcessScanlines().*/ #define LODEPNG_IMAGE_DATA_SIZE_MAX 0xffffffffU /* The following #defines are used to create code sections. They can be disabled to disable code sections, which can give faster compile time and smaller binary. The "NO_COMPILE" defines are designed to be used to pass as defines to the compiler command to disable them without modifying this header, e.g. -DLODEPNG_NO_COMPILE_ZLIB for gcc. In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to allow implementing a custom lodepng_crc32. */ /*deflate & zlib. If disabled, you must specify alternative zlib functions in the custom_zlib field of the compress and decompress settings*/ #ifndef LODEPNG_NO_COMPILE_ZLIB #define LODEPNG_COMPILE_ZLIB #endif /*png encoder and png decoder*/ #ifndef LODEPNG_NO_COMPILE_PNG #define LODEPNG_COMPILE_PNG #endif /*deflate&zlib decoder and png decoder*/ #ifndef LODEPNG_NO_COMPILE_DECODER #define LODEPNG_COMPILE_DECODER #endif /*deflate&zlib encoder and png encoder*/ #ifndef LODEPNG_NO_COMPILE_ENCODER #define LODEPNG_COMPILE_ENCODER #endif /*the optional built in harddisk file loading and saving functions*/ #ifndef LODEPNG_NO_COMPILE_DISK #define LODEPNG_COMPILE_DISK #endif /*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ #ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS #define LODEPNG_COMPILE_ANCILLARY_CHUNKS #endif /*ability to convert error numerical codes to English text string*/ #ifndef LODEPNG_NO_COMPILE_ERROR_TEXT #define LODEPNG_COMPILE_ERROR_TEXT #endif /*Compile the default allocators (C's free, malloc and realloc). If you disable this, you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your source files with custom allocators.*/ #ifndef LODEPNG_NO_COMPILE_ALLOCATORS #define LODEPNG_COMPILE_ALLOCATORS #endif /*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ #ifdef __cplusplus #ifndef LODEPNG_NO_COMPILE_CPP #define LODEPNG_COMPILE_CPP #endif #endif #ifdef LODEPNG_COMPILE_CPP #include #include #endif /*LODEPNG_COMPILE_CPP*/ #ifdef LODEPNG_COMPILE_PNG /*The PNG color types (also used for raw image).*/ typedef enum LodePNGColorType { LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ LCT_RGB = 2, /*RGB: 8,16 bit*/ LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid byte value from 0 to 255 that could be present in an invalid PNG file header. Do not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use the valid color type names above, or numeric values like 1 or 7 when checking for particular disallowed color type byte values, or cast to integer to print it.*/ LCT_MAX_OCTET_VALUE = 255 } LodePNGColorType; #ifdef LODEPNG_COMPILE_DECODER /* Converts PNG data in memory to raw pixel data. out: Output parameter. Pointer to buffer that will contain the raw pixel data. After decoding, its size is w * h * (bytes per pixel) bytes larger than initially. Bytes per pixel depends on colortype and bitdepth. Must be freed after usage with free(*out). Note: for 16-bit per channel colors, uses big endian format like PNG does. w: Output parameter. Pointer to width of pixel data. h: Output parameter. Pointer to height of pixel data. in: Memory buffer with the PNG file. insize: size of the in buffer. colortype: the desired color type for the raw output image. See explanation on PNG color types. bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. Return value: LodePNG error code (0 means no error). */ unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); /*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); #ifdef LODEPNG_COMPILE_DISK /* Load PNG from disk, from file with given name. Same as the other decode functions, but instead takes a filename as input. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); /*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); #endif /*LODEPNG_COMPILE_DISK*/ #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Converts raw pixel data into a PNG image in memory. The colortype and bitdepth of the output PNG image cannot be chosen, they are automatically determined by the colortype, bitdepth and content of the input pixel data. Note: for 16-bit per channel colors, needs big endian format like PNG does. out: Output parameter. Pointer to buffer that will contain the PNG image data. Must be freed after usage with free(*out). outsize: Output parameter. Pointer to the size in bytes of the out buffer. image: The raw pixel data to encode. The size of this buffer should be w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. w: width of the raw pixel data in pixels. h: height of the raw pixel data in pixels. colortype: the color type of the raw input image. See explanation on PNG color types. bitdepth: the bit depth of the raw input image. See explanation on PNG color types. Return value: LodePNG error code (0 means no error). */ unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); /*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); #ifdef LODEPNG_COMPILE_DISK /* Converts raw pixel data into a PNG file on disk. Same as the other encode functions, but instead takes a filename as output. NOTE: This overwrites existing files without warning! NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); /*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); #endif /*LODEPNG_COMPILE_DISK*/ #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_CPP namespace lodepng { #ifdef LODEPNG_COMPILE_DECODER /*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, size_t insize, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #ifdef LODEPNG_COMPILE_DISK /* Converts PNG file from disk to raw pixel data in memory. Same as the other decode functions, but instead takes a filename as input. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER /*Same as lodepng_encode_memory, but encodes to an std::vector. colortype is that of the raw input data. The output PNG color type will be auto chosen.*/ unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #ifdef LODEPNG_COMPILE_DISK /* Converts 32-bit RGBA raw pixel data into a PNG file on disk. Same as the other encode functions, but instead takes a filename as output. NOTE: This overwrites existing files without warning! NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_ENCODER */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ERROR_TEXT /*Returns an English description of the numerical error code.*/ const char* lodepng_error_text(unsigned code); #endif /*LODEPNG_COMPILE_ERROR_TEXT*/ #ifdef LODEPNG_COMPILE_DECODER /*Settings for zlib decompression*/ typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; struct LodePNGDecompressSettings { /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, return an error, output a data size > max_output_size and all the data up to that point. This is not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. Set to 0 to impose no limit (the default).*/ size_t max_output_size; /*use custom zlib decoder instead of built in one (default: null). Should return 0 if success, any non-0 if error (numeric value not exposed).*/ unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); /*use custom deflate decoder instead of built in one (default: null) if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). Should return 0 if success, any non-0 if error (numeric value not exposed).*/ unsigned (*custom_inflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); const void* custom_context; /*optional custom settings for custom functions*/ }; extern const LodePNGDecompressSettings lodepng_default_decompress_settings; void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Settings for zlib compression. Tweaking these settings tweaks the balance between speed and compression ratio. */ typedef struct LodePNGCompressSettings LodePNGCompressSettings; struct LodePNGCompressSettings /*deflate = compress*/ { /*LZ77 related settings*/ unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ /*use custom zlib encoder instead of built in one (default: null)*/ unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGCompressSettings*); /*use custom deflate encoder instead of built in one (default: null) if custom_zlib is used, custom_deflate is ignored since only the built in zlib function will call custom_deflate*/ unsigned (*custom_deflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGCompressSettings*); const void* custom_context; /*optional custom settings for custom functions*/ }; extern const LodePNGCompressSettings lodepng_default_compress_settings; void lodepng_compress_settings_init(LodePNGCompressSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_PNG /* Color mode of an image. Contains all information required to decode the pixel bits to RGBA colors. This information is the same as used in the PNG file format, and is used both for PNG and raw image data in LodePNG. */ typedef struct LodePNGColorMode { /*header (IHDR)*/ LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ /* palette (PLTE and tRNS) Dynamically allocated with the colors of the palette, including alpha. This field may not be allocated directly, use lodepng_color_mode_init first, then lodepng_palette_add per color to correctly initialize it (to ensure size of exactly 1024 bytes). The alpha channels must be set as well, set them to 255 for opaque images. When decoding, by default you can ignore this palette, since LodePNG already fills the palette colors in the pixels of the raw RGBA output. The palette is only supported for color type 3. */ unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ /* transparent color key (tRNS) This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. For grayscale PNGs, r, g and b will all 3 be set to the same. When decoding, by default you can ignore this information, since LodePNG sets pixels with this key to transparent already in the raw RGBA output. The color key is only supported for color types 0 and 2. */ unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ unsigned key_r; /*red/grayscale component of color key*/ unsigned key_g; /*green component of color key*/ unsigned key_b; /*blue component of color key*/ } LodePNGColorMode; /*init, cleanup and copy functions to use with this struct*/ void lodepng_color_mode_init(LodePNGColorMode* info); void lodepng_color_mode_cleanup(LodePNGColorMode* info); /*return value is error code (0 means no error)*/ unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); /* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); void lodepng_palette_clear(LodePNGColorMode* info); /*add 1 color to the palette*/ unsigned lodepng_palette_add(LodePNGColorMode* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a); /*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ unsigned lodepng_get_bpp(const LodePNGColorMode* info); /*get the amount of color channels used, based on colortype in the struct. If a palette is used, it counts as 1 channel.*/ unsigned lodepng_get_channels(const LodePNGColorMode* info); /*is it a grayscale type? (only colortype 0 or 4)*/ unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); /*has it got an alpha channel? (only colortype 2 or 6)*/ unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); /*has it got a palette? (only colortype 3)*/ unsigned lodepng_is_palette_type(const LodePNGColorMode* info); /*only returns true if there is a palette and there is a value in the palette with alpha < 255. Loops through the palette to check this.*/ unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); /* Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). Returns false if the image can only have opaque pixels. In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, or if "key_defined" is true. */ unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); /*Returns the byte size of a raw image buffer with given width, height and color mode*/ size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*The information of a Time chunk in PNG.*/ typedef struct LodePNGTime { unsigned year; /*2 bytes used (0-65535)*/ unsigned month; /*1-12*/ unsigned day; /*1-31*/ unsigned hour; /*0-23*/ unsigned minute; /*0-59*/ unsigned second; /*0-60 (to allow for leap seconds)*/ } LodePNGTime; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*Information about the PNG image, except pixels, width and height.*/ typedef struct LodePNGInfo { /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ unsigned compression_method;/*compression method of the original file. Always 0.*/ unsigned filter_method; /*filter method of the original file*/ unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /* Suggested background color chunk (bKGD) This uses the same color mode and bit depth as the PNG (except no alpha channel), with values truncated to the bit depth in the unsigned integer. For grayscale and palette PNGs, the value is stored in background_r. The values in background_g and background_b are then unused. So when decoding, you may get these in a different color mode than the one you requested for the raw pixels. When encoding with auto_convert, you must use the color model defined in info_png.color for these values. The encoder normally ignores info_png.color when auto_convert is on, but will use it to interpret these values (and convert copies of them to its chosen color model). When encoding, avoid setting this to an expensive color, such as a non-gray value when the image is gray, or the compression will be worse since it will be forced to write the PNG with a more expensive color mode (when auto_convert is on). The decoder does not use this background color to edit the color of pixels. This is a completely optional metadata feature. */ unsigned background_defined; /*is a suggested background color given?*/ unsigned background_r; /*red/gray/palette component of suggested background color*/ unsigned background_g; /*green component of suggested background color*/ unsigned background_b; /*blue component of suggested background color*/ /* Non-international text chunks (tEXt and zTXt) The char** arrays each contain num strings. The actual messages are in text_strings, while text_keys are keywords that give a short description what the actual text represents, e.g. Title, Author, Description, or anything else. All the string fields below including strings, keys, names and language tags are null terminated. The PNG specification uses null characters for the keys, names and tags, and forbids null characters to appear in the main text which is why we can use null termination everywhere here. A keyword is minimum 1 character and maximum 79 characters long (plus the additional null terminator). It's discouraged to use a single line length longer than 79 characters for texts. Don't allocate these text buffers yourself. Use the init/cleanup functions correctly and use lodepng_add_text and lodepng_clear_text. Standard text chunk keywords and strings are encoded using Latin-1. */ size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ char** text_strings; /*the actual text*/ /* International text chunks (iTXt) Similar to the non-international text chunks, but with additional strings "langtags" and "transkeys", and the following text encodings are used: keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. keys must be 1-79 characters (plus the additional null terminator), the other strings are any length. */ size_t itext_num; /*the amount of international texts in this PNG*/ char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ char** itext_strings; /*the actual international text - UTF-8 string*/ /*time chunk (tIME)*/ unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ LodePNGTime time; /*phys chunk (pHYs)*/ unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ unsigned phys_x; /*pixels per unit in x direction*/ unsigned phys_y; /*pixels per unit in y direction*/ unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ /* Color profile related chunks: gAMA, cHRM, sRGB, iCPP LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please use these values with a color management library. See the PNG, ICC and sRGB specifications for more information about the meaning of these values. */ /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ unsigned gama_gamma; /* Gamma exponent times 100000 */ /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ unsigned chrm_white_x; /* White Point x times 100000 */ unsigned chrm_white_y; /* White Point y times 100000 */ unsigned chrm_red_x; /* Red x times 100000 */ unsigned chrm_red_y; /* Red y times 100000 */ unsigned chrm_green_x; /* Green x times 100000 */ unsigned chrm_green_y; /* Green y times 100000 */ unsigned chrm_blue_x; /* Blue x times 100000 */ unsigned chrm_blue_y; /* Blue y times 100000 */ /* sRGB chunk: optional. May not appear at the same time as iCCP. If gAMA is also present gAMA must contain value 45455. If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. */ unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ /* iCCP chunk: optional. May not appear at the same time as sRGB. LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color management and conversions. For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel data if the pixels could be encoded as grayscale but the ICC profile is RGB. To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so make sure you compute it carefully to avoid the above problems. */ unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ /* The ICC profile in iccp_profile_size bytes. Don't allocate this buffer yourself. Use the init/cleanup functions correctly and use lodepng_set_icc and lodepng_clear_icc. */ unsigned char* iccp_profile; unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ /* End of color profile related chunks */ /* unknown chunks: chunks not known by LodePNG, passed on byte for byte. There are 3 buffers, one for each position in the PNG where unknown chunks can appear. Each buffer contains all unknown chunks for that position consecutively. The 3 positions are: 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag above in here, since the encoder will blindly follow this and could then encode an invalid PNG file (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), or any non-standard PNG chunk. Do not allocate or traverse this data yourself. Use the chunk traversing functions declared later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. */ unsigned char* unknown_chunks_data[3]; size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGInfo; /*init, cleanup and copy functions to use with this struct*/ void lodepng_info_init(LodePNGInfo* info); void lodepng_info_cleanup(LodePNGInfo* info); /*return value is error code (0 means no error)*/ unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ /*replaces if exists*/ unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /* Converts raw buffer from one color type to another color type, based on LodePNGColorMode structs to describe the input and output color type. See the reference manual at the end of this header file to see which color conversions are supported. return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel of the output color type (lodepng_get_bpp). For < 8 bpp images, there should not be padding bits at the end of scanlines. For 16-bit per channel colors, uses big endian format like PNG does. Return value is LodePNG error code */ unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h); unsigned lodepng_convert_rgb(unsigned* r_out, unsigned* g_out, unsigned* b_out, unsigned r_in, unsigned g_in, unsigned b_in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in); #ifdef LODEPNG_COMPILE_DECODER /* Settings for the decoder. This contains settings for the PNG and the Zlib decoder, but not the Info settings from the Info structs. */ typedef struct LodePNGDecoderSettings { LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ unsigned ignore_crc; /*ignore CRC checksums*/ unsigned ignore_critical; /*ignore unknown critical chunks*/ unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters in string keys, etc... */ unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ unsigned remember_unknown_chunks; /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. By default it is a value that prevents unreasonably large strings from hogging memory. */ size_t max_text_size; /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any legitimate profile could be to hog memory. */ size_t max_icc_size; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGDecoderSettings; void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ typedef enum LodePNGFilterStrategy { /*every filter at zero*/ LFS_ZERO = 0, /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ LFS_ONE = 1, LFS_TWO = 2, LFS_THREE = 3, LFS_FOUR = 4, /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ LFS_MINSUM, /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending on the image, this is better or worse than minsum.*/ LFS_ENTROPY, /* Brute-force-search PNG filters by compressing each filter for each scanline. Experimental, very slow, and only rarely gives better compression than MINSUM. */ LFS_BRUTE_FORCE, /*use predefined_filters buffer: you specify the filter type for each scanline*/ LFS_PREDEFINED } LodePNGFilterStrategy; /*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), which helps decide which color model to use for encoding. Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ typedef struct LodePNGColorStats { unsigned colored; /*not grayscale*/ unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ unsigned short key_g; unsigned short key_b; unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ size_t numpixels; /*user settings for computing/using the stats*/ unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ } LodePNGColorStats; void lodepng_color_stats_init(LodePNGColorStats* stats); /*Get a LodePNGColorStats of the image. The stats must already have been inited. Returns error code (e.g. alloc fail) or 0 if ok.*/ unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, const unsigned char* image, unsigned w, unsigned h, const LodePNGColorMode* mode_in); /*Settings for the encoder.*/ typedef struct LodePNGEncoderSettings { LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to completely follow the official PNG heuristic, filter_palette_zero must be true and filter_strategy must be LFS_MINSUM*/ unsigned filter_palette_zero; /*Which filter strategy to use when not using zeroes due to filter_palette_zero. Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ LodePNGFilterStrategy filter_strategy; /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with the same length as the amount of scanlines in the image, and each value must <= 5. You have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ const unsigned char* predefined_filters; /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). If colortype is 3, PLTE is _always_ created.*/ unsigned force_palette; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*add LodePNG identifier and version as a text chunk, for debugging*/ unsigned add_id; /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ unsigned text_compression; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGEncoderSettings; void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) /*The settings, state and information for extended encoding and decoding.*/ typedef struct LodePNGState { #ifdef LODEPNG_COMPILE_DECODER LodePNGDecoderSettings decoder; /*the decoding settings*/ #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER LodePNGEncoderSettings encoder; /*the encoding settings*/ #endif /*LODEPNG_COMPILE_ENCODER*/ LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ unsigned error; } LodePNGState; /*init, cleanup and copy functions to use with this struct*/ void lodepng_state_init(LodePNGState* state); void lodepng_state_cleanup(LodePNGState* state); void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); #endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ #ifdef LODEPNG_COMPILE_DECODER /* Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and getting much more information about the PNG image and color mode. */ unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); /* Read the PNG header, but not the actual data. This returns only the information that is in the IHDR chunk of the PNG, such as width, height and color type. The information is placed in the info_png field of the LodePNGState. */ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); #endif /*LODEPNG_COMPILE_DECODER*/ /* Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it read in the state. Returns error code on failure. Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const to find the desired chunk type, and if non null use lodepng_inspect_chunk (with chunk_pointer - start_of_file as pos). Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). Requirements: &in[pos] must point to start of a chunk, must use regular lodepng_inspect first since format of most other chunks depends on IHDR, and if there is a PLTE chunk, that one must be inspected before tRNS or bKGD. */ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, const unsigned char* in, size_t insize); #ifdef LODEPNG_COMPILE_ENCODER /*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ unsigned lodepng_encode(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGState* state); #endif /*LODEPNG_COMPILE_ENCODER*/ /* The lodepng_chunk functions are normally not needed, except to traverse the unknown chunks stored in the LodePNGInfo struct, or add new ones to it. It also allows traversing the chunks of an encoded PNG file yourself. The chunk pointer always points to the beginning of the chunk itself, that is the first byte of the 4 length bytes. In the PNG file format, chunks have the following format: -4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) -4 bytes chunk type (ASCII a-z,A-Z only, see below) -length bytes of data (may be 0 bytes if length was 0) -4 bytes of CRC, computed on chunk name + data The first chunk starts at the 8th byte of the PNG file, the entire rest of the file exists out of concatenated chunks with the above format. PNG standard chunk ASCII naming conventions: -First byte: uppercase = critical, lowercase = ancillary -Second byte: uppercase = public, lowercase = private -Third byte: must be uppercase -Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy */ /* Gets the length of the data of the chunk. Total chunk length has 12 bytes more. There must be at least 4 bytes to read from. If the result value is too large, it may be corrupt data. */ unsigned lodepng_chunk_length(const unsigned char* chunk); /*puts the 4-byte type in null terminated string*/ void lodepng_chunk_type(char type[5], const unsigned char* chunk); /*check if the type is the given type*/ unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); /*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); /*0: public, 1: private (see PNG standard)*/ unsigned char lodepng_chunk_private(const unsigned char* chunk); /*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); /*get pointer to the data of the chunk, where the input points to the header of the chunk*/ unsigned char* lodepng_chunk_data(unsigned char* chunk); const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); #ifndef LODEPNG_NO_COMPILE_CRC /*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ unsigned lodepng_chunk_check_crc(const unsigned char* chunk); #endif /*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ void lodepng_chunk_generate_crc(unsigned char* chunk); /* Iterate to next chunks, allows iterating through all chunks of the PNG file. Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, or the 8th byte of a PNG file which always has the first chunk), or alternatively may point to the first byte of the PNG file (which is not a chunk but the magic header, the function will then skip over it and return the first real chunk). Will output pointer to the start of the next chunk, or at or beyond end of the file if there is no more chunk after this or possibly if the chunk is corrupt. Start this process at the 8th byte of the PNG file. In a non-corrupt PNG file, the last chunk should have name "IEND". */ unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); /*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); /* Appends chunk to the data in out. The given chunk should already have its chunk header. The out variable and outsize are updated to reflect the new reallocated buffer. Returns error code (0 if it went ok) */ unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); /* Appends new chunk to out. The chunk to append is given by giving its length, type and data separately. The type is a 4-letter string. The out variable and outsize are updated to reflect the new reallocated buffer. Returne error code (0 if it went ok) */ unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, const char* type, const unsigned char* data); /*Calculate CRC32 of buffer*/ unsigned lodepng_crc32(const unsigned char* buf, size_t len); #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ZLIB /* This zlib part can be used independently to zlib compress and decompress a buffer. It cannot be used to create gzip files however, and it only supports the part of zlib that is required for PNG, it does not support dictionaries. */ #ifdef LODEPNG_COMPILE_DECODER /*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ unsigned lodepng_inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings); /* Decompresses Zlib data. Reallocates the out buffer and appends the data. The data must be according to the zlib specification. Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes. out must be freed by user after usage. */ unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Compresses data with Zlib. Reallocates the out buffer and appends the data. Zlib adds a small header and trailer around the deflate data. The data is output in the format of the zlib specification. Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes. out must be freed by user after usage. */ unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings); /* Find length-limited Huffman code for given frequencies. This function is in the public interface only for tests, it's used internally by lodepng_deflate. */ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, size_t numcodes, unsigned maxbitlen); /*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ unsigned lodepng_deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ #ifdef LODEPNG_COMPILE_DISK /* Load a file from disk into buffer. The function allocates the out buffer, and after usage you should free it. out: output parameter, contains pointer to loaded buffer. outsize: output parameter, size of the allocated out buffer filename: the path to the file to load return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); /* Save a file from buffer to disk. Warning, if it exists, this function overwrites the file without warning! buffer: the buffer to write buffersize: size of the buffer to write filename: the path to the file to save to return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory */ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); #endif /*LODEPNG_COMPILE_DISK*/ #ifdef LODEPNG_COMPILE_CPP /* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ namespace lodepng { #ifdef LODEPNG_COMPILE_PNG class State : public LodePNGState { public: State(); State(const State& other); ~State(); State& operator=(const State& other); }; #ifdef LODEPNG_COMPILE_DECODER /* Same as other lodepng::decode, but using a State for more settings and information. */ unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const unsigned char* in, size_t insize); unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const std::vector& in); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Same as other lodepng::encode, but using a State for more settings and information. */ unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, State& state); unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, State& state); #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DISK /* Load a file from disk into an std::vector. return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory */ unsigned load_file(std::vector& buffer, const std::string& filename); /* Save the binary data in an std::vector to a file on disk. The file is overwritten without warning. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory */ unsigned save_file(const std::vector& buffer, const std::string& filename); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_PNG */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_DECODER /* Zlib-decompress an unsigned char buffer */ unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); /* Zlib-decompress an std::vector */ unsigned decompress(std::vector& out, const std::vector& in, const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER /* Zlib-compress an unsigned char buffer */ unsigned compress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGCompressSettings& settings = lodepng_default_compress_settings); /* Zlib-compress an std::vector */ unsigned compress(std::vector& out, const std::vector& in, const LodePNGCompressSettings& settings = lodepng_default_compress_settings); #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_ZLIB */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ /* TODO: [.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often [.] check compatibility with various compilers - done but needs to be redone for every newer version [X] converting color to 16-bit per channel types [X] support color profile chunk types (but never let them touch RGB values by default) [ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) [ ] make sure encoder generates no chunks with size > (2^31)-1 [ ] partial decoding (stream processing) [X] let the "isFullyOpaque" function check color keys and transparent palettes too [X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" [ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) [ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... [ ] error messages with line numbers (and version) [ ] errors in state instead of as return code? [ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk [ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes [ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... [ ] allow user to give data (void*) to custom allocator [X] provide alternatives for C library functions not present on some platforms (memcpy, ...) */ #endif /*LODEPNG_H inclusion guard*/ /* LodePNG Documentation --------------------- 0. table of contents -------------------- 1. about 1.1. supported features 1.2. features not supported 2. C and C++ version 3. security 4. decoding 5. encoding 6. color conversions 6.1. PNG color types 6.2. color conversions 6.3. padding bits 6.4. A note about 16-bits per channel and endianness 7. error values 8. chunks and PNG editing 9. compiler support 10. examples 10.1. decoder C++ example 10.2. decoder C example 11. state settings reference 12. changes 13. contact information 1. about -------- PNG is a file format to store raster images losslessly with good compression, supporting different color types and alpha channel. LodePNG is a PNG codec according to the Portable Network Graphics (PNG) Specification (Second Edition) - W3C Recommendation 10 November 2003. The specifications used are: *) Portable Network Graphics (PNG) Specification (Second Edition): http://www.w3.org/TR/2003/REC-PNG-20031110 *) RFC 1950 ZLIB Compressed Data Format version 3.3: http://www.gzip.org/zlib/rfc-zlib.html *) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: http://www.gzip.org/zlib/rfc-deflate.html The most recent version of LodePNG can currently be found at http://lodev.org/lodepng/ LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds extra functionality. LodePNG exists out of two files: -lodepng.h: the header file for both C and C++ -lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage If you want to start using LodePNG right away without reading this doc, get the examples from the LodePNG website to see how to use it in code, or check the smaller examples in chapter 13 here. LodePNG is simple but only supports the basic requirements. To achieve simplicity, the following design choices were made: There are no dependencies on any external library. There are functions to decode and encode a PNG with a single function call, and extended versions of these functions taking a LodePNGState struct allowing to specify or get more information. By default the colors of the raw image are always RGB or RGBA, no matter what color type the PNG file uses. To read and write files, there are simple functions to convert the files to/from buffers in memory. This all makes LodePNG suitable for loading textures in games, demos and small programs, ... It's less suitable for full fledged image editors, loading PNGs over network (it requires all the image data to be available before decoding can begin), life-critical systems, ... 1.1. supported features ----------------------- The following features are supported by the decoder: *) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, or the same color type as the PNG *) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image *) Adam7 interlace and deinterlace for any color type *) loading the image from harddisk or decoding it from a buffer from other sources than harddisk *) support for alpha channels, including RGBA color model, translucent palettes and color keying *) zlib decompression (inflate) *) zlib compression (deflate) *) CRC32 and ADLER32 checksums *) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. *) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. *) the following chunks are supported by both encoder and decoder: IHDR: header information PLTE: color palette IDAT: pixel data IEND: the final chunk tRNS: transparency for palettized images tEXt: textual information zTXt: compressed textual information iTXt: international textual information bKGD: suggested background color pHYs: physical dimensions tIME: modification time cHRM: RGB chromaticities gAMA: RGB gamma correction iCCP: ICC color profile sRGB: rendering intent 1.2. features not supported --------------------------- The following features are _not_ supported: *) some features needed to make a conformant PNG-Editor might be still missing. *) partial loading/stream processing. All data must be available and is processed in one call. *) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: sBIT hIST sPLT 2. C and C++ version -------------------- The C version uses buffers allocated with alloc that you need to free() yourself. You need to use init and cleanup functions for each struct whenever using a struct from the C version to avoid exploits and memory leaks. The C++ version has extra functions with std::vectors in the interface and the lodepng::State class which is a LodePNGState with constructor and destructor. These files work without modification for both C and C++ compilers because all the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers ignore it, and the C code is made to compile both with strict ISO C90 and C++. To use the C++ version, you need to rename the source file to lodepng.cpp (instead of lodepng.c), and compile it with a C++ compiler. To use the C version, you need to rename the source file to lodepng.c (instead of lodepng.cpp), and compile it with a C compiler. 3. Security ----------- Even if carefully designed, it's always possible that LodePNG contains possible exploits. If you discover one, please let me know, and it will be fixed. When using LodePNG, care has to be taken with the C version of LodePNG, as well as the C-style structs when working with C++. The following conventions are used for all C-style structs: -if a struct has a corresponding init function, always call the init function when making a new one -if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks -if a struct has a corresponding copy function, use the copy function instead of "=". The destination must also be inited already. 4. Decoding ----------- Decoding converts a PNG compressed image to a raw pixel buffer. Most documentation on using the decoder is at its declarations in the header above. For C, simple decoding can be done with functions such as lodepng_decode32, and more advanced decoding can be done with the struct LodePNGState and lodepng_decode. For C++, all decoding can be done with the various lodepng::decode functions, and lodepng::State can be used for advanced features. When using the LodePNGState, it uses the following fields for decoding: *) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here *) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get *) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use LodePNGInfo info_png -------------------- After decoding, this contains extra information of the PNG image, except the actual pixels, width and height because these are already gotten directly from the decoder functions. It contains for example the original color type of the PNG image, text comments, suggested background color, etc... More details about the LodePNGInfo struct are at its declaration documentation. LodePNGColorMode info_raw ------------------------- When decoding, here you can specify which color type you want the resulting raw image to be. If this is different from the colortype of the PNG, then the decoder will automatically convert the result. This conversion always works, except if you want it to convert a color PNG to grayscale or to a palette with missing colors. By default, 32-bit color is used for the result. LodePNGDecoderSettings decoder ------------------------------ The settings can be used to ignore the errors created by invalid CRC and Adler32 chunks, and to disable the decoding of tEXt chunks. There's also a setting color_convert, true by default. If false, no conversion is done, the resulting data will be as it was in the PNG (after decompression) and you'll have to puzzle the colors of the pixels together yourself using the color type information in the LodePNGInfo. 5. Encoding ----------- Encoding converts a raw pixel buffer to a PNG compressed image. Most documentation on using the encoder is at its declarations in the header above. For C, simple encoding can be done with functions such as lodepng_encode32, and more advanced decoding can be done with the struct LodePNGState and lodepng_encode. For C++, all encoding can be done with the various lodepng::encode functions, and lodepng::State can be used for advanced features. Like the decoder, the encoder can also give errors. However it gives less errors since the encoder input is trusted, the decoder input (a PNG image that could be forged by anyone) is not trusted. When using the LodePNGState, it uses the following fields for encoding: *) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. *) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has *) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use LodePNGInfo info_png -------------------- When encoding, you use this the opposite way as when decoding: for encoding, you fill in the values you want the PNG to have before encoding. By default it's not needed to specify a color type for the PNG since it's automatically chosen, but it's possible to choose it yourself given the right settings. The encoder will not always exactly match the LodePNGInfo struct you give, it tries as close as possible. Some things are ignored by the encoder. The encoder uses, for example, the following settings from it when applicable: colortype and bitdepth, text chunks, time chunk, the color key, the palette, the background color, the interlace method, unknown chunks, ... When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. If the palette contains any colors for which the alpha channel is not 255 (so there are translucent colors in the palette), it'll add a tRNS chunk. LodePNGColorMode info_raw ------------------------- You specify the color type of the raw image that you give to the input here, including a possible transparent color key and palette you happen to be using in your raw image data. By default, 32-bit color is assumed, meaning your input has to be in RGBA format with 4 bytes (unsigned chars) per pixel. LodePNGEncoderSettings encoder ------------------------------ The following settings are supported (some are in sub-structs): *) auto_convert: when this option is enabled, the encoder will automatically choose the smallest possible color mode (including color key) that can encode the colors of all pixels without information loss. *) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, 2 = dynamic huffman tree (best compression). Should be 2 for proper compression. *) use_lz77: whether or not to use LZ77 for compressed block types. Should be true for proper compression. *) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value 2048 by default, but can be set to 32768 for better, but slow, compression. *) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE chunk if force_palette is true. This can used as suggested palette to convert to by viewers that don't support more than 256 colors (if those still exist) *) add_id: add text chunk "Encoder: LodePNG " to the image. *) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. zTXt chunks use zlib compression on the text. This gives a smaller result on large texts but a larger result on small texts (such as a single program name). It's all tEXt or all zTXt though, there's no separate setting per text yet. 6. color conversions -------------------- An important thing to note about LodePNG, is that the color type of the PNG, and the color type of the raw image, are completely independent. By default, when you decode a PNG, you get the result as a raw image in the color type you want, no matter whether the PNG was encoded with a palette, grayscale or RGBA color. And if you encode an image, by default LodePNG will automatically choose the PNG color type that gives good compression based on the values of colors and amount of colors in the image. It can be configured to let you control it instead as well, though. To be able to do this, LodePNG does conversions from one color mode to another. It can convert from almost any color type to any other color type, except the following conversions: RGB to grayscale is not supported, and converting to a palette when the palette doesn't have a required color is not supported. This is not supported on purpose: this is information loss which requires a color reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray is easy, but there are multiple ways if you want to give some channels more weight). By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB color, no matter what color type the PNG has. And by default when encoding, LodePNG automatically picks the best color model for the output PNG, and expects the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control the color format of the images yourself, you can skip this chapter. 6.1. PNG color types -------------------- A PNG image can have many color types, ranging from 1-bit color to 64-bit color, as well as palettized color modes. After the zlib decompression and unfiltering in the PNG image is done, the raw pixel data will have that color type and thus a certain amount of bits per pixel. If you want the output raw image after decoding to have another color type, a conversion is done by LodePNG. The PNG specification gives the following color types: 0: grayscale, bit depths 1, 2, 4, 8, 16 2: RGB, bit depths 8 and 16 3: palette, bit depths 1, 2, 4 and 8 4: grayscale with alpha, bit depths 8 and 16 6: RGBA, bit depths 8 and 16 Bit depth is the amount of bits per pixel per color channel. So the total amount of bits per pixel is: amount of channels * bitdepth. 6.2. color conversions ---------------------- As explained in the sections about the encoder and decoder, you can specify color types and bit depths in info_png and info_raw to change the default behaviour. If, when decoding, you want the raw image to be something else than the default, you need to set the color type and bit depth you want in the LodePNGColorMode, or the parameters colortype and bitdepth of the simple decoding function. If, when encoding, you use another color type than the default in the raw input image, you need to specify its color type and bit depth in the LodePNGColorMode of the raw image, or use the parameters colortype and bitdepth of the simple encoding function. If, when encoding, you don't want LodePNG to choose the output PNG color type but control it yourself, you need to set auto_convert in the encoder settings to false, and specify the color type you want in the LodePNGInfo of the encoder (including palette: it can generate a palette if auto_convert is true, otherwise not). If the input and output color type differ (whether user chosen or auto chosen), LodePNG will do a color conversion, which follows the rules below, and may sometimes result in an error. To avoid some confusion: -the decoder converts from PNG to raw image -the encoder converts from raw image to PNG -the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image -the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG -when encoding, the color type in LodePNGInfo is ignored if auto_convert is enabled, it is automatically generated instead -when decoding, the color type in LodePNGInfo is set by the decoder to that of the original PNG image, but it can be ignored since the raw image has the color type you requested instead -if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion between the color types is done if the color types are supported. If it is not supported, an error is returned. If the types are the same, no conversion is done. -even though some conversions aren't supported, LodePNG supports loading PNGs from any colortype and saving PNGs to any colortype, sometimes it just requires preparing the raw image correctly before encoding. -both encoder and decoder use the same color converter. The function lodepng_convert does the color conversion. It is available in the interface but normally isn't needed since the encoder and decoder already call it. Non supported color conversions: -color to grayscale when non-gray pixels are present: no error is thrown, but the result will look ugly because only the red channel is taken (it assumes all three channels are the same in this case so ignores green and blue). The reason no error is given is to allow converting from three-channel grayscale images to one-channel even if there are numerical imprecisions. -anything to palette when the palette does not have an exact match for a from-color in it: in this case an error is thrown Supported color conversions: -anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA -any gray or gray+alpha, to gray or gray+alpha -anything to a palette, as long as the palette has the requested colors in it -removing alpha channel -higher to smaller bitdepth, and vice versa If you want no color conversion to be done (e.g. for speed or control): -In the encoder, you can make it save a PNG with any color type by giving the raw color mode and LodePNGInfo the same color mode, and setting auto_convert to false. -In the decoder, you can make it store the pixel data in the same color type as the PNG has, by setting the color_convert setting to false. Settings in info_raw are then ignored. 6.3. padding bits ----------------- In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines have a bit amount that isn't a multiple of 8, then padding bits are used so that each scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. The raw input image you give to the encoder, and the raw output image you get from the decoder will NOT have these padding bits, e.g. in the case of a 1-bit image with a width of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, not the first bit of a new byte. 6.4. A note about 16-bits per channel and endianness ---------------------------------------------------- LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like for any other color format. The 16-bit values are stored in big endian (most significant byte first) in these arrays. This is the opposite order of the little endian used by x86 CPU's. LodePNG always uses big endian because the PNG file format does so internally. Conversions to other formats than PNG uses internally are not supported by LodePNG on purpose, there are myriads of formats, including endianness of 16-bit colors, the order in which you store R, G, B and A, and so on. Supporting and converting to/from all that is outside the scope of LodePNG. This may mean that, depending on your use case, you may want to convert the big endian output of LodePNG to little endian with a for loop. This is certainly not always needed, many applications and libraries support big endian 16-bit colors anyway, but it means you cannot simply cast the unsigned char* buffer to an unsigned short* buffer on x86 CPUs. 7. error values --------------- All functions in LodePNG that return an error code, return 0 if everything went OK, or a non-zero code if there was an error. The meaning of the LodePNG error values can be retrieved with the function lodepng_error_text: given the numerical error code, it returns a description of the error in English as a string. Check the implementation of lodepng_error_text to see the meaning of each code. It is not recommended to use the numerical values to programmatically make different decisions based on error types as the numbers are not guaranteed to stay backwards compatible. They are for human consumption only. Programmatically only 0 or non-0 matter. 8. chunks and PNG editing ------------------------- If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG editor that should follow the rules about handling of unknown chunks, or if your program is able to read other types of chunks than the ones handled by LodePNG, then that's possible with the chunk functions of LodePNG. A PNG chunk has the following layout: 4 bytes length 4 bytes type name length bytes data 4 bytes CRC 8.1. iterating through chunks ----------------------------- If you have a buffer containing the PNG image data, then the first chunk (the IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the signature of the PNG and are not part of a chunk. But if you start at byte 8 then you have a chunk, and can check the following things of it. NOTE: none of these functions check for memory buffer boundaries. To avoid exploits, always make sure the buffer contains all the data of the chunks. When using lodepng_chunk_next, make sure the returned value is within the allocated memory. unsigned lodepng_chunk_length(const unsigned char* chunk): Get the length of the chunk's data. The total chunk length is this length + 12. void lodepng_chunk_type(char type[5], const unsigned char* chunk): unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): Get the type of the chunk or compare if it's a certain type unsigned char lodepng_chunk_critical(const unsigned char* chunk): unsigned char lodepng_chunk_private(const unsigned char* chunk): unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). Check if the chunk is private (public chunks are part of the standard, private ones not). Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your program doesn't handle that type of unknown chunk. unsigned char* lodepng_chunk_data(unsigned char* chunk): const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): Get a pointer to the start of the data of the chunk. unsigned lodepng_chunk_check_crc(const unsigned char* chunk): void lodepng_chunk_generate_crc(unsigned char* chunk): Check if the crc is correct or generate a correct one. unsigned char* lodepng_chunk_next(unsigned char* chunk): const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these functions do no boundary checking of the allocated data whatsoever, so make sure there is enough data available in the buffer to be able to go to the next chunk. unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, const char* type, const unsigned char* data): These functions are used to create new chunks that are appended to the data in *out that has length *outsize. The append function appends an existing chunk to the new data. The create function creates a new chunk with the given parameters and appends it. Type is the 4-letter name of the chunk. 8.2. chunks in info_png ----------------------- The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 buffers (each with size) to contain 3 types of unknown chunks: the ones that come before the PLTE chunk, the ones that come between the PLTE and the IDAT chunks, and the ones that come after the IDAT chunks. It's necessary to make the distinction between these 3 cases because the PNG standard forces to keep the ordering of unknown chunks compared to the critical chunks, but does not force any other ordering rules. info_png.unknown_chunks_data[0] is the chunks before PLTE info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT info_png.unknown_chunks_data[2] is the chunks after IDAT The chunks in these 3 buffers can be iterated through and read by using the same way described in the previous subchapter. When using the decoder to decode a PNG, you can make it store all unknown chunks if you set the option settings.remember_unknown_chunks to 1. By default, this option is off (0). The encoder will always encode unknown chunks that are stored in the info_png. If you need it to add a particular chunk that isn't known by LodePNG, you can use lodepng_chunk_append or lodepng_chunk_create to the chunk data in info_png.unknown_chunks_data[x]. Chunks that are known by LodePNG should not be added in that way. E.g. to make LodePNG add a bKGD chunk, set background_defined to true and add the correct parameters there instead. 9. compiler support ------------------- No libraries other than the current standard C library are needed to compile LodePNG. For the C++ version, only the standard C++ library is needed on top. Add the files lodepng.c(pp) and lodepng.h to your project, include lodepng.h where needed, and your program can read/write PNG files. It is compatible with C90 and up, and C++03 and up. If performance is important, use optimization when compiling! For both the encoder and decoder, this makes a large difference. Make sure that LodePNG is compiled with the same compiler of the same version and with the same settings as the rest of the program, or the interfaces with std::vectors and std::strings in C++ can be incompatible. CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. *) gcc and g++ LodePNG is developed in gcc so this compiler is natively supported. It gives no warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ version 4.7.1 on Linux, 32-bit and 64-bit. *) Clang Fully supported and warning-free. *) Mingw The Mingw compiler (a port of gcc for Windows) should be fully supported by LodePNG. *) Visual Studio and Visual C++ Express Edition LodePNG should be warning-free with warning level W4. Two warnings were disabled with pragmas though: warning 4244 about implicit conversions, and warning 4996 where it wants to use a non-standard function fopen_s instead of the standard C fopen. Visual Studio may want "stdafx.h" files to be included in each source file and give an error "unexpected end of file while looking for precompiled header". This is not standard C++ and will not be added to the stock LodePNG. You can disable it for lodepng.cpp only by right clicking it, Properties, C/C++, Precompiled Headers, and set it to Not Using Precompiled Headers there. NOTE: Modern versions of VS should be fully supported, but old versions, e.g. VS6, are not guaranteed to work. *) Compilers on Macintosh LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for C and C++. *) Other Compilers If you encounter problems on any compilers, feel free to let me know and I may try to fix it if the compiler is modern and standards compliant. 10. examples ------------ This decoder example shows the most basic usage of LodePNG. More complex examples can be found on the LodePNG website. NOTE: these examples do not support wide-character filenames, you can use an external method to handle such files and encode or decode in-memory 10.1. decoder C++ example ------------------------- #include "lodepng.h" #include int main(int argc, char *argv[]) { const char* filename = argc > 1 ? argv[1] : "test.png"; //load and decode std::vector image; unsigned width, height; unsigned error = lodepng::decode(image, width, height, filename); //if there's an error, display it if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... } 10.2. decoder C example ----------------------- #include "lodepng.h" int main(int argc, char *argv[]) { unsigned error; unsigned char* image; size_t width, height; const char* filename = argc > 1 ? argv[1] : "test.png"; error = lodepng_decode32_file(&image, &width, &height, filename); if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); / * use image here * / free(image); return 0; } 11. state settings reference ---------------------------- A quick reference of some settings to set on the LodePNGState For decoding: state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums state.decoder.zlibsettings.custom_...: use custom inflate function state.decoder.ignore_crc: ignore CRC checksums state.decoder.ignore_critical: ignore unknown critical chunks state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors state.decoder.color_convert: convert internal PNG color to chosen one state.decoder.read_text_chunks: whether to read in text metadata chunks state.decoder.remember_unknown_chunks: whether to read in unknown chunks state.info_raw.colortype: desired color type for decoded image state.info_raw.bitdepth: desired bit depth for decoded image state.info_raw....: more color settings, see struct LodePNGColorMode state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo For encoding: state.encoder.zlibsettings.btype: disable compression by setting it to 0 state.encoder.zlibsettings.use_lz77: use LZ77 in compression state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching state.encoder.zlibsettings.lazymatching: try one more LZ77 matching state.encoder.zlibsettings.custom_...: use custom deflate function state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png state.encoder.filter_palette_zero: PNG filter strategy for palette state.encoder.filter_strategy: PNG filter strategy to encode with state.encoder.force_palette: add palette even if not encoding to one state.encoder.add_id: add LodePNG identifier and version as a text chunk state.encoder.text_compression: use compressed text chunks for metadata state.info_raw.colortype: color type of raw input image you provide state.info_raw.bitdepth: bit depth of raw input image you provide state.info_raw: more color settings, see struct LodePNGColorMode state.info_png.color.colortype: desired color type if auto_convert is false state.info_png.color.bitdepth: desired bit depth if auto_convert is false state.info_png.color....: more color settings, see struct LodePNGColorMode state.info_png....: more PNG related settings, see struct LodePNGInfo 12. changes ----------- The version number of LodePNG is the date of the change given in the format yyyymmdd. Some changes aren't backwards compatible. Those are indicated with a (!) symbol. Not all changes are listed here, the commit history in github lists more: https://github.com/lvandeve/lodepng *) 09 jan 2022: minor decoder speed improvements. *) 27 jun 2021: added warnings that file reading/writing functions don't support wide-character filenames (support for this is not planned, opening files is not the core part of PNG decoding/decoding and is platform dependent). *) 17 okt 2020: prevent decoding too large text/icc chunks by default. *) 06 mar 2020: simplified some of the dynamic memory allocations. *) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct overflow checks. *) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. *) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette if gray ICC profile) and non-ICC LodePNGColorProfile renamed to LodePNGColorStats. *) 30 dec 2018: code style changes only: removed newlines before opening braces. *) 10 sep 2018: added way to inspect metadata chunks without full decoding. *) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use palette index in case of palette. *) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This change is backwards compatible unless you relied on unknown_chunks for those. *) 11 jun 2018: less restrictive check for pixel size integer overflow *) 14 jan 2018: allow optionally ignoring a few more recoverable errors *) 17 sep 2017: fix memory leak for some encoder input error cases *) 27 nov 2016: grey+alpha auto color model detection bugfix *) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). *) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within the limits of pure C90). *) 08 dec 2015: Made load_file function return error if file can't be opened. *) 24 okt 2015: Bugfix with decoding to palette output. *) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. *) 24 aug 2014: Moved to github *) 23 aug 2014: Reduced needless memory usage of decoder. *) 28 jun 2014: Removed fix_png setting, always support palette OOB for simplicity. Made ColorProfile public. *) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. *) 22 dec 2013: Power of two windowsize required for optimization. *) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. *) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). *) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" prefix for the custom allocators and made it possible with a new #define to use custom ones in your project without needing to change lodepng's code. *) 28 jan 2013: Bugfix with color key. *) 27 okt 2012: Tweaks in text chunk keyword length error handling. *) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. (no palette). Better deflate tree encoding. New compression tweak settings. Faster color conversions while decoding. Some internal cleanups. *) 23 sep 2012: Reduced warnings in Visual Studio a little bit. *) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions and made it work with function pointers instead. *) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc and free functions and toggle #defines from compiler flags. Small fixes. *) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. *) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed redundant C++ codec classes. Reduced amount of structs. Everything changed, but it is cleaner now imho and functionality remains the same. Also fixed several bugs and shrunk the implementation code. Made new samples. *) 6 nov 2011: (!) By default, the encoder now automatically chooses the best PNG color model and bit depth, based on the amount and type of colors of the raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. *) 9 okt 2011: simpler hash chain implementation for the encoder. *) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. *) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. A bug with the PNG filtertype heuristic was fixed, so that it chooses much better ones (it's quite significant). A setting to do an experimental, slow, brute force search for PNG filter types is added. *) 17 aug 2011: (!) changed some C zlib related function names. *) 16 aug 2011: made the code less wide (max 120 characters per line). *) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. *) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. *) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman to optimize long sequences of zeros. *) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and LodePNG_InfoColor_canHaveAlpha functions for convenience. *) 7 nov 2010: added LodePNG_error_text function to get error code description. *) 30 okt 2010: made decoding slightly faster *) 26 okt 2010: (!) changed some C function and struct names (more consistent). Reorganized the documentation and the declaration order in the header. *) 08 aug 2010: only changed some comments and external samples. *) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. *) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. *) 02 sep 2008: fixed bug where it could create empty tree that linux apps could read by ignoring the problem but windows apps couldn't. *) 06 jun 2008: added more error checks for out of memory cases. *) 26 apr 2008: added a few more checks here and there to ensure more safety. *) 06 mar 2008: crash with encoding of strings fixed *) 02 feb 2008: support for international text chunks added (iTXt) *) 23 jan 2008: small cleanups, and #defines to divide code in sections *) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. *) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. *) 17 jan 2008: ability to encode and decode compressed zTXt chunks added Also various fixes, such as in the deflate and the padding bits code. *) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved filtering code of encoder. *) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A C++ wrapper around this provides an interface almost identical to before. Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code are together in these files but it works both for C and C++ compilers. *) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks *) 30 aug 2007: bug fixed which makes this Borland C++ compatible *) 09 aug 2007: some VS2005 warnings removed again *) 21 jul 2007: deflate code placed in new namespace separate from zlib code *) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images *) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing invalid std::vector element [0] fixed, and level 3 and 4 warnings removed *) 02 jun 2007: made the encoder add a tag with version by default *) 27 may 2007: zlib and png code separated (but still in the same file), simple encoder/decoder functions added for more simple usage cases *) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), moved some examples from here to lodepng_examples.cpp *) 12 may 2007: palette decoding bug fixed *) 24 apr 2007: changed the license from BSD to the zlib license *) 11 mar 2007: very simple addition: ability to encode bKGD chunks. *) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding palettized PNG images. Plus little interface change with palette and texts. *) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. Fixed a bug where the end code of a block had length 0 in the Huffman tree. *) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented and supported by the encoder, resulting in smaller PNGs at the output. *) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. *) 24 jan 2007: gave encoder an error interface. Added color conversion from any greyscale type to 8-bit greyscale with or without alpha. *) 21 jan 2007: (!) Totally changed the interface. It allows more color types to convert to and is more uniform. See the manual for how it works now. *) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: encode/decode custom tEXt chunks, separate classes for zlib & deflate, and at last made the decoder give errors for incorrect Adler32 or Crc. *) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. *) 29 dec 2006: Added support for encoding images without alpha channel, and cleaned out code as well as making certain parts faster. *) 28 dec 2006: Added "Settings" to the encoder. *) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. Removed some code duplication in the decoder. Fixed little bug in an example. *) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. Fixed a bug of the decoder with 16-bit per color. *) 15 okt 2006: Changed documentation structure *) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the given image buffer, however for now it's not compressed. *) 08 sep 2006: (!) Changed to interface with a Decoder class *) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different way. Renamed decodePNG to decodePNGGeneric. *) 29 jul 2006: (!) Changed the interface: image info is now returned as a struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. *) 28 jul 2006: Cleaned the code and added new error checks. Corrected terminology "deflate" into "inflate". *) 23 jun 2006: Added SDL example in the documentation in the header, this example allows easy debugging by displaying the PNG and its transparency. *) 22 jun 2006: (!) Changed way to obtain error value. Added loadFile function for convenience. Made decodePNG32 faster. *) 21 jun 2006: (!) Changed type of info vector to unsigned. Changed position of palette in info vector. Fixed an important bug that happened on PNGs with an uncompressed block. *) 16 jun 2006: Internally changed unsigned into unsigned where needed, and performed some optimizations. *) 07 jun 2006: (!) Renamed functions to decodePNG and placed them in LodePNG namespace. Changed the order of the parameters. Rewrote the documentation in the header. Renamed files to lodepng.cpp and lodepng.h *) 22 apr 2006: Optimized and improved some code *) 07 sep 2005: (!) Changed to std::vector interface *) 12 aug 2005: Initial release (C++, decoder only) 13. contact information ----------------------- Feel free to contact me with suggestions, problems, comments, ... concerning LodePNG. If you encounter a PNG image that doesn't work properly with this decoder, feel free to send it and I'll use it to find and fix the problem. My email address is (puzzle the account and domain together with an @ symbol): Domain: gmail dot com. Account: lode dot vandevenne. Copyright (c) 2005-2022 Lode Vandevenne */ chafa-1.14.5/tests/000077500000000000000000000000001471154763100140175ustar00rootroot00000000000000chafa-1.14.5/tests/Makefile.am000066400000000000000000000021761471154763100160610ustar00rootroot00000000000000SUBDIRS = data AM_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) LDADD = $(GLIB_LIBS) $(top_builddir)/chafa/libchafa.la ## --- Test driver --- # Our custom test driver is based on Automake's, with the addition that # it will print logs of failed tests to the console. This simplifies log # perusal in build systems. Requires Automake 1.12+. LOG_DRIVER = $(top_srcdir)/tests/test-driver.sh ## --- Backend tests --- noinst_PROGRAMS = \ term-info-test term_info_test_SOURCES = \ term-info-test.c ## --- Frontend tests --- if WANT_TOOLS TOOL_CHECKS = \ chafa-tool-bad-test.sh \ chafa-tool-cmode-test.sh \ chafa-tool-format-test.sh \ chafa-tool-loader-test.sh \ chafa-tool-options-test.sh \ chafa-tool-pipe-test.sh else TOOL_CHECKS = endif TESTS = \ term-info-test \ $(TOOL_CHECKS) AM_TESTS_ENVIRONMENT = \ export top_builddir=$(top_builddir) \ export top_srcdir=$(top_srcdir) \ ; ## --- General --- ## Include $(top_builddir)/chafa to get generated chafaconfig.h. AM_CPPFLAGS = \ -I$(top_srcdir)/chafa \ -I$(top_builddir)/chafa EXTRA_DIST = \ $(TOOL_CHECKS) \ chafa-tool-gallery.sh \ chafa-tool-test-common.sh \ test-driver.sh chafa-1.14.5/tests/adaptive.c000066400000000000000000000227531471154763100157710ustar00rootroot00000000000000/* This file is in the public domain, and you are free to use it as you see fit. * * Compile with: * * cc adaptive.c -o adaptive $(pkg-config --libs --cflags chafa) */ #include #include #include /* Include after chafa.h for G_OS_WIN32 */ #ifdef G_OS_WIN32 # ifdef HAVE_WINDOWS_H # include # endif # include #else # include /* ioctl */ #endif typedef struct { gint width_cells, height_cells; gint width_pixels, height_pixels; } TermSize; 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; /* Examine the environment variables and guess what the terminal can do */ envp = g_get_environ (); term_info = chafa_term_db_detect (chafa_term_db_get_default (), envp); /* See which control sequences were defined, and use that to pick the most * high-quality rendering possible */ if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1)) { pixel_mode = CHAFA_PIXEL_MODE_KITTY; mode = CHAFA_CANVAS_MODE_TRUECOLOR; } else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS)) { pixel_mode = CHAFA_PIXEL_MODE_SIXELS; mode = CHAFA_CANVAS_MODE_TRUECOLOR; } else { pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; 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; } /* Hand over the information to caller */ *term_info_out = term_info; *mode_out = mode; *pixel_mode_out = pixel_mode; /* Cleanup */ g_strfreev (envp); } static void get_tty_size (TermSize *term_size_out) { TermSize term_size; term_size.width_cells = term_size.height_cells = term_size.width_pixels = term_size.height_pixels = -1; #ifdef G_OS_WIN32 { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csb_info; if (chd != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo (chd, &csb_info)) { term_size.width_cells = csb_info.srWindow.Right - csb_info.srWindow.Left + 1; term_size.height_cells = csb_info.srWindow.Bottom - csb_info.srWindow.Top + 1; } } #else { 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) { term_size.width_cells = w.ws_col; term_size.height_cells = w.ws_row; term_size.width_pixels = w.ws_xpixel; term_size.height_pixels = w.ws_ypixel; } } #endif if (term_size.width_cells <= 0) term_size.width_cells = -1; if (term_size.height_cells <= 2) term_size.height_cells = -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 (term_size.width_pixels <= 0 || term_size.height_pixels <= 0) { term_size.width_pixels = -1; term_size.height_pixels = -1; } *term_size_out = term_size; } static void tty_init (void) { #ifdef G_OS_WIN32 { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); saved_console_output_cp = GetConsoleOutputCP (); saved_console_input_cp = GetConsoleCP (); /* Enable ANSI escape sequence parsing etc. on MS Windows command prompt */ if (chd != INVALID_HANDLE_VALUE) { if (!SetConsoleMode (chd, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) win32_stdout_is_file = TRUE; } /* Set UTF-8 code page I/O */ SetConsoleOutputCP (65001); SetConsoleCP (65001); } #endif } static GString * convert_image (const void *pixels, gint pix_width, gint pix_height, gint pix_rowstride, ChafaPixelType pixel_type, gint width_cells, gint height_cells, gint cell_width, gint cell_height) { ChafaTermInfo *term_info; ChafaCanvasMode mode; ChafaPixelMode pixel_mode; ChafaSymbolMap *symbol_map; ChafaCanvasConfig *config; ChafaCanvas *canvas; GString *printable; detect_terminal (&term_info, &mode, &pixel_mode); /* Specify the symbols we want */ symbol_map = chafa_symbol_map_new (); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_BLOCK); /* 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_pixel_mode (config, pixel_mode); chafa_canvas_config_set_geometry (config, width_cells, height_cells); chafa_canvas_config_set_symbol_map (config, symbol_map); if (cell_width > 0 && cell_height > 0) { /* We know the pixel dimensions of each cell. Store it in the config. */ chafa_canvas_config_set_cell_geometry (config, cell_width, cell_height); } /* Create canvas */ canvas = chafa_canvas_new (config); /* Draw pixels to the canvas */ chafa_canvas_draw_all_pixels (canvas, pixel_type, pixels, pix_width, pix_height, pix_rowstride); /* Build printable string */ printable = chafa_canvas_print (canvas, term_info); /* Clean up and return */ chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); chafa_symbol_map_unref (symbol_map); chafa_term_info_unref (term_info); return printable; } #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 }; TermSize term_size; GString *printable; gfloat font_ratio = 0.5; gint cell_width = -1, cell_height = -1; /* Size of each character cell, in pixels */ gint width_cells, height_cells; /* Size of output image, in cells */ /* Initialize the tty device if needed */ tty_init (); /* Get the terminal dimensions and determine the output size, preserving * aspect ratio */ get_tty_size (&term_size); if (term_size.width_cells > 0 && term_size.height_cells > 0 && term_size.width_pixels > 0 && term_size.height_pixels > 0) { cell_width = term_size.width_pixels / term_size.width_cells; cell_height = term_size.height_pixels / term_size.height_cells; font_ratio = (gdouble) cell_width / (gdouble) cell_height; } width_cells = term_size.width_cells; height_cells = term_size.height_cells; chafa_calc_canvas_geometry (PIX_WIDTH, PIX_HEIGHT, &width_cells, &height_cells, font_ratio, TRUE, FALSE); /* Convert the image to a printable string */ printable = convert_image (pixels, PIX_WIDTH, PIX_HEIGHT, PIX_WIDTH * N_CHANNELS, CHAFA_PIXEL_RGBA8_UNASSOCIATED, width_cells, height_cells, cell_width, cell_height); /* Print the string */ fwrite (printable->str, sizeof (char), printable->len, stdout); fputc ('\n', stdout); /* Free resources and exit */ g_string_free (printable, TRUE); return 0; } chafa-1.14.5/tests/chafa-tool-bad-test.sh000077500000000000000000000004221471154763100200720ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" for format in $formats; do for size in $sizes; do run_cmd_all_bad_files "$tool -f $format -c 16 --dither fs -s $size --threads 12 -d 0 --speed max" || exit $? done done chafa-1.14.5/tests/chafa-tool-cmode-test.sh000077500000000000000000000004721471154763100204400ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" for cmode in $cmodes; do for size in $sizes; do for n_threads in $thread_counts; do run_cmd "$tool -f symbol -c $cmode -s $size --threads $n_threads --animate no" || exit $? done done done chafa-1.14.5/tests/chafa-tool-format-test.sh000077500000000000000000000005161471154763100206400ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" for format in $formats; do for size in $sizes; do for n_threads in $thread_counts; do run_cmd_all_safe_files "$tool -f $format -c full -s $size --threads $n_threads -d 0 --speed max" || exit $? done done done chafa-1.14.5/tests/chafa-tool-gallery.sh000077500000000000000000000062571471154763100200420ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" tempdir="${top_builddir}/tests/.temp" xvfb=$(which Xvfb) [ "x${xvfb}" = "x" ] && echo "Missing Xvfb" && exit 1 gterm_name=xfce4-terminal gterm=$(which ${gterm_name}) [ "x${gterm}" = "x" ] && echo "Missing ${gterm_name}" && exit 1 montage=$(which montage) [ "x${montage}" = "x" ] && echo "Missing montage" && exit 1 # Terminal parameters font='xos4 Terminus 8' geometry=60x40 screen_size=1280x1024 # Chafa parameter lists color_modes="tc 240 16 8 2 none" color_spaces="rgb din99d" color_extractors="average median" dither_types="none ordered diffusion" symbol_selectors="+0 all wide" work_factors="1 5 9" # Input filename. Must be in data/good/. file="taxic.jpg" function gen_screenshot { FNAME="$1" echo -- Running -e "bash -c \"${tool} -s ${geometry} -w ${5} -c ${2} --symbols ${3} --color-space ${4} \ ${top_srcdir}/tests/data/good/${FNAME} ; sleep 0.5 ; import -window chafa out/out-${2}-${3}-${5}-${4}.png -trim\"" HOME="${tempdir}" XDG_CONFIG_HOME="${HOME}/.config" DISPLAY=:99 ${gterm} \ -T chafa \ --disable-server \ --geometry ${geometry}+0+0 \ --font "$font" \ --hide-menubar \ --hide-toolbar \ --hide-borders \ --hide-scrollbar \ -e "bash -c \"${tool} -s ${geometry} -w ${5} -c ${2} --symbols ${3} --color-space ${4} \ ${top_srcdir}/tests/data/good/${FNAME}; sleep 0.5 ; import -window chafa ${tempdir}/out/${2}-${3}-${5}-${4}.png -trim >/dev/null 2>&1\"" >/dev/null 2>&1 } trap "" EXIT SIGQUIT SIGSTOP SIGTERM # Make sure we don't blow away any old .temp dir rm -Rf "${tempdir}/.config" rm -Rf "${tempdir}/out" rmdir "${tempdir}" mkdir -p "${tempdir}/out" mkdir -p ${tempdir}/.config/xfce4/terminal cat >${tempdir}/.config/xfce4/terminal/terminalrc </dev/null 2>&1 & XVFB_PID=$! sleep 2 for cmode in ${color_modes}; do for symbols in ${symbol_selectors}; do for cspace in ${color_spaces}; do for work in ${work_factors}; do gen_screenshot "$file" "$cmode" "$symbols" "$cspace" "$work" done done done done kill ${XVFB_PID} rm -f "${top_builddir}/tests/chafa-tool-gallery.png" ${montage} -stroke white -background grey20 -geometry +4+4 -tile 6 -label '%f' \ "${tempdir}/out/*.png" \ "${top_builddir}/tests/chafa-tool-gallery.png" chafa-1.14.5/tests/chafa-tool-loader-test.sh000077500000000000000000000004571471154763100206220ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" extensions="$(get_supported_loaders)" for ext in $extensions; do [ "x${ext}" = "ximagemagick" ] && continue run_cmd_single_file "$tool -f sixel --threads 12 --animate no" "good/pixel.$ext" || exit $? done chafa-1.14.5/tests/chafa-tool-options-test.sh000077500000000000000000000015121471154763100210400ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" color_spaces="rgb din99d" color_extractors="average median" dither_types="none ordered diffusion" symbol_selectors="none all wide 0..1ffff" for format in $formats; do for color_space in $color_spaces; do for color_extractor in $color_extractors; do for dither_type in $dither_types; do for main_symbols in $symbol_selectors; do for fill_symbols in $symbol_selectors; do run_cmd "$tool -f $format -c 240 -s 53 --threads 12 --animate no --color-space $color_space \ --color-extractor $color_extractor --dither $dither_type --symbols $main_symbols --fill $fill_symbols" || exit $? done done done done done done chafa-1.14.5/tests/chafa-tool-pipe-test.sh000077500000000000000000000002511471154763100203010ustar00rootroot00000000000000#!/bin/sh [ "x${srcdir}" = "x" ] && srcdir="." . "${srcdir}/chafa-tool-test-common.sh" run_cmd "$tool -f symbol -c full -s 63 --threads 12 --animate no < " || exit $? chafa-1.14.5/tests/chafa-tool-test-common.sh000077500000000000000000000035161471154763100206430ustar00rootroot00000000000000#!/bin/sh run_cmd () { cmd="$1 ${top_srcdir}/tests/data/good/card-32c-noalpha.png >/dev/null" echo "$cmd" >&2 sh -c "$cmd" || exit $? } run_cmd_single_file () { file="$2" cmd="$1 ${top_srcdir}/tests/data/$file >/dev/null" echo "$cmd" >&2 sh -c "$cmd" || exit $? } run_cmd_all_safe_files () { # Only run on files for which we're guaranteed to have loaders. # '$dir/*.{gif,png,xwd}' is a Bash-ism, so we can't use it. dir="${top_srcdir}/tests/data/good" cmd="$1 $dir/*.gif $dir/*.png $dir/*.xwd >/dev/null" echo "$cmd" >&2 sh -c "$cmd" || exit $? } run_cmd_all_bad_files () { # Since this only fails on crashes, it's fine to run it on absolutely # everything (including unsupported formats, build files etc). cmd="$1 ${top_srcdir}/tests/data/bad/* >/dev/null" echo "$cmd" >&2 sh -c "$cmd" result=$? # For corrupt files, an exit value of 1 or 2 is a fine result, # but other values are trouble (e.g. terminated by signal). if test $result -gt 2; then exit $result; fi } get_supported_loaders () { sh -c "$tool --version" \ | grep '^Loaders: ' \ | sed 's/[^:]*: *\(.*\)/\1/' \ | tr [:upper:] [:lower:] } [ "x${top_srcdir}" = "x" ] && top_srcdir="${srcdir}/.." [ "x${top_builddir}" = "x" ] && top_builddir=".." [ "x$1" = "xlong" ] && long="yes" tool="${top_builddir}/tools/chafa/chafa" if [ "x$long" = "xyes" ]; then formats="symbol sixel kitty iterm" cmodes="none 2 8 16/8 16 240 256 full" sizes="$(seq 1 100)" thread_counts="$(seq 1 32) 61" else # The 13x13 size is specifically required to trigger the normalization # overflow (see commit 60d7718f9d8fa591d3d69079fe583913c58c19d9). formats="symbol sixel kitty iterm" cmodes="none 2 16/8 16 256 full" sizes="1 3 13x13 133" thread_counts="1 2 3 12 32 61" fi chafa-1.14.5/tests/data/000077500000000000000000000000001471154763100147305ustar00rootroot00000000000000chafa-1.14.5/tests/data/Makefile.am000066400000000000000000000000231471154763100167570ustar00rootroot00000000000000SUBDIRS = bad good chafa-1.14.5/tests/data/bad/000077500000000000000000000000001471154763100154565ustar00rootroot00000000000000chafa-1.14.5/tests/data/bad/Makefile.am000066400000000000000000000005511471154763100175130ustar00rootroot00000000000000EXTRA_DIST = \ libnsgif-lzw-oob.gif \ libnsgif-uninitialized-frame-fields.gif \ lodepng-adam7-mystery-over-read.png \ lodepng-zero-length-literal.png \ lodepng-zlib-big-alloc.png \ no-frame-data.gif \ pixops-normalize-signed-overflow.gif \ smolscale-unaligned-1.xwd \ smolscale-unaligned-2.xwd \ smolscale-unaligned-3.xwd \ smolscale-unaligned-4.xwd chafa-1.14.5/tests/data/bad/libavif-inf-frame-duration.avif000066400000000000000000000364001471154763100234310ustar00rootroot00000000000000 ftypavisavismsf1miafMA1Bmeta2hdlrpictGPAC pict Handlerpitmiloc@ :(iinfinfeav01ImagetiprpUipcoispepaspav1C pixiipmamoovlmvhdڲ]9ڲ]9@iodsOdtrak\tkhdڲ]9@$edtselstmdia mdhdڲ]9U+hdlrpictGPAC avifsminfvmhd$dinfdref url Istblstsdav01HHav1C paspccst| fielbtrt :  sttsstssstsc(stsz : L 3stcoU8mdat 2m58*bvPqDI$~wqwI$IWqDe՘3@ϽhR_ ݉ZJZ 7n2nM J5'B~@ hS@ 08xu a=SlZ2ʣ>gtp$]ssKJ5(!2D2t'20{/6EYi`CK$ 44o'^Ik%!Lcޥh0E=g&o.)UQ 㰌S"{p][ #VHr3XdxB;Jny&>JEr `&7zd$f@)#gKռL%]VI1SU=BBmW &q/H.c ͤnY ^lCψ N4rF4rE`ꎷ ^/g/40 t߿U'(L.cnpoD8Aq- 8C-(BߌbnV|W*o [n57<BQǪ@J0YAdl;0U^dC pG#]"OTc@'MCxGUR+j&bv&Zȅ#";k"奊|oI.'ou /TtɿQi `6s5)Es i-.>@fm7M_-~GzvkA;kvkvLd6kBl+VZ >s Q-nas T2>bh*Y|:)O'{#Qm(jKS|sH؁~6a$*Vf/g&Ѯ&m-1*Ҏhfx!iE~ǫ\Ǘ3J])C/(c|;,'xqElgtp$]ssKJ5(!2D2t'20{/6EYi`CK$ 44o'^Ik%!Lcޥh0E=g&o.)UQ 㰌S"{p][ #zzzzzzzzzzzVHr3XdxB;Jny&>JEr `&7zd$f@)#gKռL%]VI1SU=BBmW &q/H.c ͤnY ^lCψ N4rF4rE`ꎷ ^/g/40 t߿U'(L.cnpoD8A q- 8C-(BߌbnV|W*o [n57<BQǪ@J0YAdl;0U^dC pG#]"OTc@'MCxGUR+j&bv&Zȅ#";k"奊|oI.'ou /TtɿQi `6s5)Es i-.>@fm7M_-~GzvkA;kvkvLd6kBl+VZ >s Q-nas T2>bh*Y|:)O'{#Qm(jKS|sH؁~6a$*Vf/g&Ѯ&m-1*Ҏhfx!iE~ǫ\Ǘ3J])C/(c|;,'xqElfߤeobΠeM;>x'XwV=GJIq.2k-4809K;Qen[Vy*aXx,a_GP!ߣqʡklh|*%ԍr=`;td&(@CFBn ;`Ifb5OLv5CSUgQ_4񇓏l-ڲeI2љ6R0H©&Q1+@6з|}o2VP^_}FY08{#Βtt229#@ ^?ˊXa9O#}Fypr̕mۑqH bSC?Ռ;n` v^7m,p\"&QlkN+G8"ְyԐeUIʏ\&B<9@t<8dcA`Z{c])X23[  wKWa5x ' Kr@kEyRCu!)= 6Ex` 5'\5n% Ҁ}􃛖 <x{9#'FD'ˆAǵzw%  hWSobZsd݅hvJ(u)xN,*R!}<  "ECfa.E <"E}&O)w9+R@Rh5tIqGF/ <<>>_5=AbkWX9>YkL[̱kM7d<mP/sl#7De}'[xtYh&qӣ<-$ Nu K&y&KBrcТ8K !fYtEB |/wۺ,*>2F toGF7N!w()ԼKygX FXxB(]d9b~k-"%TXOTZrz6l^n:bmcCvTAk'[9ydTf6~U9N@ )zLk )9Cffwisڹ se0>ڤ/3ϋɿe<֠dO^&\w0 tbQcxlm-_Xs_Bte?oA'2d%uͲeUK+kD x] e7>8cb`j2W,8ݶhDMo2VP^_}FY08{#ߒttڂxJCS#=7w 0v҄j3Vz/[ő%ҩu`}xȯq'=3v`Vl9Űdge 3w~ڼfS'oednIܞdӉA2GUN3C: E?P"gs[I1^4nrn}CH$e y>t1e2\7I;tck uB>NڵC K={61 ?|mǩZZ#@T{)y Mt$gL20?pG C->x뜫-v;)}}{7.dǿS))F[nq;PN 9o% ge}iQDy3!4N zS9d1eӊ"WA 'S0tQVx2XG3U ~ҤFfDz;9 oQU}dG igjѢpag['U71sN蝟 Y1]T&gs_iґS=($Ni9o"Lцf{a;!avh`PՈAJ9._m} 8oe_*!jwM'an򵝁tE?CEY*< }' /Cdj`rȧH吏Bi2f,dGU_}|bNsM&W*ŝ+!v%$/l^M~f/0 kDMtU.?< \gc̑<5in!r+9i)<NǨ\X]^e.Lt`eJK=Z mu:e1n0%X06}iCt/Er?!lGRE~U,jc <ʼӂV-+İE! dv.|©c,*1ص:)jO,,L1??c_&f8*W^0tQVx2XG3U ~9ՠ20.~%;z׷TbFF. diP#2qTeSAQS 8fD; 1b-RU$b ţߡҍߋxr 9gK42Ă3dIΒAT,7 7zqbA֪tQ`=Ű9RPYC~ׯ2AW:QYN\8ŠèʯLA3z̟WnVWYshVW8G.PWD ]$jB)@}F!'Ĺ]bOX7;9d +\ųG~Qp<"kVGZRf = 8#,q8Ki /2rݷj4y^9pɱI6/\yxEX0YL dZ3/lë-ds "edrW'~ 7N.m}167hgf*Td;QOgOcȿȫ*± ȫڰ*¬+vaqzZ1XxW$6+ TS3תVi9O_0m 9/i5Uw t+6ҋS7{qAT \lk:w 45g6VQ :-9'[*# '.j9ad2I!hc< [z?V0FTE4 w#YA8t/6O|IQ~~wCNGYkmEv <9kƭiVM1}vۈ+GEέ1J]n^HU&5X XrK/m] [v~ͧG%:7fןs ŕW7EY5G!kcg H /Uy*S pG[!^( 8IءcAW!MM7lGSnt1C1_me?VoGЗwB+:l/g'dZۘ]ʨv$3aT}̮fnY,x`& ãYdo l%UXҰnz &VV'"AWqn>y&a+Qx1XV{7% Ch{mΪ.,d8Y=`*{7}x9TֽZk4'HAפufL@"J"5L$>h;1KP j`Z yTdShhU)#ΌAb[܅Զכc JdxueoH?e&Ȣ|3M(uys_P8K67SᖶB8mH=Nnj[#SW=Vm8'2rY7Mrte&3yqjz#N[wGH#20E.~ ];DQ!꫔73j0,QYl=Ă&YoRA{CxmA"h0EpUn {rmM9oZFa@.;X,waa\\YtciM.!*_\d\N?_:%%qXA랧Yw-]$Ѓh}d ڋMkM f FZ%UiFa:R\zW*LYE|ZFsއ*.`i ;VBJ({$xTR0%w >I4v8FH^~SB/D%vǖ}&5~>AAUG tª<ٳ`j;( \ՋZI ST7YRj|hl'K{3R:=R{TÒ:Kb]UXbj)c= VD3:IԜf6@ż-)MަLO#Mm4|4\rt"/ YW&.RnMUĩ[^1EIe`RS3 ro#j 2 psةQl4u8~zG& ]S)I fydWZM_`Hn(ۗl4!WZC7H˷$K GN狿C8-ݵ\EFn}ǖЈ$VX b*/p9)*/Cф]*J N`FmH8B@T.S!OŶrUMEܡaC);$hb0t_!O–vC3eRr oT\rێ5V^-1'cа^7:W!,tt[ ^Ty՜|-W0a&..`/'-U8 !zTjzW9Hv94m_{8䒁9z1+7"oĪ31Qޛ:hi~dlt-=)jvaB5_aq(JGe#;՗=E S{[KR{`X݋l(@~ԫU d`аs\2s+s4En<,ATB}S9 (E:\=QxȢ1 ?CzNkA#gcBj{x^2kc~aMƨA`AY9C!4wDȯ3Ck6Kl!bĜz i㼷AK mpճb6ۊ3w5guT𿈮fb@F7JV;/&f#,f_"z8RȚ<]ϡRe4 r0@sbaB{ᚊ`[כs"| 1ʬTsYk)}KQXknrE{&2}ٝ,A}Hqt΅6BsVJ⛛o_;>_Ѧ+/ONIJHސ;‚ky5(iIØ;!= Q!=zW[J(+I7Dvj5}NYYbE=fBI5 FtRnNv gH`&K2~8Y(*NbO<N#n%$A$΢aBlQe4,4vm,'&jo}iiÇ| >o4|kQas׋]3T]2!K㔱Fn!pYn;7{Mַ-( h,+*?ib䌵EOO CQ YdSchafa-1.14.5/tests/data/bad/libnsgif-lzw-oob.gif000066400000000000000000000015301471154763100213300ustar00rootroot00000000000000GIF87a fffff3f3333f333f3̙f3̙̤f3̙f3ff̙ffff3f33̙33f333̙f3@fffff3̙f3f3ffffff3f3333f333f3̣ff3̙f3̙̙̙̙f̙3̙fff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f2333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3 ̙f3f3fffffff 3333fB33f3wUD"wUd&<d"PNG  IHDRQX{aGQ!, q D"UUUwwwwwwDDD"""wUfscLj9(&Jchafa-1.14.5/tests/data/bad/libnsgif-uninitialized-frame-fields.gif000066400000000000000000000001071471154763100251420ustar00rootroot00000000000000GIF89a;;;;;;;;;;;;;;;!Creaed wth GMS,D;chafa-1.14.5/tests/data/bad/lodepng-adam7-mystery-over-read.png000066400000000000000000000011051471154763100241740ustar00rootroot00000000000000PNG  IHDRpcHgAMA aiCCPICC profile(}=H@_SkE* qP,VBЪɥB$Qp-8XupqU?@]%ݽ;@fnxLLgV+@'3˘Z{zY9ԬH< &^'ܴ aVUs.Hu7y6S90obYԈ'#SXY+Yuc BA(FV Iڏ~\ 614Ȯ~wkǼP 8j>v T>I4н \\74e ٔ]OS3 { tzq pp ){Ż;{Lznr (bKGEC pHYs.#.#x?vtIME!¿tEXtCommentCreated with GIMPW IDATc/&~IENDB`chafa-1.14.5/tests/data/bad/lodepng-zero-length-literal.png000066400000000000000000000001671471154763100235060ustar00rootroot00000000000000PNG  IHDRpcHgAMA iCCPICC profdle(P,"V&63 zqchafa-1.14.5/tests/data/bad/lodepng-zlib-big-alloc.png000066400000000000000000000005411471154763100224010ustar00rootroot00000000000000PNG  IHDR000000000000(IDATx^ fF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000IEND0000chafa-1.14.5/tests/data/bad/no-frame-data.gif000066400000000000000000000000161471154763100205550ustar00rootroot00000000000000GIF89a;chafa-1.14.5/tests/data/bad/pixops-normalize-signed-overflow.gif000066400000000000000000000001401471154763100245700ustar00rootroot00000000000000GIF89aNreat,YreatGIF89a)F!&CbWWWWWWWWWWWWWWKWWWWWWWWWWWMP,F!chafa-1.14.5/tests/data/bad/smolscale-unaligned-1.xwd000066400000000000000000000005651471154763100222740ustar00rootroot00000000000000gA  @@QNG pcHgAMV V iCC;ICC pr@`SkEd Hݽ BA(FV Iڏ~\ 614ȼiCCPICC Uswkchafa-1.14.5/tests/data/bad/smolscale-unaligned-2.xwd000066400000000000000000000012651471154763100222730ustar00rootroot00000000000000   DL, DDDDDDDD̈ULIݓ"""ffffUOUOU333wwww]U]DDDDݓDDDDDDDDDDD̈DDUUULIUUUUULLIILLLLOOOU]]UUUÜDDDDD!fdU@̈DLݙLLݓUOIF89aDO]]]]]U]GIF89aHHHHHHHHHHH,9A(2+EDdDIIIIIݓݓIݓݓIݓ̼ӫ_D̈#Uchafa-1.14.5/tests/data/bad/smolscale-unaligned-3.xwd000066400000000000000000000011711471154763100222700ustar00rootroot00000000000000   DL, DDDDDDDD̈ULIݓ""ffffUOc BAOU33wwww]U]DDDDݓDDDD&DDDDDD̈DDLLOOOU]]UUUÜDDDDD!fdU@̈DLݙLLݓUOIF89aDO]]]]]U]GIF89aHHHHHHHHHHH,9A(2+UUDdDIIIIIݓݓIݓ̈#Uchafa-1.14.5/tests/data/bad/smolscale-unaligned-4.xwd000066400000000000000000000012651471154763100222750ustar00rootroot00000000000000ݓ  DL, DDDDDDDD̈ULI """ffffUOUOU333wwww]U]DDDDݓDDDDDDDDDDD̈DDUUULIUUUUULLIILLLLOOOU]]UUUÜDDDDD!fdU@̈DLݙLLݓUOIF89aDO]]]]]U]GIF89aHHHHHHHHHHH,9A(2+EDdDIIIIIݓݓIݓݓIݓ̼ӫ_D̈#Uchafa-1.14.5/tests/data/good/000077500000000000000000000000001471154763100156605ustar00rootroot00000000000000chafa-1.14.5/tests/data/good/Makefile.am000066400000000000000000000005661471154763100177230ustar00rootroot00000000000000EXTRA_DIST = \ anim-local-cmaps.gif \ anim.gif \ card-32c-alpha.png \ card-32c-noalpha.png \ card-full-alpha.png \ card-full-noalpha.png \ noise-32x32-indexed.png \ noise-32x32.gif \ noise-32x32.png \ noise-32x32.xwd \ pixel.avif \ pixel.gif \ pixel.jpeg \ pixel.jxl \ pixel.png \ pixel.qoi \ pixel.svg \ pixel.tiff \ pixel.webp \ pixel.xwd \ taxic.jpg chafa-1.14.5/tests/data/good/anim-local-cmaps.gif000066400000000000000000000070501471154763100214660ustar00rootroot00000000000000GIF89a ~JKX&nIzh7B{Uu.浖,6nuwN[ry4uyur|T>@AqVnj@ gjUzI|jlovȴX@MQU^Pnt~SuEڇiŭ1ܫ'zӞvcfg6v&/<YQi ^t8AQ&T8RT! NETSCAPE2.0!Created with GIMP!2, &<Y-56=R?AILMRkUXhZ_bMgh)ly{onTFǯΈ/`XМitXQ ^ e O x {&'++K3h5u7:88=s@AGGL#L+ OQQRTTTTTVYZT[[w[\k]^nc~d[fzhD"hdjkmnopqqZr*tBvvmvx]y{J}>=χO؈ۊR*czŗGZWDȣҧkƩgTAsxkQzĹMKS&JItţ˜ct,bѮ(8g՘k܅>݋S0sf8\\~uT|5,EOs| (Էz8"3Bs|2ȝr/Xʍa-@& D]`]lMH ^9d?e+ FKq# d5"dRw( (0W WjǔQ5LX-H,8@2$lE4V:@-0z /ȗKཡ @ |!˗6 P2dsDA+2a^ 1N᳌Wbƥ4p'qm#$b@']p \ .3H@ 74P@q PKdJ2td .Db )@#6!MB0%PK9ԓ $  @=zܰ @"}쀁d|C ܈W3 Әb&^\B `%`ꩨꪬ꫰*무j뭸x, 00W$P$Ɣ,'$B3%D@pta#|:2HGA7)A(E"|AsPؤ0e!M-E pI>6(1p J5q !z2l VP"z 0qF@!2, Y  u %0.x@,`hϓN>U n-K E!,l MH $Tk@6Ç F`P"ܣϜgXH@4Eó&@"5 22p2 LÈ+^̸ǐ#KLX" elF-(82:LaQ8$aFin9``Ij0U1kbbAJkνËOӫ_Ͼ˟^ LL,_ B)6c!H4XЂ.}4TmnfzМMkkFTQtBţȡYΈEgpux]\܅gsZѮ۶Qhd҇O˜cG݋՘c~qZg6jznUnIiTl~Pٟܰ! NETSCAPE2.0!Created with GIMP!2, :G $0!H E2(ф- +,2K@F4U $F }Qih:a3' %P T LNd'0(Pw`M?N5b!֡+Y kHhO0G4Q(j BC@ fU;6iYfjL&@c ,'M,Ze" EG ;3ph"#I!CtE0@OE8DFJ#X<5_Ss&FF ¿(hxE\(%JTPat+0&R!G!sCE#bl #K EB,aA+BhH$ J\ r|DRG'yL@R`a&H$Ae$ATPIC|34d@X`J` r*AA!BtXf4졇H`*ꨤjꩨꪬ꫰*무ڐuP0Hu@"P BQM2C!lQ^ Y JB(%A.h1zHr[AHp!%s0B0w!œuJx$A.؁(8""G 4 8q#dGJCQ s@d(_A! H qǯ"@)Vz1H 8%2yG FK {mR0I\aqXIxyd2o̠TU\2R-TPH1A}!2, EA^ " !<`ƃ6"LQΛpӌP 偈w$OI1J pEF#`y$PPaA6 QD/D< ^#eAȷ%&x_=!#P\9t LÈ+^̸ǐ#KLT iKCƙ*:PpFaNĖECHHvąTx!Zh %ZسkνËOӫ_ϾDM%6R `%$T,@E6oPM\l@npA D@ܠq$dDX31SDB+`5H16@4O DAJI=m-p@%BD]!2, @phyN~JlJQ) d&Kt6m;v}gG*.q/qmid  m q,+i"-$LA!2,=:86578;57:ؗ;݋5:ŋ8;  32<1@ S&< ;chafa-1.14.5/tests/data/good/card-32c-alpha.png000066400000000000000000000441301471154763100207510ustar00rootroot00000000000000PNG  IHDRL;g_cPLTEf50(33/p+NPMufknm}D-k|ѿ/t殚 D|{ԎiotRNS@ftIME" 7] IDATx ްf=OudYiZadLEP@/v;3F?/F'ͽl2^=Y9+7D"EYe5|8#| 2_/噴iV!IքsP60 #sƮ;"BSs>1EJz%> IvճsAYeFO a"$v ޕjEwJSv朒ƄOOzL|Wf[ xӃs>QvH+K@hBs*4ٶ2ଜ.9t% [D]ic\D2*L٘D9W 08+ŭā#$ JU>&"˛8V$ւIֻLL01`)L$oLjWLa"I4L ɦ01j `"Lh3) ]'3GWt4 6dm&`D ^3&`&;¤#YX]a"dt%1!L% 00)41dM!20)S0blhU`&`RځGuH6| ICmB00jr5 LL27s kQ &`>21A9BQ`&`xGt%( R( $9y~U=`&`2UE$QB01y&`&kIH>Pfy&`&MUn&)1avEl?2IrX6IAwD+Av'`&`2C,(0&;B2&4LL$hG (`&`"Hbdj1 ' %riSNL-IQyR4&"_Lr~&2ƅ~0&^"QYd3d>&L2FyÒ퐞㮴9&W0)97+QsN$i3)\y̻oSl?&=0)_dѨ>&Ӌi"L~0鮺h:0)15LF?jtLl 4 m3)rcWal`ޜ"{D)b8Q+a2f61Q>6gLdb52"$I+$&80ɹ$Sp$ &v&`1&14(K򩨂U NL$1iM^s_]L$6XSa(CJLs&`%&\&.^{<0IxT͉L-969cQ'.ݜi>NLD|ҧ&ۉW>q9h'`&a>4XTY&`F@ &R&`-%0 IlJdL{(0ɨ7'%0X,"cr'80(YB\K<wm I.,bI\L0gqYQ|Ln0<0YL66e-&7 $j\߆Ma0{dTX|g<<\J\uIJLQ-FI&`:2Y0,9LƚNLc(%aҮI&&C/LRVs–*&&2Í` ZN{L$!&˧D?k8=&ƀ $D`L:Ph3(ovL0Q`&cxk2L'0};=W& dl\i쵝ޫY{90Y{qaqUp&٬ɴ Dd=Kr&T>L$"&봼}vwz0TuL<ɧ`rULijӂɶ1ݫ&`c/7&O{fd^0}2%`y x4$dNGmL&8zuL2m.yıd Z[L$0IUL$cK>0q) V6=&ޚ 7# UÉNL2m.a%Lvz?PML&XҶc򸬎xLYiIF)0hI4 [brq{\ZNjrCjd:5ILZLn8"1y[ &M`&S5R1` ѰDY%2Ip1ΓAk`\''):2J)%P:LwׇNCd&Sy?&Z&{Ťj|LLTS dۘu-br]&`%`_Lf6‚I)m ycLIޚI&`j#,l!*0ܛM$LLa C#l/2Ldf&$%`sLaT`&)49LX&PUt${LTrL\#,l T x6)Y:&YާLsG4cb10LD&֨TgOIL0yI0cIV)&>&6 *oW05t0߱k=&Y>/%m3Q]21uX8&-(ٰʨm*0!]#&ˆ$0Z)=9y#`՛Qvm*a%bbӻ>MfZ6a΃Vo,֝ɯ|t"t?LfpL> BѢ:T>$yjqMA>I%yV 'uc0+1tYhILZR7b=OGy"U~_fGɻ%P m_훷TL*h NP&Fd]tݷH 5jmPk3O'ك^{< \(oGԬ=[[=h<53odjJE@r#7bAk2&IMg"^ϒerʳ=+o_LDG9ش*dk(&rak%g&0yL+E&:gTL`q5G.x1D) gS61iLɤȤ>L$L&qo`Ts| MVsLdO0Is0 A5HL>7+`d\m0YFfiog"6Is}cҀ D` 䃉0`&Yb",L I8&ׇTLl&`r{ Dor"&`&`!2LL"`RLrDI!|L$3LI}0&%``&`&L$;LIs00 DLJDIT`&e`"$oL00ܗL$GLɣ d|# E&L LIs00 䊉<1iLL1-0&9bRLDI~<LLL<0`&b"$7L&`&`&1`&Eb"$Ak ɧ]&L¤: cҀ Dd0&`RL DI.00I01`&LLɷ310002v L0)N$& ? 8& 0` 8& dGZ(Y2&dR00+c+&$LD4b" DdZ}F&`LL LC&4K,&K`&m#=p.ILKb҈0Df^ c8:Sidr :a5' &FF&DKƤi.]dɒ=.ۍLd pLLLIR(tku!Ƽ=09q4xkW^'WQWj|ݹׯ}R5&^{YL&%'F6_;Lq?1K5!Iy#yy 0B6&LMGO,7ǫӏizv'm_aOm72&BdVraeLN mG[]2';%lJp5#8t3M/yײʅ1P+}5dzzDly=5.LMvK&M~};ZLN__UXDj 6[ &M~;=&&}5&XURoad=LR&&-7AkX&gL."tLfaba0|zKN_hdr0%9;'1 ք# $/0Y`&;Z ILdEL~ l@M;}ͯ`&`qLV $$L0ɱ&`&D 9҃ LLaFd& l&`5~`&`uLV LdL$& x`Ld˜6LL6ɿ`&d&`c-LLɪ I:L1 &c &cLL$e=LJ&`dYLLw0Yq\&`70Yu0&ar0&B0ɰLL6LdWkd&`&o`&Ǖ f1y0&9`2)00&o` &?Lw0Y0Ę M$þ00&&&`&`AL`&;dFX070; IL0&IlL>1&1 &`KL$*&I0x&`&`&QB0' I0&i0 0I II4L`&`&0Ldxj o~5 䵨LL6O0bן&v # lLLL0VLo^`&;몑뚘ȕ1I&_LdjD֌V Lׯ xpLM0=&3%1]kۭHV&VeW&߻Q%bBٴ1F _rKELmKabk/^\|jT62ydX= )&sR_KDLJ.&TQ*c,'9^38m&*5ǔ8MZlLr^DDj8`W&D9Y߂LֈLZb1i5=&o3S̠3$wLL M ^32y0H`&`DO0000Y&9rLdC3000000ɦ/ L!2ɱL$L&&``X]Z l# I$L0F00(w&`&I &?LħLL$J d8L$+L00Y&`&`&KbLLMLL4I;d Ld+n20!20 bLLdz &`&`&`&`_L+ &`&9`cgI^Lj4_LLLLr lLfk`&`&Q&9v 09 O0000Y;+`; I9`&aLLL`r00O0000Is_Ld &`&e`"LLLd7 IƘ ɾ0 &`5&?LĿ;g<&'L&/`&LQdW07&ZiDLTNk`?]Vؑw30crk`1ҧSnxxPn9+Pzyhziۗ 6MU'sOUQ upd$J'}5pVQp<[sJFg0&&(ߣ6~=eY:/"Iu`ʈmhCݚ=eLZtСZSuyd+*M6f2kXkas1vrZL9`z*xGc+ ĄSSc.7Boٛ acf T#Ԇn&byL͊`3L8GLv Is00(00(400w4LLLLX004 $000)000L$#L Is!2LL7000LLL`L^LL #`LL&`b"<LD&L+C0vNS"םt Fd 1<4RLk&7~lSӟ?[;@J?nC;^ ˡ󨶘P5~#a1 a툪JNm[?[ǤR9js$<Fv+1"}PN10YhA)'_R_nӢM]yz=~9ݥȋ/tʪ_xxܽn~ٺ:- syڜRrYߒg r0&4]7֭r56vki#QnoītounyU<ӇCw餩ˡ:/AX dVҕ\mNL.0O+#}$c]xU5}dKTkVa\OO[gޝDIvn䰼}0sνbuz~w]v{?Gc^4 }6[㘌}!vGV9W* [mml}߾T7Dp R7jw+ ި^,MecpVm;Dcs܇7tnT%,0}(½e_F tWkanzmֻavth;]֢46Z>-@En=Hg0Gbz n3VdW}ھ]?GRC-zz"?9'0o9-nncy "'Z}l֮]O2 s^E!3ٽ6dt.7Cn1qMvϿ_Y'=mo4ejU~6igtGk3EenкJt %`gC]ԺRu0AV`{٨Z`窇麛.Uet;}_cN&-e']<WV͈m&>&͑&NƤ X\`7>nn77ˏy9M097Sfjpݽa#E {9`ڠ5 &11`cNǤMI$LWϳл &`v!LlEI= & 0 0) 6L 0`"60fRĤj/`DLh"p'30i7N¤=IaO0)C[8& D X0Yh 0O=`8}`&`Bd&`&=D&Ku l jLLLda0MaR3L/ z;e3qmJI:hMGƤ>XUUÏ_鳭魫ndILje&r00YLDA̭ oa3sLtL*0zILb2&`R:&p äķ&K5 I &1h&`R8&0Y*40)&`&` LdV=LlLk9`2e5޺+0Y&`;L,ɬzIј&`Ը501` &M&`+L* &Tsf4 ICdPd2c I LdbhR LL`L dN-L瀉:0)JrxsLdV-L&0`Ҁɒ4`&{`d5Ƿ&4ɼZL]0 & ,Z Z, &`R*&3k9`Ը50B1RCMLJDҘH0=`Ҁ4`&dv-L W0Y LHLL`&EbR 0YLHL&i01i1iL䘈0h0)ͯD&sLJDZ0-cRjL &`R &ͯ`X,Iy&`T,Iy`"&`Lä 唆IQ`R&Uɘ`"JDxomjL>LĤsd$djh&`&UDL&b214Y&`rIjNdeL&v IC1Ѵn|&`j2;dL6I D0c"cc"N{4010Q0> j"&b8OL&>&TϘޜٍ&pLt9(@L̔3ŬS11OD><ĴFbr u7ČcR`bcÝAkyf¤ƤN!zLy ==r !~z5/lB1I`}[|TNM\i6A d<:Ž-엺+L`G&v[;;lfgh(C3\4"`m€=7}S8&szHk=UANEwJc.tF B)}ht?=()ԹF| ߰%Qۥf~V7Q7}Pvuʬqoeհ-6'ASىzxa˱&/tWӵ[nmӭ ݇[E2­Mo}fVng/aEMM7}_<{OKkX")04/iݔ ^vod_J7RؙLݺ{CސKٿlZ]e_Zte_F1&+a#&i1 WZo7Pϩ\Wm A5yz?@*~ׯ94pӗp_+6w-y.Tm1"ckkCt6$ĭ,rQ,M킝Y>7/a>AIh (\e#aR5lB瓪Ճ#yPĝIϦbabbb#zs L|7ވx'q0`-2 <LG&];a&D,ɡ LDK!%b`Bd2C"01i$L wZMLrdƙ &ʷ<MLba$iLj&` %`  䋉`&`&q09_Zf@lL$&ZIzL Ę1$ih&`%2IJLR&`&Q"%$Ld &`R6&71$ ML"`ryS0IIV0"$5&B0pLn&i1I c$#L&`&\?LcCL$ &Ya*40 qt`T I(&7$xL$IrLr L$ sI b$?L a)0,0L  &b$40 DIT&``gd Ld>&N`&yD& *:`&ɝL2dL$ 2`/&&`&11L2dq`&311$cL֮ ̍Lr&`rELd.&ZIޘ<Ldn5GI %`& DIz,L&`&&`&s01ZI = &`2 &e`b, L&Y`$AELd&ZI)W0<+q`&UtL1Ldq`&ޘh&%aVEL󼼁IvT0DK0) B0OL0YgT=&fh0:ML01ZIy xa* `b0L&X&ybB&Ld&`&1%),60`&Fo9 &rhDm=iOɜ#1ѓ0YG'LLJL+gS{y d&r&&9rdiM6LdZ%G`LdL'҅_+)Vyv>6ŤǤI9 Lbҝ;~Çxn-}dbN= =lS)&3g܏G aׄ`BF&cNl 6&`LR˂1md'ܻ+H/|4˧zyojᘨcXQC<7iJpFI$;26%r-P2j11ޡkXyTWn1MPT!R3JM2K=9iJV 7V Bװ^ i6 >NvnNvQV]$Ԅ &~%mu &rAki1yY.0I`S,&E~&?DtLr|&WLd2M({ѥc"IfJ#%l D&c/B5'L&cyI ?Z L Dxk&`h9b}L0QD&`w I M({dLhB 1&s&`&b2`R &wSwɼ{I15yLr"r1%x|B L$I 0<-0YYJ>10) ut(;0m5=ր ĩ㨎0-&KkB Md$캚zJN01J_P`cL uLv_YXJ.09LɒJA촚3&=`/dט,Pw`&7$0 `B5L>41`&700Y,6o-Ln2P7V`&"Ca)04o[KLӄeL>YB00 ,P{Y&`20bgUL0y.t(LӄIL}K"`ƒ˷Iwi&`Տ&31[$n%x%¤j,&ӡo$l<2 %x{<LdAM(D?/R`&51`&C2`BɽN&`2\ !_s`L?=L04GS :J0)g Q&%B0uPhWʣӺM|0& O5-\Y`bf$|YqH9QzuOG~t4<)L}s%Ɖx;3<0q[eRãecϢQzIǣȃk/g>jL1^~I*'l8GI&`LbY&`;l%`&~]JfX&`:`܋&`2gi&wZKr%`&ޜlK0 oy0ɦZN$%`&;LB;qLM5Ih'Iߒ&`r݋#LdKْ0JLfkN$%`&;LN&`#Ы&s Ld6& &` iZ!'d>*0[N>&`KUg1 `R&cLdqW1`;L $1&Wc^ >&&L>O `"aRmxIDATSW0IGŤ`Nvɧ`&0W|p& DhB[bw[F  009`2E0)Oaf`&zscR&;CqIvv}aH`&aRa`&bR֘``&9bR`2a0L19k&^&`&O2 vLyI߱yLǪ8`&9Ws':&gJLdL'8&)0aHll0;v6ɔvW009ff1yz~0I dvQL޽0R0y61J &]LdC췈ɤ%[-00DDFDɹcL2$R &`2*;ݿ@L1iD̯%vS϶$j[d&s99rL'^6r0LfG&וw0Yz\ f"¤a-pL11&k^,&9D&,L~X Ld&I+ӎ<d'o&r%LdIP" ;j:85qvxLޣP"U`r 0\W31Z%5Qtx x4&}cl阜( =1CPho0K,tdZdҌ5~ނ t1alZU1%tM;HfrkF1yD8n.[j޶;IvJƤ|0phӾYάy=}yc|)]*vڅQaetb"cҾR1 )Yi²rn qJEXZ uhgLޜӮTWd栒gex|95Q?zћ3`2c]*(QD<);73G|*ҟS&&+"wQ>&ɝ pČӖvÿBϩPLII`՜(;%N8A1bP!iDL8yJ>VIcLdLqL^d8 $j3YhGY\&=r21 ,,ɚ'%`2" 'c>G`&`<;Yc>Iآ1{d'b>Y0`&O*; )&wNQMcqe@!01`&Ƴɜ[C F'Ya>y`&`w+I))0arV0 IȎ8ƋP2&Ld&(mI1yqd*L$,] 1|&`&9}݆JLncTm&`gLlG|,q:00L\nNVe-LxF"rpLL5NBU0y& R͹(Y_9ra9&`&YY?Q"aϓHdLL2i ɕ( `rߑs LL:Jy"a9q@4g0?L>zU矗)]`&%`L[E%)STsL*'-CZ^Y&`&`t?]f9`&cb9dm00IIp 0- "0 ̡DpDeLfcye&rq9uH0I  sD D550 U7+i3!2)̈́ޜH $Q$+Ld#`jf%&1ʢ0 t)dMW>4(> !c+U]xGd1#&Z+݁K66שs?tL#·!Ʈ[ؕ&# D"'0!Hq0QL|8kcIENDB`chafa-1.14.5/tests/data/good/card-32c-noalpha.png000066400000000000000000000441421471154763100213110ustar00rootroot00000000000000PNG  IHDRL;g_`PLTE(33/p+NPMufknm}D-k|ѿ/t殚 D|{ԎiGtIME#&ki IDATx c}s}tN'3?]^&M$~ٝFj*$MЫ':+e~AlI{>nRINkLX*<em;"<}JL#"9c[WRʐdW8!EPm9!Br!9qW]_D9aGl)iLxdwe~쁗;=8eW2 ?Bs]'3 ʥHW¿EIOݕ&=&eHD(Cr\I/)J@hqE2Wܦ]&L61.U>C2cztEאs/aLf&L(Hպ=<+`&`r LzBa09&LI_ k#9^;l01KC"019#&mZ&`&xqTds/A >Ό;M(`&`&=@Mf IFֆbn~+ #`LG&8HF( LL%BrEƒ0d;=7g'ϯ LL^#J=`&`4&4{OLdv G"OLDBM4P$%&"_Lr~&2ƅ~0&^I&&˞Y &1`=&w1smwȁ|0I ,YRdSL{v2$IId|$dC11~I&F%1_La0鯺h:0)15L/ ٕ=az_m&UsBn*S`ps`3 LٛSTdvr1y8e[L'j#LT&] Z'&*>ҧe֘F&Y30 ~tDd'`&9Dx DdNL2$Ɲ|IU0I Iy87&][3 df9r. e`R9'`&Yba"u0s0G՜Ԓaj3Y2u"9+vL$;LT'}z`R}x̒3YМKq&`&*Ce@e&] t`ҭII`&I1Y0Y INĦLr0zs[ E2A] 2&w0l0YM0y~+Y,c!}~n90U,f3L<.+L$L֡$.&sMM?k 'MLbrވd3Lnoærx0=2Y+,OY3v Lf#LnuL$%&ÉV$& b]&wu0Ig&SM'`&1Y06¤Xf&)9jeKdLGIude I&&`S"L_5cLa:0&=(I L wI &]n(01Y5&aPML2dsL4Nlɽۜ 80*8ldcLa%aRmI  DdtN&3Cc<12L6 xVq:07&{uLri lҫ&`6LaL0dL騋gUpPI%19Ak7 d\&i1 dl& .]xFTc I~K>(\5L$;Tarj&`2Òeu4dJK¤3J D$UL2ħLd &:]sUA@`&q0IjWLb2Lm3LT4ILJDn6`&3GY6&f$8OIrMt"&D+!bB<0QrVMz^~N;%K INW5egꇇW^LD&:,OɦԘtCϩeIcdScHIL vSb}WvDAx v1I-([%~}LY094&M&`Z"c2};&=3kVkIӊH^IL.9 &`r$L DǤLDP) & 2#&WGL$Yb2yڛ"Ǚ]cV`rlLZ0(\l0|0`R&u&`%&L¤LL1=ԥb"d0)g(ItLvIu7 IȤ00IS 䌉B0If0)0&%``&`&L$;LI[ c"*00`;&5I0LL$&%0&b>}`&Yb"$`&`&~I] \1i+00y I0LL$0l1`#&M&`R&Lq`&`&`IS 0y ǤLDI^Z0/LdI] c҂ Dd0&`T`&c"$L? ,L LhLd0b=& LLL挀.`&`&30iL0IɌ&`&`2I &`Ldf`&`&`2I &bV,a"JD0ULd+LƘIS(PLDa"+`rLI[eIFbҺ&aeY)82s ꘴&&8) cLdF0:=&ʍ3i몜jN&LLL9Q01*42fZ6&mp5"#LW_Lqod"LT0&xc"bOJG_ 1%^^Nl^_:L^_^OW-/L}퓚oq0|/_b6+9 P W4"hc◌Itj^'%k{\|:~tYxrsڭ6?n%I}C7c\Kz͢P21YcbwZn;ْן?/qrsyN?W+.Ia3[F&`&`2Z LLvB0 &`&`&`&s$Z ~&`&`&"`&`&`&`&Ts09D&96 L KZ0`r00ɿ`&`h&`&``Lr/0 ' 4 Di4 G d_`&`&sLLdML~ W I&`rw=i M&`&D&`&`CL~ lV_LLLL2+Iv|00LĻ;Lr0 Y&`c+ I.Y0`/ xl LL$$00&'00000 &`&`&`&kt%L0y0a0ɱ3L$;L~ ILN`&`&0 &`&`&`&i.KL L F00000w0009&L' wRL$㏉01"LB1&2VK1?Bd[L@L-^*僂[11hQ&֘A?2d}N~}ʚ |{(Tcid3ELiz%,Mfpz}ϟY=NSGItINŷmAqSLLPLByҝ^2jm:d#Z?u=G=ϯ>j nKW:냨߯anO", Loo9JM`L7 gL|GQ0qGm&f˲t3]D&,93{ʘ& 60C)h 8VU8m&үdaװ4&Jhc䴘T9`zut*xGc+ ĄSSS.Ao\؛aceT+Ԏn&b}LfE09&ESdmi`&ńksL$9Հ IL IL LL 00000)LL &`&`& 0iLLLds LLLh30&`&`jL-009cLLLh000)W0021&`&`&`&`X00'I6O0`⃉dd2bD&LS͜ ]߿Bn  !N=.oǎ׺krhd'2y9/ɓyPa[1ĸ+þvqN6wX:uTuA-P6|wOnGU9|`_G~l៥i&9u;G1,- 1:1"$֪-&M9qΩޜ&&ؘL0hmsj&?:LT &`&$kst%`&`&`&`&`&`&`&`&`&`&`&`&ESLL$9U'&<LLLLLLLLv L0|1yD.Ĭ0r0`&`&1Q`&`&`rfR ɽ`⇉0[Id&`&D&`&`&`&`&cL$9Nc7D<2+\0Y9-#``2l>&L$6&fuL&bjAy՜0rNՓ!&fD FI}[KL]h5[d .C&# m&`œ*5aDk܌19:Tk >l9tm]bmnZ!r|ƈLMc5;db n|KCAhmϋM&?7͘t>MWF^-=駻UmvD7o 6yvƐ{\nؔfܐzHÖ ?kW1>Ȱn]+nΟmްH[>޸|ԏ9ʐFɽM~ka?oq۠.{q>'M=L]ea֏_ĕ$ JO8&R՗\mL.0 /+в\MN!O|x*ߙ!%3t~an72Nw_"Zä ;ner\޾1Dva9gPg1:a;M.0Gk^ [ӘL}!vGW69* [mmm}߾T7Dp R7w+ h^,mmcp֕ѝPoKJeecjkU p)ʾpoٗU®<]F?ؿnD?]NhLoE&MO+P;GLL;U2Fevvhj!lX >O}tZ%FN B tF޼qȉ֜=oWh\Vw{5f&צ~q6V5]0&.t+岧-־]J&썛(rm&¾׳٭ZU{0}]FתRu-6>em_Ռu?]N˻vvNL_NKvYWV͈m&>&,͑&NƤ.X\a04>n>Oo?-/?7<ߨϙѵ 畝ho^=uzaCUco_[cnµk R&͈Œvug.qLrw+q0~SuFkݨ>1޿%wK{#Y8s0c`b"`rE&ץ{&&CLvWqzLkL+Sg_׏w;{߁r107C6>`Jww.C>t_ӿev}yy5{F.gWWs6dAkLcbDǤIYT7oЇ &`n%LlEI3  &0 0) .L 0`"60fR/Ĥ`D̨h"p'4 07¤Ƥ' II &`&`&0`&`?LVh&`&`"LK0߶1N0  0Z9`&¤7000YoLdW4` LLs$[LsLCLLg$&7.PטajOL4&'LcL>t3h Sխ=01747:ף Ā~Gq=C"ĸ`2L'M(&2io160S+ͦK5b!L]Ƕw{?.{*11"3a0Ca7u?ۯ•@Ͽ1fX\O7[,9ƌ2U vĖn0gLS[LN͠CmgLjC=bR]1cbF{\rt{GFLTs~&r-< 5T}Ǥ&9,tɇ=#6e{`㯃FLM1o dZ%"hͲ1hԹHrrݡzLwn JZ.۝ݗv@sڃҟ"ZKkjG=l-.m7 )M]׭U99ֽaU"n7:ۜlmm').Ǧ ھp[BLnaN6o N7jZYJ;0_XLƩ7F6a}_k0./njkG.kPӚFۿ^6vS7{ٿa })Kag2M禋 }~C/{w}isӕ~OǘdtKJŤ/^)&k݌zN?ǿ[wyn ۿZ? \k>Ѷmmvemskuh>;Eiago 9,Ӹ% aO~;]VbSB^i1`sٸq-݂jh'W V?retS-rd9uyc8]6N?{a7ΓE?hn!?N$Cu7\ivJ?NLRL7ED%;|Ϛ_4WU]IMwL7 2`!AJBwDž +}g}*8\'7ܗ3whK w[h6bZں.,=zgS`']xiSaԢ"mt'+<؜UܷP@ɰ xaQev_ DKHjOcCՓs^';L7R̒}OiRSAeÍ5WCڥĻ8Bd",yiNs]ESLv1hÊFL{W  ;5eKL L$QOᲘP-&&`#7ǡ0id"i&Gѥc"If09|d}aLo՜1I1I&䰘ՇQ0Vsc` I:)b&`rDI09t5g`rL)`&wWsVL&`rtLּWA3&`rL5c09&Z \ߐL$` 0Ā LdL`&:1 Lˁ_e(,O ϖ /5 &&Ld%`&+&`_LK4L.0 >`WL0y.td:9z.11- .&M W?,D}:SH0)ZZ͑c"$u:`CL` $&TscKLVLv~^Ljb?: frSǀ lz)7Cל)O*L<7Sn ՜]aՓSLY &J`f=.2ZiG5s7iOOdDM)&mwE9& Wd,0 8c}iIS'<1K i!<%0Y8/at&nL@sx9al\iw?$+ȃ1g>겉L>L`ƙ 05c2כq&fq&`Ɂ4Y(IdrAkR `).b~&`2d9&%F<6(m;kB`f=s&,!2ٜ0(0ɮ;uyXr*RĄJ{j&Q8`&>La' 0e &`L ]j&w)Y` &Ip/ɒ i-9 xs/M$Ld&j9X \0 0Y~wp&8`&~Om/0=i&K(0YN8X 09[b IL܎C^)0Řt{#s+I*LGLRb"o99:&IZL{pL>.V1&`IjBj0e]h1)܌yD&[IDAT`X\w0)wU0ɹSL?1Pr`&%c?1I ICbdߘPIdޱcL活 ɥ%6KNvLJd$Ӷb IN_T&טh0b"sbGLf ,+&oo?&2%&2&8L.;`$bU0FQ&xgDDbDlI&DLd~-}%Ts"0YZɑ`>*ݷ6`82켃 R6Q&oلcrJXՠw0u1!2i&oea;Ē}`"0L]vQ4=&=y1a"LXLTQ9%Om'[O= %R*&0Kp_;=Pu`#$) }&ɝ pĂֈvÿBϩPLXII`՜(;%N8A1bP!iDLn8yJ>VIw`LdLiL^d8 $j3YiGlY\&=r21 ,ɖ'%`2! 'c>G`&`< ;Yc>Iآ1Gd'b>[0`&O*;+)&WlwNQ]cqu@!01`&Ƴɒ[C F'Yay`&`wIN))0aٓ`8&0qdm')9L BPdbɑϫ0"t's { `m7*I0r8h3#cbs8O6Kfց gҗilUfNm$"LQ,Tg I.՜{_UI#WS`&`偿}%&?D{~L$L܈&8L$kLn;)&_3>DK|00KV~FREٕw J r8cJdcs:CD!;"|bJ]i%;BH$Rp> HlBX˪gIENDB`chafa-1.14.5/tests/data/good/card-full-alpha.png000066400000000000000000001133321471154763100213250ustar00rootroot00000000000000PNG  IHDRL;PMtIME(S IDATx}p]}UMw":$}oi)ҪHȢ+@Ȏƒ,;8[o,gIV8xVf4IdMzhR)%EW |au)k\\\ߗ`=9Gcuw Z->V}~6ɦ}~wb` "U*=k鈘cڞJDT*ӯGt}>}\Wlݶ=kر'#";hn>Vz퐴vq=Ҳj>Gu\\\9¹29BKRI?Rn`ruc EnɈذvݬW7U18CuSuem9Cy-&&缞~tLD)ϕz\tzLϕEmd'V}\YsD,:88+j8rTɯgwnqF""Z1V\TozPv6~v| ㍓yہe K0V3vzrԏ/1^m]?SRB}K} d_>8Jg+P??ǃCE.jcPT- =غm ~!]H=,џj}LMΕsI5Q~e5#Jo-Ky=hЄ1(rh- c>:KnLe\NuE/?تCj&AP:hغm{㶈[mS'O4?|{LBVU'|/8Oq{\Qa"4  Ú0Ct18+wb.ցMo||aȍj~]Sr&#AZU~xVT%fIWUj;02/ BSh"f"in])y?ׁ[ vv/*4%3n-{g`$?4T:q , noM Sc9>Rim&bwTYB+Z_D Bc ^:HYj5 XOVh@fsÆ#]ojb ZtuI M(X1B7-Cꦪjҩ?ׅ'/42ibjһ%V뇤5W!qSzϋΕdXK!4 Sn+ !Ϛ+QfغmI 2MȌAaVeppWDD*P$>,tAQBz.};d "cuA >kn=P_6Nh@W,V=",kkf^n<%n>X_6Rh@%$ Vytx%n?`_7Vh@4WH$2NWOJ/-4`Ez/YyY mԕ}&cuYzM2`!4}Gx tdbjr]2*Mhi9 ّ~6/+<⼾,91^? 5{_k@qUS“XMJU%|. _ϭnY,@rS`MJ$]MaʓdSr:'ӗFHKj5'Tpc M mJwV%O/&41wV>Voܮ8`l *Y}b} Ea:c!I)74Oi5eGpݕPA^&@ I:>O#ܪd dYI2ǕI;`X}՝aSn^jXl2eGxi<<ɼMYrhu}a@#r4%B +Z-cFxbXk-YV'5L M [$0ɖ[yC5 \b#'Z5L<[e*yzy]՚&$#MaP|{ zެ_Jޞp24 Qnɠ7\'9as;E|JNOqMVsY*y|y|S'O9V.(dZc=0cjcqv|^vepabjrsDQMQ:V2#GJ (cƞIoQNeJ^^]DDezzէkX}RG,VK9ed3ZiHzynX|-2 <&*|,gǩf}]^u*x`=:ڄv | qؿnć~C1~v<ο4۾C->Ta18UK#"mu9[7?32YFFja$Ŗ-s?{[vhW&SWj|^qH|cKۙMJޏ5LC2/luQTNI%G/Iz-UXϿp$^:984y[.缕HYRdc`62T|ٺfϩe1~v|֚eG$Z-9\TүlcWg_z>-9Ϭ(|q 5ǥsD%S'Ozc1Z4gtغm5MKq8rl߶e>yzjBoeY'j<7gXse KZU\eZ]\bxx[F?^lx85☧sřU~슱z=aIߜ'm] LG/,I㍃l';g&ͫm AIYbKW0#a1ߘcV{My_SowVt'&M;f: h-qҟ|O[s:y"vBoe9( y9Lɩ'f/+K JRč*T`i/[vEo)~CPy ksʼnAu9ވKW|ofU7giR +Pz:o)(I00pL'ENLMz㥨ՙOC㣼%"o\_Iv ցcF^"g_*F)ɾ^q1iSu\w(ԅ9 (O`!4 DPRs}r-Gq  ;j^s|i9K[hhJS&@ J?x6(.ϾZUJ}^,xXRX+ӴtTt ryN%IG(5]%R..'>rDDYBsnwpbh ";^=c3sw}V#GGM)݅&jeL큋<@>Y9z\gwިSu'P>\̹ЌG1)R&XֲxkD%|L,ZcZ(œ^6"fq"8:,[äї#XXLA RM ,}OMvTޜ(juIi’Vcɥ׸&z-(YɺGsIO&8)?<[ KP6vbjrN0*0I5Ax[E>)U%JW!wz@Xrd. 1MUo{R4w'_yo;dDMбI@;*)sV6u9eppi:@W.5`6}`M֍8UX]Ɂ6dVXR:7:}is L'k`%%P24 Suv .R, &&|*tPܱ%=Enڭp*]2potj[:QU-VDII}^Bwwu4&K;h`1J ޛTzk`mNWX+tvo4&˸kAIziH;Z5*AXBE :-Ky>}+}k&X@ J(5YȮf{z-y<%E;ֈ90 VN`zCS krZv4 okY JMڵ’UrfN2YbX¾7J盧؛ @vN`&%4غm{wժ1<[PBNHu?- ;krVU,PF5ϢPn&hIgM GJ% Ej[oݶ8i’6t Ο-O<"&a’t2dbjrûCCB T$&@& K:D`RN`0n@kw(#I&zo``jX-@,0IMGvS`Pk@&K@`~&w, ^ l]ttM!,q+}G9A%]$0QF@gݲw8ks^W*>}c_r]ep@WKL`þ&@ zAC5zXh?jQ2-I;5;yQƭMсIߚVûUйuӻׂo;ltF L2ra F2EVshaI LCh.8t: ӪL.qh By315٘38KXTܲwXc${T"vn QKFcut;7}J!}N ;&٤^ YcmX֭"0pEe+| ,$&&4һ*KoȞ&0 I`}z8Z&&W4OHbpcK5֧hM]|лWB??|\hBO˚ &wCܲwxV 0`U,* %dAz1 弌hg Cz1X %#,ɉ+4鰤>V ,ӨTHg׭I?rcdqݛ2_]qad |RiB4%*L(K8d`)B$&Kpn͐yMո6@X $߄&˰a:Aɝpҋ M(aINNj,"Y cbjRh!%.LpY|I7tY$'&&:{۶GD3d;scR} 4YnNw7>ej3}.ٰvFGlE&}G92zэK6]+E̼ᚖ.7=Jy50#n>k=YIR>MK&غm{v.z:OW@Mu7]wuG;s:FFr:ѸNR0V]UC?9eϰ҅%:Dys}NJ:%4Y y7^}PW;zwOɯ}}&vZpn;Sqp.t٦svYZxo44%c]QűcOO1SmcJμJSY2>+4I -ʸ)Ţy70cBt<ʴvBzpn-L>J-76"es:|p醡= Fn bd8wt|XպNIhOm}V1ӂk7.A>Sڔ&AY}>ga.+w%|| qn;q{?W~qozn8M.`I5G_so.*4 xaIF%qw /}~kwK_ MfEz }8,I249<0qReImrF >ƙ/M7 udCW&(&%R^a ɺ&!4W%215MYGB;gUpY'a mɂBLXRrQiBK^IBvߦ1aII\ hu{0 v8xFI94mTY^D d&%4aEs%@֘CKJPEMhŠ Kf’*T`jm%4aE5a eCKJy”"&,ó/==$,2Oh* KJdbj2'0!QdE/y߁8wt|랸jJXXg_z>5;7{&R Lhfzs<|Q GBHe Z ," M_.6]g6̈́%"0a>B%@$ɻn4a sLXФd<|0Ν9QiMT&,FhR0M?;<*KBy*6nK\]:;O<=SC KҤ .I%Ulqik%0a9&TLLM KRlKX %4)$,7ir4F KX2 +!4)a dVb E`J M ≧G%w 6o&_T8&,a%J }O~AhCq9!I}qDTZ*M2nbj;"֒GUiRiQYŠ LhIWzO,, M_e\*EhaEʾ_,AvIŮ*jIhaǎ=>rDX Ih2!%vdd S'O/jey*6n> 6( k#G7.,}b``ƀ)*M2bbj2;zHXJ럊/磺:k82Ae m'0&=6156>>5a !0ӄ&=",_OGc!4a #0&]415֮'tcGͷI K( ̆n ASB(. ݤҤ }ya @CIW,+&tФ}xs]&Z'4a ]#0&>y*6nMct+] Mp]]uGID M:FXB] >(,1Tk笒;dǹ3㑣cﭻcx)xL_ٱ?諸W&ddqiE`v&4a =%0!+&4:LvɈՔ~/qi'm rؓ1:(sgNC?eb߬HK5K{ѰR=wfv־'N.,a:+05߼A[6|dw/q_XĹ33'ءsw5e[oܼ%FGiK W*~鑈y7{Ut=Ձ[{▎o?;/]v̌رpTf]`2xcjW]rBgǭcw]*nul۠^yݒ:%tAsC>GڸUE˰q>{>锕21BZaoWv}P]|Z $%unͱqe ')JXҤDJQ[\%ǯ!e6'0 sgNi&@j(*wÁ3%k5ISqXɪ u,7j1(س>VkэR;$%=TTi҆Y$?+S\ȉBT}@Ǟ0ٰvj$* 1KG֎q9pMS-C1W믩0)Z-vmFhR 3J hv5O%8=TA Wʒ^k7%,yb]c Ln۞q^j$@) /o5׽x#"+{}m*~G9~Wj M~aoFDʏG9_`_{?bMjŷ{oWyjٿo!T)Z-n;N(2==r?bǩէkYǧ ;:ю8r }o~tފ7j<Y]|ߞgW;_wF$oܼ%.sw߾mK8y:ӛKz Vi_|{w/Y|sV/b_<8wym~fOgݧKxg;yh_ᚖ?3}qg I)6"*;}.wW"R5^;Oʉ+6~wYϮZe-߿g:Ǚ3%>HZx2~];>y}m^Nqg'.s79?ݓV>.otߖ/mܿ}߬p5=k{y=mIh=l&na(.+ !?>ѳg^ j"!~7>q<'!n6,j۽q[K:ueDD/WRwxi4D.o_wBu[>`z|ѼIqKkxyV`rؓqܮwLM9rL$~-yoyipGq/L B&<>?nC4^|nb]2<۽-^!Vb{_BD1VǑ#K^b208<_k;簤YT൧'/kڇ~|c1o~tDLW{1~SGV?J<}\yKڼvk"zRYyYGO1K+}c"+A̧cvNySuf~’.&Y ځL)o7b^/`Y|W4/C11M-g8+ i k;[e0IKX: r2Vw(>"JK|hDiMh4’%wΨ9_?;6>|JتxRT۶G}r<󽤓8 ,Mz_k>ijdя0swoo}9'sM'~""b(;ux[7.^2mppWuf[RE:`[{"o~?ß4~bGߺGnܹmyogd]?{Wz8%gg/ʚ #2^mf&ǎ=Nlݶ=k5pJ}cz^{*""`w&%a;z|gd]|'@<<14tcDDwx';7[~*M`i,J[% ƞƞ=ۯ_ LN<gseyNLM5]^J’##OЍrN$i͏1_@w|oʚqb=s& K)LLMY6.9g 1gmk{%BwFƞ+o L&& |h7>ygϣ_ONGKÇ_c3Z劢;4yaWz?s0%/q5iyYÄb+G,c_#4&&:{*L %2:&dI&WT7/+az*L gVI`R0ޜlƔLLLh¤`8%%@`2%'+@r ]WTm|n&t^{J#G4HY&KXDŽ^)FLɡTGDX3=<2 {qJCS*L qzM`@fYDŽ^ut yW4=e ճ Ydɺ3*kkzB @ =aW`cB/^KP(LPaBLLM6>+5\Z9g4=#0 /5L:|t5K(o7Ͽ'_yH5*L:w &M&t;KeWzE`ĢtśuY+<]55]„yR#6>? BL*w &MaBYeMZf @0RXE#u2oN@W L w O&tK#d`„\y)gbjR:΂@uϬ7]Q ]0dW4@!X T1n) @^ L w O&J¯Z| ZݞZ`]rspx};ƠT44Qo_jrG`/M"0|F{4%0) kEDJwʛu ,ޯ2'&5@D\Q+vu1V7POAI2{?GI;Xk/ngǻZZ/@Ȩ &~Gwǰ;b^xcGpOƟXwEL!F^jk|;;:/$kntjǎ;K5=׽yѾoW#'Kmѻ67 #"ݎ?⚿x<.g}'N.a-':>p?ʼnUw\k.G?v87\c܀kd~8/𑈝mOKm~3ݳZ k?x{L_L&&YHK\eq)4)YcO6>Z ͉kZ'btx衈;CC7F_o3_/9ҾspSc;0oZ-ͥc3JuL̼."~WeIDx9"F>zt ggWQ IDATL9gTGۑ2z7\8wtܶK},n_m>qx{s?åv]wmo{c{">i[|78?k ؿ@O^oƩ8uD[cZ-bԘ3b&<*šЧ 7sLəGDD\9xzwSDD vt3ldkq K_ߕ 'ox>mae9BoK~L#kÕ^oxΑ]'v㿍)>S'O_ .bfmDZ{ܸ`tPlX.FGsذv]v0uƹ3KބMUxcxxw?{}LlrMDDZ?<tsLL!y + [/fw))@яD^Gn3J8>/KdLkU4kOQ Q t†fMh'\Yȓd$ @ a5yL`Bǜ0iA,H7LLL:m`&M&t{Wr8>M2Lic}^s䩨T*k*v*0K„ټtLuSU#1rIw礣&qeTD, :m+ ,ՋoIsoeQa,zQ`R0*j`&_i&&5#0 ȝ3_ ke91eRd˥ٙG_fvp9Xz㬯a;JG^fu? =""N9Q-{cbjѯ)S?Uju.Oe1+8YUMw Lb^Z -]'x8 ׭۶G'P9&>ue_X=q=V|}˯ys^} lTٕ?|?UX>r$cWӯ<.9/5݆ 9&z}_^_ݳ]}>_U=v#"_y(3adT7U{z QǑ#ct kT7Ug{;s|+>û_}R|n;ر'?f@=/,1߹u8uQy{JݗuIot9Yjy{_oFDDO0=q=z=_.\;ﺣq/{;lݶ=j}&xzkwyQT]㉧GJ{Ls/}lʉNv )S==Գ5L6nҘew^3۰ L?~8cW~g)3exlܼ3_l|^3CS;$!AۡBNe=W<7oi|]EÙ/ͬ +H Io+~R%ɶ&2j>yΕZ W8\69-&,rL%e!0h"0h"0h"0hRyRa@jF&Ѝ1\% @ @ Q)GcC~Rm[Ѝw}azz24tcϞw}! =t?"n@[a\X?vF+mX;K LcuGmU7U50159{*L(қ&:bxDD ݨ1ڸyK5'&thQaB LL.0Uۼ<;Rx.0ihnEB6nޢ o;YYMHKZ}vg0ٓ6]7o գ/Pd&W+W(,IMi}2)sgN;.IΕX l+_3vHs*LsJhz6';ibjvI'놵2q(Q_j-ڡP!SK~-Μ~1sߍ1Ȇb``GUp7ر,[;b7+0i%Na2[8uDDDwm>@cǞlf K)*{ghҶ/6*3$kpd郣ckh*/{ݯmm]n{!CXL: o4?W';g?Ν9=GcsC-k5|ͷ3_[3_sgN7}r?y=~sgN20[7[:y"kYH|E?R?wuǬ}qmNV~~n7g3IzRҺܿjk28cammPї(qظy˼r|Js{4?t6ҏ>N~ Μn68rp̼Jk1>NnU}i~5_'{_##9.?;G^X5sFX6\yye6{F;4Ki9e)禥Ŷ[o-tMOWuyUj<|J>MzP[ͷ=ץnB^~96ogSrnAz<Ɵ{vC|iwϙhnFj^<2T&1{8;djݜz]w?Wjxqܿ덟K_d?-{cppWqᖏ?4tO~vǿw7."ǎ=>re`YsǿV϶6?~Dœ~y6^#V?~7s7Jq Cq]n3sI%}6i~1Vǩ'6<̇b_ő#FMAɠ&\{d{;CC7ƙ/Ư8w6knrMuw}{k_}(FGi<Ѝټ 70Ԙvɶ'~̇9adxk>o/guwܶ>>+2omcﭻct_Ԝ}y C;s;}=6nhq{ݷŇ?q/}qŭӿ߁x3#߁!"G5ƻnl<~r|衈~4ć?]ooDD4-߸\#"zsƗˍ6$|8##O59:s>Ly̼12T㹎>/L'~_6GvOB|<󍿑nd[ώ7~&qqܙq{}>>ko߈wx鑸o~c:F6nl,'N3b RZd(rV*I ,YBen! l~S`MdbdmZıj"a$uq&"vm\{|9w={9{cVϗ)/c~sYV Uxۊ+qq݋|աس/vw7}!N_w_ ogg{wxcϾ{imͯ?=mg~?ڷ//vC?7㙳=N_[m{^x3o>_xn`(3+ ?76kߜ`ns̔sOz% Ɓ&/[tR&'ߏQ^ť#G{ވN=S"O9TsgzK̴V5tju~2wzmm{+ʗ_8WڭV1ݧ_ۇ-c?=~Ljvx9GbPyf~} ;(…Zv֝龾==c1?s/{~ݿo~(kv`uz7.5px}…z޷>r4^:w罒uSou?|h #G6//,vS]Ν++ݰ#2Ҷ]~'̙o26olStoׯ]ue$cn/|<^mv;x2RҶ6K~7vO_^vOm^=:w&ͿWq>?9Llwzo}3Μ9>V߿^O@Ï={>.}Wj<#[ݶ9vxؿoi9}[zmRuhzxx/w?M[δi쿴c_|/O_˻3g|)?mkןk랞˟~9>O6X}V|0u//.uV%,Ʌ%W\9V)SHI LtVl,ͷcdP,^&3a4ſ ?X l}d Za?s{N=>tUvo~:YYabj+F^AaJqlZ={̰}~;{eom}7_HHAUm/yL>ɟ VtCtW<ΧuR||\zB~۞$-#=>uL ,=}Bj7}[UA/n{؜띲\ bgU2f1 2h@."{PiY.v!PjO *>;ִ&>lwd{NgQ>1w_ض1=4j;NK5Xc>$޴]5raY"ebE _x7Η+\řYvCFh6$U1o&tAe4nle90`]`D`~LG~2uqƘ_#BcGޯs3:M;,'K(H/O`PPDr24Cq'j>LnLGYfLgֹKJ&0qa^^t Y[w*t(s-3"HKΫ_qj[p[Ҡ^D &L]YfNfI`L(J&LyI0$0K]J̭5ԗpFۿoI;3¤nV(¸+L &̄0."&0af{afɠdW"2k*L%%tTR[_?a\DfN`(*&_523J`  akԩ'ԩ'*i7-+$10qᠭN Mc`4b=#"ionvߊϩ=b/~N Zn+v99+W̙ft GDD݊VUL!~/z  JKyD]`kh ~f?oRϱ>8wll،;/8|h|OxoovCT{=IKrޏ=bqg#"_ο_V0~J< qy^ht~lobasO`rq Iլ\Gꖓɲʼ+W\{3`oZVo}ľ0'{[yc2סڳN8_dfvvq'*M7؁J vVu|P߲cRoƽ=-_ Ӳ__?Qp:F7Y\{PkA^F;P̌I1N=nЦ _nA 'ZV`&uu*PRpTvyNی7sU\654 6c،kGt x5@mX]u?"T*L|\&8lja(g=h߸.ER`vVUoמK<`0_T% Y_q (3_(QW'b%WqUzi)4.n8#NF`Q8fW|S ;mI_Au9eK-uǁ%R:y|=޽~M4Pa)TR:TS&2&RX]Sa *K*Jo'j5; զD)W tTA_)40P/UN%-pj8u4V鄩}F IDAT &qL%bvB fյC/TPjˋK駍cL*KjHwB 3z*Pz@=r4^vd5-0y{Kjر{[%c2I?r[&_-d+K׹g}Gja[`9|he2v/ enel]];46>-:YVy}DoRmρյZ;}^}v@k:!{ň03Vsy_#4%02avj˟~U&@۾w$~VXZO/0n)ίZkQʼ2 |;9dwrpeܼ>}7ƽg_t~/eo;_-=WgA=y;߸y{k2=GImRly'{[ֿvxkRiD`r=mJe/]t#Gnd No7ے[h[k'ߖOcw_~su~? ޚ7ˋKQqN[|8j1o}m͞\bCNֳׯ]#GnOqMʯ׺Šv,ޞ֟uvuwY3r#㬿~G[MwQ~/GO njS3h?m=smʯ'm_^\mT//.m}п>{~5%cյC 76n~+~7SV׶f~۞?Q=ϚA- aaρյdwÕV'U Z<#~t9yǎ=^ǼA_OzBZX3v+^'9KCԩ'♳Wj)-}(fX]qh[u8?pۋߌS0~_+O~Aߦ?xw+WA݇ҥ̙x́|_> Ŷtycj~ؽ/#G"7,uH7+_8v5xܙxmڼ/V+66 xܙ}/o|ȗ~"nɲؼm[76#G{֓V{[tͷ+o_َ>Nvu/.62Ϸf8gyqge\'n8Z|MzR S]=ZK'> ϥ6U|.ֳ֟ԳNm{8s?յCc?[m<>o}sWuWbuPdmP|]U9X]:u{V[oZƓյؿo{~c\YFqo{ziۊ-.>?&/}}og,9ڳY'=bO߷V{N_W.wC|ۜugN_.cyq)VtoKǦʲg>=H\/>YoZFqu0-##G3gv}LZ[nuND_Ų'q$oY'Vu/~ί?seǎ}Dpa}-B~} t0ht|ˠOG7~M'iyV_甿eTr^)KUvisvJ'ׯ3tGmG!]K毮{[KwcxbuP>|[Cô-ׯwm/cyq>iY"&_/]DD =i7oou!-+]X{>璿I9y|͞;vnbyZtmU|L~=[=bxVzo1}|Ovz;}'XM_woY_mv^[^1eKowc~=~OzSSB4n~Zf>i[-cv [o~Vב-o]}c:6Mo܋-.k/=vGq[Oz'1NݷV/n-c2]܅{_J8uꉅW{Ivmg9}MO"bҰǍg7>~t!">~n"˛Ϯ L"+_R3g L &s;|Kw~?{s L7綾° %A~pd7 Ö1w-G}^3m/S7b {+Naϰ"-c"䗟ܠ>7j^|4|">8u;v< b/˘#[͚"l !umHp1v_${AM7ĹYx铟X{Շ L9nZ^`;pR&]8uKy_ו+qnvh3pa=u-H)>q֑0,dg \7&u9]o jfquA/V4Y1Wّn+;( v|h1,HeoIВ`rvxLzLQd^˘SH=1{yN?n+E}kHYlLI#j6Kΰo,"7Mzfۗ2 n@XBt[>֍ܽnA&;P4;m(OS`NgTiq^^\YWyo+ޯ}1`K?} )gSWUq\pSÒ/Kxf1U0ŪYX]Rj)w3ucev]L,+5q̚8 ٯ^ۥ[V<u SXA@hPVAgA<;2\78^:Q3OVWY^\'O?R#T g SaBcKX&5xX0IűK`\+W* 0131da g%5_2]K%t*Lh4IE+LT0.Th-/\eJ`s1GX̌[]MK1&P]é0>fx5 S+L1L`]s`Z.^|SW"&^Ï/<_ZO`-Lb!0-SV4+̆ oa rt!{,km?jY9^okw4R߷䯮NrdzM2رǻUoڭjStެendXf { =wf(V{ ]̂Hi}kS]w1y:߽~duP޿oIל{Ʊh&7:*ųץyݙ3gzM5m[)nj~ǧN=kRͮ=;wwv|דumm;w"ÓcWar'",}НV5cOq~ړrbסIO碧\6ol&$C>=H|ߋ?7(6uڡxGo͗4FI\r^vs~Ӹm-d ,[xk,D½KFL>n? ? sYv ߬M]gnMᛚ v֦ŋo}&WÏS/o<~0?'~;?h9B\1<اsSW7C(kSY'oGfIoe{˗E?u؊wþv;^\N~OqnO(?8sfҬ+{ M-0[#jkOpfk>㖬\Thc@~KU.d7ol#GeTqK:aB,4I[²NVqK&TŞÒ*ϓ8u {@Eml|Mw~,5D ܼ+Wzn3 T+#ՠ„[wj.^|Ӹ%PALXR 4+5T 44ȫ8PMHX2dw3ϠPA7oomD'  PB,@ũ0LTiyqۭR>RؼH`¤KJ=s U^r[MG`$K*Hh_#$fW’ a/4蕺\v,/.uo%,9p)4hE'T„SYR!ټ|K'?FJamDDT$0a7%5yc3.^|t@#ÒAJ@L)aI?}د\$M&x͡aI:WE`NKj,)BI Kf0.aI\r9"&@]fg]ry&veavTLEeICC P'`4\>4=tM` "~hkPu),1 0 Kyc366Ǚ3g&@% KP$,/ PUKVhHh40ɲ:*Ae [*LHT0_ۈ+*M3"%P{Ni{%0AX®\vDD74:( K=4][^\ M%@d,66k`&%,aτ&@YA76un4%ḺcorX`:Yf6`j7p'3)R:)IZAF1 fY`qSQsd'+BZ9
aI8krp%Μ9!8v-ds68ugm[rpg 4ŃigOIi7U;W>aI{3^EˋKq`J\nyq' V]҄sy%&߼}?P$>jz.f%~}{Qlyܵ~".\xk.Y\&yL1>2_W?tI3VyF} U Rܯ6izoymdYw^z}xg߈G6⯯_У}skNAY_tY,[؊W ?s2s~c u vtSr4:ҲLCd[v2l;T;1z+ȿʾ;>XSm}{Ɂdě7 ֔KO~""gyExVzRӻG&Kq; bcw31ъ,C'Q/0ظVɃu|2Zȇ&œ' L8< K~*]X4σ%H/uOJ9 1KRXR.8@=P'1LCݲ\:AN9Aa a,v&5A}W6m:qʏia 4,,Q]Rr)tɩa Q$(@ & ,Q'BQ]pTepV؏*Xj2I Kh|I140 TYp*rSG.}!,J`RNcX&Pn8U%0)a 1(4I'hB~A&@C]t`^̂ԕ V K>DDW4d6%0g?l[/zDNv+N|X^N@3< iVˋ$@ܵY $*LKe RzDO颓?YTm@S 28}f4dz%Pr|8OzN ͦ@ 28}V;fL*ߠMhA?5N`2y)4I'BbXUI~byq)9рFLY9r'~C]tqdyq)nފW.ǩSOh@q&#,6olvCiձӠdTuPNt3$| M(A4(h" K(=}w_I)a LFXʚq 7j`#0=a kd8{0*8y,K%jd'\ɶ YU@< ^l)I'7|0M*&;j[B)I34`%iTM"0(D,:LGXBtja&_J&O`2[14 {2,(I=Ld8a cfщDOnpd0a gO'|HT̞?a 4FM'b,48%!0NXL\:-'a*b?+=I/a 4[32IF :',fT5l!0KXMNq`X @= F@Pa Pڤ">TKa P*fԉU'6ja @9590M'DhP^f[Sa Pj|xbj6>~m1Lnފ+Šɰ1N'5ja ˭?Z糼&,y;x@ Q'.8:$Οۼa$8NYk`*U'dj7ZvҎP'ꨖI݊x'y{˷$JqpXƨ\1zϾ!4)\w&M0}!Dw)D I|qL4/i]`RH%@u87Π$6N5Ic1  E QSkOŭcFv27qџ|5[xŪ~/'1Yt!x1wU&5T.9 )I>,z5[:1485|w_ڭsx2hJẝo*0,;y|=~ ǟ?ЉA4dyq)9رZo%?8lqVbAb&RM2yQow`kv*fDy>r49W\%V+:-O?j;Gܸ{GM!ݽߟ䭈?~ mTEG?dY\r9>tP{9oC?[۾?_߷qO77sgjEɢjbOpΝ#N'dwZ֝~C6ɟzu.qðռa\޿w$G""1"b=Ѡ$$o3{XggWdUf䃑tvڥ~3 zMf< f$b]u Z_d~?&_;։D`"0AG?M ܋ί=~ui6ڣ_Y_w^Y )Z- ڦt[?Q)pRSߌW.7e&+WO~7}xxF$V1 $@|P|_=tye3#dIMk~󩕃+qYSڡv1Z D([հ$m{O]SLO!"I(6o͗cD)~jH ἲgFS??SvEs5|eU1퐶=?˃\q_ UMbXx(iM <άdu(N>OW VceTO`R)8vڻvH#ڭF@cD݊N|o OtfTX$+wB[56&AA67l`P:Ӫ*ye^:]ˋK?Z`22,I9.q`tёI>8QqLҠqI4|M (t=]De 쒾?(6w/<1,,QI2|*8w/?zJ`", 0I.L>$6da$`rTP}?X'iW+[Q} "$rSEA=0QYPq(56.7Ka @ O !@ɘ$eݠK3AgʶWBVez*c`",hl;uPvG*3KN=-04PڤT_P#먬Kn4 b|dv! UPDX@ŋT*O`Fu0 WDX06TyP`wv߯P5L%ڰi#;Jlm'Qv~Akn^ 4mqkPL%ܠ%a8DxB$(?_FfK(wP<ǣjE'Oʶoԇ;S$PLҵ&nXNĥKoF(dYB:e`",tZ;}(=fkd] OItI#7ooE|8jVR_A&G¸ٟ^tFE`",]P%U2Ez \BI&J;YǏ304׏8d֚dBأi&cX~Aʰ ja#_ǎ A:(nK4$`dABM7k} i&a~ғM/rD,y9;[߾1Oviҁv!<;$G)wC_5uXvzeoNk&` I*~,""oZB#}&dfaUVT`",R%gކ]p6b{sDD+'s ?8qW+mڝ?}\GFDğ|=@M"0Pyu}7x}scSڝ?ӻ’mqوzDlV|U[.KbR,Cq1قnmdyM&ڭVܼոVO@1,hV,Қu24R. LJt7d0ؼ!v:#vT enExop76%#fI`NEݼ{z TZap70@+LtɁڭ`9 vmgcB~]TBT6,QYw0>3`W.;FW\SpgT`RX _/,9 /haI%+Kpqۍ|0۱҅{)& vjilg;a{SMnO.qر{9Xh#r L9]j#G{4gsmZԹ3-G7Yۡܛzq?V;F4]+vO|#^ i3rLjv{so[מu>* wy?"hXxp@=/}m PIENDB`chafa-1.14.5/tests/data/good/card-full-noalpha.png000066400000000000000000000757751471154763100217040ustar00rootroot00000000000000PNG  IHDRL;tIME$+Zo IDATx{paC.IVeOA^ Cph, \WrX|(YוF+ۉ8k9׺vxpdlGl=H]B\bTw@I$=vtuW>S)g=ݧ_=tpܬɚ}Dm =lۯnw[8ٖؑ>F"hp`Iτl.|@Vk֞p' Bx !6Y7뺆n B;/<=5>89:95, N Sþ{\1y*&sh¹Vc#Z"A 0b+gBI)>6:"E)>0z_x< {=SNp v'Q'㚻; Vw ߍC95^E69 g`{_r/[omJW}Equ]57e+b룉[QjwYͺo0_ʬp2=u>A<Z[Yѧ55(9O!fe^Ah>jН7]{r 5~;aUMU8yrP 䀚4ǎAwBP fW crp! C/2\gKH+Ц…lrPBq]}ltDԻJ=~ʛN[jnK #n>Y=9ԁHe[6uump KUԪF„l@fTEbCD l $TC,0=>#E|Ոr3t(箮ME&S߾胐RSɚt)7H~"!&5ѐjBN>n=trBج_Ut aF̣SŞM=BfѽbD;Oa'R09` أ2O)NSȑF^Cczkhسm396o73Gxf2Ⱥnk[{a@yjfg * `4@C3Q~V}ظ5[%,46:LvxgEC$M(kLvxn ԛ[{Vn޴[1=/uj*Ọ w7޷{W׌~gWQ,U|2.WL{ !]|Y\m3v W`2[a ˶ZVݐ V9)v\ou56by !{嵠jbYʢ&{AQq)=:BR*rtW W<ibO܃7̻Zzm]]<wjs^$]ՠg.yjbd2CBENQ`V{Q-}o.:U kYrU_Խ-MԽZ;{T819 ]b@5%Gpb+礚D'v핇5#eun֝*oι.Sɜb]C^u30oO} Ljl]o$3Bq-" rmϫC_:5MU@C2L%o6ll~E12:=/_`/[yFq񺷞*94jn5.'0!!'=7mʻYP?LSjղՋ̕Չ_rObS렒5 U0e&&ïɣij *xw) pZMo /"X a^AjAp5[m'B,׶}Ef\Qdc'pO5xS\NNtum (%$$'ڛ/>R@!Gߖ" E?Ҏ+ g׿ZwWck: ě n=5x#ђhXUUQGl^=ӹON>ch_h!Mm/k-[FYc,:!:L}0A@1-B}Z#mՑ#v0ÅgJX+O½1xB1mZT1? I8K=9uvጇ/:|csΕ#_MM%ozSGTyŅd/YkSqurmCQ~M%ߒK:Bwf]^! oX ! <)coW륾kP|g1'㝁Db@PBK8Ԃe˖sBK;2 swoxjV9;=Ǜ[;uB:tM:J=9X .ćo}" 8:ޙ:  .sohu~ !Ӥ{1Pݨ;Osroh !sl&{6|:^@Ržjo7TxM;uroP捝4uֶvB`ټRsTVP-n5zqETnK_%6EΦPsp,=0(@`pB:M9|ЯY-eŢXwqQk9=WLYX1F@ :6-s-7 @Dtgkot5Vo`Q^=:@0 K±^eJ~nZ:PNqhj -97rtu+5]&!.99H80ɂ*ƀXdDׄvEtB J8YZWlkptޡvr@y'9 z/9SxߥPj0 A(4sNzZ} MtxCa؏XPڹLHrhaltohZ `9 B8pֶvjqx(`ֶxSU:tzmj0S8Yh צ=)Ԡ(4kr!D!: $+Q!@n PjiØ񦸜E>hjK!f%0$ѠN!|r6^,NMrٜ7MQ%Н$Z04%P~vWSh9G^ya(@@*l2딛9G]9!b.jmU]3{rnW&9G(`a 'Ʀ>usy@ tN;NI8zbv5@#8 &b`Sa5ټյ圄s] B2ّNP)?!ҧ fY86JԢmP#N1D .j hԹ)$g]c QbX!:!7p쪐B`9!:DpxNgT@Eڪ"ʍB>0D .jz>zRnk@9@$uC-TgXVPͶ'АsrE j 뺄 dL<pܬKi詺C׿ bz tVs612Zr-9GY{IRf*yc~Ja gw$#r r"5" AQJɑ$uR)~]*X W+ jEֶv!YY-ܐdGevī{䇭i1z4 Aorz本zt{H-.&+xBxSۂdQUj}CjaimkW٣F :Zڵ:HwDw%[l I8 u{wMMg2C{. !򫓜uM6Md(ԽO&+MhMÇxo߼1ioP)d&Ʒto__NfLX+Q ɺnG8WW|LZKM&#>k+Yz,1oU¦}K6X3}l޶~Zqu€-}Mxn9%WF5h=77ő-.sG9y:CY(WM> 8~K/ZcrinƼ t*y3GR$<#?Ȗ:Rxzӆ8-k(!Sm ђ;CtU@T7{ 9XDʻ|iiCGN?X B@s(i|L@MMQ0&Xp 45>g{vJX(Zrџ$@:B{`UaR膄 Fз9y|gՀx604P "nyT# c$@9祗xj< G:PO֯^Ki`1d7h9! `Ω_kM晍 $%9g}$(&Ps*MMS sK/7/f]|hA-7stypޟ%B٧YMiLsBpEa3J8`\Qr!c͓p@pF眓3@CG|?,5@lvt&眻xxSb!Ы`Ǐyu)r;$ MM=ޯ~$VZ$9GQ*5r K/ Pg ߦrW˓s@l7B3$N?FA`ج.K?#-SKvf.'9=hɁ (9BU1s`1Zr`'9a4@ќstr!ZaSÇQ #PI% f$&SGs<þCyL<{ěGOMSTBMBLv&s9vϞ8~j,(464Mqr9vlEݼl(` ?zI351~y֠%6`:5BFׯ^KiDlz6?3g:5sWbLGw5p'(J9y:C9Do PUsԭ !iMOΐpǏx^j) 190PujpEsђ#x~jbr  T'朾>rpVB '!= Pq1Zr`_'`IP];51.H:uؕĭ@ exp;ƕSU3Bdc )u\U$7d8ljeXV5hI{Sa 6Uu5~mԓ3r$:Y`rd=;Z wu+WV˗r_bY=|ŗ^> ޻eM+ f͟sU yBəܢILMoޘ5vAayƔ>!G'ӳټl@WI{uݱёW8,MbؒʭU.Z|v5&s6 )zD$j-jDw(Nf%px,",KE  [B՘j5! 3ge%G+/ 2rSՓ9I@J%-z{,{ C5jqLn޹|P!Ǿ;k^ם< K @:qx4I5> ,슩L2iOLr(wÇs@PW~9@ȁUBj751N9@98sBqyq@ȁ%|4P#} of.O!?ȣ5u@ȁ^zc IDAT 9 .@ȁ%o P /*B,Pbr`K/ИB,q@ȁ F7@ET$Bt43Dž Xaz6y jb s@ȁ%f6XfP9 0.؃h!hsSJ_9!؃fr`<(r39Xrybhr`3tTCc9D39k~˦qnI8Xk28 *3stK5=gd9zt1KЌr2D398U*X@hүc,G3o TV_p1 9VKP! 9Nqlڜ<84?0G0|)9Vͺd]׎/:qTx#s:09>t n^Dv|p ,o{#;#=o8 =HypP}R-DRGqɎdĕMdqih ؙ|ߨ2MкԭByÅnh~筦oZumwk!]*>S--ǔVݱf{챧>j|4d#rq緄殫Jխʥy)uvWcs"РQ/2tr݇>!ďn?fu}ߛMrΟnwG_?}5(R)xgOQygOE7T~uȮ_>"?J?]A~;M}Vxվ/[yp%A-B_aFFܤt䴷_.!!Gv$u&s;,?߶5O^9|}[>+鹝2oyX}-տƗcm'[ѹfj_6}ੱ}k]]9K^jM•?2oʸmkggD6|DHb>ƷJ2|߾z*xyi'BVc)e\`c?~vnuM,kS6Un'>$ͮU? F潡߼7> $,n;nMC Sr궧[}uA@*Z%YB''#HDB\7{Oi&7Ε::Hv]!č3 =>hɁA% ޠhYVgy9g^jH\+ϼ'\]n g+e3΂(S19lƹʳ-_36sj@yqz~ 99qdc@"I8@'FX!ZhŽ9o7BJbh"rrTU!Ƌ7S]ח&oh:S=glЌTؘr0U!P]Zk "L9G5r04SИB`e49}T5s@<BlьLCoS `4ȏn9@B;@B5rPGCc \gz6qE5rpU9ΚuQt\G6ܶ5M0ڳ :L9@5rj)@ȁ%x==@ȩu9`@9 : l Jc!5fr` q@ȁ%u؊9@ȩuUbX!5 1 GͱBjE)N!1,ŀ`7rP+~G1!9|0W zrP[hkqB!9krB8>+#q/ݭ&~vݦg l 1zs+ iìv0Zm"&OzBrar&ЊA[`jݪc@5sZ'g= ;o|I~B>7MAUumnJ [ᭅk5upjEM'7'v,._9 ~%q-9N~}CK_H[r|2v~~ĘbkoZVƼkފlMGEͺje#-9!ܱ]~ߧyI[BϷث J-aE,S.GNf6;9v"._ڰl噢p6#6Z<56:Ϛ'ǚͺY׍ސF.o*H8{_db7"{ZTS~!뾟>7M{~;|-TnMܖ)ᮮM36:I O׬2'N&3Tf2CTlxUxgK(/gY0 @vL9²n\T(_9ZrjvrPCȁ >wf!U8r1Y`@9@AM1 Y 9XhY 1!7! X9C,1w!`B0@B `:P 9Cȁad_5f(CoS*X#!QcC,r90l^~`RV<@ȁ1֬{׀GK! r{sr` V!9S t@`3J0!&aj5r0  ؉*CoS*LFȁZ X9!!%9 5p7(BKrY 9@A? ؉Bp0oBUu577!U9i>6HrTwB9醯SBx(RBL7}0R@)! rM) !jZEne]{ Ǒ6V7D!ԎW^$=7z;Bbd#7BL-g?f{Bww 8H:3!s?6,o}b~U*q2ّu]Ury|P+f S7&g]78BZQ.?I8tOpsLW*:.xЃl̡ڼ8/21Cw|Q=@)oQu>1am ~++ZW&!Dk[>pU+ K\~vt$vY O~q|}K'䭿&[7ύ !Uk+Ko !FFǍ>)Zn`Uݐ[yB}AlT1423_3?8*L~q3Oh KB!IqxU=[?> sBEQ96D-Mۡ"9zZq:xJ~g$7Y@R>{1!y Ju o|o~Ǟ2YOBךɠ_ d'?8~LaH:zF ^>Ӊ[=mOAՅJ}oWuH<BW>ލBcejb-B()oY?<'[?|!ЯN{yK^K/4lB/ @70$x/;M :sr 1_:q{ bl4>6:"['[%Vݔ#mٺYW*wՕWgSw5!īG.st&;onB|x%soW<& YN™`bO j+;u߇ -yۋ=uˬknr兦ݸ77b ܱ܀C[{c#@6ֶֶ_ mlۼ1P>W4nk55۬ ´;—p6ɕEO%]_.}cd[CRTsssPMMv&}C{@b B;rr 9\фKgBry1!@U Br u95UKBܲBX:^N13gZB8r j 9 ~!!-ʭt 9 +8mY !lB SibvlAL&3`phvPgde2Y0OEf?MH:}Pu-85؏@)Мcڮ0@$}< !FFf`ATgpGSS!|Qvݥ>LM!ܲݱ{dNQ*{Ç}DφnE*չ{LwjȯPTgcsKuWpP-h+dKTn2g6lnY~Pqۼ1VfoC&BN|1:^V.*|A捩S;w +!Dqvw=7)m޳]sa믊]_ t޴uw#ᢿ?yjи{,muo>=AUyW+Y}PJe⍹]n)Ui{7nMOʸP۹{_>S;fo !T)C}Ԗ<._,?&g48xգ&ިMdnkoZnC/těm 3u wA:ˎ^FKnͺng5Ǫ5Jun.p ک*/?㥪 \ !o8NhǛAxS|wYw`?7SU o !s !JeUx:$L,6 vp>L q ?};·~'}imk?:!Y dz뿪feR+nS&k"Ⱦ²;Ⱥ?yzӷI8gOΘe?tЋ'Og 偁~sBB=d@{wg !*T !?q%{^g]]FG㡝zsPKdg*˗6J6wjSJv&q1/M[N_̽>ʕi+d0zwLMqɆMi[gs>؉ QP&3tBї)5{j{w ͼ?3Ju d[!Ⱥ. 59ԡeVMjTgs;u-36:|{|u6*FRTr6wlz$wYVatWu-OoMwumr@oTZ޻G^Oztrvoԟ_~Gf]o?ק?_Z'G_գeClޘڽwYoIntDan2gҽm{{gEwCG'Og:"KBsG^._Bě0Ics˽|zKyg7&[*ʟtz?yZnB51{*0W37&/0H.AZv5X-}Gꓟ9c{mޘoޘZ=y:gyR Ruc%7kmkSUz|Nukk" _]%GhU]9W_Qg&qTVb.vimkWiW&<:W{koZ]kZڇ\p._%g?Xy%˗3Ç|F̋^w oM8oɲT?W蟔y#Wwj?-߸PW.Haa ST~Ƨf2C-[|+0oke9JF_xT?QAo='/Mܷr3U166ȲUT&3Gc&3{|2!u$A[ڏ70LJֶ3nmkO?rƕp +#h\&O#SI*ՙNIn27-ɉ ZrX>8ع#{WuκZdCTI)KF{oȧQdfb% }뿵7gyGʋ{^zY*6!._ctOC*x_w?=y:{Lf{>}Б>ȣrɎ3OWżomk+/[,4,/ImS> G] cѿrdڗ/B?g޳;Z\%/K^;MLz17# 'XpFG5! />EB  zӳd.NJϮƖ*S-*SraUxQw-\'EʾW^,| *TVI *: J,''?WPTȕQWaLM8!lZd+: ǾyˤH6VVIa#ἡȦ~;cѐYɣ2+zROJ \។M򘜚_TkK_B(b_.5ZeKߒ7zȒYPh<JUz7Vߢp8Jq&3+tkXf{ra4+d[ђc!^@j Ø 90IC] @jØ 90 ͯ1lޘJaj5Br)(*:@ȁx>P r{ЫS P Sr`$a@ȁU` (.rЫ 90O)|RNf TӳU |u%R#{K_IZr8jk!VR:@ȁ8Oȁ {@'?!6YBO~B, Bl S Ʌ벇W)y*rgT O,tp CbXqTgs˚xS-׶tGeސ}Ϫ_WhnYCQӥRW hɁl̡0S@ u1hnYC_5rpEB4QPTy@u"P htK!ElF!W kMq %Q K0}@AɄC5`r֯^K5`/0E*չycr!lޘ1fre؃ 䀋SJ`!j1\*q@41謱j `֯^KcYsA Z"pP@OL9 9ӳyJm1r8V=ycB@!4Ggrx uXҽG `I )@R1 cAR1G*q`|I8{(r-khMMSXLfȾr4|O D)1?D=L:r 94(P1B[ߖm=k,8Eј9njT' TSC]l9 *-moEuٕ @d,6=9A_|]ObYġ̙+ܬN|ɨ皅+SaQolnQ[mhT&/D{we*o?[> UL$*g]Woj bt|^ݐWn5)\s=9rI:gUkɭ8 C 9m -ilVq*ޅֶv'٘S:^2gya[\PI]|kV/~:T^WkQ-Uj}EUqlp E/f놯vf]7몕 != OM˂*jOݬmȒ,~(V /5T kjׯaz\eETY;_+S}~Tfӳ•d?S{i4T\vy UYdT_^s/Nu2R.eMn273WǤΩ2wS!wZqa)?)ȍM:y[֯^g}+BdCqyR%G hve'FR'$*RQ(wI'@!q:t)B$' H^)Su*Kw4=%U$||'3]<~ zvvfv~Hi$g%KlpzeSȒi;#qnv|8P( ?_XҚJJk5yaN|Y=gLc'VVY>'xypO@&@ӥRu'VJ͏;W9iWW #ɯ s_}A/U|A9(˖S_y':.gK 2cyvضoLڶp~zC)5s]~U)UxlTߐTv+.1qu**)vfb@NUwx$i~\+ҬżIoftDeN6^gFYA)ܺy4]Trd:%hgxdtpOtcM?"ߒX={uxdTn^qnݼň_}@AI>(e2݆DW [J{Uk5Y dIJ%-i!W]<ŖJdzd;zJ3Bn^ӓ)fAJ7JO#\?@ZҧL\nφqfgdċdQүe9KL?kA|-x,@#o;gd/ẲOC'w434M_wMFw 67 05uTm;͠635uTnu0^y!G)zQ)]\m{I)唐^rIj&oW;D \tl*szfzթQ&o:eMͨ~jk YG3}d&!DѫQ_s|uǍ0j~uz"O۔umoiތ%%D hE[u& M2ʼn7=ɝnYMt wޑ?B-[sj2|szrd'm HJ rr/+~Ji1B0jrҋ9 4(yM!Ini>Ħ'Qc?R7$WSq iPpMyw T~XX^U6ߓIdp=d3W$2F?d.Il٣cιw_߄c8&*XN`V$[? "&W$.a Tn{y5C&8GFy0hѭ-e]9G9+XZngE`l+_< ˹m >'(<'FAKdnZgvZf2E^Vz@rŁ2P?AND8o=s@}@dOy![܁zP!k~בAȁ灇N@dpOl9z8DJ̎\yw`q@ȁ-~t"b/ %nrЅ]9pIڪaBGeDa8G[5t !]P`Blt49` T X9`k!YghT4j2 kTv599uͫqB0kuT【70h8 TVlrcϝ?vlr@axdFkQ¡4ZPB jr6*s@"(gC69pRq@eH969p;< 4T![[ܷ@  B|P((F JjrUar.b!^2'( +29Tai!^9o?țBBX̃gSx\WLWGzyvk8o3ռv[7u݄3<2|pOFk!n!}蕢ɵץmo[RIa(WOMѝ[mccöMk?뚜ctTź0u:q\'})Z}jRG2+/ʯ} x\X+GF=ʦheDrwu`#7 ]YT* vD?wnsuԅfo+'A iSsOuqI8o'g֞ht#29ׅ,/ Zeu_ SSGRW.];Wڽ60mud?'Ϊoun)7ݧ wٍ^~[7oy(uK'f].Nv:iKɕ,Ӗ>Gp#QNp]Ҡk5MQnw*fsLuwǔzQ)u>.!gC>1rԽ ާZ9MEw,?NgCrNfȥ&8oRa8uˍe?w<7ҝ da]Z{0V6,lű7'L8}\ 聻6yhrV5T,=ᘅiwT>NlJ<*]9V(Nt@W8:Cmf#tNq 9x 9{C`j9TP\\]98rW9]q!4PW z8*:t=X #Vuܚ +FZlx*ŏmgc$meyņ#m( @]q@9x"%99O]qB9!'GM[yzI {]e`勢8~+u'y7wv~muܯ>1=ԛהRO>}qDֳoAKWP?1͓sɧ{?s.)9zt<6JK6_v ;C.@r4n9?7'7Lw*(=󣩏EguM-pi{EX}w='{ǑDY JfbV:&lE6~I#p ˄z{]v$:o7&=,i99yc,'sâ}QJ釬hEazm;7?>[Soj_ItJ?PXWy 4 {rzOV?9OׯJك'g{}'ؖp; }rԟrtc֕%|_kk33هO.+2zn&/'yʿ@~_xErO8@`9JRAW%)鋠q:`a0c]]Z5L8֦v2{ey@fA_#|{ϙj۔ZA{CAO~rNF 3q$x@B@Y@K dsW;U,O8oVE9@w>h o je+5c#^P]s+˄_s+x|"< 9K͆wv~  P!-ȡ*'7e¡#YG\_M݄CW .?Q%lFj@?sήR|gGu5œez56_:,9upB{rbip3p9Z9*p*PX>9-9ѥx6L,AM`Qa5pN)n@y!c p;\ 4Jw~sEM`oΑF}6z@8$=9T@f nj7$>$3ސpBm&$9s:q9s™S&sBp,ꬭ׾SJ=y7zЭx wNGр @⍺wh+KSSG8UFQo9WZ9r؅T_wbsL*sp>Q'9jmƘ@@<Αsofy{LN#F]G‘f'9uJ/f3mBrNriG ! 3Y:\K8  >9)$Gw ̜:lmVet!Hd1;(M+$I܈BZ/m50&'D@]4WR1J5'DKZZh-ґkdp!@NbiHzm̄C@ÜRc(*v,ސpl}rsE<}ul94!jrl1HEIIdE `GB`ZivQکW` ~ }/Yljr07ҋ~lZeC' sv3H/DG{B0V{^JŮ #(SamN4 chooBSm{yo":3m *t5#mh3ۤߤ ,Ue^x9 9Q MXd<1[:xcf }r#ٶ>v5JW]ܾzi9z.Mblg((ƛt˴D*p2Cs5{lcHep>E.\4_SHӺ<$/<7l8yv;:8t-l.rn5wcñoU]mv;.'!w-'פЛl&i' QrA#ޔJ\59tמs-zGF[|3JCݐ36>16>d" x[jU |у3>}_x;b jI)RgN]P“O_dp͙S8^Y^=UÊw:ؽw_0o=?|FT<ərQ\ 8em?ecZU+du7ߢ?ß|Cے+ֿ2J^g8HIۏITtٿ 9ٳ4vt}ʁ7gIs:+P%l 8C=< hޕ'H!r9Ǜ3.4::wS>H? ۲o41xM?7u+UA*,Qjul[3ɈNk^׭}mcn<znk?Tr&jS%Y@_Φڃ!^ؐm{@NNb7*F.eܭQ!8~6 ]4'mZO5 ~HT_oŢRv?i/,DuQ?m۶=f֔Rzl~z43}YY^bB+m9Rjߟ ׎EGFvwCH! -֗hTDG_TW ^N|rۇg7;K9M|뗳5Z$Pil|B)W3Bxl Pjh>$f7S RPOJ8,RJjufRjeHuj;\9ϮR|C1l8r:T`P碎.0#GM#qhkuȱ//S*89 @iȗ,9&QV=j845AAyֲ=g|vr59T頻AM_<ͯq!^BWPޝ8MV848BN7y/TK9@A}rmrr Y}8PEٜ馽~ M}󵻷]tI5&'\~{VǠ6vjl|bl|ž#csezAw[긾^84]#pEG7Gyc 8[}mccC/B_!ʥl~OgXčIENDB`chafa-1.14.5/tests/data/good/noise-32x32-indexed.png000066400000000000000000000034341471154763100217040ustar00rootroot00000000000000PNG  IHDR DPLTE"*9YjO ^ 7CZQ&0gRTk3+qT\kzTO}GG[mno}c2.Dk6SDܚzV=R,t>?*6M;r*dL#SyE*Z8+KM^Vi_UO>TfUcod==ZT`d.K`v-j8{J{AFA5%dWM@sZvRޗGnzqZX*tAZlufbS0'=uS \bfzOsyNEYtsxgbvksZo,{|ky̏@Xǯ\̙ߌs݋܅vM˜cKَc~[QҾp̦aVеw]Ȭьhd ~VtꙹyoȪEs{Υcd pHYs.#.#x?vtIME (+50ItEXtCommentCreated with GIMPWIDAT8˅o\gA'ZcK'ŭd FʟYPvvZ`E)+i]%5 4ġA*C+O;/Ճlmpr%vK1薺аCXW?iȁiĠD fʀjcf4T63*X_?ٛnFuWDdD,g|2Yެ5 WK"+3DxЎLt#`0@u4"ɻ njR|^]UiAvq'=-CI"q~{]rL (Ɗ,j.=jy&= l W}()_]ּ;T0BOoa VETRژ[)0)2V7!j& )0!h40}YWc؈>JFq\oNŅ)\ Oy ڶAz i]=` YؒkalZUJlL%X}):2^ JZwF MQ8*kp0|_8?T7 8 <]zpL'pծeyRCR AMZ]t#& E74@o=1B}@XVo;>B+~BF W}S"}b9nMEX_Z걼 Œq=?Xr1pL$Fo :uS9Ss-Nܐc_IokvLKpUkWdS[h%-[ 7Q-n1KYЗuzD?EC˃B;q駙 z0 >Sbʨ -6IENDB`chafa-1.14.5/tests/data/good/noise-32x32.gif000066400000000000000000000034671471154763100202550ustar00rootroot00000000000000GIF89a "*9YjO ^ 7CZQ&0gRTk3+qT\kzTO}GG[mno}c2.Dk6SDܚzV=R,t>?*6M;r*dL#SyE*Z8+KM^Vi_UO>TfUcod==ZT`d.K`v-j8{J{AFA5%dWM@sZvRޗGnzqZX*tAZlufbS0'=uS \bfzOsyNEYtsxgbvksZo,{|ky̏@Xǯ\̙ߌs݋܅vM˜cKَc~[QҾp̦aVеw]Ȭьhd ~VtꙹyoȪEs{Υc,  aGAM9vثM-uʅ@b_~&jnPp` +;kr-ROJ6ȋP$/8~塴A c_hE  ¨(<3K0w<-P9-u=ۅaؐ@WRAO*0@j& ktaڼj% @XxsJB—/(Q`V(+M>4!$%D5`-( 'y&zS>Δ+&ÿ(Ѐ1O( , '@`I%0J>H'40fR RK#p@(@&*@ *@ G3@K@<:TB9L4A&b 5B 9_tBx2xLEE DsB& ,'pOyA@;*/-HC4AϫIJ XC:(2*ypxR'X(Ԝ }-~̶G4sN-@"h8FʂRp_/@1OzqI/EB>@U'L 4 TKA0'-?  =5 'r nDAypI _,p*A'@7cJC5 D4`)J-60pc;:6i8pBDhr:gE3 R9/hAps\SM E€UBC-$P. L B@ r'T p@" J8P`!pv]/pN xpp-8/ + c@ xd!+R< c JY(qp!&V/|  ;chafa-1.14.5/tests/data/good/noise-32x32.png000066400000000000000000000035771471154763100202760ustar00rootroot00000000000000PNG  IHDR  pHYs.#.#x?vtIME 'u@etEXtCommentCreated with GIMPWIDATHǵYp:cG"L;U["-tPePb" ;hX a IB ; ! KI@6 YAeso-/~s{ι|+#Yj81`X@څ!%qT QoBOŅLf,aw~㌢5Hb} r2+.Y^ Y6aСg.'Tm|.wA<  z^gOM2>kr~֫.XǮmYw3]_<zUpL_t}i9pJ6QG#VA<s<KhYzbq~+ǚXg,8d{UƵūTcOǛTwދ~;ӭ/jL}MRĨPsn>wm9u(:FIFvUg*KV ^7P9=WJnq*<[ÀkKH,|S@Ig֗_D;ԛFI$@V*v\U_DYTJKZmxyÇateb|iMKYɜaTdNgrdw0XbJi0iꪇ][GXэPW<] /DܬޔcʙKȒlX}Yo7ȼOP\~5#Q|`Us^zf`k MQ(EriD"ќq1)*d\TpIOʞwdҥb͏Myf FQhdT?ُWyU:ZzKCmcs5k0GfdZdT7fYS¶UN}y\܆j8|ie`=}tw|=Xbzz4 )TsWS4qaX!J%|XU|xggkc?5[k}O7O֗L,qsC~&-T0Lvm,~qtme{lX{Pye=OzWTchafa-1.14.5/tests/data/good/pixel-cmyk.jpeg000066400000000000000000000005131471154763100206100ustar00rootroot00000000000000JFIF,,AdobedCreated with GIMPC     C     ?t/OuSchafa-1.14.5/tests/data/good/pixel.avif000066400000000000000000000005431471154763100176520ustar00rootroot00000000000000ftypavifavifmif1miafmeta!hdlrpictpitm"ilocD@7,#iinfinfeav01iprpsipcocolrnclx  av1C?ispe(clappixiipma4mdat ?m2d@$A<[ͱchafa-1.14.5/tests/data/good/pixel.gif000066400000000000000000000000701471154763100174650ustar00rootroot00000000000000GIF89a!Created with GIMP,D;chafa-1.14.5/tests/data/good/pixel.jpeg000066400000000000000000000023171471154763100176530ustar00rootroot00000000000000JFIF,,Created with GIMPICC_PROFILElcms0mntrRGB XYZ :acspAPPL-lcms desc @cprt`6wtptchad,rXYZbXYZgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ -sf32 B%nXYZ o8XYZ $XYZ bparaff Y [chrmT|L&g\mluc enUSGIMPmluc enUSsRGBC     C    =????! ???chafa-1.14.5/tests/data/good/pixel.jxl000066400000000000000000000001541471154763100175200ustar00rootroot00000000000000 JXL ftypjxl jxl Ljxlc r Q ^|buڶ$Qd8$!wx@mQ}{Vchafa-1.14.5/tests/data/good/pixel.png000066400000000000000000000011051471154763100175040ustar00rootroot00000000000000PNG  IHDRpcHgAMA aiCCPICC profile(}=H@_SkE* qP,VBЪɥB$Qp-8XupqU?@]%ݽ;@fnxLLgV+@'3˘Z{zY9ԬH< &^'ܴ aVUs.Hu7y6S90obYԈ'#SXY+Yuc BA(FV Iڏ~\ 614Ȯ~wkǼP 8j>v T>I4н \\74e ٔ]OS3 { tzq pp ){Ż;{Lznr (bKGDC pHYs.#.#x?vtIME!tEXtCommentCreated with GIMPW IDATc/&~IENDB`chafa-1.14.5/tests/data/good/pixel.qoi000066400000000000000000000000321471154763100175060ustar00rootroot00000000000000qoifchafa-1.14.5/tests/data/good/pixel.svg000066400000000000000000000004741471154763100175270ustar00rootroot00000000000000 chafa-1.14.5/tests/data/good/pixel.tiff000066400000000000000000000107261471154763100176610ustar00rootroot00000000000000II*"(:B J(1 V2dSx[ ~isCreated with GIMP,,pixel.tiffGIMP 2.10.242022:06:05 20:39:43 720220605< 203943-2039lcms0mntrRGB XYZ $;acspAPPL-lcms desc @cprt`6wtptchad,rXYZbXYZgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ -sf32 B%nXYZ o8XYZ $XYZ bparaff Y [chrmT|L&g\mluc enUSGIMPmluc enUSsRGBCreated with GIMPchafa-1.14.5/tests/data/good/pixel.webp000066400000000000000000000013401471154763100176560ustar00rootroot00000000000000RIFFWEBPVP8X ICCPlcms0mntrRGB XYZ :acspAPPL-lcms desc @cprt`6wtptchad,rXYZbXYZgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ -sf32 B%nXYZ o8XYZ $XYZ bparaff Y [chrmT|L&g\mluc enUSGIMPmluc enUSsRGBVP8L/Љchafa-1.14.5/tests/data/good/pixel.xwd000066400000000000000000000001501471154763100175210ustar00rootroot00000000000000d  @@chafa-1.14.5/tests/data/good/taxic.jpg000066400000000000000000010163561471154763100175060ustar00rootroot00000000000000JFIF^^C   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((U t\Nm 9FTa&ЮyR !I k96T qe@"ZXr.rDkqxD9 a!!$pe3'Y4va}1kZ@@ cYY mp D@GUUIہ՝TwF s\>IMؔ,!81&H +ꬲQCz)& XB\ ѣHpSVvPbx@(a! bƈ7KX0mX@'^VtUcmӪդH4!L`1 H0-pŊhHHJxwyjqd闋}3'6b 9@0% HWLYd= *e&\ Z,*\a9jqVbpVEa,=0h C"`! ( &`X /N Ĵ\[-:M.-0MUa;HݩܙP]A0Q@HR%(LZhHK U>@dhNA3)jhBJ.Х^_dKJJp)t2NY"fgf& kI@HAɵi^nvRגۆ1B NH 2'i@9Ns9KgvPՠ&uUJbkE:SS7ؒU'F\vA-^M,,|f9"!F9NM|w i4%U}/2r ݓfᄈd"ZȋQKF]kH  1309FY`)BB;XZUh@AUsH @@ m9|M;8d,Sr!>`,2$3NJ sP:ɘXh 9\S # Zb5f MiX+×K!7`gL C6ơ*hSӾ 3\rdh`1,TRDU9E9*Y\dE*ꍐBJ ) M$XAl+a)玠[@NלlTYSfsZ `@C8d jCk*F*W5B ^M8ӓrrPTL.j. nZi=AKVkV&p OHUf/hiSx+'q??ny-;ӮZ [A! M*@roFj~]L4͵6d1ڨNtd m\vL-0NE`:irJՖS`0!ʨ4]|4jm2bEBa5[Rw˕F5-WTb5\Pu4Ӵ|>!V rbjpKF@c9jir.&:^,iDq!1R2+@@B@f% BKMk AO랟Mi[f:󨝶cA!a0Ar 7QH,4@0[σeXWe0,,X"TʛsZZ ч4,˚ <4RH`饠i)ZSNI: Nl4ÙIeqӖ˗TJ5Zט!]l+ךg2.Bք1)0SDRVQKhF4x;Y @ &Xd)4 gc! d @`aOС;ATE1&&nm] ^LJ`\bz ldu@I~کL+r3cEͺuOwI)j=`hͿ=-yA*?Ӑ4hKdhQa\- 5@p,%9@6k6 jmUEm#=U'0bhF1sI0%5UW1+$⧙ )%2̰W**95&ՖYlJǣP:(7-5]zg`Vo<Q*EBN s:r7:$p株e9jS@DkcJSURgv e;]3RA.NBvЦVM,ԧA,Rke Ϲ\ޮm]9M%!UiL.Ի#zPEj!4kT˒ٞ6KM -иmUvD\1TMFRaEƢ*k3/MmgYR)0NJ̬Ҥ9Y;s6uϾ}oq,k KI5`mhB9X8Y| h:IT H:ZSgThP I\0I@n[{gJ)Lb$ sSKVM&vl!yI֥bE`+{ع݉*T3"~dXh+ ON %(BgOpd*!Hlb'ɵcLEɋ!"!p 3VٱEnk+Y.P5VȔI6g*{ 01sV!14)!P<[h'Ӡ-NrNp瓫"⌖0AH|*a̖-9D@~IqV @> @^p|4:=zA)uȊ!#Yz*W)VEWrԤ%H&`w_*FCcp@H@HppR[eμ&z7&^s[Чͧ HNU]9n 1h|&M\Y9W4⧗MJ2EHV}x,錓,A0O}OPLGI64p;k9D-M|j=ד  zKnt$1hG-z#D#?y鯲 bsL5Vm"Y<]YXz88sr.!"$5kjG!ppaSoL5MXG^+ӊ|in%60%"l!Wa-WL68Pn#Nz+ iBbU*8)]`vZ, :N% Q,~kW+s-фW) =O Oe@nӜ)L"8rizZbz(oͲp̴U [rtlJҧiɣ|H t;.{໯$HHH8lXjI!NUv +c/\^Mc\:ѥg2Lhbs 92jCCbes^j*KD܎sP@m巿S|U 3 0L`3k CH,s y#h6t5BQ% |zz.*>uJ"\ZZe}]Um $Y_DcZ9!yLEXŒuo˗-?2h44Zނ_= *+xp\"='gXkxWvr^sя5ckysrZmҷY~ÏDfϣ3hPmr88R`&Ny–PJ UƚZE?EZI< EHI,#p b"DA#pKl*XM՝IԐ#!"! Ǻ~'AkvE"QMꖦvVi ;Yϥ0 i=QݦvuOڃO5#l^qG>u{,ֹN|nu]oƍ1p"k1oe{~~PtE\Sn)iд%(!H93j4iSZ,;<7oJEeh"oE noto:x@@BE!!#%hF$HTP,bJuR ~ϯE_kkHs\D]6b7Qhb³9F7]׷8raMTOkWV(%9J7yO[0ZVV.=tΨ KT*\O+[Ӂpw GL!i{u<1 t [;r \5!y{ӠX)MWmmR)ەsFMK.tv*^t@!&8R.ZWnjT(M)G2),w1mD9[b"1z EPnwv[C1 Ydx|Xz1yK{]Kcp{ sl+[5zn_翏֤ٜ~AWz,ӽ.;^Z7*H5}Kߗ?AZV\5¿*LJPƤ x޴rU?5o_=O+.>Yz,G_aꊗ=}8;ytcPzfh~Ϋ41ĶYc_ [k5pVsѯŖ7n|9Ve+W EHֶ8T= jAc"sR a!"O"=EW({/"ܕ'Jb22}SQh&IG@H'y~dқJd;ݓx-CtOz;{P͙2 lᤡ=Y7cckynekj=>sYgM׊_iMKG :6uio ^ɻ6:].CWz^?Ybӕ)!pm|0]?)uz;=o<5f9G^ƣ:gNΖi=;^RԱ)*E ڼQ^EjOIa#!#h9]JlM)j[E''_]sRx}OMsd46+z oz9rrcsjYZγkϔ3 \'O+m8wyri|}- 9ɮjnoϓ}o&cRGKua*|Xy>ZZRĹ}uiǍcz#=7{9|Yyye2md:fAKИf hf)(ʬ~=^t_cK\>KԥjYIRHjQ4C:Igw񧫜x>߃ܼvtU]z&Mk^ɝg_W1[^}m{M.d[~u]^'P=w-$cR@nJbw9$\IRΕ0}N Fe07OGs:Tfy_KxgeZZ:/g e~ UeuX;7R7;tkB2&V c\3a1Hp@\5ߕ̍=g{ou.L\dJ:\XPC\Q$ݜG᲻5 fUg>7WZ|DC!vz/OE糊W~wz[Nzv6}on5?Qß:/e64 $i7:X>j-D\XxNg <.JHrK9O_OvNϨ͔X[Qy6#yKy`ίY[XDv݉ξQpp Vys %&m1Hp@\_#W"4jXt D#]4YQg\, )d=O4vȫjZ%kWҏ ؝e6kmVkH}ys<<}:BӏߦtVn߆cu4z>[f=~mI{Or_#Kjףeɫ~NEԂ4m'k)`uy|$JE/t/7>nsFn{<ח|U3~uT]9@F29E OY R$8%KFRL49v,榙JnGˇ#s3X|xsя3[PHp[ko~ī>#kzTht*~Pg=>sXG˔^|Msgʟl%ŖvqH'Ie}z,@vsԆZ^X]Mױ5ZI=:Wzu5.i q cR zy /}_^t\#`0%!( cA$m|RsOmup|R烕sO 5 NJ>U9bO}zr~~x=W?sתo= 6fU ǣ2/Q6x]i=󼗯 c3ߕ'hd5Mg^ْk?vYzn؉AQcie7wlIOICwݲ.z\vk×.d;+pDZrY˵Y㞐1lGȤ8l;zo2i<4ߘrJd.'Ms 99 J99ZJM0Ɯ}W<ꖦn݅NE6=Xlx]qk{xtϡKK<kyPq3ބi> 2T\̬4 s?z?MyS}~]ol*o_vYj V$m6Q"G¶N̳ԨzU/q}K}w3=Kz~;`]>F1M-Xs^?ͭ~ZRMQgEskxڿ:#ylsO3O[> A!wҏ#cJ“̐MWٙgmwl\p b MI-&Kg=;]ug[|}xGIg8wק< CJrl^^(=ȴ%=K4RZ['zOJYM4YJO mV +s% u !k L NzVg;b=ۥjV&yt5Uӣ{?CD#gԼǪf:W: _j%& 7!W"Z)e5cӘ*nC'OUւ6sD>ǥ$_4F77M&CR%jA;Dڿgּ >f!sz9>|`u/NGޖ%1JBQk|総Gsu$͉@! 4M(R!57*˂HZ^s;)k3mFL'm܈ CWU6jZ8236&)bvm<~e9=lM^66g-V7A66䓔Zr~XHBFO.aξDErR. EB_&ob。@Q7QrL8r) % C!vvmg9VFvK*d w/ӻ͏xocMn}tdŭE2Qh 2Ew/ N\jM5OW\ș$PѺ :-zBhRv4Gܫ΁z'@ITQOS{nEHZ9r˾?kh'y7볝C%O_ԏǪ9F£z= du[ҟfKr۷e쾍kw5(f>Oзw.5^s_K~d%;8F.^KJjGӋ#$$VGNvNXq@8NۼH&4EH?{3.C ?9!B XR@ )C%L U tjX5 K$!9s!! \5~~u93{5KÍ}~B@Fi(r1gi4Y 9p5!̞@ZtpKÖ6~Uk^ ߥ?ϼٜC0Ե #v{y|ȯYrXeCGkFYLTzV=+Z|<tU:ϫswS֙'m4nS^?goӍ$G{^]cJr5krlHrBNz{W5^vh4DKsh9!즥>k:V:N﫪+t6Hh(_?5T# ZfI%lLKnT9t%r6ɓR.M$bf_F^͗m^ MKP~ӝ?3 ;}NskKK^Wa#zIھٮԹzp:my}K<.,+AV/+8F/)VqKi~Tnɔy40qs5!KR,z/z{%Lj&gKe јw^^}Gi=5n ]Acb:z\P@_=1d8 :6F3r GHyGAk9~f]ߕ_ss#*"G^_?GLudٓ^6^wN/&uW"9 B Mh(eUiBDndn.$$ wl`LޟPY( x*^]灤HFy|ץ_;zʭdRt@h94;_ u׶4qe}WmX-ߟ^]r·Ȋ~;ްY9dVhݓ{&UEJҼg3G5a.em Z<ܛ9該0r8!g ڼfw{7RտvM>f]:X5ޖnPcP Gpљ ^J\W+fC.NjU֙Ñ0 9 R.nkK~S)|K\)Ne5t^0O{rX FԲ@m25*z^?߬򳖉k,EmS&{_ƛY`o|1շ=ɬ7KN<^ҏA͎ߓ>.dR>sW/F_= ͏_>^R-M\ ޼vѥRL@pYΌ |w@!!!9v Ou"HPb@AĞ)ZP&r,!R)+l^ݤ$P͎8X4+h<<Q 'Nh 4 >,Ɇ&>/z?gw^d#3/Bg;n/>Z6Rыڲץ:7'^'Q|M6{CaTw},bw?jf3c=חZ{ ҡ3!7!x_xB$+ApQGy$Ե]wUD(]}/̴_WpWTc5+Ͷ!p*mc}y"k R9Rv0USeemݴs5v< 4Ob߮ko:-QI7 vUcMPJOm+ `f"kƉ  zo(KE!#qԩ6g=]=p2EM}i=;]舘eϠε^[qhtgڱ!2?.y{/i=-=Zͩi6Xcԟ_}p%>~+|=mc Sו5ฉ~ɾPG#fo>4ì^C-9=O@RH l,fEF"'&HB7\R2D&f! ߘ2s>yc|ֿ+5ɠKjZ,Xs?e{Yx:r= ^| LJzooc}>M} [Ϧt*~dѣGs9z~ V>HUN9~մ>& 8|o?/4nZ)O/2@Sz9"d L>DO`.vEC~g#\ٻi !$l<^l6 Cbd X@1HHgUd xtHx;mrOE'k'G\O@p(CĕyS~6r.YکYx<~ރr^ɼ׻v|*GKg^Wd,>58'YU{Z'J8=~I~ͦC ?o/je-= jb#d>a g5t~旪ӌsTl<}-"R-=MFB|E Ziʂ\ c`8p .[2Hܓ<{y̙2 <)Ek{Yy,ޯ}RIkUes޶iVO Sa^Gpoϩu~uԤ Igb=kR^G3G2yC\~=zH GB7#Gfz f=,Փyz7(_$-3Iϑ> Bzsfi_=g֞D#0Q\e|Amr̴gM)bz k" @*jy;cv W˼YD, ?^>W͗rxcK*/$H%W;x|)hn>?ۨNm^y-Kwi{FX\_s|ytk!0Y6]tԽ班2@7g^>fn@(^8d=Zkrd~|ڀ0,f'7PKE\#TSd z^y\k"Su^ix{[Ɠhv)Klm&Z9f]ȿahM+it^|12z; N|wz{e$fg*^?bn WgvrpK'u3L G句Jp2jEOph@/LZ J&I&&(r@Yl$A690\9kKm0;8|4jxf|ߑ:W{`"! O_vjᐥv$ c|_"i=8:UsD.Upv#D1.QVk-߮! |\4*̋y$.c @`KKE!,yC4|ܠ[-?7,>i駗:=Wb5 A! PKtyw'4fxnJz&Ya;+!t*ö&תrR >OЍ3{Q]lʔ+Emn&{yWy/C]ь82,`vi}5dXƭzQ=8|٧|XV%.\Z摢j8$9z}S- s O?>`$!Gר7ǦkLD1VD $pKJd0:_>~dÁJhE2*j܂4@O&n:vW;Lɹ7{_& `XُG{?FB<z _fZ|==ť /g뇩]9^iJC=4+hY#"F~s켍_' u)bkEKSyߠ]μkt}HB3I~@)HdUO|Y@V᪍_J~rWn-9.:=!2/Kd1zG!wζmkP8"R@**ly?K̀ p|󧈄a%TFPHs) B3 ;K9#_fpKGoP =ҍ d@>*-tyDӍH 8>s¬Q1@`L " CoS!,\23lέ=BҼڔWnZʖp ZI7<5efچCo\ljrq]{F~lkͭ`MsL}SpJ AȆÃ$R 8f/k o6x޺y,<dw}qԜ$oo[\H6S(?G^92l; KRVXA Z_6 , hi2a˔rBje*hzWp֍ϳ:&SbB`.G:H$R2g&ΌS"1) H$6 B@FDpJCJ"[WJgd%40z\рRlȥ471k3.)fLk^$pUY׉ &]/zh Pʝngׯм=/Jfh˲Tij-91ov*[\OrDP19$&~!;)s*w C|^I`ᏤƱz;\F@"!/pȄ d*:uZD $FH6B0$| 1@Hp:s*: lD !SvT( 9oTĎ@2  Dy:L.Ā_=ہ#ev>B ۄLS?G9PKj[ sG S^yF[Z}b P77H[[1W@Fn0rD% $Ud8l&Yp\1 WwUv-:㮚SRk"BB!!!`!!l~Ks.J֦šmL2&|XM$e XVJҽo^2+2|jI ]kdY@.d e^2jq^-,ƵظJ~遈rG{J'W A G:tjPA V E!1pp$cT HBH%1 S^\۳Kfϗ]:i;b@[V,>Z\ҁ@fRBv}##מ-6KCrS,(,gpըM} SiilDb[3/M8F~WzNW;g'џd`-NLsϗ?z^u C4"7b+9=^Ǜ=ҷ!Y9I՞6Z6@B+}5 Fƞu2jA qbI:d,R)s8d)psd1)أ\>Dɐ6uK?.9WqVm~Rݵhtҵ] & $AGPH'r+*ɒ\&oZ]kz4D,'mSuD s&sS45v)騰QŔ?jj?Yk3s,* UzLh҆O5CaR0H8>mbYA#$cF\U`` J)Hsr)rGx9  pZyzEYUTd2ELh^w~{I($RHDR9a w9K}c% vGz+ctƥ j-N]o>zus"+޳5k7-HK)<:ϰr5*'.4' C69FLɓ K*86C XR Q[lD6^^}Z|'#prc:<jVI!tv!FbFA|cpc$ $Drz\ٲY"Q䫔Kitڛ5ߠv&զ7)Hp&ZJFEJ],$9-5` }>c]^ Ă@@!Zk5888>kƄ6cFRbSo'LXܗ2BHHDE! H-6kcK/O::@p$\9O[yzzqpJ4-wɖ+gEJR3`0"Ia# ǕUeLކkhd Mn@lrt5qTJN9pBʙ *ua! 3ӌBZ( pip,FNtS,dvPO$$8$6 A!:ceg#!Ðq\+ -&Q!yd*} ᤤ@B4a $WIH£@3Zd "ImVEVh؅BеIYe5q9LB-V>w*jMIBB@J%$:ѽLf7<9 N=@,h)hp H48R HpR @#D"cPN R9JWW6]Eie!aߟ]RԪj>\iЯLgkG7DLg m͒'ҭF8իL9](y)uq `q~ 4Y>G 40lF@FhHp(G"Tz{<AB>ahcΝ 'VIX. kACp\1  Ac`r  #1Br .}>oZ!X=OR秞ϮgQSlO|:B#x'h H$FGv`FSM 8LkHOI 01 S!! R>%@B*cVC\1LC|OX4٥c(4% `88l5!!p"` HH B@Fb`a-SE s\_)Zn'k4ʺ4z*NiE"Su7ҶA/@P@H&[YmAlСITu@:GA|D$+D8MXI"hx S!c`@$d N:9$$b |)*aVm9ɂGd R.@p,m ǎgt=gqUf|y8!@+RۂPe=!‚-1LHYzty2 NjݾUq9b ʐHrUL h ŖB2bݤ Nn>IAhvH$ 18|(@Bk.ڹp@X_ lh1g*Oa+@\!`|%X( iV^8*}8x[yy":- NqZ9A)nySC;ܱ4Ncvn]oG[Džr"2BHn\6 W$@UnD #J!HV+0"p#Fo" @~ D' ͪ,cH9K|9HO @ $\5E!I+/I\9D+. B4[K51gq!NPr9vJMUYކ|8Fm}7ݦbiijN\g**Z&q+ L YB &fQ%6XA@@H,8.N-icR |߯B% LdkFZ"Ce07-j& q#p8jC1oVt=pp3-RG`ڌVO& AHH@,L'L 8(8\SVk V*ÛjLupWH!]S#;$0+ȉn\9bz$b NCJkjPCΗki1'@#| hCjj&T\K!PZQL@K4 P) cy"T!`I\8LZ`> `Gr.F#puGlB)Jr)iJ%hU˸LT KL *ŚkI)qPU-@"aa`PʃD00%S)JB1p965p7P ÀX.%0D  v  SETHH``'Xd,i))rVWa\xHU, )U5p *R# @ @Jd) xr9ø aQ %C XSOtd D >ybQUU_q.[;B+ǒL$mM4 $(n'AXvEŻdpp(|6UqIiHip6,BW 6uBڴ:"xi F|J-\eKEgD+&O,UxA5U]:'Xjm2P(L$ i1m !B3&g]VncrRO77Ɩ>K;o&2mI< {ab0XjL5Z4 ,U4W> b\i> ֐Wy5  Ab4 vf;,ZRU-j@LU Uzz4iR`ۅE5&OkL;tty-[uA@A`he5LVlQT ,Zr X4Z$J$=iR9uS6ahxj Nuz9RCRɢf@!7΢(MRԆ̈́435jd<ލK,RA^dQ@mPE5Jf8TST!7R]sThѪ6 umpTpTө̖!BJoF\ 18I4cΘgPhٙI6i1WB1:TFD 5@BYϛz+E z̙E?5E̼.@U 3YaHָL"JkBfZ\ך}ZazhB`iұs奶kn`LTƙTi\&j<*zW6!ZgM@h5(mk-DVIm`V rmR\'p̤E,6 +ЩxSFTu^rGL4ˣ6Ƶ^dhN@88qVY֘<BFd; , 8Y%Ջ^s;Z, eઝz:j@A|V[V/֊wןMMY料K+;"`$MRMsp7^*Y- HATMr0Oj șݺVAj-4VuWa޿ .+QrK}e0;5WW]\=,:)TRۮ+}{nk=o-)v=nþN'h w\u=Mf`5͠{M[22ҭ[ .ZUګ3 W`7M,S2f{g'89%Icf)6Lfb_EF03l?0{[kcsDfVH6)[ 5$݉ԉh3rʇ7ݛ̾Sc!v C;!&ntM}Pyyf##=>Ox &5l`%,d;{O ; |9| ]e(ۧ t)Z_ȩe`+nEPVvX>ΝP4KPs7f L+a7f\~ ֱkucW.:: .]%wN)uN&mqyyfP613X,ru3 1߂Ӎֻ dJ0 uwO dr`oStFC'ռ[}jb: Lv'nq: ^&Ѷ"glW) 3'w=u 3N%wf)*\LKEDWlճuel%Dn #1 guo[2u-ܧP` ;`zx`FUY)˗K,Y˶@6]E-b s,#՝Ldcrrb#9Yawӹl'NQSvdz0n@vM-W疣@-HSpt[cU,B{^(jPdcC3faOUW6f Xc' mv6{Afac=FzVA";<ꙒXDlwg[*Φ}L{Q)@[S:umSZe6ChA3 ?l8D.JJ x;TKeirĨ$J3wjx"B`+^ĬqdfdSޫ0/f6KXkN ԓזTKm`ȣl_sj&;-{ڭ'~;Օ#j%.y|w5-(ͫĨu 'i7Gj]&έK/YgPgyf8P2!H5'iR@Ahk0b[.$N\"얁LD!|6+0#@lm c{;b3zjdWQ-+:ͨV&}@-\ֳA j\t =Δ#Wtpi0R@ mfc+`ݩWڦ[WWZϫ}dxǐ6Ȍ0؇S;k]j7naF6Z| FPCfOt$BmZ1Y.)k깉 4̫dNAXY*ft;b.AJô eN`l؛h ,L[1 q^o8q=X7ӹ3I&5X_{(_Plw 43YݏF7d@:j&^تfv>apۣז_zpl;2OjUšJ֡eRgc5pT}iZ3LiŚDTjz3Yӽli|sW\=ӱƼbخUmyi]ݳZo$bX^h=G1Bc{zj/mm Emƶ i;ج2}fGI Rib& { e-(F 7=WfoLF=)7*׈ J+ Nmo\55 [qQ=сgԃtQjZoUXDi3w*3!|GnQml0. :gTl&p 10m,;rǭ+6KT&%/aE]eÝ?gA -c^ѧeڮdӌP"ϫٶTc:'\j0WVu@^b`IbѝW|XW^Z[XYk)ZK(3OK?]  bJ=!;vi[p.Jmk,e,v+l;q2 S!ۉf`ŖYr,:|%lIZɞ! ׇjk,ȘE;vgIJwF*ҭ}EL7bzZ,+3Lo !"+״xɸXny::d؛E]P %-)f;@D܉oLo[fNeij;0ٕVeڝEUR7r3tLbD7L1>-colw|:|bͦX(LP#fytXJz6FbA=j&@y i[u̝>#}]@Qb8P eHF՝~cٯ֬ΙGk-(^^a~'+J`E 6 #w[7MAl,I^V:.Up zW`'۸i]>}T'H'шԸ5weL M0؄ud7[ڇg^ X΁Ub۾C[XYk΍ɼmle00cٵ L͙t0aiUg!J=ӴșKmg!<;Wu.c}JuKe66j-]Z o;}yϧ P&vNVI6%:%vJ0A+.>a&&_H)kdWmFoef`Ybg2˻{U!dtQ*7 x=İF9?fyǑLxR5iS~EUuq" ikLLF㑂Jf:}}Z OcDDQgy;kӎ,@0f' ͍iB O~<ə{̙2e>f}6ӪhI&6T|1V. vOi|8SKG vcB/zTwNK-N6zLCa}h1B2+2סcKZ@911 -ӫ)ѐio3309-( S_Si%ޚv%0byxgykk gITͦbb`ͳl6@DYyi3nD(LgQ մ]n ,$3aӰb̭ `YN%S-߰f<Ԍ'U@l+34-TǩFܧ-]~pΓв}=鬟Kd['>æ}-}Q5<0fϦá! N#l"MX 7T~}i@+,q2]b&9/l̎/ꭣKmC_jpݡWPYjrtXn FҚE4Oz}*HvgeT⍻VXiQL_j-k/ê٦'9VܶRwUf%\U/A"G>KOg8yw }nɪ'" B};jGl>Q Xm > uSL _[Ud79—39s iRiS[5VWoVe2٦VٷW--l5>ə[L[:6e^Hf8AۄwpT.QYZ ZVsm5Ηߩ &Yd/|&&ƟEtj]a5Xx׸Ls j'D=h}Y&4abE2~=啪?h&{L2""dsmCwZjCmJմmE 鮟KtKg_>wå}-+%mDHYpY&^ &^)b=sL< ŶcBJhƉ]>NtGwEJSlW,>}:MEMaE YIXb&o[U{tZԬ-`(*'S_ƚfjVjI{tt7msPVXڪO KZTb*5hUUH> Q]T0_ߙ8Rg+Xo7?eݭӌgeo!! b&-4>v}(Fh䃷?F1c\~bUcZbCPfP4 tf2p/u\ ;*AA>~Ϟ_v[9|si|sW4so|G=`kht,s9d8+4ZJܡ:lXw.,75]LUӫV{ki0g˵[4:nVG'Wn%6hzlJY/yh 4|Wv'ELb[njX{2fN4^=xWo&g1&n՘1730Yߧ)^R3h[#QsXKնULiآ5}gy7_r5_?>32&fDț7 Ypp&n׾0.s4zӧ}UiIDORL/}ѹ5I&(i^2қEk@KNz_z_Q.-hk::Uym+'LJ)YYf & 6 J&<ى۝VvսxEf*z++^F:yQ4\JttqgKWfmWHSۍ5,plA6E牎NNޯc9a>>`fPߓA~Γմ7#UWJAwSL&'Q5jjt j붷:WMGLKs.vzéZ9U61OKmn5U# %x]o:}b~oOڐry91bjM6>z~Z@Xt %,X1Od{k|u~.~9>3ؾ9nmN&79U/ 8x4u;f[}hqQ7rӾƥï$8Н{_7+\34/ݶl(!30vu!;UI$ħV~Dw/|sGg?L>yMymC,M~Vg:kveuګJҚ.֦zm8Kg_:vͱ*ۇӹ,0 =mqםzsS6H|vHp9P{qzwTGz}ZZg`O}uF.7&FyrՕk(M뎢NAbJlT~;K%Ӈp6֊:da=+wuMAm[[N!QoRfq.ĒPzRd} fek:].|_SL=S+׻Kmݎv]f.H:lJ&&!첶j}ZnQN22](ӐN?a5wV3øG}>n>(%+Uێu ^fXk3&wzw=yc7ع8g1F\ b}: YYYQ4j:~W&yiih/;WYAbkj3u5l=qb[.b{'}IJ& zS_OTm~N`|>~N?.1ΊNΒNU]F:7ʜ6?顠[Y>ZmW5;PWJdM++U6!o%_p=8z:=h`ѶӮRUF[Ef)AZ?4m6r#9voKHxq1[m|ro-?&5:#Q?7#jj5585 `à hc;| ~+y}Z>l{CCFՆBj'Pxቶu;_l_kS5^~VGIe*-+R4GOdA+?PٺtJeevmcCgrįmA, W~^??soo=2OpZ8\zɽwSG[Xliedt]&'EL$*aHہ{A]?3)Y[nLTxb[ĽMlZѝ-<4&&\M|@޼ffy/~uojOdkg[oykR"iJ׳Ti$3AhX;^j45wϪj{O֎ 64!hv&k!6B11 c=!-l+E;iaVEB;4kB:I"©SCQp١ӪUIcmUX-Ƭ(¨۟gUP%^]m4?Zsn>N*}:/31>}4wA>N瑚)_o-}6r^8^/rgoZa ӲtZ@sйB!Мؓ"ޙSNpxu4p2?*C9|W̳s>y&QD׊庇Tֵ:DX;BC\50$kQ[Vq2hMs<{.szow4K x7΢A:VYė'm}u.c4ˊijfʗ4hIi٪SǢU,ѭƚdL㕐 OhbNfa0ۙ4tnX?g!9Πsoo*jڎFjj%Vgn6|3U=a j)Xx{,a3L.j/ ,fi InfU;Im ѩ /;.ؾ4+ƫ٫ќ\=S/ְ^IVƛ,h+bi+pԬpʵ+uMMI??{\3ûS˅_ob`*5ڎ95ښxioƟ'xx tg`9 g=OScvr>9MZG WY^-16#RĘșh&.rirbg7v߶V\щ7ݑ2&D,&7 fyrCu/bI׽j4k mi= k*/f߲;A10Ifn馳2Kf1+d *_~ΧpenV$ҜFkjh}?䇩m童Í?$mв*X׵U-=Si6aRf6m}) ܫd6w11zYrާ4` 7l`D~˴FX6kֳbL |b|􀽩5UJ"#Kfg3GshN4)|!ߢc'AbZ"::1I||V;iyÏ_׫~8=D6ՠe1 m',E/}F3AWNo=ZWɽjNkt|o.4Uc_zKv9L`-y&;޺iȋx>Pnp05mK[qonGS6u?PA'xi΁<5/yé%asH2S)&FD|DcUIcaznT9 C44n|>R{Y+F UL>߳x\(oF"Dvo&9޼[><.{17sWG`9>9o<5-!A2t٩;Rቺ>>OzkQlYT(wz=e~~VTKutw}{\ E.1Wrn|NMSh69_abܸ}Ahg^ٶb0؈fJ-!Wa'}P5W6+汄>㑟Wt&XLcOs16 [\QfS1n6j36"uώmlAV#>tӍi߻gfp_fDȊDȚf.m'ؑ8|phoIJ܎:3n56]pNc΢)^1)vofe-{>j =It9%WFIi̝یXfFt,޿+7B~7oH&dj4iv=2KRkA^z=Өz4MXuOE[LAsXynh ' C+ ?gxNQ0 Zb?t {&&&&2&#/`&&LOd>Cw8)#`Mb`MGE0:zׁx&@a)9k76If"Z+ܵ6Y} ?^CRJoXӇ90/dn*sr?G45DXy7s N1o kX8*`uurmS[Y4qX"MSy|}@}.L,z0bffru_/9|s9|;ne'm]p>o/!|s^H}|u7ifyͿu^c38jL(Lm0 ϩt>4ͺ!#{S_^JBY0ӵUih|t'Ɩ̼#D"xDrY ewSq*pC|5{=czi\RV:7D3{ MrOš-mZFr?glV3Y4Jk)g?c8j9-H%QpFk{OZ)`'t,&,!44zbfgKOe> ɡ8 s$a=mk޷י cq*ktiΓndLۄ&f&5)7M: K(tTI´m=aMq-Ķ;Nviiq>=KgfԈfcA1 M1FTzt:Rtfˣixm>?$HwO\ntM@"z7̾oX'(G8 g4 Mɋ-XCX;xoq?O+=e/G К{֥ΎMxY(4Wmƭ9zoIm|fqˇjWZWen0a#}~ǎZp qDxҮ(%ZWQWUJf{]Z\:ZGrf{ta}f9ρ'y]xjOgmڹ-9_o1 aVO\QRimPlb8b;/$M/ tZ,2 T9I]FFzxӛGM1Z|y/ mch(;Z4;ʰWWVv;{yAڶ֚5V]4w,SME*Һ08u` *(M=JĚѻ@Gqc-[}\?n ^O0gIjxz[/ӽONƗR5j*3`%[U߰aWx v-A/46Yx1[9:H,ۚnhぜs\;W1@w;?fmӌ-=Pfzzݻ=s=њ~XwXKt47;b &Ѷ/G-lC$\=64O_v:8L<:$bx} Z^k+twL4@+醦Ysg~{#=Na6Zz  gN"hi 6M:Gm2g~Y3&:ڍgMz[.SOLD#+d<" UX:A nn{rGk㖡WXui#/%jX~Iץʎ꾎%WbyYF5^y]o<=Ӌ*]璘b"\g uy}bC[.ZyWRoCVUՇ;ne8}4 ӳ6ޖXR}5 M'ݨ[]߃]֟LUTdgݮ``<=φSە߱|s_ 3m^2'٥a# 3Xn`Eyw:5vm=[U~uk`Q56i[qO,ӟWy3Ә?Fh*D_o +#NfZAmimծш]GqO8gh̫yYbyţ~;o&Ax=ixk8U{ƳOy 6tzb SX6~?<O*,UMa#ĭk4ޢ gS[Tjj,5YE~CԺT6h٤_D"0{zSRؕU%GgSSMbڿem;tmi><1F ?SnÛ5UgQcX::7z77ffjj)̴̰4t3t|7]mC7]wH3tb| M޾/iM{*nv+l9ɛoii7v:;͈&e[T66icqҫ 4Tj. LKnl-+ҊOɛ #5E-@& ;3?x;rϞZ4|v8r⨾k~o4]oǣy~w{ī}IVh_~gnIG NiGR8pxWlTme8|F 11y=>ϞN2,B#FӍ8EOo}g ŇӶr.t-1߳߭f9e^㖜N3_FnH6c#;x_{juZ{:s(\ik> 9Ŀg?r9<791]_ cb_WBl2vA>ӈxs?_=xݡG%Ͽe߬J;UnJ}Iƫ&xKb,?e 4~Ύ8g Vq à&T~dpFGRomu[ϟ}qe4#x%̨37 Jϧ3"dỎ23y .Vy1kO}(͠E#nDțB&fff}{&C,zxs.NNFe}'mF u>u3NNWzm *]\ ֯wZukju 4秬.M7$֕_[(\W4)@CktmӨkJ׭H`}:[Kd޲EuUnlm1SDW޳z7 )L=;+r$g#%Y+sۤ?{oQ:7yj\3"vlY@f&& 6b{(&C xÛc6 ,M6, mXgjŠFޞp&6P:t?]`u6,سoؓb 7m)Uq0'hTFX٭|`LL X0L @Qǿ?PN4u~`T L |rn5G k]=j[t/`梁Su{4WR=rUTEGރIzԞf%i{Q5Zۨa ,rٻlc9h#K\ʿo5>y6.6y|#d֘ ܗW9'obU18Q]YpvUY`[_ݵٿY~o/'8<_w1|<\Lr5Lh_${no[vrXfĆti?o{ifjYSVh\?_v-uzʺZ3)oRD9i{/uu{s+k)ҥp&ծEsé]R_96b[୷Yro/lQ=sfe'N IVEӛ&أնmf&ٴMXY[N&٥HVq#fIMl 16ͳ;D&6 tNtⵯ#bTYOMB#DN}8UMJoUCBʫt:{}=-RՍbtҿ6Eu!g;6?!,_֛ۇՒ,;6Uh>3`3.5&ݢ}|ڏ~ As?wk@kmN'W3﮹צ]^A*-{7 Y>uijƇq伛a:ϩAVڛyӨd\ ZOC5:e"1}b^f~iFͤ۟3lޭnlΗiΜ`onu}}gKud5cJm5}`盬a+K] J>fG/n057m&7jmee?~`|,嶱Dg=37oXC63CK[ ~όњ,NhӪK4G=X_ ._ΖLGc䈠~KK_ŸvTw;HcEbumO[*;>DӜ%~9:L}{u&+E1},{|so͖*vͳd5i P3IuXcM`i+PѸb썢G똕eՕ?Lm7]HmhgOgKPDͳZuU]5LWwu]e:Q\ 7O7G>th-YYtwmï*`z< rѺ"UeXKSKK1 M6A f0lM?~0( ==Gg?9}6MTeatV2mL}8yzieCR6&&%edl2ڵ+raڶdnZb͟{{ῧ]^<,SMwk4O.*STԎ>Ww5,Q<>@J7EIϙf3lBd\o̶̼2/2wT;zx-74fmsرuTu3';f[ӾWn)ŹCbn?pl:,|άNPAYT *֢5²Ř 'S.WGM W?GS'FB VRkj]h5Lz }ESYT^\54wQGf] R5]dtduj@M*"`'Q+De gQfa20;5 gC~1x0~f`㚜8Nm9_o5W u,ǃ_gZY\3>G-E+y473zk VՈu7LjTr܏Vp \GN2)5:p쿞͔M{*U|Mb/?!̦]@ Gmx+6{Lsg{yz6(1g$~TiV&m*D3v cm6:{@Tl^v LcY#jzu1a?38~bǁg\s##T/\]qźf%KR/ؾLp,ԱzX3*(κq-0"_?Wfcx_MGmGh 5uc'Aɼog5E49 hqӋŒiԽrBXG=]hdAvr LB LB{'ASJWbs;k&~9?:Xco sMn3w:y7#t$юWt7Ll/PM8t& Ʊ}y$Yk^ΪDwՏ6ϨC=@ Ma0w$ՙM1p!awmpn\4 !oϑiQ9j@؋*~~}_g9j䲊)_^{MBKW =Zqo''6`FX>FG/ Fв9g3Wgu :I^3m)n'6E꾵:{lLt[Jt<=7111LJH$zSOIMLϥ.=%Vm(CѮ 5 J>%*(\&4F.aW%RY@tDD:43+Ӹ䔁&ٲlfmLMךּQC#b`Ɠ?y{K˿h?w3c߲~G%\-vW.pXtؐCFohp5[_ifr C!RlffaEbn7?{ {jB3t̰fbU^p;A=g-ͽͽ:?U{T:g%:\-eDw ʃ81P,d MG~9y!ܢWS噩xf-/nujПOGEcs sAʿo*{gOo/Nw?kUcx䱥Q`gQaq&:wNiYM1w. az㨓ze۹f7L33|E dtd0m2fɼ( o:l۲weҪs]:%Yj[J(Tù{bP l@/1da뚓3C8ωw C3-;Jj,\FnXoG |r_toY)h/so<3{G@6M6,E\mb@l Mc䃠%if Ox9ɷ ;ND]Jt֬~s*Ū)s5Y" V5 3J(g)K(L'ӈ(ekf}<O) "3n1yӲ[]>Νd5 KfҖk֘]Q/%=m~?4ߴ&l vmeM`7<͌nyf/gۢ?D{˴T&]2yMvӞ'}h;_ͽ s3Ĭu\4X*?]uK3VH6Ozrgv{ٯ9[j=J) C -43 :zO0#&f8g|fWHw\}Vxw-өg49n19j?OiV"g 5?nUfZyvo%33ZП& >7Nh){>" MnoYM&D% &f 2ADycMRSӤMsRIР뮞g{9`AWL`+ ۞iȹ )wDyωo< i} <37@}[y h|r˧=oSK`Lj‹6*`AZJ)fՆ1+"7l` :͋6rf;mfdgM*J5[%OPB:mX>&fۥݤ_1t#Q]F:i~J6%+oP f֘MoK67m^&U u~YDg\l[,3t)Զou,<k /3 5u[ķmlδƿ,uP"Z>g]gXgb`(7`A7@nLCnGUgUgYe.#cSWpO~U9)6AgWW,3QG3vf;Tߔ4-Y]yتZj*OLcꝡ1|w p|g=;h+f{X?4^VƯ742syf3&Xi1YS䆙31B,7Ze~۳j Xx>ܓ,c{t3&:.D}jtzVUb9hYS=@,iݒ:}1pPR.eԮ6Θ¿LQV%m{!zMCn f!՜f-v6dng*1[quGi5 +齀7,J;P;/VמS骖PkZWu`4±XKI'B٦c2[l߃S+Jԝ=dݻM)%WNlT,`/ILfk4%7F[{X/0{te` kt{,l3:M:&&&B[Z#4ȋ`PBP{i_p [ 㮸cSYT4jt&RޟOJ&;bnFS^u:zd[@T"I54L9/!hV`}r4c]Nt0<0Nz/fy uSڶΟPxK.8P^ݞib X{.o{eDǔz33Q]c3#!O2j@=.mdq7b5QWV_WHAQs^rtuPޗe'm]zvSEu U%COpuTӪ`rPlfeDe8b _3W =M?Oѹk.GX5ʷ4!͕P,5 :۷L9xwiV-6K4ak{PW[+ӦEө5**ikUP+{b7Qr0vSJ˨gQ^Wj ;l)j='{.,H2KYGvǐMʲ^[mnJ6NԱ3n bVIy왝yn `%wn)RKlQ&b~FLNѪpdZJ0rOL#5~r=0uJi}8-Y(mcrvbQqutY{wu}tGu0z)ZK%i>Y_FEU9YuEkUeںs165 99jYp 2l^ z]պVJFDkWYhMuHHfuOI11 v95dVnF0#ޤ ]w[nEB=cj-&1ʎҸr mk,Zvg :63bA$0ІYڔm@jѪXv^WMSdn5Z8QN%ge|L=W}fuq,u M(iB,zӹ@JwY_ZۢJ}^h89 sUbSf/׹Sd;T20, 8jV{&eTVIɕ+@^ʕ-=n@989U> j"h+RЩS]/b0>Oً݇hSq vnlvٸl_"T6NJF`ej=6G ?|Tnj6JxbZ^h;M9-:5gj%ELp31`4m"4]  ⧬ &L+llfaBL{@zJcezj_^l]SmzضF+h)fE97c gU0kek9ZvTO.ʝBM`wդE;f&"ZXFjdX6ϦjnZKCa-U01O&+oİEQݽ*&tg}& rf-f;h2jOCrH,~+muTڠCTZ|jMvU- n)]+Ik.|C \s ·3 JlO5.7vV XkAi Z n|w*jjlӥYڪa^҉5Hi0M6Uy=`,LwlAt@\Yع֗驲[F"9"xVQIίIe&. 9"[ܔ",jެOiSu"W|v5{ ]FfteNCxԜ^L'VGҝY& t)d{K^ 8eIJԡKKBVjT@:]쭌ų$M3` gހa+E(5,ʵȞZ`BkXhfx:0vz%R2(Tm,~ݥ}]7J%cnXjW oP-m .P XQ_Nhܪ6s'w5fLޢYԮk۸ -*`2 BM [ok)cЙn3Oi,LPL"6`5=tiVV-)WILy?zmPSMd{UN,sS|?N}E5WmE(lѣS. u%MQ_FR,«Otf}N\reX 1K\&j*Nؤ-UBIJZųdԹUb[  +4;j[tP;"m(ʻUl͘s:r*M)meޢf-tӱ slcvi͢-{cԈVxI.tZ`J%ev)Ri4 +7F&H^,_@~u5ud5 kۻq.DZb[^@&[jcKoh{5`V, \NtbQ;So#Z5cY~0fɆ~ ٜJ31{)f{rWe@G{EUGЍX6] .5!욁2C xMB%]-`Uu嶳E@ eK-ɦ&s;ED͕aV魟2+]@EZCr $+qm骖iNd5܁]X@ia]@&xh\4]/Pii%h|Zm .Nť&٪Х%MY4f eσ᜙]VҦjR 21ؤ 0q+58~Lj. d`~t0O[1-[P0.pi7-wc1Whzj4څ7 Y:UЁ-졻",%ʱnԯs1H8+Cwۥ!4fQ0Ӂj?ml%j8q(e3VɷC)&H 8'1f&L働~\XhXEׂ Hl6" &;jomںvRݕKwR{qFr7@U&٪}q)Lb肥FYŏ%3-!CZ!rv<):L"F6e\2!=H(Kmikר%P\.NV.Q=Nj=CѲEmBE+DOT`©٫D@̑_pgT@9jR#n"5l)xkUJA<[J ;+%RM=40gNj\ڲfp/}aIgU.f};3zzݦծM:nfnVbZE;Ȅca#1=0\"/*sqCMEoPɮڍ-jtM]fԸU[C'~Oį-(9oy}MbtKҪY r4ANAZl]FmDUfm.]kX":i_(sm&&U7=T*}OA1]tX&Etli6fgvull*]BHtp6Cqq3WiZn31;yjW}zJ Ma4Tjކ/]~ޙfYU~wUMKw 4ՌomMl˘ṅSypݩJ)9$RVgxf,4fX{ӈ[zY{Q(bڔ =yy&uZYnΧgQu m`v5 (nh;QVF{u5+ק+JTrނR`7jTWӦd*(+v'Jp~- !01A@Q"2BPa#qR?>! GN2k5XZ}ָ'Fƫ_d(#V^(Ŧ>I+5\뎛_Mp!QEb?~"iD_OQD[ExRb;F/(Ye$^,I4QEQY(QER(1N*(rbH%.MndtQX_C|)⍥J6fDžQXD(m6 P:)*?b]⍱6܄lMDެ;qrF|tMm4F?YeichPbґHY#bCZ) ,Mv""/X7.7Xxe{,ewʕfӡRԗX#6_r^wf)yKYeუɛ} v9Ѽ&,7,i6%C#6tIWYQ#^/RCqW[_bJHC(DG,%=᮱ M ] x+J=FH{/ ( $K]liʍA-G,y+"Rڬ.vKNIU91FY$DȾFȎi )Q3dd?96n6/|+6Y^ gFԢR^%w2$CBxC)vYn…"/4m+cJ/TR%QJ>V4Dp=ӷ%Cg86El=b_eB_Q |hHXw+4U(4V+) x)7+;cH)'!_Ht76").٩4#Nj&z ѩe h܍ȴonǮ>QE22SܸE y ,x/YEfZ7YeŗYrLh/^ -]rCȭ~?D@ j|dFlsf[-  'C:e'Cw,^,ѹѼzFvJt}d_s7>R,^lrHmGDm+K ׁ^Mͧee%Re>>>ig}7Qfš#d}3Geɖ^/r%\Pm6p&zG(~$wO2bwzjh,'zd|i9F!2?FWŠ(HK7DQ\J+/er{71zDG=i2~4[LDGYer7#z>gaŚIiEQH*::::>//XD./4׆42ipn9,,Xt_yFX,vvvS6iX5w%=wt}}fFNȲ#VJU*(}_slZFʚ#bf8/ }Kb(QEPE#}Qf,e&Y]2r(+^n;B\k>~,‘ez՞Tz3Y9O5nr}||?8,,m+8epR?#h"OD`/4k>Y|:(Ep*5C,Zt S@Un<,2 @g_dc'77o pXHK-MF,/XHcM>~ZxY~+О%x6JR=ToC^#Z}rE|E'q!a8 4h^E pyJ4/xmfiW^l,eB\ʉT6Ye𱱬z;;Ξ%||o9)8Kj/šClfix|vYeYbb,2PeQvP+4QCEQEV(j)x,Ye(l^7"$Q%BqbxĽ%K9jn>V}qfjcb&.ҳEy,Ix~"" 9t?Y65Ll,#ᢊ(҆ce,^L*!+e j(1%5.><7)mV|h9=n1ioMYbѮ l?ȋ/z\||?C,Yg~=QExh5=Q\c+^FXpdpn"8oTºGvƆQ੟*6C[jFv|vMhōO|N͔^"+9 ^e1x! &,}[UXB^I.&Ύ*<"Y'|5=(~4"\hm6k3jhՏAR>L=Hbĕ@l,qtR 'CE6IablEW$PE4DC^_ <<,.),l,a&}ڰB{qrR_.Kɋάk^go/ -ODU /<2R ̕H| #Pb6"] .6_n?dj9Rp>>EB'ؠ(a51hbQ+<.Tm+BEq< |#,%ydS]p|k!x(\eQ5P>OɯaGGE]ڙ\lZ-R YGJP!VĨB$ŋ/ [s?|W)ppme{%忣s7>~/Ȉ,$,73s7|/=+hL·X-{//й2V$9e,!bXfŒa΄%g!РmE5 t:::::::::\,_B|Zj6G/?KQ~ZRFGFn(m+ %$nc",Yc$,eaÑeD|f͐y_>H&,,^OXqxBeI2,/x"!"a+Z,Yxjz~~p#H[;(W2y\Y|"9eJ2+ Q,98x"!q\X72zǣi8MǢ((yvmf};x\fZ- /4(QEr|+.<"(i%X|7B%,'šxc?x~p\k(y7Ɨ./jXEqXX܇3{.#͕x͖Y'on,Z,oC(Q5\D4Q1e+ ɒ?bఘcdSbaG}HqiƯQ1 -2Ñ~'sBS6U,~d!XPG7~ _o-(mśB͖^%OY>68(lLzEaP\ Yo;n7y2F\V\qBrd XEQE ]XEX("(H]Bo%($%bQXbnHލdO$5epŗDexLEV(bX b#ū8ez,#fne[-t^W5ؕfO a"2&4x\ر5脜2CćEfq>iwlo/%:*#fRVWv'c(\b|bdfn,T._$""5z/,@y. 1z"^ȪBUu+xdz,Z?GBDXA4HLa&'xv/PEFT'xRr7XxHrV^ _xp\4D]~*兖!VFō4$,Etix"n;-p= ȉXx%!藆ŖYe&YeYb4)=>r%~}+b#5bTK¬J9,cC/e e7C:NzExqำKbX|X%6w~2Ňj,%,"e$(EYZ>%ᔨqfE~X~j |&nGB'2!HB &YdU*^J5Y>/z~Wa2/?Yx5q<. mk\_@\cxY,Z輿xV6XM wC]+&1pdq5XK n,Ģ(EQFiIe"(t&'u\7gTЎИC4>G+ _F6%YW77"jcO(#6i(% bYX2&_EН 9#y\{"!4{bUBK66_؈. G(+Õ6?xYT]"'Q%?Z/(!|&p#iBECEb=s\V6,,fH%?d^bX7EtBW·YdpKhnP3ynd5d ])X< R5>sMy{, #(D6pD|ƣ ;(+W*"WV%G͍>znWlfp^MOX^ǕyMZ|ڱE|+QEI(k!bYcxm)ӗ/. +*y 5D\.n?Wp,,Ye'XXxBJaX~f$SXD]e]dE2(*i/ Oϑ  S"a^MOE21Yͥ2(iQCEQECX~Y}Xcx^,o- ~_-qKum_vP_%ͪRO7"GDEnC>}=Foe>zo]f(((HXhѸQ\FYcfÆAa;EX,,MleQi(ۯ(pK߅1 {yX(Ye >2?!^()#B5=*v"/,}/QEQ\kw4֚|j[4qRB"|߆$>╕X딝5سEb8Z~Yyym">,6Yy(#QiVAXYB#I͖n/>OR%GwFĸMtǘb(|E?5u(b!¸_ (b,,ZOSQ2sZp|Y}|_oGYĻ5h]K\Lqd E,[dZ,k~TxAd3Cct(KFҎѸL~-6Y[,/YȻ/X!Y,QEQ/d#GDua?G̶64ϏmW/TW ~i] uEqz7,nE= (ۈEKqfkѯz4=>ĩa- f>F3h,%((3_O)WhVP1V-O^+ |t~iI"QE捦Ҳ.'xn%4zP/x^~(Ņ㢊+c[4C> NzB;"D"?ظDyDxdn'ēXh(eez_WFμ/ 6,,YfqW/B>JVnC~45 ԕБcvz"#B4/ f\k2ym6IJ|5e-[.YGEn,Xe6YeYe}neEgٹqeG[gd ?k5~ ?1"%,EfȻ r?g$4$Q(&M\.e|=u,7f=BHn/u4Ƭ}<",Q\(eXCt/6viKto- Hc^/! q"zce7vV[E OYs/e8>eaafŖZV(6m6Mb$*5aT)"G•ĝD.(ʍY|߃Й/xaf,2#YceɼV᧤?Éo2~x!rR(lq=QM2/C^H4S)bl"GEfe^Z,VY|locV%5jYf5t:Z-GXe:GGGB:B:;QmfmeHcfҹjhFd?gӤ/qn,^e G5b|~LPe2L:O])"y^>pXXq_hKT9canEwQP%_NW69rG[`&^5KE\?< yٹټ܋,6nfo7JTn7W(ٰm#ŖY5:,u<'oBΔwI"125-GpO7yZR}}ײ?^b> xn/ɩxX y~ȼ/ ,Y.K:JGGGGGGBGGGGGXgGBB Dؿj|xKfײEq܅ס+x$WEV(mm6Jghn7,b"eY|QexO-BeeFcFo*~p/ )1A _?ܲ_ lWK ,кW+,~J[Y|:+:F]x_ݗB58,,AQ%YاB떻1} QauE s|!>~y~ Sj|u5㦍]W7lyNgO/X>ŊB_,n7qcR=OmfmetQFҊ(QPLS(S(+C8t)z,mI5Cdy횼(5C__"_؟'/ ,[,˗\`KHCes3s#"إfq#p>#qm Fqn7ee"E΋F- gGGCu澫⎋x~y>㦭WhdtGQ1Ʊ:F̲</x4D6⬢~e฿/pp\|ίѭ=}zf1RD&~ + _#o]zEQQE QBCcY|: #MԍI7.M,^C%z,oƬ^e~p\_|Љx7\VhD|o Ye[-YlefqeYe)4}e&Ye,6n7,YjE?Ը8.%ĸGCPhiQHt*)CH(Q((h(QO"Y(I#߳ae?./~p^{~aAG_?~Ž1"<>+e?\?8.>(Z|?0cW ¸,5ch]eGK+(k EVರ[6>+ѓ잛Y ,lB6JD`W͆oY#!ʍ .Ϩ(\! "9fJ'/C肤~n,e,Fᑃf}iHGH? /*-e"Q~B(ǧGGGNϤ̈́ qK?B8a=YUXmfmbE2F52Q(|_XBgEtW9 ퟥuq\_XLՏV>3h>COÖ:)ࢲXq1ܾCxIHr8dcf}da\(ĕL̶[-enfqgfdvYeXjEX6F_fpmRqТ4ꍌϭpcb)b_+PfC^V_'plk4PMZŦKl 684$AwLPPʹ$/>ʴ1!eF=m+ 60ڊ+J+4&,1 xX#aI4^_/|=oEQSXupx\bK'2rze,ˆCe:7>/,>Kn-|6VWk5>_ 6l+jBx\,Q-b.YjPWE!ġŚ1(s|(."&nEafyj#6Y|ӡ# txmfRJ1@QH(/) VV=wd\ruq_e_ƈX#qٹƍzC%iju,w cb: Y~r㧄Gd|o+of!nWʼn\/DBaw^ wT>W\QiWK6Yx(qo4<< c#$JWƊ^+xPI^fopբEb*>w<.I¼ X\+Yk /)~$+;7lJlhO V,qEƸ'Xx\hebCX^D_^V}=Ѽn~$'272|h|,E>/F+fҸ+5(yub߃XF9U,,>ia𢼵 S)YeyHeBn#l9h΅.^$<(4VWQ\o1VUaS|>r7&,&_|k- pEhS73s&cӬQx(ЗpCc~6"+Άg9421qxCE4!\^/+yY^a+oBXhXXq>kbEx+p,,Pv,qG 2! 1"A02@QaBP#3R`Cq?m"Q_Yvy6|~ FWeEE>((/Fy+KҵqcEk[Yz=h%ؘKK^ G} /ee_]?'qeiE"ё3q'zR,(l,7ŖYeY"M.(_CvE/\j_ezm Pظ- "7"OKַzy짫eprr{NOqLͥMQh'(^{>KWeQF҄+WeѢ(w7i"Dzzz&e(HciZHZ6MJ6Wh(QzQ]K* jZa(Hxի6?w:m(Q6QEiEDHl6hQi.ZF$KK7=eȖ(<ǐ (KJKOeX]fEsaȦ z7.+ZSL'QFHڄ88e#'iemKTQEZP/4N57ɼN䯡Ek]ХzY6")-/[,NQ6V]WQ].9,EMi{hD_eiZ֖YLQCBzm+Z(\S,z-ݛHȺn7Yn72lYKѭV3o<Vm+k7EG,V12#Ζ)$d^bj.Mkz-zdfQEm((o71s6'Bz1U%BWg/[Z}l 䢅h;*_~JF֊+ZG"C|zYS,E,|^t>Kѡ=ov9._N=b(j(e(H(eS:U%R7 VX՛Dm1!7#F,^^t+Iˁ=]]բtHQFbtHZ)m6 r.҅wVqZ$-le} sw:%\:eXCZ"E1=,hKW裥fL92VCySIQ].b4PM<=/ݭj+пZhLJb/-69܈Bō ߁m+GZ}\$2+|-B%$-ӦEzDr|;#ZP#lJ hQcEvLmeͬShWYt]u*ZW| EPF"neޒ%!P9#QL6QFhڏz2eL"!(ءgm(HEοC,dsDJэE l*gy3{#6zٽ%Ւǣ,J"祲/e]Q4m6Q ?l#9HqF=3hI?E!aDTnm< w*bE6²2tp(iZ9DM?=h\ExC1. >IvI%b~F,n7=ԏ=h~Z'Չ"oGEIb??< D"zѾ?|Cx=E:i\Fi<>O] wRkh;!&zSk&؛ue2ȬdYɀON%r6M%QŲ`$u9T=)moC&I:L=6IdPdEܹ"\/DV$t߻N3$|bWĖQCX6o79{7cz2m=C?%D$d)Q^Ҏ-:Y g$9jΣ BDdQ(i!ͣ 1M ˋi&(:洽z̴0%l$"V4`JSf^N Y"ty脆<>XȽ7s#Q#WC4g[ D]L'Өc0<;ckKΝL-A=4m-ьxpzlVJ[,#{=FFV'٧VshL9w:[e"˒G'PC3V82R\TwceE#&M$2)>'B:yv;Dn^gYm\ڒ:,q,%^FMQ,ힺ^M|:߉P\=¹42OߧP31r%KΝWBI2ج(hk61G|IER8=q+OH$-7?80mL|Nx*ݦus#B"}-y6ĨO/nN%$"qeTl|D9Liy#M7F|F r&g؝>_qoguH6nfnc7F)2=F,;0eެ2&G,qhElQu̱Ft_U(Q?IYJHd!fԑzDq(B4G6Y,o73se112efyS#8_M{IkIG9y#y?K!Ҽr.tV5|cZ:uНvBT9K|Lp I̸Ƈk䴼2ԈN͋y(я>AzYbuYvƆ.mY(֑FQd.E:UHFoKk(8qdYf#(s'dGd6zr!U1[Iu)6nf?䞝g"J6Jwz"%kh("O~LXTc,IPt>!L}Ki3nΧ()J<ɏIGr:mf Yuxw-ȣU1;::d:.8zf|>=ˤu7NtEQ5Gc##yΗ&Q-:[bRVMRֈszr%Se&>ΝDC2HnFeZc^y\bF҈?o&n7g'%2ll" 5&L#:n}+s%o:IC%2vd8dv̟ѯi<2UgQӐ({YGN̂)FVr:B;UVJzrtxdؒ84=1QEDtVf?賢cVʒ"rebh͊E=)9V1͖bRI"Fi]eN{HnGR=&.|= _= _t|G???gz36I;0d♹M/3[Y9YKzr9dHLn:[$=EVcQ?kf\~Gl=s!*R:|_7J̲Slڿf#ȼ==IM:N$ !? V9巧Mdśo3>Li=qseǁ>9'XҐچ?>yOTTsluWТZKY,2(*bN|Gi#]$#c1]^,y%yYf<_&uq92=Gy:[d%P.m:vx#Ɠ3f8o R^բF$t$#ͦeX. "zY?Ȯ&U&/gS/0!cfot j}zo\ص^LC?ш k_uE#y&ٗY7$a-LfIQd}Ng\#֯""]5GT̏S?(S&D_dDGHZO%麫U"ifI2H-2p6f {{&aQdEٹz2,0t%2q*䏓3|?i1hz:C*g6CG7 tE=Ө\0b=:KD##Hzl׽YNb|;!GݧHi9r9-)ϖz>+tGV)$Q3ƵZV:n1`dp Ǒg?t/bJ$er,O@hK=0}^eɑ9(MpG?IJ:c\鸘f;VDb,OHw*d}kd"t$Q, JGj &XG/t~Y8GSjD*~;Kɓ b|{L]KZ:#Cv2Yc j<rغIV>C<3Kr2yd:hD&N/ɞR$,S$Q<2O=@,BebżJ a~яz,ǎS!GIMG<(%?\4Dn[!{uK$|iq#yIzg&Gddz)|_"8!lܒJO 䗒}R_iWX/?T-勪fzu=֏ƭB5OIIGgߥ8Cl&F,qRFH8>Z?d-bǹT%&d:sgCRF-:(АFiID3 Wd栭rO"7 $mQ_ v{m l.&*ۧS՞{m I3ɍAӋ2I3cHQ=R> pӨ؛qn,eاL×z'SǘG71d."'Cl&rrtsBR7,ܐSD鈖z1y'iY5iv[:b9^I߸Zu12~L34=3*b3qv9+gS1 EI nD|Bm!+cF[:'".Zu+%ǃ7VfdtX2ncLi{Y nCVu];(zģihDVs$;zC1y>ٟg Ɔ1yC_"N'o^6յv$f~㥏dؖ+N y^J1GltهHe q% %6c\ecӬBMs//eW-"z7*ӫދLCGO՝|"1e_InY)xT36mQieX7O<Wzb~@z׫ZG\n3t=p3/bD'Q:/'N[ԫZ'Y/so&=$+:> }/||ۋm._R<|}F%{t;/O:ݤ঩:Bs7GM&*ר,}ͥ HzbͰԐ%q(:ӑ莓F\Q9z(z>hZyX"A[#'ۣLΠү&lTS3a5GFhFEqNihx,NJM$?,zu}?LQh[D`՝ 6rN c :^-ia~HŇ{2fQgQ9Xl_B?dgN&KLB%""T ȴZ88= czd '%6>Q[#|ƬxMMܘ⠩ȑi.F,n\"5<,vA/ VUg[999-lˑloy6;:I5:g̜=0/nSntG(JΎMJ?hhVO3L, vOΝ#/kN{K1ࠫLٶpd{CV.>嵏LT]mᑒ%2K>ǚ= /8"Q)3Xq6ėz:/Z?ΪBeW|l4?ř]eTΪB1JB D$L $+1eTĹ&&G$:t>1qN|蕟K\lG)X#/IeQYVqn&]VK?Q $O%G&UYf?> J n_$4eٴ-Wh2)Kxb K"hd~Өu.FlwP~I_i$%Nq<7HFI0aZ1xݣU6χz4OO- ] Gȉx1?"FKFKD|OuJ\%>N*LΕ53"b:q)=ӫcWe1AG2#L BTI&zR0Jxb{=3cP$mtzr=9ͬQfmf'q$"HTLdGÛyhbHLD|ن{yF]u~tî$DW$׸d8gWYӻTJ[U>m'l~Lw9Q?jX%\2ii"+'Q~51mvbnɧrIWہD-:Dq^~ED,ItXe mdeZC $lpld8JxE.J:ID>ih>!cӉ/krzK$(ì #0t>#/Ld~ʙ{Y碅Jr.H#tΒ+0͙'Y9_'Or2AN4cPtdd(*fL[x6j80bJZ(_(~_lIi.L==!EGƛYQ+0d,HÙES3Os/F7kqR1l,SyKܐ՞6Q'n,2\3b#ly<".ΡS#Sd2qh`Dt؎{l2v|V%6IHEY,6XTuY9?)bv1Z܈qnYvN9VX޹iotBi)(2ux?!e+eǵh0;M؊+P)cT^rڦ^gɵjW: =X編iSToChq&l$6 K)Ò{map:|QGWJ6l FM;OxJ䂨(;Ft/OdҲ8_xΝ^K&8 J:RsojRce͍A 7/ѽ~Oӑ )Z{FɑA[2;d21-nf:Yn,&ertiQZ%F,2td 7JiӾ{$Ř1I=%2+ѽmFzgIhmK^éW~DoDfkƴ#{>AgA?l^I.J6n܋IKj$EK$#HGU:Ui[]gQtq|<yZ5|S*ieHK?ه7:ngB'=ٛ++?Y{kNsQhy(ёE[dgW1}'QIm_G"$7Kqtie{eF/pt2# Z8clDL.Iptf&&OuhGSIrFMPb 1t'Z?ztk}rMI*4躍k|'Ψ9NNŕd5iGCr#:xb}ZB{Y.Wޒ.5#{vn~x>UDeQ,l6l> ͤ8Vc{B|?p7Ȼ<_TSfΜx.z2!$4ζ<&B'w,̳*L'=L= ꌉTyy'fzhxbɾ7ٚw2^K,L#F >;f:G. j1yblϓ'!?K㿪̱ $#6FiJ((lBƨ:[G*dq'<+a#!c6dU|ir<b}Mظd)_P_=SfUq_pVTbK'ϓ:݌O5d`dRc6ٵ~ΖaE=Sg¢ͨ6$dҿ%Gm_|YIDV1?=UUdY=hਔ%nPz#%oȾҊd4QE8eOO :\,R7Hn77◵NhlO2q1:Y_d"-,~J1y?|%șGKq3²2rpXf5Q?Xʑ:=zK.c>Z:lcWx/$fw=:OhڍڍVXE1FɕTv!-d/$u/&r=l~4k0{f.bdHy6 /_}O(,ڙιIǑgJAGVVHlG N, ޴)nVuX}Hiё{(50xf9ў;F֤6n7Ln,z៦<:?I=k#O𒱪 Jݙt˒n?61((iLO[F Q:F9r]3xcw^?L['!hdr&3g]c:yQۑHcqɰb&xݝHߤQ[^ǧUl䢊槵uxȓGMflIC$>QHDٱ%id9dQ%+gB[S"hY:Fx|T$,dD#\YLiLr2GD|>?9mgQ b'~?j:#egYE%/KFkdlDb$Q.FƉf~$E5GQ ̱݈q7K{ʙ9QH$zr=)B_N3:"fx4".џ+=)NFmҐ$R!(2];LX4PV. RRf#O͌Ͳlqc=&D0B᜝zrN],ZC:bYU};憭&bgFf0oF_%I27H%G#=AK{7Kv;#?q)Fzep3cDa&?Iɐ:u#y^nfحB#deɨcw{lVX)=K]ȏ:w.g1b=:#{7pߴp2^lڴr^9YG*%A#Ҽk"dȩYHi"SvAK#a-%1. ɍL͞uQvb[0Ʌ1e1GOb鞲TV%T9ig/^WGIiGE [YjfN$;Gf,R&l^2qcf?P?nN[$ɮD1!?mZ31#iB\脽> k:NC, ܢ.c+JԁrΣi V?updr^82FX2LQi/'GӎnȢܪ(vƎBVҬts. PChTbsE8V^{%~,f ߼zD蟔2 ^,Z$0ƢM| x3žfgV/G4ժBF'hqh7[d^ ~o%ه&5hȩHJӢJd"-U(=N;]l~J*W>alBIxH%fL0aj[G 4%D&2zy:K:u?qBCdͬEprD'GG/p} 3aY4`İ·I͝TJ&+D?iԬ:Zlhh%fRM2Ix:N\ÕȤNTme{Jebu+3-гƙgZ|iZ%IAnrN:<>N"OOė\ u#m0t}%+ V#P? $bqq gh7:SS7\ 8Ѱ4$_o7߁?1rJ"ȳ1S÷<GM72s-P,z73{7 nUǑ=ad&'iD&CܴRoя$!_=AdT1d1K~3d- x73y9ǐYf[hFGٸn1Yņ8݉$9ߒ5g$ &L$:qC3Y& (~Kf))QN=GuIr4vʈNlF(mVΧ>Q&$IңFtJ5:6Y\r'\3J(meK/H:9|R;R(PBF(lhbwrAR&P7,JhIC^Mlfr6ŗ73PTNIeG1rn"(-Ɗe|jVaw1s[U"0fy׵C fN >IX}K+5ҏ/T'~ŦGQ]M:(#=:<:H Eik[#o`kv=+SQ% +^HKFG-dxHHsFnѾ?*ؿ16{Aٵ -:j\UiAIC ÁؕiXwJgD[,֏IrG+,8=> ܍ȱ#j6#a^-&g=t<ݍ"8$6)> ld.|FlM21<0> $@ER4dܭPqt"]QHmyFa֯-JT3& (HtdDF-Ȃ&謾ƅͨ &ck#Bɽȓ%"~F֊;B ٻQSTp6_㧝=:[>ώ듙r|=3C|F[΍ѓ%B~t2# ՙ=FmlPG6s[LGcD/(¾tGb!ICMW&HluXktG%i_lBFf{.zѢrN+%z./Y[LnEQ]Ի(+ mx=J9 YoA cj\EQ;g4'cz^zҭI~w} {}/4ck[MєQZ_б77"O[_Q_ԭ,|D19;'4OT/:1hZ_5۹loD[,oV.Zٴ/9 khHZY}^XZXʢ^4^tZ[Z1}JVK/E1Dx-~F7y=kUږ^e֌_JNEz^}XR-./"?(1VzPPQC"oY>{b%G,L}n,L˾3z^m+K/})v%zWlZl.ޖ6"N/KEOZl:zQ]X,RZ)vQZ-.3iK[ы-7^czXz,;m5Zs97"ܵCR% %}cw(=eįVG/U+֖n,Yg~Hn7$ȷ.JJƉ?ֵZ>ťx9}|#pn9CmzY}#eie|vypBJWb>Bս"yWs"1WrZ>,E$m(ZQEhmrxF!h=W]t|i>GZICJҵֵ67}6-&=^-PVPmh*hǭED%QEheޔ5֏I/e E G"$P+蕕B]U)*/OZ((c+DKF!hZl.ѱ,hE ̄f+HEiz7+=+vлl/֫/b_AIqKbU閙z=,zQCz]YZ֭izYbqҩ/j-/WeVVl+$(VWh(Vz] 7ѻu^J+J(GҊ좄X7#qKQ<ދGb]b% 1~ w})|c'\D$K}h+MB* (v4Yz||Ҋi}J%@q,.E,}e5oZE ]iQ<} ,{loEz܍ȴ}-?fҾe le]>HkOdtr/GtZ>G[/,}jlpēSBM$H>F럜*r*R++r+V9a;uTŘ$X(Z㤻x*d}7a$h *<\rBE M K JX\B$[#g,Dst4*xd' L"ŋɓSByrƋE>asgRpr7dN"nDss(X-a\DΘ\Sb[G^RIa4R TGDD[ G6K HMEa' Y(jO>1>DdL}z ^zt.VQ.ĭ2Ps;w$DK 5wΤC΋N^eK¸J*rAn"7 (dީE):$,͗1%Q_lbgb /JO,v*Ir`"XuXXND5Q"T-)9L4.V.4w!!SHV$Pdpr'*tWbМ0'XʥPb0b焰n9U!K䫩J)w!D*pjXU%GЎ XYX%ضjY۲qopӨԡrU.7JQ!8+3C6MXyY6GC򣈜O'c")r!{'mFEұrr(gD}NXU '^Ȯ H!B$q*BH.dQw%9Lv> XP:a8q݅R}byI'.Xes7**)9VsOYIEICRIe}$9߹;>tMȭFx)7b}#wfp#$I."D27',7a7KJQ\C/  ('+R8E u*J2t$2l'.xKQBI̚XT(&ԫL%]%!K a9v?ܪ;%8Jg>cQ(3II~0<* ^.xG*&&f:X^Hd8u7=M}#ؤ~`yoIz 3ʭ輼vPI)w,:M'JLhHФ*k8J2DNt'WBJÊ>CvS4Eݜ(3$yDr}07;BxMD-\lኯ+%dJʷU ;XPgcz)8J2O:e0z…NxѼy$YhHe+:MMjTCa*C JzYUpe 6Ir…0s*N[(}\7vQWa1<%ŧbt: c9GR; E/&NooRm>Kk BqmS F4J%$Ni?FfXOgq} +=2ƓV7rfKٻb/3U$#w&h\jC.V> [a39aDObdrV'VQ-"!ԭI;&LW/(N8!HEHl71?R!>a>9nx#,JIМdI$_*)C)2ED$:̒IC,B dI iPr$T7j)0CvR%HXOgؗ1jJ 2ɪ?jN?fR^g~b(2'#u’R+bp(囡cG.O1*I:BN(i؜ m&XuFt8ܟ>EHc4,StpٛUrgN\,rdu%>[ՙċ͖,*PYNEb;܊\J)WMİ,s'2o aADԉ$̝GAu.U.V^k:9aZ.7ShUC*8JՉ\Ire)7W&Ψ,d-feweg>Vo$FE ?J)#6)R^jWJ|Ч?g̳% E|W?Yr$$5R>Vϓ3\b$r2%',+.Th#"eH%I7̤ebB *ϰQvQP$,I:.I8ro>F J, $Pn!jpBeQbe„E/'?cqDJ曉EZ'Iw37Q"\[R8$>XISLb_FՉ"t3bCO/ro R^r'FQ̡A̛Q$oLpQx3Xww23IqLKrn\\†NY4|2 ɺ"#+7QVqJh}K HQ4:TBO ] ~I1a^q8ScޙGگJ[hG4GN D -N&.+suė;"gQ$꺍vO2MvL^,>>]H\RXyEڤstB- #77 Å Dh$nnRe(2k3s- &$7>z!ejU-|qa/e<*"%$YHZh%7 eT%%,j\vI…+Pܫ(J1#rGW*DEkBMC^ȅwh.p!//^0C>deE13m8U,JD#˜%0sedqƚ`QUpUh~1R'q˞8TJXM .QJlEJĈ ϨDK4+N6We!*_Oƈi_FSbԚ4~jr$ժ"8Z-(W]pzoq1ӡUzc4+:!+Х lg!(;~,.gx2̳,p}&Xף{FI9wߞpdiXݷәLq28#NVr7y/S'}n~x5 W.g 0),XXdr]^/RE)z(\".DŽIߤCt,ȴ`9Zb32–O XFQ]Vx[U,[Lr#._ Lre"6tfp?gˈiipErx=T+W(Ȱ $V5pv:tf? = Z_ejd.5>"s4?C+'dRz?F1)QNR)DeS2' 5cgNX5T_BqH{ȣ6R?4p"FbYg(Wg.nƑU) ٪u+\% 75 ;jfrQʰrd|w'D08_P!YۡNfh;!/ ZFqxQlY *2ydK0!2w~~-ӑD33[24p@~G/G+QuL~t/3Sjrfj\XENi?O3&va8 $T,.q!+×iשx{uh<6L)MD虞Mˡ;xr*2rȊR/ 9") )g&T6rwb9B.C2H[Haނ⋮X/})gC7`¯?- ?,8~^'%b_~ER8KM%"8z"rCžq5~GR)R6N(Eٙ|tspC2RE r;g5Ռ&5]#ɱ6~p%1xд2]Q /݆Ѫ"l(E(KG"t8j2TMUh&"-P)SK7/jr(Z~z$ImoB3u7eR:}UXQvR&N +(#D+F?8燄X'^1iAv/#ɰdl#/ƅD^ .fnLn'$7e;|&N FmC䋱=0l ('^W%q@'0rxRNмa#47"ev!k 8,/t7RP;|hƽnH|(xJ" a8E+l>4WcOD5mxCg5bQR8hǃ;2(:q2ݳiQv~4=+hF^KͧCD3,e0~ |= $ޅ("R3t8A$8nb-·|<jqt7!Hl݊6J(GD=.r9cMȤEsG)NU$q/g8ĥ2y*l(}ҊHC9C5q^Xbop*&ٗaDۮBHe8N8~EY6?OȔȺ8GR˗?&Tw_/.˱b2މFo,$N<",C+NbFG὜5Cnؔ0s6" "krÖr6c2ž [8AWŋ' E9'B,WLç!o(!J}AY#z6AN|B v? _ ͤC8NQ"D^tLe?/"5"6Pxm0/S /BBY)al)2v8E~>\c8Qbŕ"S([B"\(ĉF.٭lԼ#7~HD#b?88#I6rEB"(PBCc>Z>4?"/K?E,nr6r†7"NIzFKX 'Sv&ԥ'B1` >qEJ. $N(Sd>4m<ƈe?9.cJn3?D=C0K͋B>hsxXeZ19cԞ4*sԴBӳFKc(cQB)D-煵HKB!bb~V3>4m<ƈtE.Ȱn 1Ct$FxW3DQESW.t] N+NbS.3N.\g2|u9fY*d尕G,ʊC xE4wv.gQ$̳,1PbAF'CRp˚+8"pEd+_q8a_" ‡Ll9D0UKז0?Ǿ ?2/&q2q ]/'[u "6r[eݓ>XD[ pE, ,/% f_J/N#.αsloi} !}HtN|`=JbiCо.VeO ђv搷He 8E^1GFIf-, r%HVxV%"( :LqD2Ẏؿ I,?eb$1IDEF8Z l$o)K%bKҙl;e vqLE`GGm_pXHbhHYDhX"X!?DՂ,Q!D )KD>DkQhx(gZ^klq朇\$M``Yk7 (T[3^^JM9}V2!DŽk8cr?1Eq2̰)qFMgJpƥ-x%dZ!gX~D^-nȊ.\!f?e"l83h8p9a1!n̔0/3$\?v&',9\Xܸ.qT.iΤaK,%I2Y%+>ǍfY BxXt3JM4p-E^B.R^ Q%I+sp >k #x} Q'!&"sr/<'Ӱvq3#!){.&:乓i#6E-3B eQunS,Y \((Y}) = ~5".D'[-?8C'dWbc-yj ;2BRGAQ*hH _ wurD5rQR4՘8pCOT4p'|a>ZK Ⲷ+E0q.-<3x=jVX-Vl2"^1X Z#Gٿ8_F<_irQ 1Aq! X?:CƷð_AAV+'=ό^ ,3W2DG1 ]ET5!0yb0!+1ƮsJx'mUԜ.kMǢH%Ë޾Ҥ꾌YiUSui儰_AB |#l} 7 q.52. 0&aqG9F!G3.ӡqsgɳhlwpTLYሇv+1[&pjZ˱p3Sr5lxN9u-,Xe}"Џv J=v(Jr%i7s.Qe90G"țBR 5̛7[LY&^"8s1j͟,XXFa +[XX<ODپcWCqa00QY'hQxX% (]4TXnEGRrEJu*}TPO$ڮ4m!AS.?8"}x2S'ԣ| "1+|`ϑ cBBv'մ/{E?OzZ=.B`(`K? 1!DȢ1 s*w%FfX)~0;A cN.XP Z>Q48#W['5lWC<% (ՙpxFJj"<7ⱒc԰>cٿ+L <}rDXȉwG&/!9r$DŽR6$OAL\oZ_?ۆD!GEc )Bށ?pM2OњFXbrѳi̬U,jY͸b-)^# %b,J'E܎EPr9#Ir]O~ܼ\C?b~&Vr}̷^EUu˹x=7Sg&q/BR^?K16'dHf_cq?m4 $By9JG~7l.RYS]rqdNi 9 q#?d3̔BΜx~"_ru(.]e˕)\8ѳ4C]4*Q<W1y$Uc.E؟Z]/8?8R3l`D?v8ٛ ,I ;[&c.%l٪d PvC #L{7ʨK ϐ̿|a]o|WZػw6)"Am:g|h~'HS8Dl0Fu ?vlx,:!,D[`q>PhϩМt;1h踻RHdL{=5Lbb.+=. i ܦh)Sv6Q'+ŎI"\c0A"vNN?2bQu$2)W6(W/.Ckn}SgI.\re ċ1 ,煙fY-z-Ek+##!nDA"8b8"8"!ybpF8rF9S5#!S_cˋ~\_byyNG ?-ˈ66\ ކ%Rejcv~Zv8p}-˩$CJ]N½ D[rWG *sX,!C@X*XbÚ!ʩܲ9ar!M2,َEb$XT+C"}-yhh=P2hGZ" <~=QD~5 2ĈʔI]aN̠_t˂"/c/΅u]1}>Z(cGƈOOG-?m;hPC}y/i U/LG sujx:诠|)!`?:>4A$=x6FD: h?_Gp $f\b+!$~,GcYPR:%O0CЇzhS\xir r|.^z> .+̺"Sd[>8}p̬E3{8G8C(}p8D}dFiy?2ec {p8O<6\$vą(ˣ{8׳ihwRqCD- dI42d[]\V%̥#Hji8Z8XQ7uXD2pĈkGĎ$U(o3z(K5z.\qB_EˢV(",ϒ+6:4(G 8WnG psJebŰ Uz}/%dYV,"Ȇ"ȗ! ,YXD9EB9pG pCT^^!l:rt\O,"eÃ-Ӗ8W4("gm.Ke8p+5o(b%ئb(;o:srdzr&u&)nhQC35ZzzaITe!X=1u:Zg #cB-!1L}ƈOn2Eg~t,6W\K<{-,~ID(xQL(6ɬ)_qDD^t= BR&a1hZ!?a}fA?ų\fwx6E,WA|0x"/!?'2h1c?:6jdmZ8eSj7V*g(/%x) 9"#/g^(x~슮K]؎~~~ʓ.MÙ,~˲U~˿g]xV-Q8C~g;gH'N(ȼfs>N(~'~wI8~{u*ȝN8q%~(yQQ{%657aϦo7d/3]((̈rC"?2?gGKfG΅tDHk ΚۨFh5X(oBЉ% ؓyhܛ5ƍD2EB7#̔.E0ɞ%c]a"6~4|_DXfnmH^~$/ЫM&?MxP·hZ,?&>)y(hZ>tEXդně M *M(Y$N,\G؊qhBRƏB6Xst?Xlosq}/Eq/\_c*%^%srJrT\qG"?182YPXVq oq}/جFr謹J%*2nC0C)pAG pV9Ȉ2­K"s8a"JOˇˇ}P/S##".p/X)IWgC8y8Wg?pCE ^_p/*$3b5,TEP$"oE7 v^J谵EDz>4/б̡c2rԔ0ͳפ&ѯ(ntao'R1)?43gVkc~p'Adԋ /._E͛^:+T~tE>~ZV}٣?QTXl*0?5nJ8Y?~-I{O hb?6MDEhٹu^0M|DV)$'!sV%əYf2!G6L' ѳ|WT+ᔜZ}$9,Vhj+cJ[ ?0bCϩՙ,?ܴۢE^49!&bR8GfƘJBx<`D~1}y||XL!)h~>/L>qSfg)>}Q{--[½^/bpeМYȲY{e,X%*HEoBt(pQ,g G 8"8RYG?*2)Bz3(YXb ?G~^HQ?G GpV)̛8s9ޯr?W~EߢQO>̛Gb)~ $_cq)/. cE7vqq f!sbwR8\Q)D""B.q"u9{?O"tC 3vЩCrCS Vp<%б~UhBhr)Ȣ$7ga5rQq ] a)v)b\Pr/SdW,ܬ'%4CCΏ8*PK4 ,XY z!rDz>4C-Y1)^Eqc" xhW"Q8_I1A+ΈP7Vp)>ĺa3ΕO DUb;>~Z:iЃ~\缰|D:"$x1*G :$m|FҰ>t\̲C:7Ś8ȧ 8"8"8b#"֎Y,8Yfp:2*?EZ/E~G?G?C衚oyEͦ򫧣hE)HċI8q"yDP>d >4*E^JC_׍Y\ifXЎ}4O\&R"+B+GƆᔴCW_S*BЋƕ6ұf,?R8WʈڅNW8 ^t?_+ ș,YgaeG>Cj?.Gj&XKa<dMteUN_JoxUN((ٻBط=轉f/g^(8]ev]dLg8~ŒU}uzƈt/?A:!.UKI[ dK5"XܱCnI) WL',b-ye r|,B4")dgå}_Ef?,;m/D:+CҴ|z_KQ%k"OD<`s6?_.zq>TЧԧЇ/Kk;xEK2eMt99??y%2VR$94q#HB.2XN(DiU:Hw% IamNdkD 6Ap)cj"C1N/قwCE%otF,'hС3xR-c;}!мhBbŋ#+Y(JJ~ "Ȃ|H1msQBѺab9SSQO92{/9#,;$!Qt/E"?ȼ2o5z3iHԟ^{O+G_+jJ˳ȝlK4G3!Ĉ{4ؿmb_ԉQzG?Hyے~t^?9?9DvosO?7%7/z'DM$R-?GZ<_f_fy.~%2%CH}08aUC[G˹D½+½E!^^/e8~?rXKԋƈ|鞶-/T'c\$K7YWDm059aVABoRSLpR"e^+_no?p2E1~пrƣk}YwK1bƈ|CЋΆRL9coѲ~pK i 1u͏-]&焥ER?$mwhc\>t-+=C3qO#~/41`RkȺ.Wt\FRX] OQhx9T!LuQDI,ѲпrCkwfG?'t|b¤_: 4GNhcUL͡}CƸBŋK"ŖE†[+ ,*(ENg?g^/dWsss;?bUe߲8ż(88'8y["S~c)m_"6rN5'C'C&P} ECy!˴Ix8q¾(=IE jJ(=nG?M @G}Jg pNI5r_ȖEGC0r_sIϹ/?*/Q9G=WeD 4N KiWӱ?8#1--U~?Wu39O=qdIUDPCq D>~+D^tED:=iGhٵƈPC6ew(9žSx)Le%5bxGEXSJXtSE+5TvBCBVx` d^4/bkt2,!n)XEKaci>rƖ–&𣸲bx\u3NO3) 'sT#/hl=.¡bm| ?pWD/hzADX՛Q[e+Ebe03"*(S)\#~ilRg3O&(GcH6O{ĽDC9%7ģTIR ,*66%3!iãzq*5(|MCYߢЫ(Wܸ8)ƩES; 6Ɨ.d̆}_"hzRȦ P|Jd8ey_+زN9L.e˔n3S'tNȐچE3QM#Y\ 9S*6Lĩrbmc+5'lwo.c>eWzk|7rH2|.PYnՏ2x&RFza7jI\[7{eaG"݆!HB(G8IJ,X~˿l_sssss].9#)ee<,nJ)36t]>DmE\$ *1bD.xЋ1a\\aFUG-RmBO  ¥TLscSw1Q(QA:ߩۤ\5(ӈ϶beO*oŠ$7stޛItE'ACUVfZfW|3SczةL%2gan+?"ކcߢz\˳$PbdR7ТIDSjM.RS܎(_c9М!Q69$E4>,VS͛|fv> 5rptfWH9K67LϑTC>$nĦJUJ(hr8t3Z ^4!MK5GcfTz2jfQ.[$I9DjPH9"&r4de6?wŽ#tpVbJY̔ nh7 ~$Ps~ b+)Cʼ,D.&b˴O%+N{CxlΡNM/<3I,q9I2ʄS2e6i&]S/RDan]ܣI4)V'v Z"dYECy"?bH,bqr.Ubr3C]hv;o,ʹr|NHV"1J%Ͳ\ѼзR2iG2\$.WB*E} i\$,?Qf\Bf\Y>J4K Bɨ7(~K%PܟȻHC  v2E)Q[2MBZ$ݭ6I$⾉; CMP*܄e86,Ev5'C (lo򩕷#z RhENV ,7)eľWԢE(G*)W35M"q)+UD\,Q#CQW BeEh̝BԚjćZO u̧#u?G 8qȿPHKK82QEfgU,6,Ҋvlˑ>)qTf;=I6-gݓ+ C~r3}E.2[4VD1h]܍g$vrx[w ʙC62Q_R&Ev2e0(Юݫ'bܷlK(gS\:9pIM4T77BYIS6Jr!D*!R"\LޡsuTP+ Tdt??lMUYER$s"wJ[ [HlMfhl&喸LC w7}Վs\H'd-^xnD?2p,O͑r vdĢ5:st9*[[Eemvs.dγiR'n.ęfpTR 93|2 NiT8,p(Q[HR&|cT$R'33BD}1P9 Uzw7ս K'ĝSvh7c~BuDV*B(v-U,OEi.jmh5FS(CIEԔuR$K4N]КЉtHaZ)zD{X$\+O$1h܂'Uȣ)#-sz&nf!"r2I xOZp<̰,79,C3:RD((b|}ӱ9S ar9az>OQ &E+&XI4cYP˹JJ"D3w%2P¢C$: XܥIOE*bp7Te RF'µ)Y*[9(qE#bY7uYN.g=iVpfhU3 DA5Kl4P18nVr+ľdfYT)Ţn䰩i-ӪJ(rC7H%.cqҜ][$sJM>if&Bpi DHȾe,UDΰw7l9((F\dwT'آr*&\kIXJ#Jb$8f&Q ~$Jg8YfeJ+2s7\2&BDdbɵM%+r7M@eȕz}Ȼ:L1BL}ȓF3u]R}4o} ’2,smz)+ST(/É9\5)ػ;)fKh(XNe Omg?)P72rk2.f\ry=PŖY%AxBzs"兦tRPdL턑ܒ$^E E *E:2xtfQSsþ.CAYCQ*z-28LQCZ@DzMx,X\CДm&3g\z -f3|'GI&Jcq5YYDo/gmbo|KГu&822R(ʴ2B D:68]88$(q}52l,NH5,>ڦ䊕(J#wu&iHx TNJ"'M΄.v&Ed' 7cuO7P YI?FHzWbnfe} ]yS\4n>MEa(bݐVϨps96*T)ƤI$(s7hC:դRd㔺R'RQQ+"R4 d,O DCzniND,4"{a ԔJd] Ur%hɚEbO79\Г72HNr\DQA8Q|+VHlvC-H}plcSy RSH͚gBDR,9T":*JI[?oԜǚq2EZ;" s>g|*5j 9 jϞaaN'QC)J+7!dB1E t;'+ 9s6qTs.Seƒ䉺"d E,Alc"YQH&ѺnBoa$&\(K&&N2(ad~D~lڃ|JnL7nR59>Sx"RC_2W*ћf9$މ>.D*R}pVQùd𝎥!2J )%!dJ}&'6pߠh]Ȕ,,ōYSdPᝰStH r$@Q&u R1&Qj51Ek5XLe TzJTKO)y1GI7"JmeY*М6:h3gkЊ6J%J/ C*M?I/hS"OibP(=I6Ok+!XS{v' $ 0r;M!C܊2SFMP'e3dJŊ!С>ceێRvY"cb 2(Ait/P,㬼Z1qIFh[JۉAbR g9[v#x!S3 `uW ]+s>ATArԳ-Z+qJX슞]zBN6D|v"&?E03Ku.ut/0CpK):Ct΁m~!M[; `xX ټ”:(<1}X?,TZfǯ K ;JL/mHK-+/^f1[,@iAYqD',U*p<ϒr^EyÂ{BuӴ,7U:*y6.o% Q/ i2k[~eο*s7xԿB ='E,M(]3b_ J2-' MLBaW!fPaK=O"sv/:%gF 1˸9f[[uER&6nXmT\ѮJ̞ \T΄Pl{z"t[a8>p6Ԡu1_;1V̰Gq8T,r% e1̆\NrZANҺ)f|Q˖n!cVdm\V`kBF"9Jn\#UaS83>14"Vf N<TPI3kq9ܵwWL.`98 =H"1FY<$Qz%&W-M(p,*Ȗ/o1*TeH<5RS!YHx cRt8]cY12n6ܨ%AXQ9LUO*Y\Φ/.=p*rzfj9e3ݟ$Eb/ޢ$=eZQPq2x Uٌ"'U S7Rbk]P1ʱQmN/ I05@L-JgGiqi1.Q5]j-_2'I H j%O{YEQU)eJY+P3Jeo%q0 9v=<{c ?([,i>'\UkߴQg-A|ǜ9J-`c'ۘb.äȯ0$lP!E%w<ԙUt9.8DemE3Ioux9g3}" _#0kCRYn0LE2X("u;&!4ƣu:Jf%L vŖ&h+ eyqȭJ7@{|?)GbB*IoH̪R%u 2§w]`rvb5ڧm+/.8 AߘO}qCP_#q)jkQ73cTՑzI.B}[u:&°:um!6P07 xJb(!58LK+y^וLsCE;xtqceA㔃H(d_<zL2D i)jk'iE=fRCYdsUY fB"ҝКu2sllT ]:P!N\b!m"Wab,™f9mWXJi梂q,e]1l3Qt>Ϳ(;"P9[T 2|niiHPHq1ȝKl_(i[5!L3PCVfq@)CQI@@\[ =TLf\}{GIJZ:$=ep[=`GXl )@y{ n7[CRw1iJ&r[<0ī͚T znS㈢.)OIDx6j*7SVԙ++*+3D`JVn\q 0IbkaKG_s[gtCRQzB1AhV*6]"-,Q9ޚm .c]ȇ%7CJV Fv3ۚ *UC>e&@ kLZ}Q#kU+30-M Uޑ5Z5Z)0b0 R8fsQ1Pʁ;U՘8^11orP b L(%"b9a$Fz0wh^$ ',J4- -ToP@@k,!1W |h#gI.=uQ>C[pg 2٘Ԫi"AU +;58]bdj%ݷP[Z Y^5]9sn hlr6b2&|!*?7s:gW#ڨ!5Ԣ6Ƙ-)X5& \#1(R٫\Ch~8%vqQ)(n9;T G Bii%S=mGSU-0tw%n-ߝ`kI~+E ט!d.)0ܪ(zYWVsU6}eG¬#g (Y)9c/0#TTH/`NG;YP6ݒ!01_hjo!]cX P@]D d9 +K M?3f:*JX )= - \R_<-s q\wfsUNHvYq!dxvVk4rW;8 똆FyY?6[㼸6T4N/sHx!ic^w+hkcCp ߙ-/Xr,k uQ8\큒(-=h`C5F(ij3h燃.Z^`p\.fwt y3%E]w#Z%ur"Y] Mw@>9J_ĪuP*9e:R%*l^Ϙ3z Ei114pM#=+>DޫQ:icVc'0] 79ٙnZsCh+{p>QSԖ+NP+tPP Tn[:(&b7'9%U2&! LOuGbXeKZk- 1=լڻAHeȾ<ͷ(=^Н)yzj-~aꏼYcc GE}2dE]EWbVf9EiB: XedϨIrbR.2=n,pqRbSr j9TT帘 0ex1NJFRe T̡Zٖ %Ӽ"kb{em9Gm$!e`u ջNi)bq f8æ ph7-|YyF@r޻oS4D`r G`.L]"<1Y2O_mǘu;r̊/QPNb=o/3+Rxp[їyaE1A$كTrtx=iv̝WGDe;KUCbjX˽U(5Ic&ep3337K;@.Wf dZUEZ0ε]gQ1golau9n8#xHU0)^;a|E_DAeu#A* ekE'cbT+*E^-Ha4eEKYV:LDUWb_\xЗ2vs;Yf 5wjD.7FaVNb1Mg(}=Փ+[PvGVQŴr/'b-(Tat*9Ucb]rta/ԧ ToTtYuDLsBt:5 tm>F ɷgXØΦw.5WUѡq`e#_ߙ`9<'YU eJ&2[g!7h`ۜLll^bh. 2T @<rm*qRvZ 拉trX<dQr'0*jeN`ܝՁS@:,cӊUK?rs#08t,4Qǣ\BQ(+{4u0/Pj9/i[xrdPg'6w/ZWZFcPpLqbi-3+*X6HA?ЄL6#xl1T .%Zb&ºaIK+x_=Pְby ڳ_*$ii]ܼKKn{:Fb\xA UVq %ĵ1Ue0NʘaWF :12Bx2,%ZmHcN%`o2ȭ]\/5;f Clh!> |[%,0oZ `0a~`O#ƪ_ Db%[b6iCOXӆYZbτ*4 ޸uJ̒WKLeE@&h)/0R\12kϑ-<& E{ UsO-cX4j8ХVmbb"8Ya>LՏ*B Uo !Gd YL #mWoEM\Lْ6.R!Bx5 EL\m:i,ri3 '04*>ePEtiU % qh2lTΗ9dJD,:S8ye;eE H c:U]}bo9MB*~ùgfJu:`oa7PWT9_nj*>43/5 ֥4b]E)9S9J!2%=q-zQ8/N;!/nk+9JeW«$ . O9%djm!aѹӹ {Jښ={=_6r*8MWP.4F@xgI 6IrSHvӮ5S(HMGg03k" z~%!U ,JV4 0@죳n9OaSSx@PJ>٘ "J-utkü&wpr;{T,L /v,=APB2 ~)|ITnQ9\$5Xb/:zA5:׷w2Liꍇ[StјKo+<ǼY,ԿD]~lR/SзRR n퀵D3,E-)w9begڌ}- JAai6w%EmP !҈s?Cp .`S8˨-&ܥ8 ձ GB )+ЃdJdP\=4цz2Zu2]n_x뙒a)Na@! liJQ5:h.W~eݘ&J~&6W;_0k_S[ȷBgg[Ю\h6,yKuAKrNIm+6kNw34E: 5"<~fڶVxo1Fs:Rg% l&;G02_1hK`z J 1C'c0vَ$CRc-ێ'$88"{!IuWxl~bO3d+opDljހFDh /EaI0_Njz~%zDp%:?R+Gh^ Hu4ޘK-sr,-|ܬ;͎,8!0=E/7%zAM`A?ǫv}3LҾ>9~>|~]۬)1I?h8& *cHҭ`oji(754Ojdf ɉDz`R6 f_ȜO%@\~i0AkQ$%T|"DYb=!_27hiQ9hDVUe6Pwv ` 9?-.\ =#؊0] sfC_z^X"#}f=ı6ECu5D"F ba9T;[gBKYKFutpp7=ӗ, B[uR8D20.]zhLCGUqW GR6MO>3bp磙f=3lxl]OMj V ]X"Uz{=&'О!@~,ɞR5d0)ᖄ ?,ENYQZ~$Lg5 ։UiW@TMK@Sde8>Kw*nUga vox*^HGq!K-A0~b,LeZ3 'h՘n/V00s5CPa;Qד<;M%fq+ F4jS) S~"Zrfc8 ~ hJK.,EZjU/|__ˇiqm]3SqrrpRۛg.C'HͳviCf#FEV#ӈe~Q BKI|Jt%!%I\KuڧYApg0x Ɔ;1؀)tm'9vx{NJ@.8 ,aژ@~JoJR?,YiK9,^ nyo6@9!w5ηWߡOx r>Y3p.2J7dzu ^xW~#$hl*#> s:P!Šh?ߦ3z#G`#;K bkؗȿǘ%$u^H0>2R~g5Qr%aT= aG<.P ЇR~?2 =&=v}S_׮Dҽ?_rOEIu`J%T˩vHڋVLwNgX1Z:k R&Q,lC>*JRWǢ_#͏֥}Q~ ʄ%ASyZ,uD~+c1* 2K%ˋDf% ,\vb=QEUOZi;hkg)y>S]!`{Cy=-NsԗLYwч(R>a?K]Lna/mq\W bA:=wz}"u̪hL F\+hq:NN+&mF _/.NAKzY)- +sJu!q,rnw N7 &IOڢ_S< |@ӻ.NjY#c'\L6NJ<^R#'2HᚑlŠuy0%,&Q<˄ѓL#,-k~1%WH.i`/TK{G/cRT '0+ JT#9`8)ISnj7M`6#;$tgO;i ?@XLW4PJ}L2wT^ g3efr*5hPnfZ51&zYjc/zJS` *=hq\JeGBWo~B&'be3a"ߥpp:DnmbӔq eW ?U,IV:1R629VոS.(ߑB92ѡHk_`]*pŷī/N6ȃ$/Q鷤zOWnY_?˚=.1~R&&=E ,oz ?>z?C %z,Ǿ e{FY5Ë?H)0x#q^\VhH"W8c]jhNW0b92/h}B:ᆽqg/k:C$&C0 Wۦ_?x}_]0,0= ="U1 "OQYݳy&6^!YgJlʦ'xK5 & ŝ%pX)Rt#J'9C/6Lzm>?K~tf!;Iv%8dͪ:/ELz+\(Y 1('N,Cge\_̲ R&=ay+[nś%2 pb4S̯e:K↼A|2ԠiFR;?I,,O/L ^ԎX[!׈3U=_Aamb"zi 731=M1L@9uLF^?n8?A+^8kr/J^:` f^$#^?c3H.[їk~f]bFeQP Ii)IH*}B7gyBP,C+(X̯27(טcAfڞ cK aw>dRF/B/C m gį"O%1.s ,葼ü%wa&d2̎X-J.4Dtv?" "28%)ijY@m|F1_|@y>%/h$W  J n!8Лn2jΧ;m? w!Q|!pJj˘ƟNKbf*>& 9+,:%Vd ?Xb.e>a+rhAAJo}U4e6Ͳ.Z bTˮݟPopވs@ R5/B[e) 9U@ Ƨ%< y|0#n:$VXe2ҹ0(:K-HjkQ\: sdpy%\6yY/qy1B@?W @a̾cq =n)q d|0S]}od?37fQQ!3},r"FZ{b&c1̤1GbH>LTX@_G O}LB&VqL<9P*'*jftL=]?{͡CK Ja"p# JX3}6WBy~&^c~V'e9jM8N'?y>޶Bx64ᏈR+fJ /By5 6 +f>K!-E%T<ջH IDǧ>auł:.B7_n*/Hzf{wfmwؘنz}f )5m^ RA*^r[OxL,Q (=5F*} !ϠOƛG򼟨91qXt B&bc-8eÛ&stexC/~3-1*<4TX|1!M߄o_xufW"eLet'JeK"g0ԗwW:}=4u}yJ&Ћ-}MgԱpA)r/(>iᖆpo<(QvTvb'/BIus"&d?go1J`JLNA10)Mn>L\~UѕT:%n0e\[ud6p_1KO߈͓&Ľ%AwSi#KQOl*dtlRmB!Tq  21 JTls+I97---[l@1C$MxS9!/F0G7?9<jRjNGYy HxdUbyb,'|OJ[]ҎN|#(eh.%'GM*1jX.&& !HFe*1 0W":QJ\ulu ?E:n9Ԕ2RķRʤ %Fp5]eBN&$I`.P"Z"%\lF=\N2H=TRZd{}˗>`w;Jlp_կD%yQWw#cQEo-1GvA(wMG)a%U (b3YT_e]k26Kt ?P?UibEa₂hղpNFؕyq,o- d )> f91 g~Jp*B?Vߠ}DW ;Q?|\jySћo08l ZH3-]x '5RlekTIp=  4J%7|L#aFY'ux=o5蜚 J!I1Q`T! aNg/AA[%ӤJWP` \Ad!{8d1%X\ҧ]~x쒔E\(!*K{&]68@hwk<=ڽe' yj"U2u|7A7ps Q&Q1 dA#j46$OBWJ]hc^%.3ȋľŇ3@opDZHXXN:qᖑYqx1%}%R`咒nZ+Q6a;jԳB>"Lx;@-jdxİnȀ _M!a*@ЈYQ?/S,C#9<%bEb'2Ӣ&&}kɹOd+/d䯙AdƜ\j(_:G7ep.HvĬxN cxS?lÂxg)Z~_/65%3^V[g_ ";F^'0 5H:tAo .Cx'Z,r'> SW5/ wl_aY"iB6x(inS<2ڀtƾNz_ *'@NЀHNeykD`;4bzI,! W3YDM="%Gp`T&z]ge|ĭ8./Qe@R?󥂕ngDNǣ?7T5\F <ɏ@\[UAnΙA{ڡ XJ.cש>p.\L{&SDq Agk3/D\Ә@m6K,50MůlD+ 鉟 .z =*gi,#.WId@2nS_G+5ن<&sɚ/i_4pfYEK!XV`MJ\xm YGÚcyF5CK>P DKWuc9,<z_s)Dt(nn?XhH ̆Tx`ѵ4@&"a"؂ EhkbC(:D3 PjR  R}fP4Y\H"Q֬:Fj~GÔ oQIfNxIhҿ,-3bs{B[r |MKp߈ *Muц` @-U^ M#m1Q1^rlt`5vZV(i&x Ir/[04N f ΢ Is.bvFpzz, {"%oE˙B}_ςh7T w?/ṯN>j >eY83=M?g5*NR#Y{i2CQ@| :Sq9a-k7s7RY(iZK>lr:{ٽ(RQOHwq LhtЩH];zgŲt } 0$$ʼnr6@`B+u^3!*TuTm^`\KeHn&V 9[ӂ#_Άi\HG䵒;[OMϙs/Lʟ;!y`I}9y4_2N" Beq*(mx pl`R+5:,GU߹L~6F|f}Sǥwfv?Hk(^M/ _B!a-UJ;RtWP=!䊩?iTǺhV@]s]~T372?D(1 %z 2,γE: yC1Y+QO \j\AEݒS=xY5 @JTz杽JRf g?ĝD&~4O!yc z!!v)Nhbf`xĩN{aVUz2᧬oC;vpT-%lZ*1rrA%(Z1'~cԔr|&w{x2ʄܓԤUUwz(g1k~6!ҟu\); J..+TK!* g'qєNA zWB jW1o+9eohhK N50>üE[-Mb&i\5/ÍSeĮ3(f= 密mOпЊ1rS݈KGwƌ_?0-A@%7d!cai}m3rEGInEȂG{39]PF%_9n;7\1^!q-83" dV!%L1lPyD5y>@mA;Wb[?ܹ?(ѧ(yºpQ<$!GUڹODZx d4 nģcHZ9[rSO(d40w3w(b"[&}0SS&,q P qɷ13 BLb•SxJ?/+Yw/ф\0~l4G#m8S\^ȰnږEBQD;b$a4nrb2,\(K0}Ibϓ\` l&qWWr!,q<Ηޚ8OPRGdfU3tfX}?^H$z3tuJ$ sepb5zA6zQ7vʊ[JOT;&UCC+נz2@Y~"#Ua -P@&%3Oƺa̙ˣRxޙ4#ӂ5]5ل[M8A dM MKMR L53g1FMGo ([W5(9s'S=⌇/r:7:,|GIkAC:ʫ?Re{>S(9 >w*TA^9J~VZٿ>y E21S;ף(uP NVbù2!Cm-^fF^ erJMFPo4gcz?hz}}4tfnx#.n *t f/$}p>+KkiPik= RN"TXJT $iչClLJ`L?G\z|I %) uD5Ρ꘷:_oTMTʪ\?T 7"}f,|C&bK/6vcGbi %Q".͢:n}^b1G\GjW|b>9*8+3fIA_D_L_LԹ|0?] {3؆"'3/2ݙf'p̱dWw:>W+)%~7N"٨~cк]##;!o&(<1QG49UP\R)'* Ej1.W~Ǵ}DMpBRSX,"_MAȽ+)?IkmYAebE\,3\q͏*W.tsfK}P3H)|85VKa$7" g{;z0 ѻg礵6Kpg0P *&ijb$QLX2|0/$\vf(kDq7B2 ]Zp3.t-6^3R~r_'5>xI>{'iaOrzTۃd"FnSL} ]!Bc':̭a ؄!؎cfQ=&V?p8fAтa69jV&u? C/Bm*zGH*]/r;9OB=Iz Dà !0FxV%׏Bf~} yPĺ6y>:1[`e0L"Z!?2=ji Wz/WcH4`jtGawJ[IGNκg}U&s*qB6;$SI}lM׮zN5=y.fDP 3 0/O(!x+gC$*+̕Wq\>k@@ϦXus?LaHb.?rSO/ v L 0ޡh+%hzu;^@=4ܮ2ӘKȴ@)l[)gSle;Htdˮf VVb8d6{KΟƔRV+tgfȋd6+r~"Wr%?uO5~Aro-.L?XctH?Ywβ>iqsKMWx؏ ̳z/ʧJ(@+j68TXgU=\KR`,N9e|[/3SEm(nJpX W5# ^+ ?UiX̏X7_1~Y䄪i}g?RzJSo! Ujyk07U+L,ڭ\6%F[xYwxduAqq"5 =BV^l&$/ ԛf/Fcg}wC!}2<^w_/0f?~= &kč0s;O)ǡɼOᷛGz~c,2{_~rtOr?WǭAdsOM}zv9eEh,g@pQ]*LsTVP{,vFVo!Eb^%t2ؕDĩPoTRFT3O'߮HD&Ǽeb=g a1~G;9<·8:\MCzWvZ W~Τms|e:x)~kGS?AO|m4w>}= B,^_epy5d+)bыZi;׳1帎af,h$^~fbta33+x,[O0P2+o~`b ǼW m_%=y_쇡EC.^ Juuԝ:/&Sn;##A'"Yy%ԲX!{w|âjvӼDN5Ju :;|ɧ=ݠV-c11a?Kl#K"91,E?Vm46 [**yVV`W>w8fJ/ %>VT{'X@ 0ؠT/d7fWNN65 ({ YAm=nx?fć̉c3GGӭ f:FJ.>zRvq` L㋶`H2tBr 5e^gK^(+F#-VyץC~G: ,R߽h.)2>ijZs?udHZ;' " RLO|N$(50ɨ"Ԉ*.*\:Hj/blTF%DIR=|? 'd56YGO? 5P  ̃kЬȶ;L^Q0eoD8]b#ȉm/z w` jd%ON:8EX|KK"&O-X[.+6E^BiM遌I[j\cvIe%C;G. 0" Cps˱֥J,jN#P,#͙,IݚR} R 1,#&Bqf}T>7zs5b/` j: d1R̿Zgv}k .Iw[}~<Ә,T? OTq9_fhbޜ0 2h;AyYn?{pwL:LIXVo}`]bJ1* Sfc/dAWo!XۇNQQr$T\ S5Mz(F47ES(q6fuut2"vǧ};&xO 鿔KsVcQԍ5/z>.fƄeǬcq"_f뙒J&~R t-{bT~ W }bES'56ԣ7 cU͋#a%j" ; 6|)CƣC̳v!:!1!?*Wh%%m5\P ˡ}WIӂ8Vl(>_lgT1ܭ ;;n+{f@/8,55vR`bU>ogd%Vb+!,. fW"]?Q:+N ߡGrz\)B_OL˟E.S,8[,2Q !sosg%5]) Ju0NfaB{c?Ja5"Hu6Au*~b8!*~fV =C/Gj͏vQ"5NEٵj@e: @q t`}rCnktTz^]# [f3{fTI890Oèp%'4@q " X&J+ywH?4.ޯw79"*!X. ' zʷ7ɀ%:ҝ~Hs_1ՍV ap?pA V*_݇eۖ.) 8!nQsXǀPUg?Od=nEa 21- Am[-bw9Nx#AĚ6[K~ODt[EfLׄmY]xjRQW4rKpc߷YԋpQΝEx(: nd=J1?3P/E QDG4D rw[fX nc:?5+3MSR!k@ұrgkͨq=|z~meԟ?sS9ugRi}x_}1?OK%˚/E +vp$?c!t@'_NuzDMe yy\=hMt58o@\8y@qi +S&>YCu?k@?RD43@K뺨GC;~LCF6al~Q M 0c,L-K=73ڣ39v9yy8D㬻<;Ax_z#~G}#2GJ3UWL^}< tf!Gk(fP#ݘpf'ghm#T()@JKpv%m]9u%[~fE=.f;8|sUE-ܘ3n42pjvmX{}?Q̅o"cZ$ *V jTUA7LT/|Ŋ(6@3ҁSe4x:hHZA)VSC3d5ǧS@w^X#*aW'}k6W{L!Ѯ>vV_­ 7~~5IkℑY 9|ʕ(Wy5c#yy hoۧ_?LAeFe',WA>^aUW r,bW?ͽ™bksewܕ/S=ea+Հ(&;0(/'E *޿ j6~ޥӧCKk{MLbs ٜ©yK1OW2{ C_7}^?JS}Gugje6{5J(pFS.>Ww~%`&XnWz>- x`؇uqmKgfSڸxz0*a`NLU e lj0=W>^B@jn5:WЅM̞e0ooG>}}8k霛jm϶'A|u{͗lqn_?!".fXN Wіzn͸$ST}hAʾɼ6Oc"hH,S`)XH3Z0fLfq(n&yov{a_X(#W.s?!4=0 AÃgXo+КXWy}%z(*tLOsΑ*V}a6 3T4`K2Y0{"Z(*~Ya^=qq?s/|$""/Ύ'Y~~/tOx|4DbJy#JA:8к;0p h+QMIPo1R=%Xe];ˤcqc(ێl=rJ?Jb4>dYދ6z 9u!K5w̺OJZ' VuJD|u04>_^ffVZ>QS{?ԡ Pzrz/h3=B*yNViCqI^R.??F udvCӉK#^'}~ #BL83W)m˜;+N)8pxr]o`Pd3;BT!Xsb8 c~@/4YeC;Q]H6ԨP3Ew'i4I͢"T#?&3=!~6hHR^wAGY=52#I SJ$HfUk7F ;& #[n\Mkцd8Tu/`ϗpyġEOޚTh!45>KeD~'Ȟʘ-М̥f|we)='_Rk( de {蟭pq81~|~#޿ٟz"wU9/l+{ '@-xGRkR1ҋ&D_WG.Z%'XKJbg鷺TJC^, ,DZ<6@"=F(dExj_se:lV-B䙧[G^L %OL,~TæveLjC`^`v@3%bo\.HUOQeĻ%$OG^`##~_>/VDrOцZצDpq B~zo>c;{z=IjWEA?&0:9߮ ;M=)%63EsWYyर/)P y|؅z@4Nj 4`L3KrW\E*mޫ5H'6F O!*5Lo P/4Qw邪{ըë?m+N *s?*#Q=>X]U9>x󆼥X b㙗ٞg˃Sľ%[?I xN[#giƀrfWe(esbW !ggJ"(;#(# JP\1ZcU4̯RhiLpyj_b WU\N^L\11Y/0Cd*doRP]pw5ʕD;K^@'jKL}"6t2yOJgF(_KpF:.a~O&=9]Wt88#zQ)Lnw_~b' _1sڋLc[2.\..9mu`b[DWoZ|&#wz`*ERRûfc=:‘\r.)*LA*V'?E(hJt>%zG%fm}.seb9n/ 1H3*{.}\I*L^6aWXRi~r]%iY|cY@ Mg%:>3q-`=.y0[pSb= R)C8y[SiMt(Ohnϱ.r˜zq*}v`cl'q4>.\54}J=u_̸)9Ld!M"i$`v%Xn9kZ KW|9m, V u1O\>%8^/BT>V% BVoq?Gu= b&'ߪ!2漿,~a/;٣/=Կ1]&F4z$>2_^7n-E9k̶/ul[nQ@+؋*dR.ZߝŘʗ51EkHqu+CpqnΤdzjZG^=" J^` Cp; !;1fH*T q|a}U鏎0vGgvX+ 桯@ݑ\*,/a?[zBP#Whk< G!|)E1g:89:K?;sO' M)VX݂O|6>e:BA)R(g #ͯe1Rbc%T~> "HxYP#\£1F?Ȭ,u9Gs;\-wԡѠK0z&,h,c[p0Sv( n,Ki uIꅕIg2P)i*6?cS4p쀸QM72֠e?(pKffaހq2aR~ a̼ƏNn}X˩Duuzdi4KcŰ"2|CK +{'7Ϫ^F,(N0>1.;4Fs> e%6\e3EƇsWh'@y)JRcHfM`d NW,n-h ZAiXq8ûʔG6"AkLo.yEMv:̨01=f;eVWY@:hR`U(re:R|#( .;ym^,a+{AsHų,cЊ +o&m? QW p9~ U ^KN ?,V+֮etLjAt2O No?U}bfJW}r%FZji_Vh<1\V aexy?)فs4GzRސx {1t`?u`,fb:. މS}͖#yzgvK g0g=9 n_H세AH[Ϩ@b 2/Ӓ}ܳwzKaMPD\]mANhqTFԾ&v|CK2rqeśmS3\u]wQ9`#~.e2 Mb+c#Ya[=4Vd 4L†Ľlu!2f+Th9Yh;+3&ᗴj3_HR$t%pOiĿR"g'"^&^bOB.\0N=?/.@-Y'俨co^.;sꨑ_ğn+ϯ> GsB fZ+7Q:}ˁvlة5L_F<,3uqm Kf;r=C;wqx0fTiu#cNc- pi^fF BCkĮ3䎏]T#\;|j2 !j Xj|U{>&?pf"uX3 xAΙ//0W`K\ͺ~otbVW#.".?+i@kyy`clf.H /(v$\uAϨ.`:/*JN|zv҃ط^ez{ %lk۩gYs S,._&\sSZ1tY 3 6_/2K+7%XR%JXܕ\ڹ'xAi3ys\BT lrfw%5tHU3w!58\kE6ˬTZ̲ҝoJ+Es738)ṭw]`㼺``{ep%L@%^"̻G%qz1>?S0`~$~b@4‡oЅYKRE + eG0C1Ǣt!AYôrq=/N=MQbvz'SyK*Ѐ3:thqv:t}Ա'@ݠl& hꆒippτaĥmKy~ 7Eǯʹ`˟Lg0cJ̲𤧼]!dǡ9q x_?Jm(Defk0 =Cc^+_SO>/DR#Msgn_(=Y:}іцϠPQLvߦ~ގ/ڙE>G(fP(tgh oK*{a12̟e.1dM*=FF!KPkJeJ:1U4耬8, HsP~qPu5+EZ?\idn;T F4UK0?>=7Oc$sej{Jܡu.c./*6Bms bNߦP%Rq7Ĭ럃UO6׍ͺ'.d; gw{__})×`MOy_E/#7ǔYuN?";Ga2n`.s7M@/.w({Lahl5$]z \˵6ZT oB_u.7p Zw]0%KQbfY|\uӔʜ@I2˅ٹwbj\C7<Թs~s26Lz\Ǐ}T o|}*ZMBK,u#>ER@tY sN\'Tl9CҦŋ-8Y.\Lzo7G\,%7}"ĘGlFt;)LuKq^ڟr\'O_&8ᝯ32+ - ـ{ahF8fYx|s/Lhi]X7qQP \ffXY1.zJvC̩EiSC&g.ZM ˬvaU˵Aphfxksd1~!=Ұy|:s[cxX^{˩91v*=F@Rjx&>`5jAjz|LGM{7/C>-cXF+1p-1yuz :.Rbqqq_ո`#6-:{b4\bZ5Pf.a9n`P5PwEkn.0)U+9#IcB_@RcOjodmSy #LLיFwZ=q5$5-N 5,Pʙģص*CF``kQb8pI66%)-Z(/Sq?){D9ՊpNa q "ӹg oC(X8e^aLUaR 24N: e&f;ߙCLdO)k'yb'hBnԘNxksC0c Kh sRn"ε7ApN^Z)sdJ'T LD:&@{0:Ϭ9c[YA'W1dfBQr.V Yjgx8PÐ^97K ,>M@ݥU{*)foX lP8+X orDbSCp!e`sC;-ds~"JLHM=eK0!Eg7/j:36KVa`eF _KrԜ-0o/Y9Y1ՔeB |Bِ w;L|o,#sΆ*k,_'^QTv6BrX׷yۈYz 2Vؾ!n7&HG\Brf 8pr Ll)Sܬf7(NS2ԤjwPʃsu!W;X))Vq4l-C E|nFk:5VľuՂV#6%LLa͵b_7A}"4[&^zYX aꍖ̋:n?w^ }D6Grd_,UoyUT0-v`+}IJ5̡}IDC/22ǡpȤMM+ɝHCm\|D#.o='Xjub2کm@Rj~)뙟un]=z$S®9\LW5яHVC d,AJ]fN8)h K J}o C#􉂇dl U sĴ oAdnV,i#!P9&+:Ea.|hI5.>lLirWqm̑*фE(j5)nYAH{p[Ũ2Aqg5 ^!뮺fa(޼A @j8S~ՠOXJ-j H[0V62n2Հ q5J.\JcrLKNJlB7ĴQW0sXʇ=\(3FCAЃqbs <@M>kDNd[:d:J,Q/"RyBJŪ+( |=*S0!KQVL_9%_J[ %5afd.Nl:s|fĢn9:%'+MMȣy} @Il㭇X.W170#Q(a]a_̵ufWZ)ePӬULS3`4#fB1ϟ9R,E)b!ίL5Ro$4)87Q 3ڹszXq <=@Hߙ!׈|!1eE%r98k0g\Js9bw-#XPtF%C!9w<3=nwz锁 [x1+z IV0UmiFOi^]^ܼz[ͱlmSMU2A%xt-lx ӬpiR7a(֝%{^.%ԭ։_j%pjPޑ9] Tjbt]\ʹwru0wkxyTp/85ʯgil=,%HFCĠΐSMl<}1W엲\$-gюJ\*VⰕ FF/gh!fSLu2s:jA1B\R_/BtmP7􉺅 "_Q(x-aT_@ %%yHIz)NL%Uat<1V"8ՠ#b[e03ۼi^|EEC[n 7M;8b4K0mB_rJog4ͤfp;Ÿ֎HMRuPxYZ&EΪ~s1 prG,߈Qb}ֶnL뫘!0+̱q*8+2{ 5p1r.: /EMv˯;cgsX2<%KA v7wAXͰOIy!R6j,$ hfܣV*h(f!Ɋij <ﯴVzByyń> esSc"3k4Be. Wn%3C^A-a[4Im%gU(F F=%$3Mɬ_0IiKaB*L1fiehq w|3dz=g 0l҈[w!n!hXara» -xsˍ ?˛c8 ڶk0\trg?^c.0)fFFƙJ{ n?o qHT9p#~cch@8X06{Goba&Z\E:̶ya./߬̒ S.P,!Ut-+W DJ8jR6v0\ `ٔt:}ʺNCu9xQPh=09^zų/FO~VL+dmkEOA+15gIt*t 0' :͑ dJfJ&1xؤ*h"9.FD:50.`Wi.D5J 2BÄzovKqY%[eጱZ.T_v7YY錔Vn5Q%07־}ۈ]"l: gcit10l+0f{qDqw/"Q㙟73D(R3/ΑZônFFNQͮIJ YQW1`selZfO@b "Sc7&g ȯ,\d{/ʼn/0]V" ꡉh3S0gYH[8uw)KH+zj">0*Rg-`zݘfqwYN T9GL˥_PP1ʌA-!Qh8\CX3vB9Ɣ9FgטyV `!A-3Ӵ(ϝ &qtTz~pP:;B9WAe_̭Hq.tg@$Ci0"@7+'W: y\^J-:KKU(r&HwKOx ATv8\+g߼.a ]!]W,ψjjԼc1A .JXXSN N~c0B7~;J3( ޶[r8K8.01ONMr{EZ9]K.LyMÇ@!k6F10cDn,F{BQG7xP(*&/ *t:E^T#(u:$7T(wG).\F|"ӉKmo )BºTʛb uBS0pwA~kF-,cng;S) , 6x1%`n}\(ۯM+Gtbu64?`[2yY`)#6쒭cy|g8}Cv]+ytp3Fp+dEe^R_"کN8?՜MJ-Ac:P0z()V:<1PHEu"(Qck[C}Ok>:U5e yV9+gbE,_I` (2`L봜ܛVc7⃲[*jqؕ @Z~EnJ6Y8BE45h_/07'!˜K?2.t// KWRz!8s_AfC4x`lKTm&Ol?.ds4! E1S#tR8^y{&VciLC-VvoqҤ;Gy\F14WWhӕ1R!a 9d% jr5\J.֕! yok5 EC|%,ڗ2.BmC,:%9o] x341s(Vr{grpD_X(pPm g .Fe) ^a}"CtԾɫŰ44JgHK@EbxJ})*< *WK3E*s44:sQ3Nd(*rZ3lp{ПWMӣw"F}#zIlb _;l5yFN $oVk\}ϩ}ĿC*M`3D3/npT[5'7[<2/x53lٛ??|ц2{`UFǚ&aRJT*N"d!Z) ME[N' }ʶC>hAvv"JejPY|m;mE&UouN34۬4 iHjwwz^H\L}m#Lɗk0[}wP_ȼ0H佯q- Y7ňXrɱLuB; 0Fu_{C:ڗ`sá7@TD2$(#0/v: rE mc Q!wɆĆf]ΪU. tY=؜Zh}k||=sw*)\IV Q!3-o=uF%mN=z$s]kQ0*V#E6ω%Gܕ@yVS5bv'J GHZkpQx͂6\vB.D );O7oXyx.u~-hZo['ޫ!ddfsOmVԺIY +QnN XI6~Q5`:D%hA5O .!('T2R}hrt:ʐ H ԓT6PwVcs = Ғ$X6 !}`iSGXIMS:*0\$!F7Ds'`"}Y˭KQ^&'j;Kx|27hkqϏ)5 ͥ!Gv/YKэPh"1f"Zpt&=Y37o(l6),=XJwRRAċ[;K۲]mEpM8Kתbv8*)1 JAXw6Xi$mmNMltvGaN]uyOL%mLWiDlOL3\*E@ZHzJ&ޠq/)7!Y^9M+Ԓ͔ /~!C*R G[t |%Dwo5MC{x!sw}s{4اOaR/H Gi.c*#v"졹FjwBߝֻyKYКU%#?*:He82`*s]#]U"BϏK)Kc?F-Lfl}ڡ & {1eOZ1L7!GA wa\kLyM7Tj#+8x,w2Neob@;Uts@4moGSh'629r7ƨtoWVH.[` p]`8* >h99yD!~g{Nn9DZ[>mv@$h|%2=ҒINr}n 'Dt9d"=XILoJNq90.-Von TQM {dJd(Jx׶ 6ĩ05R% oid<oMv& fA&Z؇y!MNLѥ*pIbMs˰&pƕ ," حB]yʐgYbQh+Q'm{6\{Le+8+yH]{ ph|&Zr MAHO%Ym{@rbfQ硚T+~Dt!LR4@|v*v>«ti]dϧ9\6#~m#7R5[=Ja?:XEjz ),Fm lEE7S-OmP8w Y%1hBM!YCZВVd؃.;&^OonA?zDcYQ#m'ό!Q÷R$D2^u ޿SAEy{01&l${Žl>%'}I`MW XCzoejZ`ʎ%-*Μ`fåBn ÔJno(2`tCCnJ  CG̲߉H ?Ll}hدR!S] t^Fv&&ޞ0Ú>SUE#دpFFy/iʌVE"jI <=wrJe692dyNe9t1o U S4"SYP,$r#y);ɚR<28Z듞6TpA_Ouu0W4:Q-8:myM(e*FsqH,JkWw6 Umwm<=Uv&eE\@[8=Kb!MTZHo vgEn$.d÷!)q | zmv>i85G;6~kpYaIm|FV_Yd];XCKD/4(Jm 0k/{$dc 0V_f@qM 'TUg ]$KA>]Y Z.L4[Vxӂ쳿&NZxp59'l0yrJ4%j*cMQT[$k l|(2J܁ʕRwpgTt@}:R3zo!8i=C eVW) D^l3Do[Kߔ#rK:[6lYm+v9Ѿp ?|r;qnJR(‡z&:2>-TGO+̈́'{_p,Ɗ}}[&_( KX֫g6OX5vH@SCOTz𷙔hd|ʕ 6T,vqv7ףS ?Ն_L%͊ćlEkazސB>nUV#qvJiz}-FKf~%+`2_Kږwc-2ݪNU"4jF{BtAdcrE%(N.V.~%Nw^Ynrvh fXZ MƖž <Nv*Zj'\;=tRRe&`"zΊD Cۿt/c=20%DN0wTЙ鈄qln8j?6"ΉkԜVst W\85f71T" C p)cF-pvM Y8u̦UVyrPV-n{PF-趱S`)yD񲎑QYbH/ӝfs6c3>V̒cb; [lK|;Pke,z,dUvTP{[AG!7A{7vCɪ_YJ'$kMuC~)yH=jP>2/ ddH?sh3g epfN2l?ט,Q˸r-? ^dgueI#!1 AQaq0@?ᗖuxdut~ կ8ɑHCeؘ^!{YYtEϛ 6NGdIlYl<&9~'̎ɓC&Ya,<}x8ωqzId[bo!5e>-F S~] wDOo(N_|2lӏ?B[iuup"˺ ɐ6`> γ7v?-2~{NK,8j3xee .G/K9m= ׍ >ܫ3mBmr׌%M"Y8㭘dp//=ZZ/Dj˒{>ko eYdq 897DE͛e #<dːRKo~^Y0NeO$ I6)&Q 9zcbw-r,՜[̈́oVx .[uwd>6aQ>䇋*OeB۩iN,;->m0Lp>%(ov0_M<"2zH 8 lln#,@ZmɟBo20׻X׬i' dˣOCci E:6s-.M۞mHv_vOBz^1=mCt,mmc3lq{gsC.Y61ݣxm㫩e][y8:׌>Kty +| 4zڥtikڞ[kjxڽmM[]'Op.vormm 6 f%P9Ku ^vmݜeȘ-rHUVDI >|%'(oÃ᳟SS\_fޭ`90,ML Cnl-Wݺv͑VԻɭd.0^t5[;F0Qk, Ru/C?>Ç<-mm-meovw]]oulIYN 07<͊Xd EvG MG[nd$_[·8mmcd^JyfAwղ Klj ~\1 f|0Yda- >C} ͛#3Q1=2~ e? 8S?&Z[A- ɛ.YգuO^޸l]~H>-u0uH nSa>Yw5mdKm ,,z 3K&璻wXuxԼcnݤў ]>#Yf9v;[܄~?= rg:g">b;d\m#o{i{meKVc2u)m̝ g/Bϲ'ENI׸Oe}%l Yeer%<~thøFNӡ#ܧy4#^rw7An=pX2e6FٗS7w7a'쟜dracccf,SKd;-gaX`Xwf #'pgYw御a^E/rf1{]6y#>[ݘ/L[c.=tOYev8lCjd· y5CE^S$.iKLvpYY>߸ WR՗V8w$v&.$6}[}ݼd[/6DS}n̈́6NR_lDpnX%Wgl'e^UKMw!ٽHD}Ri@2 ԕ͟ploi[딲 mCNxp,?fޭpeOV|3:drI"I:mp7h ``]d9t}B= ܾ큿Iww!aȿLe }6K-W_]۩~ڤz#rşA #cau vs q}d;~ Cil.,>:|L͍mCՉl[bdd%>HZn c8uSg.85Y`&Ḩ?݋b4|ׄXi/PK-o'ۺvA~SϿ,G ߻-[_e6`~,ɀ?,X/)Y @y.ڷk+-alfcdo?fωOvDYs#ŋ, /\t[ a.H=Mŷ>-b6eͲ<{^_WC x/hn^m˹~ÏVif{:& }/l( 6VXnj<`g?Sc7j́ak'jS:N}]t.±f@>'7_ov?l~l~ײk&pݻ>dqy.ݶˍlzx|-q}`θN2:&q 6ըVYI? Aa9 ep.`ɗ6d6p݃Nx.8Nj^Yl iVh 9I}_՘l~>?H ǸϦZ+i!dGZ1$>>+>Ǽ}0Xe{dyfvl ʗn}e~ "B}#EkՖYd:,ݚy'\e䳜Z6dNYzpɰI +Cl=;׃\ xe m2I26t6Dl8 OIoÌ]16S~\wɞɽXu>kܲ̀M oA0YdC,,rnɼW K'&sr*.ﴞ޾#2o?߻؁8R &d=A0p }G `ŧD1{D;έ%]azݽG}u˹u?O\ #?,-eNAS{Urjo!Թ--w̲Z}ENB-@퍫PMjԫVBmۅL@VYΖ,شmV 9#6g 7g]Odo Tz6FPޓ >0G>pDtyP'Eyៜ$>lSӇy'K݂&StZ m~ڵmyeն,HgxA.*mó-HGr%-&ؘx- X)f[j(txxCM6|Yΐ tԽ_͗q'{-9 o H3$=8u$x[3+{9/V}ic9=o q.lI6`X]]XFXXXXfAf̨j :Y{|erEa2{cdB}ٖ6w2a-݌?AztS]c{e~-,wYg]2O;#&Xxy{mrz-ߔ  y.OryDmm,r x24&R 8T왏|2} 8x l}l`&tɽ6yYguZJ[l^NlGQ?!)9?ώFJFC=)1[ :$u忲"N=Gg`"t Km-OeKGwLن}#%1h8JB˾F ;`8]ρl 8cSg.z, IJv1D|৷Ե%-Q8З-n'ќL;]༷nVfMimlV~t.-nH$E\bc5 %p%-m=p,6`XHxݴ-#hGd[̷ހ6O =l'>O`ng۽t^~ޤ3|sN 8N Tz|t_V7&A` q'Ryem{zH [wgvVEC =rDOl,ȳ#8^0GN2CH]4<ympK&y:`X/V&r2y{939uiu?A]J[Pps-\oLI][>mѫn Y[{`i^e'"[1A!:eկWfፆ\׉+6 C{-n񑻒X;!)r:w'M.k/8o>ທ݌'nݾCg-ۯ?XXK,lY5FX d3女bzl'v0ŷet_ C>-~J[Mwna 'K !汶<<2IуŒm-g[.Ws$yCz,@{ll}{&Xl\_u#cobC]np»,Mܳ又txHK};-e>l?85>],g$'C{ݛfc/=ٯB nxż:AABޥx_VYÖ vO p)ILpE}-ؓ#'NJa[*6,.b܋ 'rl;t7ōP H@#8qwuaut{`vG'w:Gk6 'c  OF'ܝlvgfc⋐PX{͒DLut-''o_>eqݓ nlw,n]"v}n+/H1Ի wdH` S5#ٛ[9n ps\a و0ĥ/θϮ DxlZv[Y+LY7Ö=|>H')b݋m#dq1xf~ l}}}`㶄kH}RiY0:;"]` ,bA&Pݺ N lmԂ'%^K',mG˼}VBxX{H=yj&clv.󁜗H0u$+Nl08>#i7C N%r-]"`$& =,Q&d $p|J|^N#.=xz["+䧏$=ldm= ܴ9WXcū/VVv1/-6XgR ]\$pɲ6w{e.g/8VkZ3O I! 5&f8 {Yr;KG{u)'gr!+;oYdwbm^˃{Ydl\.CZ(}OdIg/88H=Eg<6ѝZo-[m #*䩫_Sm_r6յa -7fyc@l2;{GWlA^ gMb u}[od&ldezV׉we?x 3,HmX.HY=]v}%s"BPȏ~|o-97;:ke^v쭱V@3x}C];˵j@v22r<ަ[mlG"ga8CM7b\g3 >ٝCPmȲyǫ!S.ٻps %m , _8鰟I0,p_l71{8BFxGKߖOK)pEKO,6n#v5nٟB^eׁd}6k/D3]lʇ3c} bOHp8 gecSw}=['[iG{ u2=c;> S{A8d{/SXmKx8|Vo1,`mv6u ?/ &oY31c,6{y8!d'^=Írw|huǩ{3/vdF_RH…]ܹi1s#~[t:Gz 9ٖztg'ˤ WzlMF,nɷ'v͒O<0ݒ̷nիԴ !,|xumʼn,e mm϶N= Ke@? K| ioy.F>H޾9&6m +se$pK`Pǽ!HO,`]4k/L%ӆY0d:76O^7 Ya,%.!~zwU:ͷ^-Pmߕ G픝[&%ؗ[g'T:l=.H1Xr 3۷gM;o%uՊ6zE]G{`G-c>x #c~`l?l?`~#_tu6Ih6exQ-(Onبm[ўŖR?dمl}Allllc{W6^cYeɖC|Tݫr>zoR)vkk o:{o)oS=3Ӈ.>2!ޯdl٫szsrϹ6(ݜtuImZpKF 0bd%,>Hq4g ,,ѳ'Yd!:v0302x,ρs(wgH&qqnSCHYe}kI'6N6Yu}86ˣc>zXeӇsVwa#-8Q:m=IeT` ϛKDh\v2<ѐ5}|o__c->y1 m>-]V\$r]m% LrxI1 !Z ? Xa -[1B,˲QKiZKw6lzqA ~\ۇvKjݹyj硲dGlpprK,amo\ D.9 #ِ| 8yv-]=: )!'ſ8{wwklH0[k:]`݊S' 1X[cnoub޾}m[ܻa^6w89GmAܓc{v3-"'\wzzf5 K$ltNƲ>{'vunjLYe݋:իREumbSPeQdYpDyy&1 ZoK;Yw1XF/375Ehl8mzws``qw<&Ii  t9@La۱Cퟻ:]a93a9|6,/v%"R->`P{mرiuu%:f1>&lq "mKO"=]uewxAnq\=!pښ]A-zlLěKx9?dʿgMy__/FckG.bSw0Q(#xGv@ݖA^́bVi(,e8X-LL,X]؂ 'g<Ւ<n:M:m)ɐwg{jw`6L djT9Ւ{#%Aq:qypEG7]<%.Ͽ<F-v=YR18ddA՜3?eݟ zu!#%_ 2 :LΥvؗ mط챌(T3dKl{ Igvr:<݄^]@Hi;Ynϱ>2ur%sෟ7YhprD=xeYa !Fq}GI(!vvf.#f%@A81<]l۔ot8v3H`:fdnŽ\ @z`9[O;#&7$NK>Xڱl,c^g!fd!~g%-1ьP$=𵒟c zǿ|>z-0K/-Yn[Vp8~+ lon ;f[tLNvr3-캶cK aA8=qK,eV -.-X2'QxK0c؁1GtpK~x8=mq?σ,A.qごp͞mXڵ.WNu /{($yk[]2vr?Q؈Gtz& A:^zqm,GI,ZmKZC` 'tZN}GekV(gGd}2xYqo:z|Uoxd b|3t6ۯI_v\ 2k Yu:vK;b~^\nvը,6,!gBq;!!#{._Sބ~-gx,x<5t)O$>oF__ſ;ioF$<+h|_QB񼷰I!!zԶذF8-A^}mn˾s6Bߤp#eԻˏo xn2ӄ{uYt/<,/^~pq m$<0x/ &]M 9mYd ug\yۃenaAu6x~;i<ޥ_cǂ}~\ ; miik=?plXG> {/3nӲ',vwhI"ջvHqgۻ[^@p^^ܶe޼._0 fkfn^cϊ>gx88||ȋ/#rz:`˦P|1l_`Iʎ_m/׾:KOm#r`Ǯ2lӜ <\Wd ,Fp쿜a"~~9Y:YrdtYd,'g &Ld׶~XlYiI8Aw^g YPok,e?ngg&罃񂽲N!/PdL Bx,ٱCcWagT2--vж V6{gV_Ր7,^}f|v|]=Z|O/|}z"k ۈcvC<-q`}&ȝVmjZgauux ea#*`៶|m $F=>_r.D?#|z"@gz->I޲rrXGZq2X˥G>}KWk*Gi`،} חqqk댖mm@˴MiUxSmpl\dC:p_}z owQ)N:el~_\}!u3vqOlYzeaf 8 տ?ݟ`l~Q_W}nپpL[}'#lKb͙ `24\H J2/<@!:c~Eu#YkbuoY߮Qeݿc 2c}|Jc~03[~\w{F=Z}x^b݉|$t<޳m1$ٓ]})KώsՕk/cN{o:Z2,Fŗ";:BJ5lZڅ[Vo */?-#zOXd>{z~u Ԧ>uomw죽e@Yd{lCf,݁c^GXYe6dO|{NoeZ>oR%'/SO|=oFK='\-܊xȄ0 aӳ6IJCpGw|Co;q8 ԩ]^ar63|ٟy!|Ar&|8 [j6څro{+CZjFNla = =Ba:}j'_eYkq<7v̕wyBmy0%ioax}!ܶcfN7pymDpE1M9|I=Xd~ն kkk'm_l_ed,W]C9;=. wl¾1>f$A[!%Zɰ907/#+f3_;ݺaIg䏢,~_ mc'!6ijKbHKKKKn'2낊mեSKKAb'9|Y[xI<~g>OͳzXaHl;^N7bM r2l> [vN5E8hc,}Y.z޸W e!zo x}$,EwV[^|8r=C/Տ{>=r /]1ٷ1\}roOrv$ä;v}ue{@{}lYV{elp^<E(2LNv Hn&} >nC{}zrq}IM=e=6d{mjHڿ"[Vݻrx9?,~]_~Qd udD%/#tHv#𾌿@&t:5ԇ?~%zp#d?q_ O^7}ݽ_Oퟦ=?:-~WݵjݬX(lɮ J19|CXM#iGwh ޤg|$=7e^/^}m oFs佳ďvlsVdrϻr{2;@-uzg =O[ r>ȝWնvbemmL<u<3N!Jԝ&{xOxs.76xl:I̶ؑ2crd݃&ݘGvc%[e[nOwgy=x߃9K6;! yƳϐ^ ^dp= c;=ϼ2cm<{8f*C/Rm ?a=od`B!xKXȚ=% x-+S-{!OR|w+2:x'E[>[|vqis>7y=z[n}x%_x|zp9.IVk3쭭U=1s/I z#^56!_7 b*:!]-(}bH];7-ubHwgw~I+6;Z7`AL=.~x&e̲Ir=[[_sV' O m+%+`csM[f|g^Z @l[If6fX>Ɇq㍘!ρƬvK݀OjR;d3m˫/aܻD /VG|6ŋ3oEy,)Ynϓ k..юَ"fp&Zb m#CI]X^oordIvMY *[ xޭ33m }GQhٻǜ%N:yoHH /`lwgn,r|{G[G,K-I'qDCaXzSݗEgS߰3=Y!3՛d =ԟHe 㗨xC gC/9XGl)g$]O ONr ^E:vIXB ;}YmO>ly/,X%K ܳ8{aS*e| 16d,&!1A Qa0q@P?6^p{-,è2K"Ѳxm"8ܜ?wˤu1a{'9#f_g,>[6m_՛9/ /x3vӀ 8z '9?.:I%o\:xx xӂ]VZ"[tiD#l#o_g:[i ] mɥ X,/Y@1`v7lڟc휰laNGp[[Xc9l9'uo/ln͛!maՋlX;K,usXm-[g-x\8{wuCaGoYwuaGvv,-ŲYAb-W'ٝn[ J9CT6u:I"{ed G|m-l3P|dR~ Yp[2 !gfdtx= nՒRzHzcld쬻we gf\tθeYeYcwyqYmsfw=73F pթ_Z{,Cj~;#-!%HXap, &&_!]xlYre-{'ٕo][Ȁzଐŏjݩ~w 2uVd6Ycx$c..g9Yg {,cK=3lldeՋbnZbPB[L[N{HljO k?coﻣݓ,ݱ&?H}ZĄ.{+ onHLnlCWfFDnٺ{.̺aǫ]C,qVd#[[V9`' ;6InK-v-2kmؒ _Wflf9aն$l=k+;-ޯoWNY'.n ǰ0}3gKܻ:NLlZ,{cDǻx$l u6j]ds |)>0!2Pޤk~[:&6Y0olȴHl=١=c}gNߓn!՛X:'~Jāl8 ytAg %9I~ﶶfFVa f->~pHv OErY=?6d S|nZnH;ӓC Ll= -G^ud8 JNSnM{b]tB{nv.Gzߢj[sۇp| -!_MO-P/'"#N&0#ޮɾ )u fG>ےó{eu =4޲EŘSvAA^6gIc;ө^Kɸ݀w{ՂIf6:adw|4`A.>!0r= `]vnudA{.IlPS:._/X 6W":pk}K -s)mrN;.gFB߄q.C-1"dFr[(ܝ̓%YL'm{M|%Ϻ'^S8MPzzvKd'f>QQ_fZcDz[elΎ$&Oj?KH H;0|XvWmNa,? ^5a!o/v[0$K ն{eui~۷QR8,A=٥,ά̖6u݆?hbg) sfw,H HoYed 9] 5I#~Nt,wk #"d}mj.!>'Psmx$BoQm>6R0pv[Ig\ّ8&d`pIeYY%X&X@x IgnᴴԚ6:/0,q}Nkjk][=]}->x0wP[[ ow!~Y6(P;didgbŌu8cDYd?`</Iv1c i4hg*̼0[hmэdB0z}:oބ] qak2񞧳&[EY6J eoN(OSPD] B2ɃjkJ5gu<_׈6ckXOFY9wNGʉcoWfCIŨO89._l~Dlմձ`>cn` Զ wIc3-,=ul? R_0d^۸ӂV$]`ެC{>!) P>ۄr,xCgYbF ?+mgXvDk;3g<q0fM[][ݜ=I]l6ǜO~Eå}yg~V$l;Qrkne8q_}%IvbK 3]2#kqKݫna=fo>2?w/-{&06gnEpLmxcmgu2I7]6K`cm69=i' lnH3ծ¼=b~TO-l2]H<~İJ{.͑|fnOoLEx@L@1_މ 7Tq ݘ)$IgR2A{}^Y{lՔ)b3,yh$ޝNBX(uXAt:#Cbnla3ۃْ%KH> oOZ1v0~C>#${L(@B{=C>J$L+O{쏫_O ׳bGo[hIwxw>}az2v]_xxSs R:gHDdehdHBC:cR2t ?J&j `8A7wmdxv= r>^vByEoi :kz褞YlNa$}3,,n&e; $+->Ǐd_e 8Q.-p32:){v /AFO,Wvݹ,J]TZF+ /$h?gk7{_a{+uyǯ=g]:K#'Ee1yIHg?QZoCnQ,g`y䳢[sqa &PH^8)jpOwg1pPb" u2xpщ˹Og%#쳕ݫH}[0?L>XA7^6Ǿ'!ד%ia?c8xIuG( rJH :' hDlٶj튞CLݓ4dqt>@#: 8߯ |{;Gywz`p&NX}8Mw!B#,=IAEH0E{w/ܻr/sxg73 =:&QYA:q0.ɦX#1im avLk*Je]a8>aJue?6f?8]~6VöCHԏg{-{oJ10gu#.4rw/CQ& ⿂K<~bݻ~-l>>|{%!-1a\?u ?|!ӻ{  z=/D]PdEY;c_L `NBv1cٻ oCmeHlSpu4H]>KGH;mӸd'_=#وm,ĺM0Z{|/c8$ua?2D-1m~.m߶~V|sz{`R2Gzڃ8kax!6xH;Å TO cлgaȼ=[:.pR]t#U]8K"Ԥva<#`Eɼ[o)a2F؏"gd9Y]\J ;Dcc{zw ZH5ܦݫ[\L]rlVa,=ط%{+LDD uiH__ 2jg we4t ~C6Oܻ,L{Ɯ.\û Bn)hI1-'iw/ h^aԢUC%'d^~۱o(ّާfg(ޣ>I[t܏}*O(LH.>B .џe<\7;`=O[M?J&N%_`iHۤ ӹ6x1momqqԿmB_`~c)gcXOzMМahk-$|P8Dg7ªe'K[c* 7#${%` g8mX. ㅜ^,p~յ}?S$qݢAz`4g>d:Ya qU>C9P"8LZ+l0<af들p:RoWjݑF­x\&_f,`}!Y@=e7S8B~gcm5C:zaP?ܾ%i7}ppAd8G4iѹԾ`;M 逕]Op6^i lklsG=e& ;OWYf]wն-vMKp/BlV"uXK KKސ`_cߵ+/6ZO<7*Vxj?Jދ"zػ7 6q>0gqԷ6L.R*ݞ?u[<͠}ktlQ]P3 k)m~Y2&#=ZzV"B-}usm"=y'`^̛ \ /]¡>A۵ob7r^9}EW/d*οl Xs #v t#-V&w"WBXHr!헐ygO f7rRp'UD;Zb gR/љ z5 q,@Ɠ39&7^Dqs ]-f$Ec0:lSc/ᑟB~W"8pe6l[%0]qfj{Xmcwtٻy,?6p&w,}P jݶpFhṭaRcd=m~aq[)ZzcA{$tFK˨~{{2K>@^Gْq3!%xauS:&; @u'$lp`Z ps3,ȶضӗƁfP`_$bbm ՃِW#$dַ߷g1VeՑ;;r=d; d:a#Yݵp!L'/Lt` ϭm_!Ȳ@) #!p.,Hp9~~/oK?m&~`,bnd>P{9nňńO}$ m|CƁy/`YGEe:glگ oI[R ̈́vJlnvhC` w%}`I 4Ru%y¶wp6ޒ)vSbӹm[guf! - 3,mb7e ,3Lɓ6_;uzBܵGsPa tv~gWqT˻g 0Y\33\;1mI }òؐ؉ab [߯$uΛrq۶zO}]o^6:d|D l#RN[s |ِ:>6cwj@lNN O0bm%eFC]i?p 3rdh]vVM !ldgkdeq4D1fGP^αqD,cl̗͹ Dǭ:u>\x]u#t&^?,8Z#?e>Z"c IФSz<ڧ\e;A^-#ڹ&Xˉ^]c'o u?Sc#?$6t˧|6H|n~έ*OؐHF&H=6`HV1!0!R}#g5A 6a!qPz>Pp`!߷\- ?eѳ0ݒ7P=. r_D5.?RwWsr.'/<%% Bзu7,fm &Esg-{9L%~NARc ۻ]#V?Ĝwv-n2\fݙ Dݸ: 8;1ɣO"%nVSIq"j]'ޭ;]aNPY/DYtr"UZM~Ps}r-B@CJc>MwdLv^& #c?HV&l5zyy978\6 Ho1"?ra#q~Vx㷩v]m8ha^ed}E%p٤g'e7D}ZHG%ؠ-7A`BG0?e!!~˱zC8:P< ˻喟/6_:uzZa܋b۷% O%#IKݔ6إd1nw{iCoV0պ~^l7zeۻ{I|T:i&LU澖3E66dm'8&d9YH&ť!o{d<6ۈwl+{4^2a _a# gq,%_Sbی Aq)L,F"O{yb,C@A |+_HEv&߮GM gh" 2<@c,Uv38.doY3-K3g 4H~8&8xy?/\_v_EhBF#T:f1>h ZY zqϦ3:$%Y6"g(1J v?|Kl' &`6?,V:׀9N 3mc'!v%K`r0ۅ:"ԏj|#  ?WLt'Aj/]>L#%H6v23-~bip׌mfJB$ͱ)fl% u9"Z΅,q-cMq>qz?''3'JX~fۂh<`xm^^ؚaQ Md2dLO&]A{LI2IЕ-{K/G"?2I^k^z6# vōe> 2DBLrZZBrj̟X2GpQ|o&5v<a<Hdo_=~qϼ{|l`P%ccw[Z&w/D~0qW=_}[/࿊cBшgvaM-}s;ob= .8yʄvxI8-P}:iYue["BÝeXih >ֺnL/__htZațe0uuu/L,9gO$xf$t]LZ$U3{-ִv*Ջ7vJJ?2״jN:],0~pG8>Med8B]@e]Tl&9[#>x'Ela03l nNEȤ;ڻp5¸c8V{aKKc39kI9# =^Q-!uBə׀*lX<6x?,.5S躇yv37$a"-w3lvݖl*g˾W.C6orhHv|0)}SqKkYȮջܴXm?peM~['\LN <`-K6gI~[>ȫ`: ݒw=-&2t0`֒ڗK`?P~o vؐK]w9 ;d&[0^ŻG=L$܂k(0^^[F,`_|ĦeDy'MV> L$X-H$1bnry[8_3g&'@@`[٢#F7"m{< ޗc*bvtWtmJ$! bYӞ b$xA,~ mx-IjY}o!Ցhe;VOo=챬|.H43g,uF h{l` W%P;Ǡ|^3)Mc~J˗e'M'7j{`;$޴F+_ݥ}K7/Wslɟǡ`63Qk>#ՌF#V'L8OۥՏ 3&eZow фO,%M'XKv+#q#Ԟ:73[?\>[ix!۾;.|b/w+RGzH~m[Ծ_'\_ùZ>m Տ ;on\񭻷>e]ptKz.eݼ'[TyKgwa)-],O*zKEܩ޲,wwqF ~,PD]P&[[r^ KBWFhFy4bE@v5 l~y/p[Y}CwͶ`Ȩ8iVuƭ'`Āqɶ |Y zVI,~ӣ%u=͑.I8+ t_) VտdpG˰;j` 9m<..:{' #YhiO3YyGDVHvM =# ,{t`ɮ?/Z7lȴ,;{; NB7[b%.a;cp~>JBG9:cd. }=S'Y?v!`>_ڻykŻ.}RE#` &m[N o%Fεd/Km,,3, ,,*_I~%DŽe/><3vj| 9h1@^M.[Y ˜l;֭׌$&og чtپ',ddw~l}& .;{JY-`˧Vȳ/e 'Y;l)v\㺣\ıX- ?+njD$m2cvItlX4N[{ӖA*l~Zɐ=BgR/c{9 !a :qR9uoTnJ+*dmgWl'5ȱ}]v w۴ tG',G`o?? ,m3^#~x6.Mokw d oBI"i%Xzqrzic p /T,!Ek ƿ-8uDc6~eek!R{$-"pL]x,YP6]l0ٗO=u|/~},~7S[sȼw.Z2Lܝձ#F* "AݗV4&6ճgtCd#=%R^wpŬe-t;ai7G̾z~l1⧅l;a_|o(FMvFZ|KIUxᝁ][ /;w>0G"`02{%%Ly%2!: &o&x[<6.BğF!~0I돟~CD~GLƂv!0ɼf2=θj,ٖKA"߶^|g?㿆m[omv%ep#c0I~Zw6mRƧZLz8p1忊ι}Ԩ>iz;zi}+_k6 mu{au Yua G!s:|']XXXFX]]XHXHBXK]X~,?vYm},a,,,,GOPgfOJ{mHGa{ mf3  |% c7 z|C g`nͳvwdIW/*'\erVlmwėi ?  ~-.lqIg'GeygYe9YYeYeqYYgg,,eAg,Ȟ1իccjlll`m~~ZcYcc9evፌGqd(eOAѿ9?w&N1? ǂ3Y&>ь2|,ۀ^O? 2,,8l&Y/Q=;;NݷNp?a F= ˳װ!1ݜxgqmX2!JY{lcdRՌۢJnղed ow v:~eW'Q했xlHB] &C\seqY%'m4n_9!͑p' %H p3n LOyml>Gx Hݸ9vzM!zy{/ [B=o{uﭬYw'vxc Pu3bAKYm|^osrku{K[%sR:~D7n:.u0F?Z~6b_8ً݅n/KGSL1z׍pa 7I{daF (O! !Y V!NVl7sI eG?/prcdV]D+m;g#N;Ը]Y0خװݡpmRU~< 8~/ l v}θ϶m#7vݽJyzn6dɮݚq0L: #]m 6z#)[`[\̛2Vm! /-2AݷYo[_$=d6dJ,/=Avu7H6#E=K, {eo=@2X$xջnjd#`\aP[m3폼0n!#~X`1L^0} [r&9! c W2=^3w. Wˣ g#cG:r3w -lSl6LYϨfu'~KV. 98CM=-dǀl9@wi&Ozo=2 \Z-w,4t#m7`!ӫl=OD!Vr`w힝J?HB}e[6wf[c+o=-3$I:ݗG`}I 6܋=F8,#/姨_Il[X x.̇t0XAi!?Xodz%!p<=Zd_ A'y9e-ONY# l _nKev6zonkIՒarmltK n웷e,r2:` gas#`R'/[oẃ] e#grKˀ]>ޮcKl;N[!dqelO-K 컙O}o+BA oP=-wbO |<3o>ݼes.JI;" ”t5p#󶽑tcfshlkOnG'|$ f۱նpΞٴvIm:6WQfj͒?=I_ |"}xKݸ:2!ƄRt ٵ۳1ǗE5AՋ0C!@rl3DwbC$u1Yh<لb{N'/6! %,Eeޯ!`e܇QՓ%tZ{/wԋK %o<񗽗gcI Et<np][me7_xMK:Lc uc-Pn/is=`mpn.dry݅[)am}-Ig`,m[rg ;`Cd[i{۵ 3ch\s6S:Yr7nMu=Ću::{>c kQHb #E dLO^Zپ[yݰR>oKw9j.qYCm.x_rve-8ml0ٝ%^]öu2'#toAvݽA C$6͖=ic6/Y`Af܌K:o6go/xݿPoLƍ'eӫ-0& ICtu8=KdGzDLFo}ČiMfN8/<Bˤf  wi`ޚ]+[{ #yo8@;.nq`Xq{l`~崨-|5ݽvuvrROq!"Gzw?9 8hzox=atz=tٗ830v/,Kͅ| xQe^c/8/^MI)$#eecWcoq|r0Y:^e~o &<!a .e (1Ccͱֻh4o&?gTu[Κ"jUsu&͈ [HA;Y;u[ LͰ. Qp<~ҟB iazQ+Jxxmcf6k19pNn.qrBZ\ 3*ce,")5Dd`>6 BgP)G%.2\h1Du/oC;l%vpn̚ o /G<&alʬ5^S[Yh8nFL la1J"MS#IVߧPJbkA mu B+%V(h[Dztpzqw)WyPuE@-(2<}B0Q.Sx\a{qZpd Jklpgf%\ծ nBZRzʫvq,RsDUWc)PsKj:P+21 kpyN0زC[LDu,C6)E )ڄ#CRV̺͑֔ ͲnL84Ta}Xf%+p Vq]\a4.VUGO'goΣ& h"LѤzw&Y#+W7RBu4q3-bYt rv,_ĭДhܸ]Y.K\G@tOG f@V 54'AjZQ|ÖIpL.B,k~I 1a 8 zK<{K*p$LF\ _1VU,mZo.%7rlcZ>G,-0 q"Y,a(8,4^DwF%\雯e# `;.0cU[hr:PDe9 CA%12tL%tLBxjfيqQgfEohm%u3%!svJIf  s`Qpڿ!@1ks̾B' y@U92O>HR D]**l5h=`*QwL\%ZS`PfXAz2 94y7?UĤ([uZ5BJ~e!EWMy6Pb 0l韨۷lHE7ZU1 kDjAU?ۆb z3̻M!|Ā$lyqJUͤbmFPFyKP,jrkjP@zD^.ZW;%fƺJ˲j#"X!-fS#43+=j o2;%e@i`,>!X!Id[ K_Xfo;+_ KD徱^eq!-WnYųDRMj47@xϬcpūKP7?)`m〆/-) 7;}1!` 4.@)G&#dQz\ g>Ap¢#f:x:0-\=X7/R+0hGkңlºG@ܾ% 5Ilze]X0P`a=#e LW1T̲(hPhYW B,ˡk3}-U=%4vaP^%2WXTЇPOpn9-uFE1jUgW [MJ29"\if)•Rd(TJlXjp,]22UX[VbG.(dV ]r059 ^4`wvZu-*Ee[EYcV% rBh3d~ȧm TAfV*_`I % "h)(ʫ F {H m*UvRRc5L;Y1g9od^nU>%9 ZCu[]I/SPP,z/8Uieׇlʙ2o%T^,`v4sL[q Y`Ek8`0,bMUK, y|<ʷk|D q :E*]9tlQڛ@a*RU> Ds)C+* JI" 6|G\"P1zGfPVn^Υ,(-,5.=%:IU%WC@ޫ ѧ*N8b*"WyK"[,BT%rKج~%]o*g!Vn8HM]#\VJe9F:>kKe#VC-bu"oj]\ւqҖ,/.ZQؒUix8U t*6`_)N􌥧Wݚ0%.) FJ+%{@}% D\  KĨ,Ւ Ƚ]1'p[3/zoB`6w|4[`k蚭2EB̞rZ{bҷ(q >vl2 ?]86+F"8|ElKҨUIp* .9-A4:g7 WcJÃ0$XyAvvjg!3^ F"[y9s,[1.h H3sL&: .biRy2FZ4W@xa{zK!%F4X69%p5s./ 9PҦQOUP**y1r)%|+n*ܘyoO m469b D0J(3%~z(3ѤFQDyu>sO&HRzT`v~]KOo!|_(\e*Қ\0[V+/CRi@jgdzNf[ҨƎeiL-PbZ.wBKʪ]rH6\ WG򖕱%hsP+JԶQQDU;~JEi&U`+!fcUGM̭~M)j4&`㾳 DDn(#+ 9/C@~ (#UcTa|cJEV!7KTD kUT)/=MX5 qɞm žOm R)Ux>J-Xlx: u+gG rI ajV`vnqn1D60.{0&'X7 0bݤeփJ }1J4AX9ymk.(8V:Uj!hM⵨c9 &KBrd]Amë8 1X58sǚjmq}xEJOt[ٔC|86EL  6nmBDZUq4(q,R ·uBEsZI 0Qp!u^mi`8Ǒ\ކ'9"%.=np V]6M3ϬeN""&V<(Enl&[R&AEU -{1aن-¢ *IjXM&DV||$Bi[/=h 58/cfQ:Wy̢68V0G-[jX;[ea ]qp86 @84LN.Yiҵt>[ة1/ P5Z10=}CFIu4@E@Z }Xu- :S+='#0+zrZeRiȗѹV0 Gk!Zn pǛmIrbS p4żJ8Q6~9oWjT8wRvP%rbئT8+ŔGbn!$(p ئ(]Z&FbKVyV%' [G2 7E0GE|Њ쇈EK{3CXsgFjXw(Ne6\ʞ%Lv#%W5Jˆ`T 5uH,)m(@-)Q_1A8UEȲ3J6F$!v`z/=q3⢝,7w=k;v5Ef<ܼjc^E)a}kR6 ,9Z۫f k("XLSvKca:3xބ_̠\U`P)Pcюo`ˑ>ESDf sT%kA;dAe\f:2 FHTL*cNk1Rw( zQjm5}!5 0"m5Y%Vn&Ҩp*$U 1{s ;ib*&%PVT6kC"j-L1.$t21К?qSJH6Kg/WQxVG21nPG7С*<V.Q­/5"YE-ƃ5 2R--% ùUE1[W1q}ӽFFh&<\PF4֝f : GpK@ _ub/A2p̓1Ԃ[Y*6/x#<pl~(:0-`<09>P8}F9ٯXO)!5b4CLĽ…h]0"%ʺk5"νWbseػ'NOR-rfq`bur(Ȳ!fT&oKAǼŐ^%XDme)zR9;rxÈQRUh'N#R,,D9Rр@{^bѬMJ&X?B5l4X U*ʔ]o1NZ*J9s\0ZTtmՍSYjP)zOv(o*aK0HE% AxZ^_&*:B@PCF_Hd~ҁÜ'o\hMF<0#]"5,bW(X vZ`= }%e͟Q `p ;v+=P_˔.31*zOP񌰝]>GHGLiP J(*1XvLUcMh}& 2Իw4Ôv8#c5O,q#ZPzH:NeVO7mzx`rpQ6瘒l !FUeЀ.kǘV8/qȟAul<ևwƽa&inK@>CB6brl3|K& 1OH 膡-jTg]l` VjmBG\:qDi~n4k*&c[GIQW~Z#dQ=4tCC|]k- 7/5XYp^MRbN+ \m#o^04*dگ)i#5 Z* ̎wFwFR:+O=جº4\C f<0:k1X+ӺوIVns)am&+KAQ93e[Ӽ1U0t}ez<&fAZOV3 P]0!NeY+讀 6LP^2l#oCr騂Yb3[wN;l4a:Tpp`?pPrҋ@k\F, LG,'*Z% D'0aD++j2kUw[ ͼ0TfWJ[1+cX| , *JaG_.^FZ+^:cJt{&cJ :[+@y}q(~be%B6cCqLZ.tLX*n8#ptx^ ^k):n~}2À?tHeZaZœ:(QO]Wo2&to[ EEk.Q}w-(.H$x&3xtv 9^ ={C ]}t$wld=U4 hJPqI1q 6z  HE@Y|,3jHAp COYBR-Yyuxپ3*ߤ:c{,B.JBl5 8DxfkyEUk 'Ê1+X]柲Z 3kAF2.4N/I)q`.EJULjRNm8_tiKʱzxr8Y#W`)}=`W hrqC| bqhc:8Par{SGlCX(J4V6uQ*圼`b !G:R)wJJ1vz_$Dڪ6Yhj )26 W7֮'n]* ϱC%ͺ1̯ns81pB -$bBs7"iell h10C@e%{]6((BЪ"$=<1+@Q B5.:Of+hRO18 yǴ- anv-a:7JA^7,MyW({-6D١ wTz5¶n>12$/n?؃ P8XilR1( Yox,̃?A7@@L?J'V-po}F)WT)F%6@y1h}HZBEd]=" 3bCdh3Q͍:+u9 Q*# Ue:vx. Kw f.2xܹ_ (M&p~ʪйxUJ1e\g~l R!9a%*x_v ^pE@F~cF /pHג$)3% λK8`( rB=`w g3=R M>?Dֆ: -K,_XjN ~PXEG@. B 蘶 ʙ(`V9؆2\Btk%L|`Y(^ wg'0ݩ^C(L4&~;@ڙFeM;d1YP͌+V Ο FR,gф$Gxc!g[6]^$4^*ܿ=눜B!_/cQ}PE&ɯ V!`<7Z]0[+Y0S-Xb\}P}(IBm =&ţdwqow yI][`RXdG'YeLSf-/^5 ,6`F}`AC 8wR).sVd#gj?&p.E(TmX"zcˎэ%1OVv#H@r!j+4v"E[ Z*٦S$V@s0h&ë-9vS鄊r#G7)0f+j,~!㘛*r=" gt~자V0KPSl K (YY/|(rc5e]`Rq0|#e1 AL UQڡ5ܸ(65C:P P<~iAsuD@U n$eVDGbKV4%'VK.)KZ(mՅqHr&BmUɅ%Pg,^16 !6gUYd0| L`pگ.*=fk]v*|InnxO%] WWL(Ddu%n 488A;Z;F.-|KnC=K):ݯaZO[%qA dk&}%as"C)[1#8%7o6/^3zK%$b_#&ل/pG#  jP( ܺB;0l'Ĵ!z劻ƍH;q:mXi0PZT$2Id |P:SQB[-L`@L Y2roUF+ 3m(. T>q-C*\L0"AN{A ;wQmkd`0hn(J]~.#nf}qu.>!U@%R10 7 )%b6Z0)gK˘'5drFoBLK@/F_Җ_qB: "Ï"fVx z0]ci揩?V$rxZ``րZ-)Z$Q)]duAs6z^%Y0XS$Hlz"kfqܻ%P0 ZeIxVy"+*\D2P8m3uf 'np6wE׻_2Ĭhh{YoI`\ӟ 7 {NEEۉ!OkV|`jH`Bz-/kUH s?+11Œh$Pc`,r>W#yto>eӭ $F(hBkE9D+?'nحNZm(4B 5j$s с$ aĮ ]9Q (ͻYf1uVԠ !vb *nzo\yQW${^ #NGq|]4a8p0Y*2XIIfF'5u@h9@~Ux~' qfEE-PSj"\YQV,??1 E)իerA(ycXz D? ,XKPp@"EbӒ9ŝȰ*\SvD Ųٿx\1+a om)-:@eW7Ph%/, R(D]ܯ(#81ce~Ѭ/kFPV|>uqT+KA̾r|KZ8 %YNv}d`|DN dZ=z|ͦ4\D?pj?rQQӱF̨Xz?q9ܳ&*{<@žTL6@Mr'Inclfp ^IME;᮳̎$J t;CDCJ(O;%Q/F#1P;RfX1==ܿ#5Pk1Gc%/Pcqm5tA"vS>H@ vyBt}@ @9k4@c:vEZJ<-10,Vî TxK%ѽH3n{JFΗ)nS&{OYiٞ {B]t& --VЙ@nlIF,/B_L4-̪()TUU?sc_?>SnP=8m xJ-uj^J3 1 '`Oh%ܯ&AqX/U6^ZEsRH>O?"VGyH:'76cCbr&AKsCa9A|\{pZ-P& k̳'yi̾jáV0kQo`+nq 1&N겤}<."Ë4}'  yqa͑?輑Pľ?Iac*CFA;{vd&T~AC֗Й]܈%S%4o)}!x/Jp s.|lQWeaX,ŠZ,*yIYijn.ծJŔ. f> E(͑R$+ȅKea?*3~%̨fY_ȼJ Ж0()['ucj(厕)V>Q^]R71k̦%5 qP,Ey@X6T͎ad/ >oZ!V\ȭ0%_ !O{EZگCsEA GCx0..5M2WvzBS, (qP]0P\C]~ Y0ˊJUAv!aܭRF@(7RXҮ\ՀR#ԕ f&%b E$H fZ3e $xa(&92e k,/OJfe[pQ`,VPnPw#b9 ]+ѭFSP,!A w"(G%``a PCL(fO? ʙv}]~(aevU]^ܥ+,`n bCӷ XhDqQ[QyEi.pY ;7Z I 0`WB1mixTܣd\[U}`%vЌIE UUYAl?x! Jq, w MZW Yb%fY ptHpL )10ªЀɚ9dOMRg q:Ӵ>%>OjU.kɛֻc =᪼:9/`m¾?%$-!"4J>Y|L(P{E@*ĽIʅJ \ʂ;X +BZ&e)+Q6wEmP]ajh"\+:a;%.`$؊u'xRǼ؟?aeR6~hTϼBpSwO8^#}׺g}e*]au]k/9rQ4FP |*_y̲ilߵ?1L-w*d(XX4qkc?=ty8hycШeN͓B5eƢ:>4sNepRV[ g0tޱlT!H3H?%m?FD9CxQQӄKPu"/DЈ=`#|F2#L< ._ y^9_ZTk NǺt{یvϢ (qD !*^GCu̜ =h0B8?NYTK* jI͙:̡!pKR"D;plBw t\/~4i7Æ{ힰ:@Z)Q:#ln. WQ)jK,KTHD05^٘uD+G}YJ2LR !l*~` (B.T v9eBs,xg0Fw`GO؀<:r@%#2Ucr|Y!a܁j .G+ WIN T hh(yL} 1I.)rEb Gej3^+p1q0½*:0A!\5 [7hpm+,;B‘&к]ほ04 Xƒ-쪗<F³xfF+TY=e*  - vV) hi$KqU)ľ-$cPȸY/Ҷ,)sĠf2u-B9m&܅?8i&S{ LU\Db*{ Snj"TF.33Ea35L(J(?lE3 uX]WQrx/ysS;Y|cp h Rcui$RK{YSua)Vl)} _m|0zr{zl,`+]g&PTg{1/>xa{RL(/,mØb*.ߦf\Ex#]!STB2b,U_%Ds E1N\jQK> O~+p\d$\k0 ~n6v U6D]JFF}Kj5Dej#/39,k"^VSH _)b$ ]0.KDwH| vKR*6}Jj1+)JchՎER&qCcLƚ;<`YEU <-&R摛*:.ZĻ~[!U83_f%! Y&"~eX ead\ #ՇXzDf# J|` %y}@Y;L?,OtLu_ZT=od9] [88RWXR6'F<_H Lw!9"K&s͹_x{`A|heՉ\'yc/J[!w!''.\B \eX*eDk )0…L :^>Ԧ!.XRW%%Mb0O}nh o&L 4 nĸGSdTx&ȖYHD V5iN!ڻ#ovsW>ɃR~N @VWX#VZlL-0&LE0*%YG@2-<S+xxLM_XzCL5\;]bg?Wq֖x]e'dr-c"GЅYK;J;*,/ ,Dz!Q{?԰Ҩ Hla}D3(VW fs\G`*qQ z+ .ϓ a\=~k,PhXGhK=aYwĭ 5f%zt@Q`fxCDֆa%+CfT90g@IJϢ):Jj%Y)n~/*s/=3 tUYG8QPEzRbjiC!C($,;B{k^='^adC&β+Kx!>D4\0L%` F%FvN 5:`CZ^~_M.2C7s(վ[Ķ0 ,Eg<R\KAGcZ2Cy.#9#~a5CPχS!֌.4q aܘDbV nZk}5V b^% #[F3JN:n^e WOA rݔ#+`bbkBW=E~?~aV1)<$%ChOQ*lK]`կQx+e+&]Ug|h~xܢ-f߂ b0δ-?=YJ0E3XP_OtҖV6!P-MSA©fUH3pR%C3.P3ԫ'"R]j:\УJe>`FzſƘ(5&/D&K>J!0 ^k],<@GÙ1: y,x+(}QL\,[} cGAE>5lNQЬς<%Ʃb]|ls9j2p$J1N; *Եۀht.mm#XBCShcsD牀Þ.ʄ%˜P'C.?q=7k'Tf2~nS;T% sV )dI1] 9.~#(M()!{PI~3造 c$ ({QKEyR?bM' {Vb +z~b{:CAYr)q{Ft{p~?LYFOql%qɔXZԂu±;LhOԢɕt |KJ󀒬beGT`qjNW2pjQF9+̡vFt.D| J`i$'Je.fs4!PĨ'*A83-Ej m4$DZ,`!茶 j~bXb{.E j iEjKKbM͏E3YG-ZqY5lN`\7x1>*- aH52 )~,TK{l3RuF*/x! uLh4{~%O1Icޠs4qRZAF``f?ʹؔyz:м-W6ԹZV)C&x*=P3l3d<0ԠJhqxU{C`̷YwObg^S8yS9݂eVI¶N=tDB/=Ắp DmD{/87mδY7'bd T~ %#5Í|epa#.n/ D/{Kۮ"3xAVaZXgHy/RKЛh3KGX@!*ɾ6V|-we՚%Xx#A@UE&l ,#kZD=ZQ <1OYz"V AZqnB`D R{DBRG2ijQ8 ( ES0 .66T4 Ge 25*9:Jy֘wĺ!=?԰iǬ{0S[W<5[MwZtp8;D #]ؖ}"戠w5vݸ#\ܢ|KKr33 H;#J4SU  [`a pb{ X.,``wH(Nf}đr0nKd3.wA?RF K4;Q.D !_5u++F1_Cm5\IJ|qH],;~D[oeiwea|6I|vn'VBJ( ,'gWfN>k ٴOx6*`&C. ~PhqiPZRQLN'b0\rE[P^!^MZLmjbWR`G6[*!*|y0X8Wtc >Piw L8c0f-`AЖs8ꢯZ(^,˕VI AD!e0 #pE6%t# ZtH;/wO[B,%b~);+65yM6D| ,c>L=ңܸ.-ZUhwMn tp3ʋPb 1Oth UYؼ(Yיf6<aYv )((n=AvTF-VyE (J J,z)7WFİЭL]PmR\XݢZ30cgF"j޿HJZ |B(gK?PuM]Y'PV牘]OT.V8ra:-8չ),;9E7]ʑ؂wNt,w`ӂKzܫw፫2̗|t)iǵ-oh ZH (״Vrѱn .#jw9%G&|lN0wETYUKIE(5cstEJ}sY@u)ÉZ ĭuܸ㴴AӴ/1 A]d0x t ep~QS.5a{_^6 V=S R|+ 隞n5 ,.=au#  [Iazg2ҵC^ JQQ LB9CPY'G[<̗e0@z1u?h W1xh߂ZztʹbPA*1GvZmLs\} 9EWK*M)o|AL-KpD!Nb;cuTCٙ.1 , Fʃ@p?E5ׇX6JrEcKG u16K\䦘iś]7ˆ)(d"-%n]wPvuW7BZ?\7wN\,JeIK9ej MɂAMJ=<Ŋ#߉^QhiyUic`R"8]? m:Or]cЗwɍR˸W!>& 2[ohyPtzC@3OT\."6Yas 2;¥)SLsaQ)a.%8 }gf\^#hbP7f9 u-Bm3]m,"8A-S2=c-?3 \ٓ0n:>yxi;1ެwC}~%.2iD J2(xq "B"D@b R6lX\ma53. hʮ&*o},H${QA,6/ G|}'[x Tt(|ܧBZ`0HZrD_4)e}= tc@u) TQ0x* 5+!hTG+щXĪr8`7@y"qG6~HA3o<1t?23*՞X#n`~N$Zs.';x&r}WN[b"=(TO6yϿʃ5j62,WM}e߅ :s s3+IJ*-.Scu`4Tl8:m.&!y1 Ȟ$>O3)#[W)e_tx1;b[Ø@gTjb8Pܠ3 ( .GPĠF2G42׈Uo-q.XP:S&Hx-$AVw,ՁP 5u݀ԉqy)ԅM>P Tľ#[jJ@-q p#D !|au0ՖUn8>z牖stPM&"115X3@Yq um3sk1BX#m iYTL ,'a ՞!Q2u5{CYD kqᏨSVBRP9)Wh6'g0wA2YPJ#!jgWYi`k,; ލ*)s\DmN6 yYZf[H>HW=1 u?y O }%`ԮR?$ "l~~`9 Z1Ĵ1ޒSt>?#0c#&xpiu&`-/ J]I\mo@KC4i P8d33k WcLp\n*) E _iK%/@"_gE952~Vcl<3qJHRo^ (OV+4B>ϲ Zj3PG7\n6Y,\XUeKĂQ6ҭgܫgG57)_.a64Ɛ 4I}2ΈoBlѨUʄw4ULw(✬lE` *1-챉"ӎI;[СDS6QM|\8v:MNQ!`Х=m+((}R,)+*ò,'in_y<-oԨ)g% d] yB}h=T!K#DXCDnLya`;?HeI*,LY/q[DZ_^`O`R%`Q'\㦡pc0K^M #@,_-((@qr7-$VAo(-{!c:#^" ^9@#gHB?Ṟ ^ųωb81RuuRq#2`!{bgqb:Ǻ!v~Htq .:•C-'.,BmyX>X {!RQq[7)UA֖Z:$KJQ]"2@nvWĶ=&G$w‹֥_lcp(zF4 ̗7ֶWI}%͂k}%zǘ_jT|L:',؛mg B%~8LZ ~? b*}D%%SS[> )ZJ{m-KuyFہTxl1`!a rB>E/57[c(sSkD }ZE9. {G0I%Ԧ⣁sfW^_YdXH'f򬽧&dSzU+S.\T| A> R,,{f$BAOBa+.QxPkT`:AMK*Cn´y"WT*aZRNo̢AZ,ʨ3 &c*Y4#+XTޥKz3stgLt_s8y)h Q\)pݜ !|GWfFg<"4?H=G9* :T<@ry[tLvlR% &nbX1Vy~"Ȇ;4+4=: ()Z``h> 4XߏL)1zWN(6b Dz>f-%Ut{]uQ0ZqXCPHfd_BD6]oIvM-`̦ /v^X ۆQ9F/H+1Su#ɥ<1fo{ -]nR[ ,D e5h7Jv,YbU{1Znud0$SRhr6aMl)u>q+Y~Mt_KQ+Zpf!X3,S Sԭ<mh4j<(}50uS!`ˤF}YCyT`IH-ڬ(􎙟ab^>LFax?̕O&Z<P%÷Ahӆ߇ gC=ӬJU $s"R& Tʀma֦`1(VT k hXhq%ZifϨ !apE]9eQ+?cfG`yT7kc7(@sƏq6u6OZѦ| P,SCrsll11 IشN!S l&Q [tjvRloRa {F:L"GHox eH」ƼʅRaFOs3PhȞ { v=t[Y㏌S,G]s"( ~C ?4)@[?!6:=`V)" ali]nA`¾eN 5M^}$hpnA V?V%{ C5V WsM)u_0Dh?6<£pS/@_xEṘdjB{Ph>ȼjEЬ>ƃqB+3 WwhLz?t!jk_tW c\*`iPvAWd_,NZn"Y" ?[:G.A,YPfLuAT>42rjf+. &p|G}ۼ8sR0R hn o=yB/(ox(7b'\g?)g7%j s5[ ^aazmH7\Rψl6hULفUC87o(نpX N {N CٍԪkRp- qNh2_e9:V,w=Q6K1/@85: ;_ eX=oe,rCfmq( ca>0xON |ħGHm5e!lCb:'g!w+9}sVA].69 npNBQo"#"Q.a2eu)wKXes9xeHeQS=&!jD|0`Dn`B%rh-ax%5x ̻FUUn%YCz5X.:#kgs5vgd-;D_70*a^!tzorW $^ ?'%UIRI#Ȏ6G=)ԙy'*(bkYĠHe)}cdr+@kgW{);]{'&Káq /#dptdm^єZ'_`?-lK>bZӑLouDy"bxA.PTQ(o80VhYᕗT/E+Ka%oAd ӘTd{U%D,qʼn^,}Gj4ݮIT9BR>КYt9 [ưZ$xDִ#p;i 2"CXY9r9]j˳0,|vP4bwSj{㴧6a{ fdK4q6 "ѾKGT@J?GeN"/qc1j HA UYjao&UmZi8Lp:qw@4x5ЃU3\Am0G=ej T.Wrpwq6U]L@HA}E|Uf J"w7h0T6# .SW1FEb/՗l"tEEDp",?Q +B; ߤ«5W+{+̪ !p0-O( ,qPm%n; W@&:ݣyUVЄ9x!zW=%H2NlKҞa(3T7%[TUS~`Vϳ6*PIzj ŖOM&d^b$UUBƠ j$6eL^a-w=2}Yu'=nˀcD+f z)lAC6D*HB9)LwcъD`v07ZyF\mjo !l؍]^vK HXeܔ(LR~L$l1Ceu}`2Kv? ;+0{F;.s6b2%.bL0^"u@Ϙ tp`7 )Xw@ C7E=%#C};= 7!*{:W mP!p4Fc aYvS#6ۗOK'>Js,;Km2Tdv(P|݈<=euIOQA6#}7?ALx_c=Y\! p|Q`KΨ0+ri`|72SWeq̨?,v-?prx.(C%7dx.Re3*F_ل b ʅH*ҕHj&iuJ"^FǞhJ&5H\XGE#S%C6c4y( Vj 'p(eo,g3P91UI.ٖ,[7ܩ,ZyXzc@*s)tjRP 9 wQU=hL#" RH0-sA~+Q8G@&9iL҃+{CS CxķЦ<-,.~`sRccnGg^ Lc4_d?b6nw_*dJs3{Gġ#z2;Fj5  r0k`D4R(_GSb,r2oP:dXhe\_EPmYGbL?{X A.i:MFG`AOޔ<%;ˀZS  U07rp4W# © eCޥp舖uq֫57_<{#hSY7"8IE]BzyRoK (^`ܻ(a Bv !.R4αR1]_+Wg0h>=/,/$q,Na/SM..b__/52:f ЇrkqAQنv2QJ:Utzol|vİJiO}kT ?'O 0R%V hƈ.p ^KDaBVfYwb-Hj DVqKIk]X_86jf|"bf ujLT9< ,$P @ш! #v>Tc?k܊\=B)C  Ն4K>8A!_Ⱥp&bY: Ơn_/VZ`K애>bg>fu1$ 5A[%.%F[_0` YB¥C_@C.PX0j>y~r2fe^&kRrHզ\XF*?#<87782Qx JK|J D!$ |A)!XQAy%VO5鄅f|"1J-y~DZfM%|,چZa ₧0V*1. (3%Xƪ>L8Bk\HUjM.bPS.VYAð.c Ht5ZA2"0T; }%̀IXڜ, _a $8ZZ Ra"jeTS|>`AxXg̲|Ps~\w]:C&&&aKX :+0NO Bˎ0P(3̅܂ap@51ѳ34dǖ$(` N=g)P/YuģXѬ) AٶV6ho<(')fQ3 ya4>.E`Rlq]S_r8yժMYv$Xf&6nV|%gXB2"TpU yD\b=ABCBsOuxQP@ : ʚkACxZ*\hH?rޠ|L0dW$I0W0g wc0]`Ϩ1'Ĉ @_T&gx7w{C]]N~g.BX#n[K+ZUQ8 `B<]8 <D&̼ i7P`^EX@E2th[q-g;A'vP󅭝oCaTz|=>I#hWpy5+dNi ~,K i? 5rŷ9ؕ.^Nkf| P?"[K u<MSs $WCLf!LW3ޟ2IexyY]e-?E95Ugj b{Elb#uaZUE5r¸, 8Qt:ADEia8̭9ħq/baT|!eL@nxz {ӼJ< HT&y]xTO(xaƨ^pjXڻmK ^R ?(BAB1,b|&v;nI@ZN1o a|9PkOÏ:X%/{OcFTס mS+KVPSn8n~S}sMCQh }̹]U/AWa >&K0,}_Xh@b2xYM(-#R i_X $ט-Π8s2,5ߴZ (g̍Ϊ-9''J:(U_#8j-KHDtKwT@P .A*Ħ~JCwXmTv2**G5=RU̯͟@!J5 q 3%bOB^Q]O{i -D%U{*-]Q8q}cKbS~:ԕ/H*]Ժ7W*%>&&3 }U_^Y uC R{4KǏ62BLzGq63t>hyn&ž#1sIQ hbb rRUZ'Ͱy(ܪ ^%1mb"Ow( zܦU4v&X}̼@&.\_$Xh0J;#opx b 1`K CYa}1@oY|̲Sߣ1(2A*W 2i:ff9^kcyetj|ť WYc()zM`hFRxR) &8YΈ5KXIBhrGCO8Xh/9; |wc,dlQ׬cYO`LYxKO.s}3_|D1ƶw|a_1v0l檖H턨\'q5"e\T*-bKyP&`%Q(gw9T 9/ނZUAXD147ՀĦTyw'6]w#Wj{k7m%t#_Ҿja;c=e[aO*1PgAdD)=$tSLVXI^XU@*-YK-K+K|ُLJ@A@zIa SZ%E)96u//" KjnA\jɈFXaWYNk:eCBS2`8HW`1x}`fq8w <6882EQ`")cq)!H9` ,@aٞ $lP5 Vq]252l|än܉7)D$Q+Fi 3 9 E] p@_-S1Lj 28 3+iupyRaTkruQ̧S]8}"⻁@1XRlK}ЄK 4CY0]yZcR]0 n ׾+2 A)̔0 ]a; wt=xv}?ڸ!P)[u]Q{VmÖpo^"84x=aCVPRu%?c0 GdU)ʧ@*4RE蚥Ή*C.I SxթԨqn>+}à-͑VI.C.ը"s]!{@"(!ЉXt"ux@@.V&ACP/Aê5 7Tb(]nOh05Ϳp.FK^QSX↉?R֟TD4_0x{ cHTѧ Q2ĴKu˃/v0Lyu O]tS ȊJ E\s~\ȯ.0RD{u+hm./*!ԮRgwjfR,eZaF kψJ0pЯSpW?@Q8FhaY=".1x T5(+|G#(оVJ-g}b ^BPܷ͂F_DC p9n1Qdף<7\HeԠC M=)kY׬H% `PtUeIr辰< Ч FŠP4vd1跅PB7WXڨtMd[WC  D BltWW,X!5A~ W5 sg~ 9PrhG }[g:Gqq#`Sʡoq2bʞ8 uQUbTABPY:_Z\u0dLBpG Yr[ 8_,ك!*^w[YuΦ #g9ˇDV~~2 q;%%֧eQq.BY 22u_jpףjփJx Y,. t~~ 3YscI0ٶSri:&15SEAG5jN1V#%=e!xyט d^N|YdEz7>9-f2*{"+RfTfB]W~f]P!.t\fc`:|ij$^?30@t,[۪VT ]zbٙ `nҒaW9es*_ST7W(ccrUpX"N&`Rs}֯Idp0!f1v:ˊwf5J%<\4YRp=`FHiWP&+.wĮNrY4pxW"Ò! gL RP|:++}% VhAE/(F,?܂ o e[W6./Uy=){DpRƫ,P$kX*k3> JK. &gRlnX-S<:@!mq+}w:£8$?HK{9zL?G;Q_#1aP$BU.~f. [6肢۫v"IFcvԠ /Z"V. os:A}i?P*lV,+580 8KO M8`3A@MO 0񘊵ޫDezS_4&S_Rj `A|.ڋiK]\WnO!Mbv068&:ˈ4=ȏmNInmb,̂XIn3XU UڂqrP N0n!]ȟ#LtwbŕUun"(,JOP,:2DU8"fS2(/V/=eGR^&H5 `D+M!Eα,%OTcEI^&rFNѡ+fPVAm;  lUOv ;0XɉCHqL@gT"+k0ֹZehAck#1#:(H+%{DT)ad*rA^ӱ_vcpPzA-GXیFqiޠ ,ӬqL U.rS .~ſLD%8%yb)Ў {@zE eō{a^PcD:̣@5=!X%y@2fl˅PFU|?mðYzbevB+y,Ѯ%a=@2r fHh0luWO,}~!` y JY*LprJ ˰4Z%G BEsQ+V(YR9Pi VBh?0O=:Qյ@\ׁ1b<ŠN*]_l)Ԡ8p€R:{L$` +Tn*[~EKkM(YkkWYҡi d߬wKSsviXI`[Yps lcj5ڶq0{< ,KiPpcqRZ6 +b`k 9wbemTa<'3hSLfBPɠZŖF/dZlT)8g%b6̺1R|@2bb^ ܬڮ,^wr4=cړRVwcDkR)YV )FDWo*w{Q؈G](3z}HxU\ ;KF oPfc?UusY? z0 Eq'ޚ CIvՎPͯĪֺkMQòu+\Gy/!nVx&0[;T6}+F aL~3jV>T9yb[]xcQ{ĽeT3 !O0r0ѧY|5e-p^ @Ρc"wwF}Uu m{ /jֆ/*CkL)r EvRz-f@*fi4S/mG.9)R/7)0Z`RH+׺o JZh{QrܻP]{ȿ萄uULXNz=YN4ךfR2t.4rPzʓ4޺InXYj:@ՎzE 6Bn4rEӇ2J kFרbVQeqоd {*^%Z`ĵRyG ŏ/- ptl!.n_`3_bM4A_ XYB3:q))|.A4W6A䈜N.\8k\irħ;;b4v9YFm DX*`&:dm<\tĹKd6Zf&TKlW1FJ/{hW eAlpVV1lO;1Gvm8l{j63%W$]Cp{` W %=6?7T8ub J԰Yћ+t.`bi2?RU|@ u+9m)XL"%ϑWK?`%& s,-n`,| [ v>Q{D dc^P8R7Ad %nǪO .BRP˨څerNub(5s`ۊ3wK!N^т:UꟘF.QC .m #E/ap-7| 06Mip5K/ /QKDv!I3B?@20.U ƀ(1RHe^07ٖ^B#$kհ{XRF:*b5|TM*;F]{SPShpoYu dFL|R$te'[ aL ^`JTy9azqN߂*{B̽n TȌBG$SEDbYM л]xE`_5K< Da-27މg9Ui:英6ѡ|h_9% tC 4[0\Gp!i3PzITK[=sE^%(j8{Z1<Q [Evx]YF.\s˥k7JYMZeHmvư?k@tQ[4QM;ND2 \crj"_o ?U7>]ഝڌ"0x'O  / NQCIx2~_2RJbh9 #0N bt\\xʒx"\DK11 ewYL|OF нz`+b=2 ; ]7_p:e(~\YS.DnuB`7A:8]?A0e+&s̨*N~e S^a׉A!]F!1J00F`2eq1x1)82WxO(2׻Ec,x^fhnߡ`~%#@U,@۔/|ba2RN*jO]C*ex钮%[-1(Ta)77F%A'f\i_u1bF}f !h??QeR4(W|ȕpS Hsgfm-pJx*ˁ~h_*J=B?RzBC5YP$F+%iBL |LLjg2\\e$VUR]Cv, wSʛ}@*gU`**_]O 87蟶o/XkEG 3MA}GYEo!36-4Q1{WYtˮxH5GOJCa0n/JuDg΂ *0 @:N)U T"@\E|l\eHd5F)JZm G"hpzY\caUd dS J) ĘM=^$1VͼE%dk0g̺3S[+"]fa:gfXr[$إ5*Eo9I_R'pKWOi`SxT%]DZ`KyWm!> $S[`X$J6vgd^ SA)9X D* oՕ )fe13`J;NERu!r "jAH ;)E`taXS$@Z:,k"-Lc̫Dd?5[~8NN.Xu dxA6EQgd %*s`!.<~sdX.++n l{yAkTk<ŲZ;\bﻴp.fs⬺4$_/Qkk* nٝvq[ejE9%#t~",Vt $޳ sqq GZI}kQu KK&DWIWV'-kԓg h 7"ǰhTe`BhWy u'_0 ,UH:Jj+F,( i6ְy/$I:ں"iH8EB/եIʞ΂dQj \]V v(r~RM=~фH?l˨h,BZ\Ӓ(AkܫKe3/2X| l`u)Z kXGDTDt2b]8bTA2_2 5x | d:u># 0Օq 48+YؑaqAYvpq*M<6. ^A1bvb.?ŒZAe-%ұP8,}+}0Q.\Կ3b|ܻ |j%*xKu+YcOX8Զc_4Ux#a/˛ߨ)h\ca*؎T=WıeD04 WPh&tσ"Y-j#714rqBX/Uc@!_N.҃gUruLܰCZ C+|FPy%PXaQy@0\ W0sk8:Z^ K9 lԆ.LϺO/v4nUZRz#JTu\iMv^KjUL.bXT][[LaLjqbS]#f{a,]eο4̪uMY~ӔEp+1zǎ \ m7vML6$3ZoI_i TZY/в/Vw+8z1 Vh~bn=-c.׿ Fn΅|@˕A /-}`ǠMqF5Vl D Pq&~.\\ֿ\?xx.[Ma@ZXCs"+MZ_&j"HyC'z92̇}@s7>Ke0",赘K 41ľEӢph@@ƥb8:2E^ 6k̩nb?4Kb1!9vb0vtfXNʬ\Ñ0qSwcU"VYݿظV(Rjgfv%?3`X` bSU.Ea؊m2Lx;B0g|IrWRkkZK~?hMtZ DE9ay2i;c`AX<, ")OvwcCsl,">`z_ rBc@aPdӞ&oK2, L)Bf]_1ʅ뛉!@:$ܗ+1juS"haʃ6Rix<$eF%7`SM: ր-W3,u:B[3"51.+,USA8S4nDcX]lhPu'cVF %ᶡ6Lɧ!v+X!7.:8F:SļBTg\Rwot,2%:Wqpo e=Nq l`%:X[W!S ; eW%&P-1(RCcg=2nү/T o(w*0\"t[G5|\տ{ny _!ܱ{!/yuR="6NeUC0iy7( ɨ`n2Q3m`eĩ'~m.4,g1CTdEqR(xn_V!T'!V nA} (r87,m]60;ƿ73BWQBd(6 MW+"B]h͌Pg]T•@4TY-[m۟Q rHqm}zB&]Fv[gd S< ׉/`px:BYoF0%@/gTLy\e4z%³Z/~WN( (u0 sIARQݏC ,pxBKQQe1ye. xiG(4EM"b` _ S_ kÓb`QGhzb>8i`jzaᥱAЙbN%dV3J_qk Ax*3e,Bq"~,8 W23Ya+~;pIS? x"[W)Ց1.1 KWT*܉{L=am Q8IR#O$nd ӟXax_y;3zx-oQ80mZ:A$D3f0ap)QsXhW5,4wjŽW |šHp0)h#UhUVZ + bҸ+ д~< :{h 7=W5M(,6}L-S[,# `PbWz"De#ӬQ%ur#S&u(`0mab F5vypg:,@:ླ5媭o~ DkW m먀4&?2ANL,nf=ub똈&/mdn+\5Eo.QW(j_/c~#B*jߩj1g_,?#-PbcȖT e)^H: yBrz8Pfcgpk,U#xsN!-_--DGR-![Br~'8^Rv5J:.g\LKj gya{ o_]  |A0ADӈ[$+b`˗ҿȃ/*R'?ltI!.`t^!m_PMTQ\4+Vep\kLxd2J_wUTsl=%Mԭ0U SzIR0殦|Klkjx !Qv@9\-,zG mr=S[)6Jdr`F-o;2#Fp(2. 2lٜE&zfe?Up7VyԸ,]g+/K8|?GvzPԲ (7#4 rnЇ6i\t[X^b RP3FMR-@rli"5C"\G6ʰZ! e$h@z"9b̶K Xi`S4ن(Nw|1H ؗOR4LT+ͥq7u+v#`KW\yIꁶd\@؋TLZ>@QJ>p9 ȳlێhFhV+Ŏ-Ax*]5/j7}ޮ Y„Uhmt\14^`d߈VRń{6`̵`^%좪w.9,$7Sļv[(ܢ~%nQW#_:<@" 0Y[RN tEE.Q"zeu9%@QE=,n e*AvTxzz첥-+5.펹7-gP:̒]rx:qiw*<u㌿쪳`.a ,7WL8*T0BׅSݟj1GE˯bc+#Y p`JYVp# ;`VbU򵟉[YN O7TT,-EZG"[RdFC[ [d1Jiv$SC|G.*՚(6Mf DZ|-R] MZuV\nhKEnhB BFPd']V]L,ɮ(n b(."އ"jjfn8S/u(m5%Z5Wx,]VGv_e.1&a6)g>7:o5p((1nS2." ECgTtII/2ICO%DkVE1#~R9xp@hpa6 5ΨGŁ[kQ2 *Ze7ԀynfM])ÐA|7 9 hYCr T/ 2@S.3b,BopYdzQ- D`iV93J[0nqKyJl [C0x ?Q+1| XqQn$xZ.q|X`z Qt}B{)Yw_rBDV栶Í]p¸[/j  4BcdQ lc &gAơ P-ehQ{xeQrB]o5|x`RQU~2PsdP`{2z@2]"4p Z5^]SAYh4?{S' E٬\]@l טh9-uCʛfa! +YE>"an9bPHд0QnU%֥)6t%hU_iSSuo5} M0mfR9"(R(DS~ڡ?Q'[bK 肶@״tW]"c1kYAwH *NĽ :_^ 'T7}"R1-`‰\;Zut*T8qe(6p_+m,Σ6j3g+9<Ŋ/U%iTbX(VD.Gᘒ<{ƒ4;n9tRPT%QYA_@-)VWb6*f_j+VN1Ùֺkջw !eWC%N@^sZ Υgehh[,ig\06.+eeFBrr]R}f XW 7j=! =L BeDg JUo1D7=J%;dv "v1z:qk+Q0 P0[4f,1 "37]G Qoba$k8`Tũdq.]ZT'/6 ΕP*v@%ֺ8AW!.˳2˗RL3\LbX7Jg -]2O(g1߬jؖ$]g.:(cC v̪ܺ`Cc6j.>y/2ITV/Q2bZQۛLoaZ?X`yҮVYC#I42eh'2 gQ%{= ju° @b$-]9IWuژ^,\ucgYn8Jf4/@S`^K?{KK/p*?Ep'-L79[E$BZNrԧt thl*k ;H#LAS.P,(GkƥB (<C-`0)eAhlFF+.{ڂ1;puQ ^DT;K&q+_S[ 2Q[4[ #haJx;nY{@*_֗'EZ^.r(F_5\8j}F͜UkjUiE [H )2=B!8vN2FCNVwEpL т#P#.S^nRR̢VwUQ%58 ~-"`fCnkۅ35YNAyF9d'~^&p1 p(U?q[ WާxRc ,םT+ܭ'hX[VS*8C(D .vzA)]J|[PǢL_7_(NϤ{ G%kPtojB˨臘gkPSĨ[P䆽"5텅641XShkWtz(]X2i9 y1TE)eщZj]ݠ&K:lȩ` ~#4.z1鶇Q$jYM %1BwqҴƭ8^k+'_%p8t8swD1U3%$p^cZH/ys zƫ=b5 ~; m], UM J4 euxϰQV`sq=&xbBe 7v;C [ϹNʳ]d]мE%KEiipB=Z,RaE5Ajeb{N]"W"ujvA q:i=%𾜁uꚕ:hWn(oXaEUNN!}م?FX cHJ ,"^Ҧc7R8nhcϨU18lX`Qs0[&VXݝ!,ūYħaHXJ.iq@UK7 ;JwZ5+=A(U{һEax|@N˫^JlİZ5: 8K/]"QK&j.P^XqABG6N 1qԤѶ !@<^}"uBĢl1ʁ  Z+R)H㤽dKky5Dm-l]&إeܖɥ,KSW IWkt |nDG8n\K.$Vk-U,2ɰЉVXNY#r190IipWxP."60FE涁uP~V/ҏ3e`PJ5ýއ: :pK`Sa!omBi%V_EYG?0#6/1F $9K6 ڤ|dLrJڮ*b shTV0'6.Jn h/RP'`^&"+RS}-w_p#]क़Ud;BjJ {,_ߴ!xb5#w^?PxcX ,C0W(rt/씔Xߥ ZQ+W~-<|/2KEWF/** Po+;[󘵻;|ز[\,H+obi}\ n:D$v>h Bc 6fn Wl0;KDL112y]b=l{T([Ou˸\^"3:PØa)-E0#s ְTk Nśq0  B!ՙގ@n)BfdaM\2ob7;]FX+ 2;1UO%Ut~l6t,J k+Bh/BAٟyt.}bVt_S3NJ H*,\`$e(va%cQP t*_Q*PR0ۑ}` q]͕PajMB=%W bJXr*~~fgNFH&pL~XڴXmULK&uq+_q얯W~3a؆Tߜ:/[84F.9Xwfh{/9k[kQԦj: 'z&-.39aiu+qҡ-k g EC3Ѣ[;Gŭм0_g"ψQ]"7ls>r"ɝ6  ꀏ)Cnb]6;hPRvn : euj(,ljx T^seޣ<7̶.23XUehhC L!w,(/qsJ qBvLY i;ERzPBad}N!rXN̴0yUzf j6 rB 4BYã/ L4Y:̢ZX T'LGP[KY\ヤ14DdթKK *γ|\Pi~#ZYWU޸ +*S,5@͔=bDJ!a|X5N% uʵ5z,VAI..GG^UPXZ--4Vޱ5)IDO˕QuS qO2L}n婠ݺ,).T =>c$sd=#a{@rSLՁCp`MtK* Q}TG#gN7WimvlR2 +3X^7QXnVg!]jmeLg4ҔWH6bUY[Xb>OSUFJ BLT*S!aϟ3Qo(Vd6'?؆q~{ .yIlEaPCSm }XZCV .I-jv|@F 's0ĶGXoeES~9F(z!WOX  S Ŋ ̬&=)7D]J̠n.Pjޞ#T@,@ wm1pm;\E+C0R٤{PLUtG8ng mmV!*6tȫyψ \hx/ +B7QKTM, ^}: Jb&ks|Ycv*xlpcx lU,ld`+SL;1Av*yb2_ 5dyKM>OhM^j *"N]d ]}b`t`΋!gtٓVpasH2e>-l:h:4X%ec8JL&ܾ7jy3} ϟ 2ރa #4:.C\=}E Rrw"mMo|CuZ}ƕmJ v66\Tv[ 8 o}PikĄH @w\_DT@ Uay y# brG1 "Ps+FV*h3y` EԒ!]mnX%!Ǚz#~uâWR^e9 X+3Z!MR(CTQM9=atU wZ}++&W@9ìx ^z;Yq/r֬m>L _L [0aZh7Dc ]YՔw.uϸi M )CWgCYܰz "q.23 9 }DPѾJy.wmQo>[b;"CcmV蛊N[vm+ j W~ kQ9JEpUG|=DrŌt@˒ z2xPumPnf4ŮCiġVnP(뿦(ݔ^u ȅ68 -Wν}7o| ]=^3xU}4ٻ^$ 0؅b0qDcHM ?r(1NqFQr*Zx4xb{3X`%Ws/X,T``eB ];Lx&(5J?ٕ7G >qȜ-3lz!B%54m]WxsfC{hj.[Gψ:xQ;cv\G?a/믩au[Jn VJNG.Yttu G|jQ S7g5*u*4*Go:kJ#!yL0wI_/(;Zr{(6_yPq~ׇ[;F{{W LZA2@ck($zs zUXn;:$ZɴzfB{0@ vj+SEZ+gn ,h.ptִ?2]5 2GH#Нs5`SMJTcR\s"֣3\ \*qc>#)ktcMre=`p hMZm#'U5WV3_QbUA+Di\y"pHP6>Uĵw3]ᒵn}KJ-#|L\ˬg UmIz[z2SnZ +ٶK#j֡: *50k- cRQD 9F* Rf0z yxxkV T# 1 a+AjmE*qh0Egƈ6NBJ!7#V:}1,m 8a^BN*'hp]|³y w̰YBŇH%QɡkjlY%o]D#/+|hڎ:fng6K[ _2 P`mjMX]5mӿ]Lo7ȱ(Ga @tDZű2GdՖyk&hB-0UW/.3_ʋĭR떎@-TTZѾZїLnOkv̝ ADC@p3bLU{B6{C V $]K/ _ߨԶիPJMW pUCgy.nرYimVyWXr0tgI7F?p1a^}$ aD'SDQ׭ V沾:YC&bJ5-RS2_V;Am "VM8 @ueA v+ Ƣe`P cPx5`rr2IP "{ T,R ư:لb*c[AnůXPyK%4B x62bmvQX.f7bxx%qlD OXi+Q-a#406(ANn~(ٮ1S|MtyVx:)eȣTs7k,u0 Xfo dneMz  č3 F@SZ+՘E |gcɝLP{ǫ9/q&;Wej`R|jUDYKŏfRC.sb\Bh(};%Jΐk}QE`]Ü6VH2.bVBp^xѝʄ({jpv+$! Ԡ:̽WspNxCKݕXWLPn&CFt7=jL&qeb:B+Oa1/c"}OS>0ѬI,M*a-bm:{>Aoy_-ϮOځN  Bi!f^E,A3 e[ܽ8=ǒ7ڢk_ay!< X"@]_r 1zVvʡ[(ty0$dU8nL`ԖļNR֡_'xzW-Nh]38g jqnS9\WM:/09 +w|K쑫mruC/z^֩"EZ^1O.k v;[7 5NqǤXC%楫 :t)tN8qy ?2h3YNL5Э.\ Ng^D>+s]/3);#l:N&J0@RC^ئT)|Va&!W_hk`xT"ƽ>.be U p8jB(ZZEŅ58x֍<6|pDko8m1sl5H]((|d£B%Z΢{Žg%*B֬0kYW-.>BM/䆌H 1p?"\/ Ϧ`SItz? /[ 0E@/+Y=g[)ĨiUZHe~ej`ƏHzgbTL RUwH 3HtRGlP5 Mw&wgvAMҝxҪ(Seݍã.4&U&a,c5(ptev;e1 .k7 Q",P$ک.-+_FIFAGEaGҢ}f83pKQ==f:d j*B>@*Pq] "4XXW)wns34B\ a8w=_Ez@B!WF.MhTq,FEzjW/Z|azh7H0#"p 蝀+V}6dOO׬ܗ&[_LXK)J\݂y8a!\o*?.QirA68TJh 'I`<+TQK X%.sc\P}*1="A)@D_ ׉gC(ղDgbzU.=y"%<~.!< J3/^^kmUJZ o_xPUnF{ ;suB(Y9f6)WB9XKeJu)Wj=僠*&fȥ[Rx3#JB#p`#O2RUP h*rȡV]&ҭn$Ⱦ^%e-,umu|h}L\J4V2%D?ىsBm**Z[c>,WΣb>E ӕ15-J !w>[U+U[Gl> Emì}.Ds\@h=S2С:Kٞ jҷ V۴_7 ^Rm:5֞*Uh0e^vR8]HNzZt޲E9)Aj) M}iFf5̲^;Q+ⳟNtvp$Eи>1(yc`kGz: rY1h ij[,|L*-~?R WO0詰2֮Y(.,Nr`KXJ>e 8zʶ#%sN o̮WtKY >2:̣|Ywq>EvS*[Ltf,8T܀/TSQk]w%QZ+TWAJ)<ܤ*+2jJ5:N/Tl-AB(gR,:c2 g{:м԰[^\VKU8wNŚ,8g#H/7goHl7LXSPfaTtH-6ʜG>8v2v[gXM18`|1ͻLz0=!MԜf P+)RB;x(k @wBVSӴbL©FfŜYux) _kªor vJ9  2@`/`0cu˒Y.00i!W+1oX."J^F5}q]F6X1T4`b0XP} Q*houRKV2Ϛ+%W 1Ѐ=B-U2z,Tr԰zw tV8:)L]AcGp_Z)YR}C7V5N1Z` P˘WPwOg4t>Z!tFŇ`Z1 (y9+ lGDSͦ|@u;8)LV$2TVE,*N~wrd=S˪+yۗW^#*Kqt8.Ulڣ]73: )H\^͇| 0>uՆ'b bbH[,GTK6cURݻKkҹ#'4TBb z MSQjF2D@ "G̬lQBuC[efPU3RpjI˘b=X;iVC"HuUyO b;'XPY#]5PE[M>]!kum^lUmގ0 `/+e=ŎO/(D/d+,Yp^9 JSvtG|%Dus>5.mW|Rm~a,AZEq9oQ}֊p/˫3̪Ƽyt/a|J#oś*X Ey[oTıK/q蒀i\zn^a[S`SV#?05mG!վht4 7~L8p"F|Vsnu3Ty{In?BSMjK.kwhX7s=!-2OIK0AbyD<]{XJ&3 @T0D`J.Ҥ dyjJzKy%WIfX.^۸K,VdAT1Q ;al-k&J|5k*CE9f!nne#~HȥrHghnW?pi us"deWG$V<]ųO`͞E Q W\#AEfp#~,(an.DU_2!)5a!*ES<$bZ1t".Eo&`N]iZ-ݨI J^1<`.ݙ艎[Xwҏn,*AFi5l> ޫ0)m%-q1(] PzCy W,ᘷ ?فh}`i/foKT@AZ@"nEӘ*BFӴ "1E\ě汅 ѸG<@R*YfQY:FCFe;ghY\JIe)\%- {Dlm͔ob 0ւr03:bT&!zq!:-woUFWPXqʹ4<,Y݁d[|4~K`£n&^[B[Di Mo ̀'fa =+QYau*PlͷPT[ YHPí:Y%t\gt1F\MɀzA%W }lk0푕n81U8b ~YQx C7bt2n]$_SF l+UB5X1IZ٧fP»#kjpfajQh[+<6ޘyB-Hg_IڈumuSUGl0EV>B(6g0X본">9#awEu Sjo)}G:\ݡ8gXpUE>=#H>~&ST lb>wD&:?1nfc*)WH'4x5nvG{mSJ[猬nPF(ܴ5'5aF #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.14.5/tests/ncurses.c000066400000000000000000000141301471154763100156440ustar00rootroot00000000000000/* 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.14.5/tests/postinstall.sh000077500000000000000000000002751471154763100167360ustar00rootroot00000000000000#!/bin/sh set -evx cc -g \ -fsanitize=address,undefined \ -fsanitize-undefined-trap-on-error \ $(pkg-config --libs --cflags chafa) \ example.c \ -o example ./example chafa-1.14.5/tests/term-info-test.c000066400000000000000000000152651471154763100170510ustar00rootroot00000000000000#include "config.h" #include #include static void formatting_test (void) { ChafaTermInfo *ti; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 14 + 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); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, "def-fg-%1-%2-%3,", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_BG, "def-bg-%1-%2-%3,", 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 = chafa_term_info_emit_set_default_fg (ti, out, 0xffff, 0x0000, 0x1234); out = chafa_term_info_emit_set_default_bg (ti, out, 0x1234, 0xffff, 0x0000); *out = '\0'; chafa_term_info_unref (ti); g_assert_cmpstr (buf, ==, "soft-reset" "cursor-up-9876" "1235-cursor-to-pos-1" "410-fg-direct-244" "0-bg-direct10099-" "1199-fgbg-99,00-9" "aix30," "aix90," "aix40," "aix100," "aix-30-40," "aix-90-100," "def-fg-ffff-0000-1234," "def-bg-1234-ffff-0000,"); } static void dynamic_test (void) { ChafaTermInfo *ti; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; gchar *out = buf; gchar *dyn; ti = chafa_term_info_new (); /* No args */ chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, "reset-soft", NULL); out = chafa_term_info_emit_reset_terminal_soft (ti, buf); *out = '\0'; dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, -1); g_assert_cmpstr (buf, ==, dyn); g_free (dyn); /* 8-bit args */ chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, "%1%2-fg-direct-%3", NULL); out = chafa_term_info_emit_set_color_fg_direct (ti, buf, 0xff, 0x00, 0x12); *out = '\0'; dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, 0xff, 0x00, 0x12, -1); g_assert_cmpstr (buf, ==, dyn); g_free (dyn); /* uint args */ chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_CURSOR_TO_POS, "%1-cursor-to-pos-%2", NULL); out = chafa_term_info_emit_cursor_to_pos (ti, buf, 100000, 200000); *out = '\0'; dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_CURSOR_TO_POS, 100000, 200000, -1); g_assert_cmpstr (buf, ==, dyn); g_free (dyn); /* 16-bit hex args */ chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, "def-fg-%1-%2-%3,", NULL); out = chafa_term_info_emit_set_default_fg (ti, buf, 0xffff, 0x0000, 0x1234); *out = '\0'; dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, 0xffff, 0x0000, 0x1234, -1); g_assert_cmpstr (buf, ==, dyn); g_free (dyn); /* Arg out of range */ dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, 0xffff, 0x10000, 0x1234, 0, -1); g_assert (dyn == NULL); /* Too many args */ dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, 0xffff, 0x0000, 0x1234, 0, -1); g_assert (dyn == NULL); /* Too few args */ dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, 0xffff, 0x0000, -1); g_assert (dyn == NULL); /* Too few (zero) args */ dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, -1); g_assert (dyn == NULL); chafa_term_info_unref (ti); } static void parsing_test (void) { ChafaTermInfo *ti; gchar *dyn; gint dyn_len; gchar *p; guint args [CHAFA_TERM_SEQ_ARGS_MAX]; ChafaParseResult result; guint i; ti = chafa_term_info_new (); /* Define and emit */ chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, "def-fg-%1-%2-%3,", NULL); dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, 0xffff, 0x0000, 0x1234, -1); /* Parse success */ p = dyn; dyn_len = strlen (dyn); result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args); g_assert (result == CHAFA_PARSE_SUCCESS); g_assert (args [0] == 0xffff); g_assert (args [1] == 0x0000); g_assert (args [2] == 0x1234); /* Not enough data */ for (i = 0; i < strlen (dyn); i++) { p = dyn; dyn_len = i; result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args); g_assert (result == CHAFA_PARSE_AGAIN); } /* Parse failure */ p = dyn + 1; dyn_len = strlen (dyn) - 1; result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args); g_assert (result == CHAFA_PARSE_FAILURE); g_free (dyn); chafa_term_info_unref (ti); } int main (int argc, char *argv []) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/term-info/formatting", formatting_test); g_test_add_func ("/term-info/dynamic", dynamic_test); g_test_add_func ("/term-info/parsing", parsing_test); return g_test_run (); } chafa-1.14.5/tests/test-driver.sh000077500000000000000000000116451471154763100166350ustar00rootroot00000000000000#! /bin/sh # test-driver - basic testsuite driver script. scriptversion=2018-03-07.03; # UTC # Copyright (C) 2011-2021 Free Software Foundation, Inc. # # 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 2, 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 . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . # Make unconditional expansion of undefined variables an error. This # helps a lot in preventing typo-related bugs. set -u usage_error () { echo "$0: $*" >&2 print_usage >&2 exit 2 } print_usage () { cat <"$log_file" "$@" >>"$log_file" 2>&1 estatus=$? if test $enable_hard_errors = no && test $estatus -eq 99; then tweaked_estatus=1 else tweaked_estatus=$estatus fi case $tweaked_estatus:$expect_failure in 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; 0:*) col=$grn res=PASS recheck=no gcopy=no;; 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; *:*) col=$red res=FAIL recheck=yes gcopy=yes;; esac # Report the test outcome and exit status in the logs, so that one can # know whether the test passed or failed simply by looking at the '.log' # file, without the need of also peaking into the corresponding '.trs' # file (automake bug#11814). echo "$res $test_name (exit status: $estatus)" >>"$log_file" # Report outcome to console. echo "${col}${res}${std}: $test_name" # Chafa-specific: On error, print log file to console. if test $estatus != 0; then cat "$log_file" fi # Register the test result, and other relevant metadata. echo ":test-result: $res" > $trs_file echo ":global-test-result: $res" >> $trs_file echo ":recheck: $recheck" >> $trs_file echo ":copy-in-global-log: $gcopy" >> $trs_file # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: chafa-1.14.5/tools/000077500000000000000000000000001471154763100140155ustar00rootroot00000000000000chafa-1.14.5/tools/Makefile.am000066400000000000000000000000441471154763100160470ustar00rootroot00000000000000SUBDIRS = chafa fontgen completions chafa-1.14.5/tools/chafa/000077500000000000000000000000001471154763100150575ustar00rootroot00000000000000chafa-1.14.5/tools/chafa/LICENSE.qoi000066400000000000000000000020631471154763100166540ustar00rootroot00000000000000MIT License Copyright (c) 2022 Dominic Szablewski 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.14.5/tools/chafa/Makefile.am000066400000000000000000000041261471154763100171160ustar00rootroot00000000000000if 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 \ media-loader.c \ media-loader.h \ placement-counter.c \ placement-counter.h \ png-loader.c \ png-loader.h \ named-colors.c \ named-colors.h \ qoi.h \ qoi-loader.c \ qoi-loader.h \ util.c \ util.h \ xwd-loader.c \ xwd-loader.h if HAVE_AVIF chafa_SOURCES += \ avif-loader.c \ avif-loader.h endif if HAVE_JPEG chafa_SOURCES += \ jpeg-loader.c \ jpeg-loader.h endif if HAVE_TIFF chafa_SOURCES += \ tiff-loader.c \ tiff-loader.h endif if HAVE_SVG chafa_SOURCES += \ svg-loader.c \ svg-loader.h endif if HAVE_WEBP chafa_SOURCES += \ webp-loader.c \ webp-loader.h endif if HAVE_JXL chafa_SOURCES += \ jxl-loader.c \ jxl-loader.h endif # 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) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(JXL_CFLAGS) $(AVIF_CFLAGS) $(FREETYPE_CFLAGS) if ENABLE_RPATH chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir) endif chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(JXL_LIBS) $(AVIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la -lm $(WIN32_LDADD) # On Microsoft Windows, we compile a resource file with windres and link it in. # This enables UTF-8 support in filenames, environment variables, etc. if IS_WIN32_BUILD WIN32_LDADD = manifest.o chafa_SOURCES += \ conhost.c \ conhost.h manifest.o: $(srcdir)/manifest.rc $(WINDRES) -o $@ $(srcdir)/manifest.rc else WIN32_LDADD = endif 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 \ -I$(top_srcdir)/lodepng EXTRA_DIST = manifest.rc ms-utf8.xml chafa-1.14.5/tools/chafa/avif-loader.c000066400000000000000000000167231471154763100174250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include "avif-loader.h" #include "util.h" #define N_CHANNELS 4 #define BYTES_PER_PIXEL N_CHANNELS struct AvifLoader { FileMapping *mapping; gconstpointer file_data; size_t file_data_len; gpointer frame_data; guint width, height; guint rowstride; avifDecoder *decoder; gint current_frame_index; guint frame_is_decoded : 1; guint frame_is_success : 1; }; static RotationType rotation [4] [3] = { { ROTATION_180_MIRROR, ROTATION_0_MIRROR, ROTATION_NONE }, { ROTATION_270_MIRROR, ROTATION_90_MIRROR, ROTATION_270 }, { ROTATION_0_MIRROR, ROTATION_180_MIRROR, ROTATION_180 }, { ROTATION_90_MIRROR, ROTATION_270_MIRROR, ROTATION_90 } }; static RotationType calc_rotation (avifTransformFlags tflags, guint angle, guint axis) { guint8 rot, mir; if (angle > 3 || axis > 1) return ROTATION_NONE; rot = (tflags & AVIF_TRANSFORM_IROT) ? angle : 0; mir = (tflags & AVIF_TRANSFORM_IMIR) ? axis : 2; return rotation [rot] [mir]; } static gboolean maybe_decode_frame (AvifLoader *loader) { avifResult avif_result; avifImage *image; avifRGBImage rgb; guint axis; if (loader->frame_is_decoded) return loader->frame_is_success; loader->frame_is_success = FALSE; rgb.pixels = NULL; avif_result = avifDecoderNextImage (loader->decoder); if (avif_result != AVIF_RESULT_OK) goto out; image = loader->decoder->image; avifRGBImageSetDefaults (&rgb, image); rgb.depth = 8; rgb.format = AVIF_RGB_FORMAT_RGBA; rgb.rowBytes = image->width * BYTES_PER_PIXEL; rgb.pixels = g_malloc (image->height * rgb.rowBytes); avif_result = avifImageYUVToRGB(image, &rgb); if (avif_result != AVIF_RESULT_OK) goto out; loader->width = image->width; loader->height = image->height; loader->rowstride = rgb.rowBytes; g_free (loader->frame_data); loader->frame_data = rgb.pixels; #if AVIF_VERSION_MAJOR >= 1 axis = image->imir.axis; #else axis = image->imir.mode; #endif rotate_image (&loader->frame_data, &loader->width, &loader->height, &loader->rowstride, N_CHANNELS, calc_rotation (image->transformFlags, image->irot.angle, axis)); loader->frame_is_success = TRUE; out: if (!loader->frame_is_success) { g_free (rgb.pixels); } return loader->frame_is_success; } static AvifLoader * avif_loader_new (void) { return g_new0 (AvifLoader, 1); } AvifLoader * avif_loader_new_from_mapping (FileMapping *mapping) { AvifLoader *loader = NULL; gboolean success = FALSE; avifResult avif_result; avifImage *image; g_return_val_if_fail (mapping != NULL, NULL); /* Quick check for the ISOBMFF ftyp box to filter out files that are * something else entirely */ if (!file_mapping_has_magic (mapping, 4, "ftyp", 4)) goto out; loader = avif_loader_new (); loader->mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; loader->decoder = avifDecoderCreate (); /* Allow for missing PixelInformationProperty, invalid clap box and * missing ImageSpatialExtentsProperty in alpha auxiliary image items */ loader->decoder->strictFlags = AVIF_STRICT_DISABLED; avif_result = avifDecoderSetIOMemory (loader->decoder, loader->file_data, loader->file_data_len); if (avif_result != AVIF_RESULT_OK) goto out; avif_result = avifDecoderParse (loader->decoder); if (avif_result != AVIF_RESULT_OK) goto out; image = loader->decoder->image; if (image->width < 1 || image->width >= (1 << 28) || image->height < 1 || image->height >= (1 << 28)) goto out; loader->width = image->width; loader->height = image->height; loader->rowstride = loader->width * BYTES_PER_PIXEL; success = TRUE; out: if (!success) { if (loader) { if (loader->decoder) { avifDecoderDestroy (loader->decoder); loader->decoder = NULL; } g_free (loader); loader = NULL; } } return loader; } void avif_loader_destroy (AvifLoader *loader) { if (loader->decoder) avifDecoderDestroy (loader->decoder); if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->frame_data) g_free (loader->frame_data); g_free (loader); } gboolean avif_loader_get_is_animation (AvifLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return loader->decoder->imageCount > 1 ? TRUE : FALSE; } gconstpointer avif_loader_get_frame_data (AvifLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); if (!maybe_decode_frame (loader)) return NULL; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (pixel_type_out) *pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED; if (rowstride_out) *rowstride_out = loader->rowstride; return loader->frame_data; } gint avif_loader_get_frame_delay (AvifLoader *loader) { gdouble duration_s; g_return_val_if_fail (loader != NULL, 0); duration_s = loader->decoder->imageTiming.duration; /* NaN or very small? */ if (duration_s != duration_s || duration_s < 0.000001) duration_s = 0.05; /* Infinite or very big? Set frame duration to a bit more than a day's time. */ if (duration_s > 99999.0) duration_s = 99999.0; return duration_s * 1000.0; } void avif_loader_goto_first_frame (AvifLoader *loader) { g_return_if_fail (loader != NULL); if (loader->current_frame_index == 0) return; loader->current_frame_index = 0; loader->frame_is_decoded = FALSE; loader->frame_is_success = FALSE; avifDecoderReset (loader->decoder); } gboolean avif_loader_goto_next_frame (AvifLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); if (loader->current_frame_index + 1 >= (gint) loader->decoder->imageCount) return FALSE; loader->current_frame_index++; loader->frame_is_decoded = FALSE; loader->frame_is_success = FALSE; return TRUE; } chafa-1.14.5/tools/chafa/avif-loader.h000066400000000000000000000030661471154763100174260ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __AVIF_LOADER_H__ #define __AVIF_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct AvifLoader AvifLoader; AvifLoader *avif_loader_new_from_mapping (FileMapping *mapping); void avif_loader_destroy (AvifLoader *loader); gboolean avif_loader_get_is_animation (AvifLoader *loader); gconstpointer avif_loader_get_frame_data (AvifLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint avif_loader_get_frame_delay (AvifLoader *loader); void avif_loader_goto_first_frame (AvifLoader *loader); gboolean avif_loader_goto_next_frame (AvifLoader *loader); G_END_DECLS #endif /* __AVIF_LOADER_H__ */ chafa-1.14.5/tools/chafa/c64-platform.c000066400000000000000000000165331471154763100174510ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 UNICODE_UNDEF 0 typedef struct { gint c; gunichar u; } CodeMap; /* It's better to keep a single entry per line, since it leaves room for annotation. * Also, maintaining tabular layouts is a pita even when we don't have out-there * Unicode characters messing up the formatting (which, in this case, we do). */ static const CodeMap c64_unshifted_pseudo_codes [] = { /* 0-127 */ { 0, '@' }, { 1, 'A' }, { 2, 'B' }, { 3, 'C' }, { 4, 'D' }, { 5, 'E' }, { 6, 'F' }, { 7, 'G' }, { 8, 'H' }, { 9, 'I' }, { 10, 'J' }, { 11, 'K' }, { 12, 'L' }, { 13, 'M' }, { 14, 'N' }, { 15, 'O' }, { 16, 'P' }, { 17, 'Q' }, { 18, 'R' }, { 19, 'S' }, { 20, 'T' }, { 21, 'U' }, { 22, 'V' }, { 23, 'W' }, { 24, 'X' }, { 25, 'Y' }, { 26, 'Z' }, { 27, '[' }, { 28, 0xa3 }, /* £ pound symbol*/ { 29, ']' }, { 30, 0x2191 }, /* ↑ up arrow */ { 31, 0x2190 }, /* ← left arrow */ { 32, ' ' }, { 33, '!' }, { 34, '"' }, { 35, '#' }, { 36, '$' }, { 37, '%' }, { 38, '&' }, { 39, '\'' }, { 40, '(' }, { 41, ')' }, { 42, '*' }, { 43, '+' }, { 44, ',' }, { 45, '-' }, { 46, '.' }, { 47, '/' }, { 48, '0' }, { 49, '1' }, { 50, '2' }, { 51, '3' }, { 52, '4' }, { 53, '5' }, { 54, '6' }, { 55, '7' }, { 56, '8' }, { 57, '9' }, { 58, ':' }, { 59, ';' }, { 60, '<' }, { 61, '=' }, { 62, '>' }, { 63, '?' }, { 64, 0x2500 }, /* ─ horizontal box drawing line */ { 65, 0x2660 }, /* ♠ black spade suit */ { 66, 0x2502 }, /* │ vertical 1/8 block 4 */ { 67, 0x1fb79 }, /* 🭹 horizontal 1/8 block 5 */ { 68, 0x1fb78 }, /* 🭸 horizontal 1/8 block 4 */ { 69, 0x1fb77 }, /* 🭷 horizontal 1/8 block 3 */ { 70, 0x1fb7a }, /* 🭶 horizontal 1/8 block 6 */ { 71, 0x1fb71 }, /* 🭱 vertical 1/8 block 3 */ { 72, 0x1fb73 }, /* 🭳 vertical 1/8 block 5 */ { 73, 0x256e }, /* ╮ left-bottom arc connector */ { 74, 0x2570 }, /* ╰ right-top arc connector */ { 75, 0x256f }, /* ╯ left-top arc connector */ { 76, 0x1fb7c }, /* 🭼 left and lower one-eight corner */ { 77, 0x2572 }, /* ╲ box drawing light diagonal UL to LR */ { 78, 0x2571 }, /* ╱ box drawing light diagonal UR to LL */ { 79, 0x1fb7d }, /* 🭽 left and upper one-eight corner */ { 80, 0x1fb7e }, /* 🭾 right and upper one-eight corner */ { 81, 0x25cf }, /* ● black circle */ { 82, 0x1fb7b }, /* 🭻 horizontal 1/8 block 7 */ { 83, 0x2665 }, /* ♥ black heart suit */ { 84, 0x1fb70 }, /* 🭰 vertical 1/8 block 2 */ { 85, 0x256d }, /* ╭ right-bottom arc connector */ { 86, 0x2573 }, /* ╳ box drawing light cross */ { 87, 0x25cb }, /* ○ white circle */ { 88, 0x2663 }, /* ♣ black club suit */ { 89, 0x1fb75 }, /* 🭵 vertical 1/8 block 7 */ { 90, 0x2666 }, /* ♦ black diamond suit */ { 91, 0x253c }, /* ┼ box drawing light vertical and horizontal */ { 92, 0x1fb8c }, /* 🮌 left half medium shade (kinda) */ { 93, 0x2502 }, /* │ vertical box drawing line */ { 94, 0x03c0 }, /* π greek small letter Pi */ { 95, 0x25e5 }, /* ◥ black upper right triangle */ { 96, ' ' }, /* looks like a space (identical to #32) */ { 97, 0x258c }, /* ▌ left half block */ { 98, 0x2584 }, /* ▄ lower half block */ { 99, 0x23ba }, /* ⎺ horizontal scan line 1 (upper) */ { 100, 0x23bd }, /* ⎽ horizontal scan line 9 (lower) */ { 101, 0x258f }, /* ▏ left 1/8 block */ { 102, 0x1fb95 }, /* 🮕 checker board fill */ { 103, 0x2595 }, /* ▕ right 1/8 block */ { 104, 0x1fb8f }, /* 🮏 lower half medium shade (kinda) */ { 105, 0x25e4 }, /* ◤ black upper left triangle */ { 106, 0x2595 }, /* ▕ right 1/8 block (identical to #103) */ { 107, 0x251c }, /* ├ box drawing light vertical and right */ { 108, 0x2597 }, /* ▗ quadrant lower right */ { 109, 0x2514 }, /* └ box drawing light up and right */ { 110, 0x2510 }, /* ┐ box drawing light down and left */ { 111, 0x2581 }, /* ▁ lower 1/8 block */ { 112, 0x250c }, /* ┌ box drawing light down and right */ { 113, 0x2534 }, /* ┴ box drawing light up and horizontal */ { 114, 0x252c }, /* ┬ box drawing light down and horizontal */ { 115, 0x2524 }, /* ┤ box drawing light vertical and left */ { 116, 0x258f }, /* ▏ left 1/8 block (identical to #101) */ { 117, 0x258e }, /* ▎ left 1/4 block */ { 118, 0x1fb87 }, /* 🮇 right 1/4 block */ { 119, 0x2594 }, /* ▔ upper 1/8 block */ { 120, 0x1fb82 }, /* 🮂 upper 1/4 block */ { 121, 0x2582 }, /* ▂ lower 1/4 block */ { 122, 0x1fb7f }, /* 🭿 right and lower 1/8 corner */ { 123, 0x2596 }, /* ▖ quadrant lower left */ { 124, 0x259d }, /* ▝ quadrant upper right */ { 125, 0x2518 }, /* ┘ box drawing light up and left */ { 126, 0x2598 }, /* ▘ quadrant upper left */ { 127, 0x259a }, /* ▚ quadrant upper left and lower right */ /* Chars 128-255 are inverted versions of 0-127. Some of them make sense * as plain characters too. We list those here. */ { 160, 0x2588 }, /* █ full block */ { 214, 0x1fbbd }, /* 🮽 negative diagonal cross */ { 220, 0x1fb94 }, /* 🮔 left medium half inverse medium shade and right half block (kinda) */ { 223, 0x25e3 }, /* ◣ black lower left triangle */ { 225, 0x2590 }, /* ▐ right half block */ { 226, 0x2580 }, /* ▀ upper half block */ { 229, 0x1fb8b }, /* 🮋 right 7/8 block */ { 230, 0x1fb96 }, /* 🮖 inverse checker board fill */ { 231, 0x2589 }, /* ▉ left 7/8 block */ { 232, 0x1fb91 }, /* 🮑 upper half block and lower half inverse medium shade (kinda) */ { 233, 0x25e2 }, /* ◢ black lower right triangle */ { 234, 0x2589 }, /* ▉ left 7/8 block, (identical to #231) */ { 236, 0x259b }, /* ▛ quadrant UL and UR and LL */ { 239, 0x1fb86 }, /* 🮆 upper 7/8 block */ { 244, 0x1fb8b }, /* 🮋 right 7/8 block (identical to #229) */ { 245, 0x1fb8a }, /* 🮊 right 3/4 block */ { 246, 0x258a }, /* ▊ left 3/4 block */ { 247, 0x2587 }, /* ▇ lower 7/8 block */ { 248, 0x2586 }, /* ▆ lower 3/4 block */ { 249, 0x1fb85 }, /* 🮅 upper 3/4 block */ { 251, 0x259c }, /* ▜ quadrant UL and UR and LR */ { 252, 0x2599 }, /* ▙ quadrant UL and LL and LR */ { 254, 0x259f }, /* ▟ quadrant UR and LL and LR */ { 255, 0x259e }, /* ▞ quadrant upper right and lower left */ { -1, 0 } }; chafa-1.14.5/tools/chafa/chafa.c000066400000000000000000003215731471154763100163000ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 /* strspn, strlen, strcmp, strncmp, memset */ #include /* setlocale */ #ifdef HAVE_SYS_IOCTL_H # include /* ioctl */ #endif #include /* open */ #include /* stat */ #include /* open */ #include /* STDOUT_FILENO */ #ifdef HAVE_SIGACTION # include /* sigaction */ #endif #include /* exit */ #ifdef HAVE_TERMIOS_H # include /* tcgetattr, tcsetattr */ #endif #include #include #include "font-loader.h" #include "media-loader.h" #include "named-colors.h" #include "placement-counter.h" /* Include after glib.h for G_OS_WIN32 */ #ifdef G_OS_WIN32 # ifdef HAVE_WINDOWS_H # include # endif # include # include # include "conhost.h" #endif /* Maximum size of stack-allocated read/write buffer */ #define BUFFER_MAX 4096 #define ANIM_FPS_MAX 100000.0 #define FILE_DURATION_DEFAULT 0.0 #define SCALE_MAX 9999.0 #define CELL_EXTENT_AUTO_MAX 65535 /* Maximum width or height of the terminal, in pixels. If it claims to be * bigger than this, assume it's broken. */ #define PIXEL_EXTENT_MAX (8192 * 3) typedef enum { TRISTATE_FALSE = 0, TRISTATE_TRUE, TRISTATE_AUTO } Tristate; typedef struct { gchar *executable_name; gboolean show_help; gboolean show_version; gboolean skip_processing; 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 polite; gboolean stretch; gboolean zoom; gboolean watch; gboolean fg_only; gboolean animate; gboolean relative; gboolean relative_set; gboolean fit_to_width; ChafaAlign horiz_align; ChafaAlign vert_align; gint view_width, view_height; gint width, height; gint cell_width, cell_height; gint margin_bottom, margin_right; gdouble scale; gdouble font_ratio; gint work_factor; gint optimization_level; gint n_threads; ChafaOptimizations optimizations; ChafaPassthrough passthrough; gboolean passthrough_set; guint32 fg_color; gboolean fg_color_set; guint32 bg_color; gboolean bg_color_set; gdouble transparency_threshold; gboolean transparency_threshold_set; 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; Tristate use_exact_size; /* Automatically set if terminal size is detected and there is * zero bottom margin. */ gboolean have_parking_row; /* Whether to perturb the options based on a seed read from the first * input file. This improves coverage when fuzzing. */ gboolean fuzz_options; ChafaTermInfo *term_info; gboolean output_utf_16_on_windows; gboolean is_conhost_mode; } GlobalOptions; typedef struct { gint width_cells, height_cells; gint width_pixels, height_pixels; } TermSize; static GlobalOptions options; static TermSize detected_term_size; static gboolean using_detected_size = FALSE; static volatile sig_atomic_t interrupted_by_user = FALSE; static PlacementCounter *placement_counter; #ifdef HAVE_TERMIOS_H static struct termios saved_termios; #endif #ifdef G_OS_WIN32 static UINT saved_console_output_cp; static UINT saved_console_input_cp; #endif #ifdef HAVE_SIGACTION static void sigint_handler (G_GNUC_UNUSED int sig) { interrupted_by_user = TRUE; } #endif static void interruptible_usleep (gdouble us) { while (us > 0.0 && !interrupted_by_user) { gdouble sleep_us = MIN (us, 50000.0); g_usleep (sleep_us); us -= sleep_us; } } static gboolean write_to_stdout (gconstpointer buf, gsize len) { gboolean result = TRUE; if (len == 0) return TRUE; #ifdef G_OS_WIN32 { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); gsize total_written = 0; const void * const newline = "\r\n"; const gchar *p0, *p1, *end; /* On MS Windows, we convert line feeds to DOS-style CRLF as we go. */ for (p0 = buf, end = p0 + len; chd != INVALID_HANDLE_VALUE && total_written < len; p0 = p1) { p1 = memchr (p0, '\n', end - p0); if (!p1) p1 = end; if (!safe_WriteConsoleA (chd, p0, p1 - p0)) break; total_written += p1 - p0; if (p1 != end) { if (!safe_WriteConsoleA (chd, newline, 2)) break; p1 += 1; total_written += 1; } } result = total_written == len ? TRUE : FALSE; } #else { gsize total_written; for (total_written = 0; total_written < len; ) { gsize n_written = fwrite (((const gchar *) buf) + total_written, 1, len - total_written, stdout); total_written += n_written; if (total_written < len && n_written == 0 && errno != EINTR) result = FALSE; } } #endif return result; } 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 gint count_dash_strings (GList *l) { gint n = 0; for ( ; l; l = g_list_next (l)) { if (!strcmp (l->data, "-")) n++; } return n; } 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-2024 Hans Petter Jansson et al.\n" "Incl. libnsgif copyright (C) 2004 Richard Wilson, copyright (C) 2008 Sean Fox\n" "Incl. LodePNG copyright (C) 2005-2018 Lode Vandevenne\n" "Incl. QOI decoder copyright (C) 2021 Dominic Szablewski\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 ()); gchar **loaders = get_loader_names (); gchar *loaders_joined = g_strjoinv (" ", loaders); g_print ("Chafa version %s\n\nLoaders: %s\nFeatures: %s\nApplying: %s\n\n%s\n", CHAFA_VERSION, loaders_joined, 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); g_strfreev (loaders); g_free (loaders_joined); } static void print_brief_summary (void) { g_printerr ("%s: You must specify input files as arguments or pipe a file to stdin.\n" "Try '%s --help' for more information.\n", options.executable_name, options.executable_name); } static void print_summary (void) { const gchar *summary = " Chafa (Character Art Facsimile) terminal graphics and character art generator.\n" "\nGeneral options:\n" " -h, --help Show help.\n" " --version Show version.\n" " -v, --verbose Be verbose.\n" "\nOutput encoding:\n" #ifdef G_OS_WIN32 " -f, --format=FORMAT Set output format; one of [conhost, iterm, kitty, sixels,\n" #else " -f, --format=FORMAT Set output format; one of [iterm, kitty, sixels,\n" #endif " symbols]. Iterm, kitty and sixels yield much higher\n" " quality but enjoy limited support. Symbols mode yields\n" " beautiful character art.\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" " --relative=BOOL Use relative cursor positioning [on, off]. When on,\n" " control sequences will be used to position images relative\n" " to the cursor. When off, newlines will be used to separate\n" " rows instead for e.g. 'less -R' interop. Defaults to off.\n" " --passthrough=MODE Graphics protocol passthrough [auto, none, screen,\n" " tmux]. Used to show pixel graphics through multiplexers.\n" " --polite=BOOL Polite mode [on, off]. Inhibits escape sequences that may\n" " confuse other programs. Defaults to off.\n" "\nSize and layout:\n" " --align=ALIGN Horizontal and vertical alignment (e.g. \"top,left\").\n" " --clear Clear screen before processing each file.\n" " --exact-size=MODE Try to match the input's size exactly [auto, on, off].\n" " --fit-width Fit images to view's width, possibly exceeding its height.\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" " --margin-bottom=NUM When terminal size is detected, reserve at least NUM\n" " rows at the bottom as a safety margin. Can be used to\n" " prevent images from scrolling out. Defaults to 1.\n" " --margin-right=NUM When terminal size is detected, reserve at least NUM\n" " columns safety margin on right-hand side. Defaults to 0.\n" " --scale=NUM Scale image, respecting view's dimensions. 1.0 approximates\n" " image's pixel dimensions. Specify \"max\" to fit view.\n" " Defaults to 1.0 for pixel graphics and 4.0 for symbols.\n" " -s, --size=WxH Set maximum image dimensions in columns and rows. By\n" " default this will be equal to the view size.\n" " --stretch Stretch image to fit output dimensions; ignore aspect.\n" " Implies --scale max.\n" " --view-size=WxH Set the view size in columns and rows. By default this\n" " will be the size of your terminal, or 80x25 if size\n" " detection fails. If one dimension is omitted, it will\n" " be set to a reasonable approximation of infinity.\n" "\nAnimation and timing:\n" " --animate=BOOL Whether to allow animation [on, off]. Defaults to on.\n" " When off, will show a still frame from each animation.\n" " -d, --duration=SECONDS How long 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" " --speed=SPEED Animation speed. Either a unitless multiplier, or a real\n" " number followed by \"fps\" to apply a specific framerate.\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" "\nColors and processing:\n" " --bg=COLOR Background color of display (color name or hex).\n" " -c, --colors=MODE Set output color mode; one of [none, 2, 8, 16/8, 16, 240,\n" " 256, full]. Defaults to best guess.\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" " --fg=COLOR Foreground color of display (color name or hex).\n" " --invert Swaps --fg and --bg. Useful with light terminal background.\n" " -p, --preprocess=BOOL Image preprocessing [on, off]. Defaults to on with 16\n" " colors or lower, off otherwise.\n" " -t, --threshold=NUM Lower threshold for full transparency [0.0 - 1.0].\n" "\nResource allocation:\n" " --threads=NUM Maximum number of CPU threads to use. If left unspecified\n" " or negative, this will equal available CPU cores.\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" "\nExtra options for symbol encoding:\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" " --glyph-file=FILE Load glyph information from FILE, which can be any\n" " font file supported by FreeType (TTF, PCF, etc).\n" " --symbols=SYMS Specify character symbols to employ in final output.\n" " See below for full usage and a list of symbol classes.\n" "\nAccepted classes for --symbols and --fill:\n" " all ascii braille extra imported narrow solid ugly\n" " alnum bad diagonal geometric inverted none space vhalf\n" " alpha block digit half latin quad stipple wedge\n" " ambiguous border dot hhalf legacy sextant technical wide\n" "\n These can be combined with + and -, e.g. block+border-diagonal or all-wide.\n" "\nExamples:\n" " $ chafa --scale max in.jpg # As big as will fit\n" " $ chafa --clear --align mid,mid -d 5 *.gif # A well-paced slideshow\n" " $ chafa -f symbols --symbols ascii -c none in.png # Old-school ASCII art\n\n" "If your OS comes with manual pages, you can type 'man chafa' for more.\n"; g_print ("Usage:\n %s [OPTION...] [FILE...]\n\n%s", options.executable_name, summary); } /* ---------------------------------- * * Option fuzzing with --fuzz-options * * ---------------------------------- */ #define FUZZ_SEED_LEN 150 static gboolean fuzz_seed_get_bool (gconstpointer seed, gint seed_len, gint *ofs) { const guchar *seed_u8 = seed; return seed_u8 [(*ofs)++ % seed_len] < 128 ? TRUE : FALSE; } static Tristate fuzz_seed_get_tristate (gconstpointer seed, gint seed_len, gint *ofs) { const guchar *seed_u8 = seed; guint v = seed_u8 [(*ofs)++ % seed_len]; return (v < 256 / 3) ? TRISTATE_FALSE : (v < (256 * 2) / 3) ? TRISTATE_TRUE : TRISTATE_AUTO; } static guint32 fuzz_seed_get_u32 (gconstpointer seed, gint seed_len, gint *ofs) { const guchar *seed_u8 = seed; guint32 u32 = 0; gint i; for (i = 0; i < 4; i++) { u32 |= seed_u8 [(*ofs)++ % seed_len]; u32 <<= 8; } return u32; } static guint32 fuzz_seed_get_uint (gconstpointer seed, gint seed_len, gint *ofs, guint32 min, guint32 max) { guint32 u32; u32 = fuzz_seed_get_u32 (seed, seed_len, ofs); return min + (u32 % (max - min)); } static gdouble fuzz_seed_get_double (gconstpointer seed, gint seed_len, gint *ofs, gdouble min, gdouble max) { guint32 u32; u32 = fuzz_seed_get_u32 (seed, seed_len, ofs); return min + (u32 % 65536) * ((max - min) / 65535.0); } static void fuzz_options_with_seed (GlobalOptions *opt, gconstpointer seed, gint seed_len) { gint ofs = 0; if (seed_len < 1) return; /* Fuzz as many options as possible, but avoid those needed to control * the fuzzing process -- e.g. n_threads, duration, animation speed, watch. */ opt->mode = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_CANVAS_MODE_MAX); opt->color_extractor = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_COLOR_EXTRACTOR_MAX); opt->color_space = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_COLOR_SPACE_MAX); opt->dither_mode = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_DITHER_MODE_MAX); opt->pixel_mode = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_PIXEL_MODE_MAX); opt->dither_grain_width = 1 << (fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 4)); opt->dither_grain_height = 1 << (fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 4)); opt->dither_intensity = fuzz_seed_get_double (seed, seed_len, &ofs, 0.0, 10.0); opt->clear = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->verbose = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->invert = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->preprocess = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->polite = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->stretch = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->zoom = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->fg_only = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->animate = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->horiz_align = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 3); opt->vert_align = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 3); opt->relative = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->relative_set = TRUE; opt->fit_to_width = fuzz_seed_get_bool (seed, seed_len, &ofs); opt->view_width = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->view_height = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->width = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->height = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->cell_width = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->cell_height = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 32); opt->margin_bottom = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 16); opt->margin_right = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 16); opt->scale = fuzz_seed_get_double (seed, seed_len, &ofs, 0.0, 10000.0); opt->work_factor = fuzz_seed_get_uint (seed, seed_len, &ofs, 1, 10); opt->optimization_level = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, 10); opt->passthrough = fuzz_seed_get_uint (seed, seed_len, &ofs, 0, CHAFA_PASSTHROUGH_MAX); opt->passthrough_set = TRUE; opt->transparency_threshold = fuzz_seed_get_double (seed, seed_len, &ofs, 0.0, 1.0); opt->transparency_threshold_set = TRUE; opt->use_exact_size = fuzz_seed_get_tristate (seed, seed_len, &ofs); } static void fuzz_options_with_file (GlobalOptions *opt, const gchar *filename) { FILE *fhd; guchar seed [FUZZ_SEED_LEN]; gint seed_len; fhd = fopen (filename, "rb"); if (!fhd) return; /* Read seed from the file's tail to avoid correlation with image * format headers etc. */ fseek (fhd, -FUZZ_SEED_LEN, SEEK_END); seed_len = fread (seed, sizeof (guchar), FUZZ_SEED_LEN, fhd); fclose (fhd); fuzz_options_with_seed (opt, seed, seed_len); } static gboolean parse_boolean_token (const gchar *token, gboolean *value_out) { gboolean success = FALSE; if (!g_ascii_strcasecmp (token, "on") || !g_ascii_strcasecmp (token, "yes") || !g_ascii_strcasecmp (token, "true")) { *value_out = TRUE; } else if (!g_ascii_strcasecmp (token, "off") || !g_ascii_strcasecmp (token, "no") || !g_ascii_strcasecmp (token, "false")) { *value_out = FALSE; } else { goto out; } success = TRUE; out: return success; } static gboolean parse_tristate_token (const gchar *token, Tristate *value_out) { gboolean success = FALSE; gboolean value_bool = FALSE; if (!g_ascii_strcasecmp (token, "auto") || !g_ascii_strcasecmp (token, "default")) { *value_out = TRISTATE_AUTO; } else { success = parse_boolean_token (token, &value_bool); if (!success) goto out; *value_out = value_bool ? TRISTATE_TRUE : TRISTATE_FALSE; } success = TRUE; out: return success; } 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, "8")) options.mode = CHAFA_CANVAS_MODE_INDEXED_8; else if (!g_ascii_strcasecmp (value, "16-8") || !g_ascii_strcasecmp (value, "16/8")) options.mode = CHAFA_CANVAS_MODE_INDEXED_16_8; else if (!g_ascii_strcasecmp (value, "16")) options.mode = CHAFA_CANVAS_MODE_INDEXED_16; 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, "direct") || !g_ascii_strcasecmp (value, "directcolor") || !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, 8, 16/8, 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 const gchar * utf8_skip_spaces (const gchar *str) { gunichar c; for (c = g_utf8_get_char (str); c != 0 && g_unichar_isspace (c); c = g_utf8_get_char (str)) { str = g_utf8_next_char (str); } return str; } static gboolean parse_fraction_or_real (const gchar *str, gdouble *real_out) { gboolean success = FALSE; gdouble ratio = G_MAXDOUBLE; gint64 width = -1, height = -1; const gchar *sep; const gchar *p0 = NULL; const gchar *end = NULL; p0 = utf8_skip_spaces (str); sep = g_utf8_strchr (p0, -1, '/'); if (!sep) sep = g_utf8_strchr (p0, -1, ':'); if (sep) { width = g_ascii_strtoll (p0, (gchar **) &end, 10); if (!end || end == p0) goto out; end = utf8_skip_spaces (end); if (g_utf8_get_char (end) != '/' && g_utf8_get_char (end) != ':') goto out; sep = g_utf8_next_char (sep); p0 = utf8_skip_spaces (sep); height = g_ascii_strtoll (p0, (gchar **) &end, 10); if (end == p0) goto out; end = utf8_skip_spaces (end); if (!end || *end) goto out; if ((gdouble) height != 0.0) ratio = width / (gdouble) height; } else { ratio = g_strtod (p0, (gchar **) &end); if (!end || end == p0) goto out; end = utf8_skip_spaces (end); if (!end || *end) goto out; } if (ratio == G_MAXDOUBLE) goto out; *real_out = ratio; success = TRUE; out: return success; } static gboolean parse_align_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean success = FALSE; gchar **tokens = NULL; ChafaAlign halign = CHAFA_ALIGN_MAX; ChafaAlign valign = CHAFA_ALIGN_MAX; gint i; tokens = g_strsplit (value, ",", -1); for (i = 0; tokens [i]; i++) { gchar *t = tokens [i]; if (i >= 2) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Too many alignment specifiers in \"%s\". Must be at most two.", value); goto out; } if (!strcasecmp (t, "left")) halign = CHAFA_ALIGN_START; else if (!strcasecmp (t, "right")) halign = CHAFA_ALIGN_END; else if (!strcasecmp (t, "hcenter") || !strcasecmp (t, "hmid")) halign = CHAFA_ALIGN_CENTER; else if (!strcasecmp (t, "top") || !strcasecmp (t, "up")) valign = CHAFA_ALIGN_START; else if (!strcasecmp (t, "bottom") || !strcasecmp (t, "down")) valign = CHAFA_ALIGN_END; else if (!strcasecmp (t, "vcenter") || !strcasecmp (t, "vmid")) valign = CHAFA_ALIGN_CENTER; else if (!strcasecmp (t, "center") || !strcasecmp (t, "mid")) ; /* Skip */ else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unknown alignment specifier \"%s\".", t); goto out; } } /* Process "center" in a second pass to allow both e.g. "center,left" * and "left,center" */ for (i = 0; tokens [i]; i++) { gchar *t = tokens [i]; if (!strcasecmp (t, "center") || !strcasecmp (t, "mid")) { /* Allow "center,center" to center in both dimensions */ if (halign == CHAFA_ALIGN_MAX) halign = CHAFA_ALIGN_CENTER; else valign = CHAFA_ALIGN_CENTER; } } if (halign != CHAFA_ALIGN_MAX) options.horiz_align = halign; if (valign != CHAFA_ALIGN_MAX) options.vert_align = valign; success = TRUE; out: g_strfreev (tokens); return success; } static gboolean parse_font_ratio_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gdouble ratio = -1.0; gboolean success = FALSE; if (!parse_fraction_or_real (value, &ratio) || ratio <= 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Font ratio must be specified as a positive real number or fraction."); goto out; } options.font_ratio = ratio; success = TRUE; out: return success; } static gboolean parse_scale_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gdouble scale = -1.0; gboolean success = FALSE; if (!strcasecmp (value, "max") || !strcasecmp (value, "fill")) { scale = SCALE_MAX; } else if (!parse_fraction_or_real (value, &scale) || scale <= 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Scale must be specified as a positive real number or fraction."); goto out; } options.scale = scale; success = TRUE; out: return success; } static gboolean parse_threshold_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gdouble threshold = -1.0; gboolean success = FALSE; if (!parse_fraction_or_real (value, &threshold) || threshold < 0.0 || threshold > 1.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Opacity threshold must be a real number or fraction in the range [0.0-1.0]."); goto out; } options.transparency_threshold = threshold; success = TRUE; out: return success; } static gboolean parse_dither_intensity_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gdouble dither_intensity = -1.0; gboolean success = FALSE; if (!parse_fraction_or_real (value, &dither_intensity) || dither_intensity < 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Dither intensity must be a positive real number or fraction."); goto out; } options.dither_intensity = dither_intensity; success = TRUE; out: return success; } static gboolean parse_duration_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gdouble duration = -1.0; gboolean success = FALSE; if (!strcasecmp (value, "max") || !strcasecmp (value, "inf") || !strcasecmp (value, "infinite")) { duration = G_MAXDOUBLE; } else if (!parse_fraction_or_real (value, &duration) || duration < 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Duration must be a positive real number or fraction, \"inf\" or \"infinite\"."); goto out; } options.file_duration_s = duration; success = TRUE; out: return success; } 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 if (!strcasecmp (value, "conhost")) { #ifdef G_OS_WIN32 options.is_conhost_mode = TRUE; pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; #else g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Conhost output is only available on MS Windows."); result = FALSE; #endif } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, #ifdef G_OS_WIN32 "Output format given as '%s'. Must be one of [conhost, iterm, kitty, sixels, symbols].", #else "Output format given as '%s'. Must be one of [iterm, kitty, sixels, symbols].", #endif value); result = FALSE; } if (result) options.pixel_mode = pixel_mode; return result; } static void parse_2d_size (const gchar *value, gint *width_out, gint *height_out) { gint n; gint o = 0; *width_out = *height_out = -1; n = sscanf (value, "%d%n", width_out, &o); if (value [o] == 'x' && value [o + 1] != '\0') { gint o2; n = sscanf (value + o + 1, "%d%n", height_out, &o2); if (n == 1 && value [o + o2 + 1] != '\0') { *width_out = *height_out = -1; } } } static gboolean parse_view_size_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; gint width, height; parse_2d_size (value, &width, &height); if (width < 0 && height < 0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "View size 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, "View size must be at least 1x1."); result = FALSE; } options.view_width = width; options.view_height = height; 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, height; parse_2d_size (value, &width, &height); if (width < 0 && height < 0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Size 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, "Size must be at least 1x1."); result = FALSE; } options.width = width; options.height = height; return result; } static gboolean parse_exact_size_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result; result = parse_tristate_token (value, &options.use_exact_size); if (!result) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Exact size selector must be one of [on, off, auto]."); } 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_passthrough_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; options.passthrough_set = TRUE; if (!g_ascii_strcasecmp (value, "none")) options.passthrough = CHAFA_PASSTHROUGH_NONE; else if (!g_ascii_strcasecmp (value, "screen")) options.passthrough = CHAFA_PASSTHROUGH_SCREEN; else if (!g_ascii_strcasecmp (value, "tmux")) options.passthrough = CHAFA_PASSTHROUGH_TMUX; else if (!g_ascii_strcasecmp (value, "auto")) { options.passthrough = CHAFA_PASSTHROUGH_NONE; options.passthrough_set = FALSE; } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Passthrough must be one of [auto, none, screen, tmux]."); result = FALSE; } 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 void dump_glyph (gunichar c, const guint8 *pix, gint width, gint height, gint rowstride) { gint x, y; const guint8 *p; gchar buf [16]; gint len; len = g_unichar_to_utf8 (c, buf); g_assert (len >= 1 && len < 16); buf [len] = '\0'; fprintf (stdout, " {\n" " /* [%s] */\n" " CHAFA_SYMBOL_TAG_,\n" " 0x%x,\n" " CHAFA_SYMBOL_OUTLINE_%s (", buf, c, (width == 8 && height == 8) ? "8X8" : (width == 16 && height == 8) ? "16X8" : "STRANGE_SIZE"); for (y = 0; y < height; y++) { p = pix + y * rowstride; fputs ("\n \"", stdout); for (x = 0; x < width; x++) { fputc (*p < 0x80 ? ' ' : 'X' , stdout); p += 4; } fputs ("\"", stdout); } fputs (")\n },\n", stdout); } /* Undocumented functionality; dumps a font file so the glyphs can be tweaked and turned * into builtins. */ static gboolean parse_dump_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; ChafaSymbolMap *temp_map = NULL; gunichar c; gpointer c_bitmap; gint width, height, rowstride; options.skip_processing = TRUE; 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; } temp_map = chafa_symbol_map_new (); while (font_loader_get_next_glyph (font_loader, &c, &c_bitmap, &width, &height)) { gpointer pix; /* By adding and then querying the glyph, we get the benefit of Chafa's * internal scaling and postprocessing. */ chafa_symbol_map_add_glyph (temp_map, c, CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, width, height, width * 4); g_free (c_bitmap); chafa_symbol_map_get_glyph (temp_map, c, CHAFA_PIXEL_RGBA8_PREMULTIPLIED, &pix, &width, &height, &rowstride); dump_glyph (c, pix, width, height, rowstride); g_free (pix); } font_loader_destroy (font_loader); result = TRUE; out: if (file_mapping) file_mapping_destroy (file_mapping); if (temp_map) chafa_symbol_map_unref (temp_map); return result; } static gboolean parse_animate_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result; result = parse_boolean_token (value, &options.animate); if (!result) g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Animate mode must be one of [on, off]."); return result; } static gboolean parse_center_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean center = FALSE; gboolean result; result = parse_boolean_token (value, ¢er); if (result) options.horiz_align = center ? CHAFA_ALIGN_CENTER : CHAFA_ALIGN_START; else g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Centering mode must be one of [on, off]."); 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; result = parse_boolean_token (value, &options.preprocess); if (!result) g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Preprocessing must be one of [on, off]."); return result; } static gboolean parse_polite_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result; result = parse_boolean_token (value, &options.polite); if (!result) g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Polite mode must be one of [on, off]."); return result; } static gboolean parse_relative_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result; result = parse_boolean_token (value, &options.relative); if (!result) g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Relative positioning must be one of [on, off]."); options.relative_set = TRUE; 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 (TermSize *term_size_out) { TermSize term_size; term_size.width_cells = term_size.height_cells = term_size.width_pixels = term_size.height_pixels = -1; #ifdef G_OS_WIN32 { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csb_info; if (chd != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo (chd, &csb_info)) { term_size.width_cells = csb_info.srWindow.Right - csb_info.srWindow.Left + 1; term_size.height_cells = csb_info.srWindow.Bottom - csb_info.srWindow.Top + 1; } } #elif defined(HAVE_SYS_IOCTL_H) { struct winsize w; gboolean have_winsz = FALSE; /* FIXME: Use tcgetwinsize() when it becomes more widely available. * See: https://www.austingroupbugs.net/view.php?id=1151#c3856 */ if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) >= 0 || ioctl (STDERR_FILENO, TIOCGWINSZ, &w) >= 0 || ioctl (STDIN_FILENO, TIOCGWINSZ, &w) >= 0) have_winsz = TRUE; # ifdef HAVE_CTERMID if (!have_winsz) { const gchar *term_path; gint fd = -1; term_path = ctermid (NULL); if (term_path) fd = g_open (term_path, O_RDONLY); if (fd >= 0) { if (ioctl (fd, TIOCGWINSZ, &w) >= 0) have_winsz = TRUE; g_close (fd, NULL); } } # endif /* HAVE_CTERMID */ if (have_winsz) { term_size.width_cells = w.ws_col; term_size.height_cells = w.ws_row; term_size.width_pixels = w.ws_xpixel; term_size.height_pixels = w.ws_ypixel; } } #endif /* HAVE_SYS_IOCTL_H */ if (term_size.width_cells <= 0) term_size.width_cells = -1; if (term_size.height_cells <= 2) term_size.height_cells = -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 (term_size.width_pixels > PIXEL_EXTENT_MAX || term_size.height_pixels > PIXEL_EXTENT_MAX) { /* https://github.com/hpjansson/chafa/issues/62 */ g_printerr ("%s: Terminal reports strange pixel dimensions of %dx%d.\n" "%s: Disregarding so as to avoid unreasonably large allocation.\n" "%s: This is sometimes caused by older versions of the 'fish' shell.\n" "%s: See https://github.com/hpjansson/chafa/issues/62 for details.\n", options.executable_name, (gint) term_size.width_pixels, (gint) term_size.height_pixels, options.executable_name, options.executable_name, options.executable_name); term_size.width_pixels = -1; term_size.height_pixels = -1; } else if (term_size.width_pixels <= 0 || term_size.height_pixels <= 0) { term_size.width_pixels = -1; term_size.height_pixels = -1; } *term_size_out = term_size; } static void tty_options_init (void) { #ifdef G_OS_WIN32 { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); saved_console_output_cp = GetConsoleOutputCP (); saved_console_input_cp = GetConsoleCP (); /* Enable ANSI escape sequence parsing etc. on MS Windows command prompt */ if (chd != INVALID_HANDLE_VALUE) { DWORD bitmask = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT; if (!options.is_conhost_mode) bitmask |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; if (!SetConsoleMode (chd, bitmask)) { if (GetLastError () == ERROR_INVALID_HANDLE) { win32_stdout_is_file = TRUE; } else { /* Compatibility with older MS Windows versions */ SetConsoleMode (chd,ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); } } } /* Set UTF-8 code page output */ SetConsoleOutputCP (65001); /* Set UTF-8 code page input, for good measure */ SetConsoleCP (65001); } #endif if (!options.polite) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2]; gchar *p0; #ifdef HAVE_TERMIOS_H if (options.is_interactive) { struct termios t; tcgetattr (STDIN_FILENO, &saved_termios); t = saved_termios; t.c_lflag &= ~ECHO; tcsetattr (STDIN_FILENO, TCSANOW, &t); } #endif if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.is_conhost_mode) { p0 = chafa_term_info_emit_disable_cursor (options.term_info, buf); write_to_stdout (buf, p0 - buf); } if (options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) { /* Most terminals should have sixel scrolling and advance-down enabled * by default, so we're not going to undo these later. */ p0 = chafa_term_info_emit_enable_sixel_scrolling (options.term_info, buf); p0 = chafa_term_info_emit_set_sixel_advance_down (options.term_info, p0); write_to_stdout (buf, p0 - buf); } } } static void tty_options_deinit (void) { #ifdef G_OS_WIN32 SetConsoleOutputCP (saved_console_output_cp); SetConsoleCP (saved_console_input_cp); #endif if (!options.polite) { if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.is_conhost_mode) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX]; gchar *p0; p0 = chafa_term_info_emit_enable_cursor (options.term_info, buf); write_to_stdout (buf, p0 - buf); } #ifdef HAVE_TERMIOS_H if (options.is_interactive) { tcsetattr (STDIN_FILENO, TCSANOW, &saved_termios); } #endif } } static gint get_tmux_version (gchar **envp) { const gchar *tmux_ver_str; const gchar *sep; tmux_ver_str = g_environ_getenv (envp, "TERM_PROGRAM_VERSION"); if (!tmux_ver_str) return 0; sep = strchr (tmux_ver_str, '.'); return g_ascii_strtoull (tmux_ver_str, NULL, 10) * 1000 + (sep ? g_ascii_strtoull (sep + 1, NULL, 10) : 0); } static void detect_terminal (gchar **envp, ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, ChafaPixelMode *pixel_mode_out, ChafaPassthrough *passthrough_out, gboolean *polite_out) { ChafaCanvasMode mode; ChafaPixelMode pixel_mode; ChafaPassthrough passthrough; ChafaTermInfo *term_info; ChafaTermInfo *fallback_info; 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_SET_COLOR_FGBG_8) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_8) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_8)) mode = CHAFA_CANVAS_MODE_INDEXED_8; 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 if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_END_ITERM2_IMAGE)) pixel_mode = CHAFA_PIXEL_MODE_ITERM2; else pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SCREEN_PASSTHROUGH)) { /* We can do passthrough for sixels and iterm too, but we won't do so * automatically, since they'll break with inner TE updates. */ if (pixel_mode != CHAFA_PIXEL_MODE_KITTY) pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; passthrough = CHAFA_PASSTHROUGH_SCREEN; } else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_TMUX_PASSTHROUGH)) { /* We can do passthrough for sixels and iterm too, but we won't do so * automatically, since they'll break with inner TE updates. However, * tmux 3.4+ supports sixels without the need for passthrough, so don't * downgrade the pixel mode in that case. */ if (pixel_mode != CHAFA_PIXEL_MODE_KITTY && get_tmux_version (envp) < 3004) pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; passthrough = CHAFA_PASSTHROUGH_TMUX; } else { passthrough = CHAFA_PASSTHROUGH_NONE; } /* 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; *passthrough_out = passthrough; /* The 'lf' file browser will choke if there are extra sequences in front * of a sixel image. Let's be polite to it. */ *polite_out = g_environ_getenv (envp, "LF_LEVEL") ? TRUE : FALSE; } static gchar *tmux_allow_passthrough_original; static gboolean tmux_allow_passthrough_is_changed; static gboolean apply_passthrough_workarounds_tmux (void) { gboolean result = FALSE; gchar *standard_output = NULL; gchar *standard_error = NULL; gchar **strings; gchar *mode = NULL; gint wait_status = -1; /* allow-passthrough can be either unset, "on" or "all". Both "on" and "all" * are fine, so don't mess with it if we don't have to. * * Also note that we may be in a remote session inside tmux. */ if (!g_spawn_command_line_sync ("tmux show allow-passthrough", &standard_output, &standard_error, &wait_status, NULL)) goto out; strings = g_strsplit_set (standard_output, " ", -1); if (strings [0] && strings [1]) { mode = g_ascii_strdown (strings [1], -1); g_strstrip (mode); } g_strfreev (strings); g_free (standard_output); standard_output = NULL; g_free (standard_error); standard_error = NULL; if (!mode || (strcmp (mode, "on") && strcmp (mode, "all"))) { result = g_spawn_command_line_sync ("tmux set-option allow-passthrough on", &standard_output, &standard_error, &wait_status, NULL); if (result) { tmux_allow_passthrough_original = mode; tmux_allow_passthrough_is_changed = TRUE; } } else { g_free (mode); } out: g_free (standard_output); g_free (standard_error); return result; } static gboolean retire_passthrough_workarounds_tmux (void) { gboolean result = FALSE; gchar *standard_output = NULL; gchar *standard_error = NULL; gchar *cmd; gint wait_status = -1; if (!tmux_allow_passthrough_is_changed) return TRUE; if (tmux_allow_passthrough_original) { cmd = g_strdup_printf ("tmux set-option allow-passthrough %s", tmux_allow_passthrough_original); } else { cmd = g_strdup ("tmux set-option -u allow-passthrough"); } result = g_spawn_command_line_sync (cmd, &standard_output, &standard_error, &wait_status, NULL); if (result) { g_free (tmux_allow_passthrough_original); tmux_allow_passthrough_original = NULL; tmux_allow_passthrough_is_changed = FALSE; } g_free (standard_output); g_free (standard_error); return result; } static gboolean parse_options (int *argc, char **argv []) { GError *error = NULL; GOptionContext *context; gboolean result = FALSE; 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 }, { "align", '\0', 0, G_OPTION_ARG_CALLBACK, parse_align_arg, "Align", NULL }, { "animate", '\0', 0, G_OPTION_ARG_CALLBACK, parse_animate_arg, "Animate", NULL }, { "bg", '\0', 0, G_OPTION_ARG_CALLBACK, parse_bg_color_arg, "Background color of display", NULL }, { "center", 'C', 0, G_OPTION_ARG_CALLBACK, parse_center_arg, "Center", 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_CALLBACK, parse_dither_intensity_arg, "Dither intensity", NULL }, { "dump-glyph-file", '\0', 0, G_OPTION_ARG_CALLBACK, parse_dump_glyph_file_arg, "Dump glyph file", NULL }, { "duration", 'd', 0, G_OPTION_ARG_CALLBACK, parse_duration_arg, "Duration", NULL }, { "exact-size", '\0', 0, G_OPTION_ARG_CALLBACK, parse_exact_size_arg, "Whether to prefer the original image size", 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 }, { "fit-width", '\0', 0, G_OPTION_ARG_NONE, &options.fit_to_width, "Fit to width", 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 }, { "fuzz-options", '\0', 0, G_OPTION_ARG_NONE, &options.fuzz_options, "Fuzz the options", 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 }, { "margin-bottom", '\0', 0, G_OPTION_ARG_INT, &options.margin_bottom, "Bottom margin", NULL }, { "margin-right", '\0', 0, G_OPTION_ARG_INT, &options.margin_right, "Right margin", NULL }, { "optimize", 'O', 0, G_OPTION_ARG_INT, &options.optimization_level, "Optimization", NULL }, { "passthrough", '\0', 0, G_OPTION_ARG_CALLBACK, parse_passthrough_arg, "Passthrough", NULL }, { "polite", '\0', 0, G_OPTION_ARG_CALLBACK, parse_polite_arg, "Polite", NULL }, { "preprocess", 'p', 0, G_OPTION_ARG_CALLBACK, parse_preprocess_arg, "Preprocessing", NULL }, { "relative", '\0', 0, G_OPTION_ARG_CALLBACK, parse_relative_arg, "Relative", NULL }, { "work", 'w', 0, G_OPTION_ARG_INT, &options.work_factor, "Work factor", NULL }, { "scale", '\0', 0, G_OPTION_ARG_CALLBACK, parse_scale_arg, "Scale", 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 }, { "threads", '\0', 0, G_OPTION_ARG_INT, &options.n_threads, "Number of threads", NULL }, { "threshold", 't', 0, G_OPTION_ARG_CALLBACK, parse_threshold_arg, "Transparency threshold", NULL }, { "view-size", '\0', 0, G_OPTION_ARG_CALLBACK, parse_view_size_arg, "View size", NULL }, { "watch", '\0', 0, G_OPTION_ARG_NONE, &options.watch, "Watch a file's contents", NULL }, /* Deprecated: Equivalent to --scale max */ { "zoom", '\0', 0, G_OPTION_ARG_NONE, &options.zoom, "Allow scaling up beyond one character per pixel", NULL }, { 0 } }; ChafaCanvasMode canvas_mode; ChafaPixelMode pixel_mode; ChafaPassthrough passthrough; gchar **envp; envp = g_get_environ (); 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 (); #ifdef G_OS_WIN32 chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_HALF); 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_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_SOLID); #else 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); #endif 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 (envp, &options.term_info, &canvas_mode, &pixel_mode, &passthrough, &options.polite); 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.animate = TRUE; options.horiz_align = CHAFA_ALIGN_START; options.vert_align = CHAFA_ALIGN_START; options.preprocess = TRUE; options.relative_set = FALSE; options.fg_only = FALSE; options.color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; options.color_space = CHAFA_COLOR_SPACE_RGB; options.view_width = -1; /* Unset */ options.view_height = -1; /* Unset */ options.width = -1; /* Unset */ options.height = -1; /* Unset */ options.fit_to_width = FALSE; options.font_ratio = -1.0; /* Unset */ options.margin_bottom = -1; /* Unset */ options.margin_right = -1; /* Unset */ options.scale = -1.0; /* Unset */ options.work_factor = 5; options.optimization_level = G_MININT; /* Unset */ options.n_threads = -1; options.fg_color = 0xffffff; options.bg_color = 0x000000; options.transparency_threshold = G_MAXDOUBLE; /* Unset */ options.file_duration_s = -1.0; /* Unset */ options.anim_fps = -1.0; options.anim_speed_multiplier = 1.0; options.use_exact_size = TRISTATE_AUTO; options.output_utf_16_on_windows = FALSE; options.is_conhost_mode = FALSE; options.cell_width = 10; options.cell_height = 20; if (!g_option_context_parse (context, argc, argv, &error)) { g_printerr ("%s: %s\n", options.executable_name, error->message); goto out; } /* If help or version info was requested, print it and bail out */ if (options.show_help) { print_summary (); options.skip_processing = TRUE; } if (options.show_version) { print_version (); options.skip_processing = TRUE; } /* Some options preclude normal arg processing */ if (options.skip_processing) { result = TRUE; goto out; } /* Optionally fuzz the options */ if (options.fuzz_options && *argc > 1) { fuzz_options_with_file (&options, (*argv) [1]); } /* Detect terminal geometry */ get_tty_size (&detected_term_size); if (detected_term_size.width_cells > 0 && detected_term_size.height_cells > 0 && detected_term_size.width_pixels > 0 && detected_term_size.height_pixels > 0) { options.cell_width = detected_term_size.width_pixels / detected_term_size.width_cells; options.cell_height = detected_term_size.height_pixels / detected_term_size.height_cells; } if (options.cell_width > 0 && options.cell_height > 0) { if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS && options.font_ratio > 0.0) { options.cell_height = options.cell_width / options.font_ratio; } else { options.font_ratio = (gdouble) options.cell_width / (gdouble) options.cell_height; } } /* Assign detected or default dimensions if none specified */ if (options.width < 0 && options.height < 0) { using_detected_size = TRUE; } if (options.margin_bottom < 0) { options.margin_bottom = 1; /* Default */ } /* Sixel output always leaves the cursor below the image, so force a bottom * margin of at least one even if the user wanted less. */ if (options.margin_bottom < 1 && options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) options.margin_bottom = 1; if (options.margin_right < 0) { options.margin_right = 0; /* Default */ /* 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 && using_detected_size && !(options.clear && options.margin_bottom >= 2)) { options.margin_right = 1; } } if (options.view_width < 0 && options.view_height < 0) { options.view_width = detected_term_size.width_cells; options.view_height = detected_term_size.height_cells; if (options.view_width < 0 && options.view_height < 0) { options.view_width = 80; options.view_height = 25; } } if (using_detected_size && ((options.stretch && (options.view_width < 0 || options.view_height < 0)) || (options.fit_to_width && options.view_width < 0))) { g_printerr ("%s: Refusing to stretch images to infinity.\n", options.executable_name); goto out; } if (options.view_width < 0) options.view_width = CELL_EXTENT_AUTO_MAX; if (options.view_height < 0) options.view_height = CELL_EXTENT_AUTO_MAX; if (using_detected_size) { options.width = options.view_width; options.height = options.view_height; options.width = (options.width > options.margin_right) ? options.width - options.margin_right : 1; options.height = (options.height > options.margin_bottom) ? options.height - options.margin_bottom : 1; if (options.fit_to_width) { options.height = CELL_EXTENT_AUTO_MAX; /* Fit-to-width only makes sense if we respect the aspect ratio. * We could complain about infinities here, but let's be nice and * let it override --stretch instead. */ options.stretch = FALSE; /* --fit-width implies --scale max */ options.scale = SCALE_MAX; } } options.have_parking_row = ((using_detected_size || options.vert_align == CHAFA_ALIGN_END) && options.margin_bottom == 0) ? FALSE : TRUE; /* 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.scale <= 0.0) options.scale = 4.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.scale <= 0.0) options.scale = 1.0; } /* Apply detected passthrough if auto */ if (!options.passthrough_set) { /* tmux 3.4+ supports sixels without the need for passthrough. We check * this here instead of in detect_terminal() because we need to react to * a user-requested sixel mode too */ if (options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS && passthrough == CHAFA_PASSTHROUGH_TMUX && get_tmux_version (envp) >= 3004) { options.passthrough = CHAFA_PASSTHROUGH_NONE; } else { options.passthrough = passthrough; } } /* Apply tmux workarounds only if both detected and desired, and a * graphics protocol is desired */ if (passthrough == CHAFA_PASSTHROUGH_TMUX && options.passthrough == CHAFA_PASSTHROUGH_TMUX && options.pixel_mode != CHAFA_PIXEL_MODE_SYMBOLS) apply_passthrough_workarounds_tmux (); 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); goto out; } if (options.transparency_threshold == G_MAXDOUBLE) options.transparency_threshold = 0.5; else options.transparency_threshold_set = TRUE; 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); goto out; } /* Collect filenames and validate count and correct usage of stdin */ if (*argc > 1) { options.args = collect_variable_arguments (argc, argv, 1); } else if (!isatty (STDIN_FILENO)) { /* Receiving data through a pipe, and no file arguments. Act as if * invoked with "chafa -". */ options.args = g_list_append (NULL, g_strdup ("-")); } else { /* No arguments, no pipe, and no cry for help. */ print_brief_summary (); goto out; } if (count_dash_strings (options.args) > 1) { g_printerr ("%s: Dash '-' to pipe from standard input can be used at most once.\n", options.executable_name); goto out; } if (options.watch) { if (g_list_length (options.args) != 1) { g_printerr ("%s: Can only use --watch with exactly one file.\n", options.executable_name); goto out; } if (!strcmp (options.args->data, "-")) { g_printerr ("%s: Can only use --watch with a filename, not a pipe.\n", options.executable_name); goto out; } } if (options.zoom) { g_printerr ("%s: Warning: --zoom is deprecated, use --scale max instead.\n", options.executable_name); options.scale = SCALE_MAX; } /* --stretch implies --scale max (same as --zoom) */ if (options.stretch) { options.scale = SCALE_MAX; } 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 < 0.0 && (!options.is_interactive || (options.args && options.args->next))) { /* Apply a zero default duration when we have multiple files or it looks * like we're part of a pipe; we don't want to get stuck if the user is * trying to e.g. batch convert files. Allow -d to override. */ 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); goto out; } /* 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; chafa_set_n_threads (options.n_threads); #ifdef G_OS_WIN32 if (options.is_conhost_mode) { options.output_utf_16_on_windows = TRUE; if (options.mode == CHAFA_CANVAS_MODE_INDEXED_240 || options.mode == CHAFA_CANVAS_MODE_INDEXED_256 || options.mode == CHAFA_CANVAS_MODE_TRUECOLOR) options.mode = CHAFA_CANVAS_MODE_INDEXED_16; } #else /* Force it to not output UTF16 when not Windows */ options.output_utf_16_on_windows = FALSE; options.is_conhost_mode = FALSE; #endif result = TRUE; out: g_option_context_free (context); g_strfreev (envp); return result; } static void pixel_to_cell_dimensions (gdouble scale, gint cell_width, gint cell_height, gint width, gint height, gint *width_out, gint *height_out) { gint extra_height; /* Scale can't be zero or negative */ scale = MAX (scale, 0.00001); /* Zero or negative cell dimensions -> presumably unknown, use 10x20 */ if (cell_width < 1) cell_width = 10; if (cell_height < 1) cell_height = 20; extra_height = options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS ? 5 : 0; if (width_out) { *width_out = ((int) (width * scale) + cell_width - 1) / cell_width; *width_out = MAX (*width_out, 1); } if (height_out) { *height_out = ((int) (height * scale) + cell_height - 1 + extra_height) / cell_height; *height_out = MAX (*height_out, 1); } } static gchar * emit_vertical_space (gchar *dest) { if (options.relative) { dest = chafa_term_info_emit_cursor_down_scroll (options.term_info, dest); } else { *dest++ = '\n'; } return dest; } static gboolean write_gstring_to_stdout (GString *gs) { return write_to_stdout (gs->str, gs->len); } static gboolean write_gstrings_to_stdout (GString **gsa) { gint i; for (i = 0; gsa [i]; i++) { if (!write_gstring_to_stdout (gsa [i])) return FALSE; } return TRUE; } static gboolean write_pad_spaces (gint n) { gchar buf [BUFFER_MAX]; gint i; g_assert (n >= 0); n = MIN (n, BUFFER_MAX); for (i = 0; i < n; i++) buf [i] = ' '; return write_to_stdout (buf, n); } static gboolean write_cursor_down_scroll_n (gint n) { gchar buf [BUFFER_MAX]; gchar *p0 = buf; if (n < 1) return TRUE; for ( ; n; n--) { p0 = chafa_term_info_emit_cursor_down_scroll (options.term_info, p0); if (p0 - buf + CHAFA_TERM_SEQ_LENGTH_MAX > BUFFER_MAX) { if (!write_to_stdout (buf, p0 - buf)) return FALSE; p0 = buf; } } return write_to_stdout (buf, p0 - buf); } static gboolean write_vertical_spaces (gint n) { gchar buf [BUFFER_MAX]; if (n < 1) return TRUE; if (options.relative) return write_cursor_down_scroll_n (n); memset (buf, '\n', MIN (n, BUFFER_MAX)); for ( ; n > 0; n -= BUFFER_MAX) { if (!write_to_stdout (buf, MIN (n, BUFFER_MAX))) return FALSE; } return TRUE; } static gboolean write_image_prologue (gboolean is_first_file, gboolean is_first_frame, gboolean is_animation, gint dest_height) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2]; gchar *p0 = buf; /* Insert a blank line between files in continuous (!clear) mode */ if (is_first_frame && !options.clear && !is_first_file) { if (!options.have_parking_row) p0 = emit_vertical_space (p0); p0 = emit_vertical_space (p0); if (!write_to_stdout (buf, p0 - buf)) return FALSE; 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); if (!write_to_stdout (buf, p0 - buf)) return FALSE; p0 = buf; } /* Insert space for vertical alignment */ if ((options.clear || is_first_frame) && !write_vertical_spaces (options.vert_align == CHAFA_ALIGN_START ? 0 : options.vert_align == CHAFA_ALIGN_CENTER ? ((options.view_height - dest_height - options.margin_bottom) / 2) : (options.view_height - dest_height - options.margin_bottom))) return FALSE; if (!options.clear && is_animation && is_first_frame) { /* Start animation: reserve space and save image origin */ if (!write_cursor_down_scroll_n (dest_height)) return FALSE; p0 = chafa_term_info_emit_cursor_up (options.term_info, p0, dest_height); p0 = chafa_term_info_emit_save_cursor_pos (options.term_info, p0); } else if (!options.clear && !is_first_frame) { /* Subsequent animation frames: cursor to image origin */ p0 = chafa_term_info_emit_restore_cursor_pos (options.term_info, p0); } return write_to_stdout (buf, p0 - buf); } static gboolean write_image_epilogue (gint dest_width) { gint left_space; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 3]; gchar *p0 = buf; left_space = options.horiz_align == CHAFA_ALIGN_START ? 0 : options.horiz_align == CHAFA_ALIGN_CENTER ? ((options.view_width - dest_width - options.margin_right) / 2) : (options.view_width - dest_width - options.margin_right); /* These modes leave cursor to the right of the bottom row */ if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS || options.pixel_mode == CHAFA_PIXEL_MODE_KITTY || options.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) { if (options.have_parking_row) { p0 = emit_vertical_space (p0); } else if (!options.relative) { /* We need this because absolute mode relies on emit_vertical_space() * producing an \n that implies a CR with Unix semantics */ *(p0++) = '\r'; } if (options.relative) p0 = chafa_term_info_emit_cursor_left (options.term_info, p0, left_space + dest_width); } else /* CHAFA_PIXEL_MODE_SIXELS */ { /* Sixel mode leaves the cursor in the leftmost column of the final band */ if (options.relative) { p0 = chafa_term_info_emit_cursor_down_scroll (options.term_info, p0); if (left_space > 0) p0 = chafa_term_info_emit_cursor_left (options.term_info, p0, left_space); } else { *(p0++) = '\n'; } } return write_to_stdout (buf, p0 - buf); } /* Write out the image data, possibly centering it */ static gboolean write_image (GString **gsa, gint dest_width) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 3 + 3]; gchar *p0 = buf; gint left_space; gboolean result = FALSE; left_space = options.horiz_align == CHAFA_ALIGN_START ? 0 : options.horiz_align == CHAFA_ALIGN_CENTER ? ((options.view_width - dest_width - options.margin_right) / 2) : (options.view_width - dest_width - options.margin_right); left_space = MAX (left_space, 0); /* Indent top left corner: Common for all modes */ if (options.relative && left_space > 0) { p0 = chafa_term_info_emit_cursor_right (options.term_info, buf, left_space); if (!write_to_stdout (buf, p0 - buf)) goto out; } else { if (!write_pad_spaces (left_space)) goto out; } if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) { gint i; /* Indent subsequent rows: Symbols mode only */ for (i = 0; gsa [i]; i++) { if (!write_gstring_to_stdout (gsa [i])) goto out; if (gsa [i + 1]) { if (options.relative) { p0 = chafa_term_info_emit_cursor_left (options.term_info, buf, left_space + dest_width); p0 = chafa_term_info_emit_cursor_down_scroll (options.term_info, p0); if (left_space > 0) p0 = chafa_term_info_emit_cursor_right (options.term_info, p0, left_space); if (!write_to_stdout (buf, p0 - buf)) goto out; } else { if (!write_to_stdout ("\n", 1)) goto out; if (!write_pad_spaces (left_space)) goto out; } } } } else { if (!write_gstrings_to_stdout (gsa)) goto out; } result = TRUE; out: return result; } static ChafaCanvasConfig * build_config (gint dest_width, gint dest_height, gboolean is_animation) { ChafaCanvasConfig *config; 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); chafa_canvas_config_set_passthrough (config, options.passthrough); /* With Kitty and sixels, animation frames should have an opaque background. * Otherwise, previous frames will show through transparent areas. */ if (is_animation && (options.pixel_mode == CHAFA_PIXEL_MODE_KITTY || options.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) && !options.transparency_threshold_set) chafa_canvas_config_set_transparency_threshold (config, 1.0f); else 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.0f); chafa_canvas_config_set_optimizations (config, options.optimizations); return config; } static ChafaCanvas * build_canvas (ChafaPixelType pixel_type, const guint8 *pixels, gint src_width, gint src_height, gint src_rowstride, const ChafaCanvasConfig *config, gint placement_id, ChafaTuck tuck) { ChafaFrame *frame; ChafaImage *image; ChafaPlacement *placement; ChafaCanvas *canvas; canvas = chafa_canvas_new (config); frame = chafa_frame_new_borrow ((gpointer) pixels, pixel_type, src_width, src_height, src_rowstride); image = chafa_image_new (); chafa_image_set_frame (image, frame); placement = chafa_placement_new (image, placement_id); chafa_placement_set_tuck (placement, tuck); chafa_placement_set_halign (placement, options.horiz_align); chafa_placement_set_valign (placement, options.vert_align); chafa_canvas_set_placement (canvas, placement); chafa_placement_unref (placement); chafa_image_unref (image); chafa_frame_unref (frame); return canvas; } typedef enum { FILE_FAILED, FILE_WAS_STILL, FILE_WAS_ANIMATION } RunResult; static RunResult run_generic (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { gboolean is_animation = FALSE; gdouble anim_duration_s = options.file_duration_s >= 0.0 ? options.file_duration_s : G_MAXDOUBLE; gdouble anim_elapsed_s = 0.0; GTimer *timer; gint loop_n = 0; MediaLoader *media_loader; GString **gsa; gint placement_id = -1; gint frame_count = 0; RunResult result = FILE_FAILED; GError *error = NULL; timer = g_timer_new (); media_loader = media_loader_new (filename, &error); if (!media_loader) { if (!quiet) g_printerr ("%s: Failed to open '%s': %s\n", options.executable_name, filename, error->message); goto out; } if (interrupted_by_user) goto out; if (options.pixel_mode == CHAFA_PIXEL_MODE_KITTY && options.passthrough != CHAFA_PASSTHROUGH_NONE) { if (!placement_counter) placement_counter = placement_counter_new (); placement_id = placement_counter_get_next_id (placement_counter); } is_animation = options.animate ? media_loader_get_is_animation (media_loader) : FALSE; result = is_animation ? FILE_WAS_ANIMATION : FILE_WAS_STILL; do { gboolean have_frame; /* Outer loop repeats animation if desired */ media_loader_goto_first_frame (media_loader); for (have_frame = TRUE; have_frame && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < anim_duration_s); have_frame = media_loader_goto_next_frame (media_loader)) { gdouble elapsed_ms, remain_ms; gint delay_ms; ChafaPixelType pixel_type; gint src_width, src_height, src_rowstride; gint uncorrected_src_width, uncorrected_src_height; gint virt_src_width, virt_src_height; gint dest_width, dest_height; const guint8 *pixels; ChafaCanvasConfig *config; ChafaCanvas *canvas; ChafaTuck tuck; g_timer_start (timer); if (options.use_exact_size == TRISTATE_TRUE) { /* True */ tuck = CHAFA_TUCK_SHRINK_TO_FIT; } else { /* False/auto */ if (options.stretch) { tuck = CHAFA_TUCK_STRETCH; } else { tuck = CHAFA_TUCK_FIT; } } pixels = media_loader_get_frame_data (media_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; delay_ms = media_loader_get_frame_delay (media_loader); /* Hack to work around the fact that chafa_calc_canvas_geometry() doesn't * support arbitrary scaling. Instead, we manipulate the source size to * achieve the desired effect. */ if (using_detected_size && options.scale < SCALE_MAX - 0.1) { pixel_to_cell_dimensions (options.scale, options.cell_width, options.cell_height, src_width, src_height, &uncorrected_src_width, &uncorrected_src_height); virt_src_width = uncorrected_src_width; if (options.cell_width > 0 && options.cell_height > 0) virt_src_height = uncorrected_src_height / options.font_ratio; else virt_src_height = uncorrected_src_height; } else { virt_src_width = uncorrected_src_width = src_width; virt_src_height = uncorrected_src_height = src_height; } if (options.use_exact_size == TRISTATE_TRUE) { dest_width = virt_src_width; dest_height = virt_src_height; } else { dest_width = options.width; dest_height = options.height; } chafa_calc_canvas_geometry (virt_src_width, virt_src_height, &dest_width, &dest_height, options.font_ratio, options.scale >= SCALE_MAX - 0.1 ? TRUE : FALSE, options.stretch); if (options.use_exact_size == TRISTATE_AUTO && dest_width == uncorrected_src_width && dest_height == uncorrected_src_height) { tuck = CHAFA_TUCK_SHRINK_TO_FIT; } #if 0 /* The size calculations are too convoluted, so we may need this to * debug --exact-size. */ g_printerr ("src=(%dx%d) unc=(%dx%d) virt=(%dx%d) dest=(%dx%d)\n", src_width, src_height, uncorrected_src_width, uncorrected_src_height, virt_src_width, virt_src_height, dest_width, dest_height); #endif config = build_config (dest_width, dest_height, is_animation); canvas = build_canvas (pixel_type, pixels, src_width, src_height, src_rowstride, config, placement_id >= 0 ? placement_id + ((frame_count++) % 2) : -1, tuck); #ifdef G_OS_WIN32 if (options.is_conhost_mode) { ConhostRow *lines; gssize s; s = canvas_to_conhost (canvas, &lines); g_assert (s >= 0); write_image_conhost (lines, (gsize) s); destroy_lines (lines, (gsize) s); } else #endif { chafa_canvas_print_rows (canvas, options.term_info, &gsa, NULL); if (!write_image_prologue (is_first_file, is_first_frame, is_animation, dest_height) || !write_image (gsa, dest_width) || !write_image_epilogue (dest_width) || fflush (stdout) != 0) { chafa_free_gstring_array (gsa); goto out; } chafa_free_gstring_array (gsa); } chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); 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 = 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.0); anim_elapsed_s += MAX (elapsed_ms, delay_ms) / 1000.0; } is_first_frame = FALSE; if (!is_animation) break; } loop_n++; } while (is_animation && !interrupted_by_user && !options.watch && anim_elapsed_s < anim_duration_s); out: /* We need two IDs per animation in order to do flicker-free flips. If the * final frame got the higher ID, increment the global counter so the next * image doesn't clobber it. */ if (placement_id >= 0 && !(frame_count % 2)) placement_id = placement_counter_get_next_id (placement_counter); if (media_loader) media_loader_destroy (media_loader); g_timer_destroy (timer); if (error) g_error_free (error); return result; } static RunResult run (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { return run_generic (filename, is_first_file, is_first_frame, quiet); } static int run_watch (const gchar *filename) { GTimer *timer; gboolean is_first_frame = TRUE; gdouble duration_s = options.file_duration_s >= 0.0 ? options.file_duration_s : G_MAXDOUBLE; tty_options_init (); 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) > duration_s) break; } g_timer_destroy (timer); tty_options_deinit (); return 0; } static int run_all (GList *filenames) { GList *l; gdouble still_duration_s = options.file_duration_s > 0.0 ? options.file_duration_s : 0.0; gint n_processed = 0; gint n_failed = 0; /* This can only happen with --help and --version, so no error */ if (!filenames) return 0; tty_options_init (); for (l = filenames; l && !interrupted_by_user; l = g_list_next (l)) { gchar *filename = l->data; RunResult result; result = run (filename, l->prev ? FALSE : TRUE, TRUE, FALSE); n_processed++; if (result == FILE_FAILED) n_failed++; if (result == FILE_WAS_STILL && still_duration_s > 0.0) { interruptible_usleep (still_duration_s * 1000000.0); } } /* Emit linefeed after last image when cursor was not in parking row */ if (!options.have_parking_row) write_to_stdout ("\n", 1); tty_options_deinit (); return (n_processed - n_failed < 1) ? 2 : (n_failed > 0) ? 1 : 0; } static void proc_init (void) { #ifdef HAVE_SIGACTION struct sigaction sa = { 0 }; sa.sa_handler = sigint_handler; sa.sa_flags = SA_RESETHAND; sigaction (SIGINT, &sa, NULL); #endif #ifdef G_OS_WIN32 setmode (fileno (stdin), O_BINARY); setmode (fileno (stdout), O_BINARY); #endif /* Must do this early. Buffer size probably doesn't matter */ setvbuf (stdout, NULL, _IOFBF, 32768); setlocale (LC_ALL, ""); /* Chafa may create and destroy GThreadPools multiple times while rendering * an image. This reduces thread churn and saves a decent amount of CPU. */ g_thread_pool_set_max_unused_threads (-1); } int main (int argc, char *argv []) { int ret; proc_init (); if (!parse_options (&argc, &argv)) exit (2); ret = options.watch ? run_watch (options.args->data) : run_all (options.args); retire_passthrough_workarounds_tmux (); if (placement_counter) placement_counter_destroy (placement_counter); 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.14.5/tools/chafa/conhost.c000066400000000000000000000154321471154763100167050ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 . */ /* Authors: oshaboy@github */ #include "conhost.h" #include gboolean win32_stdout_is_file = FALSE; static gsize unichar_to_utf16 (gunichar c, gunichar2 *str) { if (c >= 0x110000 || (c < 0xe000 && c >= 0xd800) || (c % 0x10000 >= 0xfffe)) return 0; if (c<0x10000) { *str = (gunichar2) c; return 1; } else { const gunichar temp = c-0x10000; str[0] = (temp >> 10) + 0xd800; str[1] = (temp & 0x3ff) + 0xdc00; return 2; } } #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) gssize canvas_to_conhost (ChafaCanvas *canvas, ConhostRow **lines) { gint width, height; const ChafaCanvasConfig *config; ChafaCanvasMode canvas_mode; static const gchar color_lut [16] = { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; config = chafa_canvas_peek_config (canvas); canvas_mode = chafa_canvas_config_get_canvas_mode (config); if (canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 || canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 || canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { return (gssize) -1; } chafa_canvas_config_get_geometry (config, &width, &height); (*lines) = g_malloc (height * sizeof (ConhostRow)); for (gint y = 0; y < height; y++) { ConhostRow * const line = (*lines) + y; *line = (ConhostRow) { .attributes = g_malloc (width * sizeof (ConhostAttribute)), .str = g_malloc (width * sizeof (gunichar2) * 2), .length = width, .utf16_string_length = 0 }; for (int x = 0; x < width; x++) { gunichar c = chafa_canvas_get_char_at (canvas, x, y); gunichar2 utf16_codes [2]; gsize s = unichar_to_utf16 (c, utf16_codes); if (s > 0) line->str [line->utf16_string_length++] = *utf16_codes; if (s == 2) line->str [line->utf16_string_length++] = utf16_codes [1]; if (canvas_mode == CHAFA_CANVAS_MODE_FGBG) { line->attributes [x] = FOREGROUND_ALL; } else { gint fg_out, bg_out; chafa_canvas_get_raw_colors_at (canvas, x, y, &fg_out, &bg_out); if (canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { line->attributes [x] = bg_out ? FOREGROUND_ALL : COMMON_LVB_REVERSE_VIDEO | FOREGROUND_ALL; } else { fg_out=color_lut [fg_out]; bg_out=color_lut [bg_out]; line->attributes [x] = (bg_out << 4) | fg_out; } } } line->str = g_realloc (line->str, line->utf16_string_length * sizeof (gunichar2)); } return height; } void write_image_conhost (const ConhostRow * lines, gsize s) { HANDLE outh; COORD curpos; DWORD idc; outh = GetStdHandle (STD_OUTPUT_HANDLE); { CONSOLE_SCREEN_BUFFER_INFO bufinfo; GetConsoleScreenBufferInfo (outh, &bufinfo); curpos = bufinfo.dwCursorPosition; } for (gsize y = 0; y < s; y++) { const ConhostRow line = lines [y]; WriteConsoleOutputCharacterW (outh, line.str, line.utf16_string_length, curpos,&idc); WriteConsoleOutputAttribute (outh, line.attributes, line.length, curpos, &idc); curpos.Y++; } /* WriteConsoleOutput family doesn't scroll the screen * so we have to make another API call to scroll */ SetConsoleCursorPosition (outh, curpos); } void destroy_lines (ConhostRow * lines, gsize s) { for (gsize i=0; i. */ /* Authors: oshaboy@github */ #ifndef CHARACTER_CANVAS_TO_CONHOST_H #define CHARACTER_CANVAS_TO_CONHOST_H #include #include #include typedef WORD ConhostAttribute; typedef struct { gunichar2 *str; ConhostAttribute *attributes; size_t length; size_t utf16_string_length; } ConhostRow; /* We must determine if stdout is redirected to a file, and if so, use a * different set of I/O functions. */ extern gboolean win32_stdout_is_file; gboolean safe_WriteConsoleA (HANDLE chd, const gchar *data, gsize len); gboolean safe_WriteConsoleW (HANDLE chd, const gunichar2 *data, gsize len); gssize canvas_to_conhost (ChafaCanvas *canvas, ConhostRow **lines); void write_image_conhost (const ConhostRow *lines, gsize s); void destroy_lines (ConhostRow *lines, gsize s); #endif chafa-1.14.5/tools/chafa/file-mapping.c000066400000000000000000000346461471154763100176100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #ifdef G_OS_WIN32 # include # include #endif #ifdef HAVE_MMAP # include #endif #ifdef HAVE_GETRANDOM # include #endif #include #include "file-mapping.h" #ifdef HAVE_MMAP /* Workaround for non-Linux platforms */ # ifndef MAP_NORESERVE # define MAP_NORESERVE 0 # endif #endif /* MS Windows needs files to be explicitly opened as O_BINARY. However, this * flag is not always defined in our cross builds. */ #ifndef O_BINARY # ifdef _O_BINARY # define O_BINARY _O_BINARY # else # define O_BINARY 0 # endif #endif /* Streams bigger than this must be cached in a file */ #define FILE_MEMORY_CACHE_MAX (4 * 1024 * 1024) /* Size of buffer used for copying stdin to file */ #define COPY_BUFFER_SIZE 8192 /* A contiguous magic string can't be longer than this */ #define MAGIC_LENGTH_MAX 1024 struct FileMapping { gchar *path; gpointer data; gsize length; gint fd; guint failed : 1; guint is_mmapped : 1; }; static gboolean file_is_stdin (FileMapping *file_mapping) { return file_mapping->path && file_mapping->path [0] == '-' && file_mapping->path [1] == '\0' ? TRUE : FALSE; } static gsize safe_read (gint fd, void *buf, gsize len) { gsize ntotal = 0; guint8 *buffer = buf; while (len > 0) { unsigned int nread; int iread; int saved_errno; /* 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); saved_errno = errno; 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 (saved_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 gboolean safe_write (gint fd, gconstpointer buf, gsize len) { const guint8 *buffer = buf; gboolean success = FALSE; while (len > 0) { guint to_write; gint n_written; gint saved_errno; if (len > INT_MAX) to_write = INT_MAX; else to_write = (unsigned int) len; n_written = write (fd, buffer, to_write); saved_errno = errno; if (n_written == -1) { if (saved_errno != EINTR) goto out; } else if (n_written < 0) { /* Not a valid 'write' result */ goto out; } else if (n_written > 0) { /* Continue writing until permanent failure or entire buffer written */ buffer += n_written; len -= (unsigned int) n_written; } } success = TRUE; out: return success; } static gboolean safe_copy (gint src_fd, gint dest_fd) { guint8 buf [COPY_BUFFER_SIZE]; gsize n_read; while ((n_read = safe_read (src_fd, buf, COPY_BUFFER_SIZE)) > 0) { if (!safe_write (dest_fd, buf, n_read)) return FALSE; } return TRUE; } 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) g_close (file_mapping->fd, NULL); file_mapping->fd = -1; file_mapping->data = NULL; file_mapping->is_mmapped = FALSE; } static guint64 get_random_u64 (void) { guint64 u64 = 0; guint32 *u32p = (guint32 *) &u64; gint len = 0; GTimeVal tv; #ifdef HAVE_GETRANDOM len = getrandom ((void *) &u64, sizeof (guint64), GRND_NONBLOCK); #endif if (!u64 || len < (gint) sizeof (guint64)) { gpointer p; /* FIXME: Not paranoid enough */ /* FIXME: g_get_current_time() was deprecated in GLib 2.62. Switch to * g_get_real_time () sometime, and require GLib >= 2.28. */ g_get_current_time (&tv); g_random_set_seed (tv.tv_sec ^ tv.tv_usec); u32p [0] ^= g_random_int (); g_get_current_time (&tv); g_random_set_seed (tv.tv_sec ^ tv.tv_usec); u32p [1] ^= g_random_int (); p = g_thread_self (); u64 ^= (guint64) p; } return u64; } static gint open_temp_file_in_path (const gchar *base_path) { gchar *cache_path; gint fd; if (!base_path) return -1; cache_path = g_strdup_printf ("%s%schafa-%016" G_GINT64_MODIFIER "x", base_path, G_DIR_SEPARATOR_S, get_random_u64 ()); /* Create the file and unlink it, so it goes away when we exit */ fd = g_open (cache_path, O_CREAT | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR); if (fd >= 0) { /* You can't unlink an open file on MS Windows, so this will fail there */ g_unlink (cache_path); } g_free (cache_path); return fd; } static gint open_temp_file (void) { gint fd; fd = open_temp_file_in_path (g_get_user_cache_dir ()); if (fd < 0) fd = open_temp_file_in_path (g_get_tmp_dir ()); return fd; } static gint cache_stdin (FileMapping *file_mapping, GError **error) { gpointer buf = NULL; gint stdin_fd = fileno (stdin); /* Can't use STDIN_FILENO on Windows */ size_t n_read; gint cache_fd = -1; gint success = FALSE; if (stdin_fd < 0) goto out; #ifdef G_OS_WIN32 setmode (stdin_fd, O_BINARY); #endif /* Read from stdin */ buf = g_malloc (FILE_MEMORY_CACHE_MAX); n_read = safe_read (stdin_fd, buf, FILE_MEMORY_CACHE_MAX); if (n_read < 1) goto out; /* If the stream didn't fit in memory, save it all to a file instead. * We can mmap it later. Or read it back in, ha ha. */ if (n_read == FILE_MEMORY_CACHE_MAX) { cache_fd = open_temp_file (); if (cache_fd >= 0) { if (!safe_write (cache_fd, buf, n_read)) goto out; g_free (buf); buf = NULL; if (!safe_copy (stdin_fd, cache_fd)) goto out; } } else { file_mapping->data = g_realloc (buf, n_read); file_mapping->length = n_read; buf = NULL; } success = TRUE; out: if (!success) { if (cache_fd >= 0) g_close (cache_fd, NULL); cache_fd = -1; g_free (buf); if (error && !*error) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Could not cache input stream"); } } return cache_fd; } static gint open_file (FileMapping *file_mapping, GError **error) { gint fd; if (file_is_stdin (file_mapping)) { fd = cache_stdin (file_mapping, error); } else { fd = g_open (file_mapping->path, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR); if (fd < 0) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), "%s", strerror (errno)); } } return fd; } static gboolean ensure_open_file (FileMapping *file_mapping) { if (file_mapping->data || file_mapping->fd >= 0) return TRUE; file_mapping->fd = open_file (file_mapping, NULL); if (file_mapping->data || file_mapping->fd >= 0) return TRUE; return FALSE; } 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->failed) return FALSE; if (file_mapping->data) return TRUE; if (file_mapping->fd < 0) file_mapping->fd = open_file (file_mapping, NULL); /* If the data arrived over a pipe and was reasonably small, open_file () will * populate the data fields instead of the fd. */ if (!file_mapping->data) { 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: if (!result) { file_mapping->failed = TRUE; } 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); } gboolean file_mapping_open_now (FileMapping *file_mapping, GError **error) { if (file_mapping->data || file_mapping->fd >= 0) return TRUE; file_mapping->fd = open_file (file_mapping, error); if (file_mapping->data || file_mapping->fd >= 0) return TRUE; if (error && !*error) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Open/map failed"); } return FALSE; } const gchar * file_mapping_get_path (FileMapping *file_mapping) { return file_mapping->path; } 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 (!ensure_open_file (file_mapping)) return FALSE; if (file_mapping->data) { if (ofs + length <= file_mapping->length) { memcpy (out, ((const gchar *) file_mapping->data) + ofs, length); return TRUE; } return FALSE; } 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; } gssize file_mapping_read (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length) { if (!ensure_open_file (file_mapping)) return FALSE; if (file_mapping->data) { if (ofs <= (gssize) file_mapping->length) { gssize seg_len = MIN (length, file_mapping->length - ofs); memcpy (out, ((const gchar *) file_mapping->data) + ofs, seg_len); return seg_len; } return -1; } /* FIXME: Shouldn't happen */ if (file_mapping->fd < 0) return -1; if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) return -1; return safe_read (file_mapping->fd, out, length); } gboolean file_mapping_has_magic (FileMapping *file_mapping, goffset ofs, gconstpointer data, gsize length) { gchar buf [MAGIC_LENGTH_MAX]; g_assert (length <= MAGIC_LENGTH_MAX); if (!ensure_open_file (file_mapping)) return FALSE; if (file_mapping->data) { if (ofs + length <= file_mapping->length && !memcmp ((const guint8 *) file_mapping->data + ofs, data, length)) return TRUE; return FALSE; } /* FIXME: Shouldn't happen */ if (file_mapping->fd < 0) return FALSE; if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) return FALSE; if (safe_read (file_mapping->fd, buf, length) != length) return FALSE; if (memcmp (buf, data, length)) return FALSE; return TRUE; } chafa-1.14.5/tools/chafa/file-mapping.h000066400000000000000000000032431471154763100176020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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_open_now (FileMapping *file_mapping, GError **error); const gchar *file_mapping_get_path (FileMapping *file_mapping); gboolean file_mapping_taste (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length); gssize file_mapping_read (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.14.5/tools/chafa/font-loader.c000066400000000000000000000360351471154763100174440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 + (gint) 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.14.5/tools/chafa/font-loader.h000066400000000000000000000024701471154763100174450ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/tools/chafa/gif-loader.c000066400000000000000000000144561471154763100172460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #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; guint frame_is_success : 1; }; static void * bitmap_create (int width, int height) { if ((width * (gint64) height) > (MAX_IMAGE_BYTES / BYTES_PER_PIXEL)) return NULL; return g_malloc0 (width * height * BYTES_PER_PIXEL); } static gboolean maybe_decode_frame (GifLoader *loader) { gif_result code; if (loader->frame_is_decoded) return loader->frame_is_success; code = gif_decode_frame (&loader->gif, loader->current_frame_index); loader->frame_is_success = (code == GIF_OK ? TRUE : FALSE); return loader->frame_is_success; } 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) && !file_mapping_has_magic (mapping, 0, "GIF87a", 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); } gboolean gif_loader_get_is_animation (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 > 1 ? TRUE : FALSE; } gconstpointer gif_loader_get_frame_data (GifLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); g_return_val_if_fail (loader->gif_is_initialized, NULL); if (!maybe_decode_frame (loader)) return NULL; if (width_out) *width_out = loader->gif.width; if (height_out) *height_out = loader->gif.height; if (pixel_type_out) *pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED; if (rowstride_out) *rowstride_out = loader->gif.width * 4; return loader->gif.frame_image; } gint gif_loader_get_frame_delay (GifLoader *loader) { gint frame_delay_ms; g_return_val_if_fail (loader != NULL, 0); g_return_val_if_fail (loader->gif_is_initialized, 0); if (!maybe_decode_frame (loader)) return 0; frame_delay_ms = loader->gif.frames [loader->current_frame_index].frame_delay; /* Convert from centiseconds to milliseconds */ frame_delay_ms *= 10; /* It's common for GIF animations to omit the frame delays. If it looks like that's * what's happening, go with a 20fps default. */ if (frame_delay_ms == 0) frame_delay_ms = 50; return frame_delay_ms; } void gif_loader_goto_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; loader->frame_is_success = FALSE; } gboolean gif_loader_goto_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.14.5/tools/chafa/gif-loader.h000066400000000000000000000030421471154763100172400ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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); gboolean gif_loader_get_is_animation (GifLoader *loader); gconstpointer gif_loader_get_frame_data (GifLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint gif_loader_get_frame_delay (GifLoader *loader); void gif_loader_goto_first_frame (GifLoader *loader); gboolean gif_loader_goto_next_frame (GifLoader *loader); G_END_DECLS #endif /* __GIF_LOADER_H__ */ chafa-1.14.5/tools/chafa/ibm-platform.c000066400000000000000000000311321471154763100176140ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 UNICODE_UNDEF 0 typedef struct { gint c; gunichar u; } CodeMap; /* It's better to keep a single entry per line, since it leaves room for annotation. * Also, maintaining tabular layouts is a pita even when we don't have out-there * Unicode characters messing up the formatting. */ static const CodeMap cp437_codes [] = { { 0x20, 0x0020 }, /* SPACE */ { 0x21, 0x0021 }, /* EXCLAMATION MARK */ { 0x22, 0x0022 }, /* QUOTATION MARK */ { 0x23, 0x0023 }, /* NUMBER SIGN */ { 0x24, 0x0024 }, /* DOLLAR SIGN */ { 0x25, 0x0025 }, /* PERCENT SIGN */ { 0x26, 0x0026 }, /* AMPERSAND */ { 0x27, 0x0027 }, /* APOSTROPHE */ { 0x28, 0x0028 }, /* LEFT PARENTHESIS */ { 0x29, 0x0029 }, /* RIGHT PARENTHESIS */ { 0x2a, 0x002a }, /* ASTERISK */ { 0x2b, 0x002b }, /* PLUS SIGN */ { 0x2c, 0x002c }, /* COMMA */ { 0x2d, 0x002d }, /* HYPHEN-MINUS */ { 0x2e, 0x002e }, /* FULL STOP */ { 0x2f, 0x002f }, /* SOLIDUS */ { 0x30, 0x0030 }, /* DIGIT ZERO */ { 0x31, 0x0031 }, /* DIGIT ONE */ { 0x32, 0x0032 }, /* DIGIT TWO */ { 0x33, 0x0033 }, /* DIGIT THREE */ { 0x34, 0x0034 }, /* DIGIT FOUR */ { 0x35, 0x0035 }, /* DIGIT FIVE */ { 0x36, 0x0036 }, /* DIGIT SIX */ { 0x37, 0x0037 }, /* DIGIT SEVEN */ { 0x38, 0x0038 }, /* DIGIT EIGHT */ { 0x39, 0x0039 }, /* DIGIT NINE */ { 0x3a, 0x003a }, /* COLON */ { 0x3b, 0x003b }, /* SEMICOLON */ { 0x3c, 0x003c }, /* LESS-THAN SIGN */ { 0x3d, 0x003d }, /* EQUALS SIGN */ { 0x3e, 0x003e }, /* GREATER-THAN SIGN */ { 0x3f, 0x003f }, /* QUESTION MARK */ { 0x40, 0x0040 }, /* COMMERCIAL AT */ { 0x41, 0x0041 }, /* LATIN CAPITAL LETTER A */ { 0x42, 0x0042 }, /* LATIN CAPITAL LETTER B */ { 0x43, 0x0043 }, /* LATIN CAPITAL LETTER C */ { 0x44, 0x0044 }, /* LATIN CAPITAL LETTER D */ { 0x45, 0x0045 }, /* LATIN CAPITAL LETTER E */ { 0x46, 0x0046 }, /* LATIN CAPITAL LETTER F */ { 0x47, 0x0047 }, /* LATIN CAPITAL LETTER G */ { 0x48, 0x0048 }, /* LATIN CAPITAL LETTER H */ { 0x49, 0x0049 }, /* LATIN CAPITAL LETTER I */ { 0x4a, 0x004a }, /* LATIN CAPITAL LETTER J */ { 0x4b, 0x004b }, /* LATIN CAPITAL LETTER K */ { 0x4c, 0x004c }, /* LATIN CAPITAL LETTER L */ { 0x4d, 0x004d }, /* LATIN CAPITAL LETTER M */ { 0x4e, 0x004e }, /* LATIN CAPITAL LETTER N */ { 0x4f, 0x004f }, /* LATIN CAPITAL LETTER O */ { 0x50, 0x0050 }, /* LATIN CAPITAL LETTER P */ { 0x51, 0x0051 }, /* LATIN CAPITAL LETTER Q */ { 0x52, 0x0052 }, /* LATIN CAPITAL LETTER R */ { 0x53, 0x0053 }, /* LATIN CAPITAL LETTER S */ { 0x54, 0x0054 }, /* LATIN CAPITAL LETTER T */ { 0x55, 0x0055 }, /* LATIN CAPITAL LETTER U */ { 0x56, 0x0056 }, /* LATIN CAPITAL LETTER V */ { 0x57, 0x0057 }, /* LATIN CAPITAL LETTER W */ { 0x58, 0x0058 }, /* LATIN CAPITAL LETTER X */ { 0x59, 0x0059 }, /* LATIN CAPITAL LETTER Y */ { 0x5a, 0x005a }, /* LATIN CAPITAL LETTER Z */ { 0x5b, 0x005b }, /* LEFT SQUARE BRACKET */ { 0x5c, 0x005c }, /* REVERSE SOLIDUS */ { 0x5d, 0x005d }, /* RIGHT SQUARE BRACKET */ { 0x5e, 0x005e }, /* CIRCUMFLEX ACCENT */ { 0x5f, 0x005f }, /* LOW LINE */ { 0x60, 0x0060 }, /* GRAVE ACCENT */ { 0x61, 0x0061 }, /* LATIN SMALL LETTER A */ { 0x62, 0x0062 }, /* LATIN SMALL LETTER B */ { 0x63, 0x0063 }, /* LATIN SMALL LETTER C */ { 0x64, 0x0064 }, /* LATIN SMALL LETTER D */ { 0x65, 0x0065 }, /* LATIN SMALL LETTER E */ { 0x66, 0x0066 }, /* LATIN SMALL LETTER F */ { 0x67, 0x0067 }, /* LATIN SMALL LETTER G */ { 0x68, 0x0068 }, /* LATIN SMALL LETTER H */ { 0x69, 0x0069 }, /* LATIN SMALL LETTER I */ { 0x6a, 0x006a }, /* LATIN SMALL LETTER J */ { 0x6b, 0x006b }, /* LATIN SMALL LETTER K */ { 0x6c, 0x006c }, /* LATIN SMALL LETTER L */ { 0x6d, 0x006d }, /* LATIN SMALL LETTER M */ { 0x6e, 0x006e }, /* LATIN SMALL LETTER N */ { 0x6f, 0x006f }, /* LATIN SMALL LETTER O */ { 0x70, 0x0070 }, /* LATIN SMALL LETTER P */ { 0x71, 0x0071 }, /* LATIN SMALL LETTER Q */ { 0x72, 0x0072 }, /* LATIN SMALL LETTER R */ { 0x73, 0x0073 }, /* LATIN SMALL LETTER S */ { 0x74, 0x0074 }, /* LATIN SMALL LETTER T */ { 0x75, 0x0075 }, /* LATIN SMALL LETTER U */ { 0x76, 0x0076 }, /* LATIN SMALL LETTER V */ { 0x77, 0x0077 }, /* LATIN SMALL LETTER W */ { 0x78, 0x0078 }, /* LATIN SMALL LETTER X */ { 0x79, 0x0079 }, /* LATIN SMALL LETTER Y */ { 0x7a, 0x007a }, /* LATIN SMALL LETTER Z */ { 0x7b, 0x007b }, /* LEFT CURLY BRACKET */ { 0x7c, 0x007c }, /* VERTICAL LINE */ { 0x7d, 0x007d }, /* RIGHT CURLY BRACKET */ { 0x7e, 0x007e }, /* TILDE */ { 0x80, 0x00c7 }, /* LATIN CAPITAL LETTER C WITH CEDILLA */ { 0x81, 0x00fc }, /* LATIN SMALL LETTER U WITH DIAERESIS */ { 0x82, 0x00e9 }, /* LATIN SMALL LETTER E WITH ACUTE */ { 0x83, 0x00e2 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */ { 0x84, 0x00e4 }, /* LATIN SMALL LETTER A WITH DIAERESIS */ { 0x85, 0x00e0 }, /* LATIN SMALL LETTER A WITH GRAVE */ { 0x86, 0x00e5 }, /* LATIN SMALL LETTER A WITH RING ABOVE */ { 0x87, 0x00e7 }, /* LATIN SMALL LETTER C WITH CEDILLA */ { 0x88, 0x00ea }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */ { 0x89, 0x00eb }, /* LATIN SMALL LETTER E WITH DIAERESIS */ { 0x8a, 0x00e8 }, /* LATIN SMALL LETTER E WITH GRAVE */ { 0x8b, 0x00ef }, /* LATIN SMALL LETTER I WITH DIAERESIS */ { 0x8c, 0x00ee }, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */ { 0x8d, 0x00ec }, /* LATIN SMALL LETTER I WITH GRAVE */ { 0x8e, 0x00c4 }, /* LATIN CAPITAL LETTER A WITH DIAERESIS */ { 0x8f, 0x00c5 }, /* LATIN CAPITAL LETTER A WITH RING ABOVE */ { 0x90, 0x00c9 }, /* LATIN CAPITAL LETTER E WITH ACUTE */ { 0x91, 0x00e6 }, /* LATIN SMALL LIGATURE AE */ { 0x92, 0x00c6 }, /* LATIN CAPITAL LIGATURE AE */ { 0x93, 0x00f4 }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */ { 0x94, 0x00f6 }, /* LATIN SMALL LETTER O WITH DIAERESIS */ { 0x95, 0x00f2 }, /* LATIN SMALL LETTER O WITH GRAVE */ { 0x96, 0x00fb }, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */ { 0x97, 0x00f9 }, /* LATIN SMALL LETTER U WITH GRAVE */ { 0x98, 0x00ff }, /* LATIN SMALL LETTER Y WITH DIAERESIS */ { 0x99, 0x00d6 }, /* LATIN CAPITAL LETTER O WITH DIAERESIS */ { 0x9a, 0x00dc }, /* LATIN CAPITAL LETTER U WITH DIAERESIS */ { 0x9b, 0x00a2 }, /* CENT SIGN */ { 0x9c, 0x00a3 }, /* POUND SIGN */ { 0x9d, 0x00a5 }, /* YEN SIGN */ { 0x9e, 0x20a7 }, /* PESETA SIGN */ { 0x9f, 0x0192 }, /* LATIN SMALL LETTER F WITH HOOK */ { 0xa0, 0x00e1 }, /* LATIN SMALL LETTER A WITH ACUTE */ { 0xa1, 0x00ed }, /* LATIN SMALL LETTER I WITH ACUTE */ { 0xa2, 0x00f3 }, /* LATIN SMALL LETTER O WITH ACUTE */ { 0xa3, 0x00fa }, /* LATIN SMALL LETTER U WITH ACUTE */ { 0xa4, 0x00f1 }, /* LATIN SMALL LETTER N WITH TILDE */ { 0xa5, 0x00d1 }, /* LATIN CAPITAL LETTER N WITH TILDE */ { 0xa6, 0x00aa }, /* FEMININE ORDINAL INDICATOR */ { 0xa7, 0x00ba }, /* MASCULINE ORDINAL INDICATOR */ { 0xa8, 0x00bf }, /* INVERTED QUESTION MARK */ { 0xa9, 0x2310 }, /* REVERSED NOT SIGN */ { 0xaa, 0x00ac }, /* NOT SIGN */ { 0xab, 0x00bd }, /* VULGAR FRACTION ONE HALF */ { 0xac, 0x00bc }, /* VULGAR FRACTION ONE QUARTER */ { 0xad, 0x00a1 }, /* INVERTED EXCLAMATION MARK */ { 0xae, 0x00ab }, /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */ { 0xaf, 0x00bb }, /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */ { 0xb0, 0x2591 }, /* LIGHT SHADE */ { 0xb1, 0x2592 }, /* MEDIUM SHADE */ { 0xb2, 0x2593 }, /* DARK SHADE */ { 0xb3, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ { 0xb4, 0x2524 }, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */ { 0xb5, 0x2561 }, /* BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE */ { 0xb6, 0x2562 }, /* BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE */ { 0xb7, 0x2556 }, /* BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE */ { 0xb8, 0x2555 }, /* BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE */ { 0xb9, 0x2563 }, /* BOX DRAWINGS DOUBLE VERTICAL AND LEFT */ { 0xba, 0x2551 }, /* BOX DRAWINGS DOUBLE VERTICAL */ { 0xbb, 0x2557 }, /* BOX DRAWINGS DOUBLE DOWN AND LEFT */ { 0xbc, 0x255d }, /* BOX DRAWINGS DOUBLE UP AND LEFT */ { 0xbd, 0x255c }, /* BOX DRAWINGS UP DOUBLE AND LEFT SINGLE */ { 0xbe, 0x255b }, /* BOX DRAWINGS UP SINGLE AND LEFT DOUBLE */ { 0xbf, 0x2510 }, /* BOX DRAWINGS LIGHT DOWN AND LEFT */ { 0xc0, 0x2514 }, /* BOX DRAWINGS LIGHT UP AND RIGHT */ { 0xc1, 0x2534 }, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */ { 0xc2, 0x252c }, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ { 0xc3, 0x251c }, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ { 0xc4, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ { 0xc5, 0x253c }, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ { 0xc6, 0x255e }, /* BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE */ { 0xc7, 0x255f }, /* BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE */ { 0xc8, 0x255a }, /* BOX DRAWINGS DOUBLE UP AND RIGHT */ { 0xc9, 0x2554 }, /* BOX DRAWINGS DOUBLE DOWN AND RIGHT */ { 0xca, 0x2569 }, /* BOX DRAWINGS DOUBLE UP AND HORIZONTAL */ { 0xcb, 0x2566 }, /* BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL */ { 0xcc, 0x2560 }, /* BOX DRAWINGS DOUBLE VERTICAL AND RIGHT */ { 0xcd, 0x2550 }, /* BOX DRAWINGS DOUBLE HORIZONTAL */ { 0xce, 0x256c }, /* BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL */ { 0xcf, 0x2567 }, /* BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE */ { 0xd0, 0x2568 }, /* BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE */ { 0xd1, 0x2564 }, /* BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE */ { 0xd2, 0x2565 }, /* BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE */ { 0xd3, 0x2559 }, /* BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE */ { 0xd4, 0x2558 }, /* BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE */ { 0xd5, 0x2552 }, /* BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE */ { 0xd6, 0x2553 }, /* BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE */ { 0xd7, 0x256b }, /* BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE */ { 0xd8, 0x256a }, /* BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE */ { 0xd9, 0x2518 }, /* BOX DRAWINGS LIGHT UP AND LEFT */ { 0xda, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ { 0xdb, 0x2588 }, /* FULL BLOCK */ { 0xdc, 0x2584 }, /* LOWER HALF BLOCK */ { 0xdd, 0x258c }, /* LEFT HALF BLOCK */ { 0xde, 0x2590 }, /* RIGHT HALF BLOCK */ { 0xdf, 0x2580 }, /* UPPER HALF BLOCK */ { 0xe0, 0x03b1 }, /* GREEK SMALL LETTER ALPHA */ { 0xe1, 0x00df }, /* LATIN SMALL LETTER SHARP S */ { 0xe2, 0x0393 }, /* GREEK CAPITAL LETTER GAMMA */ { 0xe3, 0x03c0 }, /* GREEK SMALL LETTER PI */ { 0xe4, 0x03a3 }, /* GREEK CAPITAL LETTER SIGMA */ { 0xe5, 0x03c3 }, /* GREEK SMALL LETTER SIGMA */ { 0xe6, 0x00b5 }, /* MICRO SIGN */ { 0xe7, 0x03c4 }, /* GREEK SMALL LETTER TAU */ { 0xe8, 0x03a6 }, /* GREEK CAPITAL LETTER PHI */ { 0xe9, 0x0398 }, /* GREEK CAPITAL LETTER THETA */ { 0xea, 0x03a9 }, /* GREEK CAPITAL LETTER OMEGA */ { 0xeb, 0x03b4 }, /* GREEK SMALL LETTER DELTA */ { 0xec, 0x221e }, /* INFINITY */ { 0xed, 0x03c6 }, /* GREEK SMALL LETTER PHI */ { 0xee, 0x03b5 }, /* GREEK SMALL LETTER EPSILON */ { 0xef, 0x2229 }, /* INTERSECTION */ { 0xf0, 0x2261 }, /* IDENTICAL TO */ { 0xf1, 0x00b1 }, /* PLUS-MINUS SIGN */ { 0xf2, 0x2265 }, /* GREATER-THAN OR EQUAL TO */ { 0xf3, 0x2264 }, /* LESS-THAN OR EQUAL TO */ { 0xf4, 0x2320 }, /* TOP HALF INTEGRAL */ { 0xf5, 0x2321 }, /* BOTTOM HALF INTEGRAL */ { 0xf6, 0x00f7 }, /* DIVISION SIGN */ { 0xf7, 0x2248 }, /* ALMOST EQUAL TO */ { 0xf8, 0x00b0 }, /* DEGREE SIGN */ { 0xf9, 0x2219 }, /* BULLET OPERATOR */ { 0xfa, 0x00b7 }, /* MIDDLE DOT */ { 0xfb, 0x221a }, /* SQUARE ROOT */ { 0xfc, 0x207f }, /* SUPERSCRIPT LATIN SMALL LETTER N */ { 0xfd, 0x00b2 }, /* SUPERSCRIPT TWO */ { 0xfe, 0x25a0 }, /* BLACK SQUARE */ { -1, 0 } }; chafa-1.14.5/tools/chafa/jpeg-loader.c000066400000000000000000000263441471154763100174250ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "jpeg-loader.h" #include "util.h" /* ----------------------- * * Global macros and types * * ----------------------- */ #undef ENABLE_DEBUG #define BYTES_PER_PIXEL 3 #define ROWSTRIDE_ALIGN 16 #define PAD_TO_N(p, n) (((p) + ((n) - 1)) & ~((unsigned) (n) - 1)) #define ROWSTRIDE_PAD(rowstride) (PAD_TO_N ((rowstride), (ROWSTRIDE_ALIGN))) struct JpegLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gpointer frame_data; gint width, height, rowstride; }; /* ----------------------- * * Exif orientation reader * * ----------------------- */ static guint16 read_uint16 (const guchar *p, gboolean is_big_endian) { return is_big_endian ? ((p [0] << 8) | p [1]) : ((p [1] << 8) | p [0]); } static guint32 read_uint32 (const guchar *p, gboolean is_big_endian) { return is_big_endian ? ((p [0] << 24) | (p [1] << 16) | (p [2] << 8) | p [3]) : ((p [3] << 24) | (p [2] << 16) | (p [1] << 8) | p [0]); } static RotationType read_orientation (JpegLoader *loader) { const guchar *p0, *end; size_t len; RotationType rot = ROTATION_NONE; gboolean is_big_endian; guint n, m, o; p0 = loader->file_data; len = loader->file_data_len; end = p0 + len; /* Assume we already checked the JPEG header. Now find the Exif header. */ p0 += 2; for (;;) { if (p0 + 20 > end) goto out; /* Get app type */ if (read_uint16 (p0, TRUE) < 0xffdb) goto out; p0 += 2; /* Get marker length; note length field includes itself */ n = read_uint16 (p0, TRUE); if (n < 2 || p0 + n > end) goto out; if (!memcmp (p0 + 2, "Exif\0\0", 6)) { p0 += 8; break; } /* Not an Exif marker; skip it */ p0 += n; } /* Get byte order */ m = read_uint16 (p0, TRUE); if (m == 0x4949) is_big_endian = FALSE; else if (m == 0x4d4d) is_big_endian = TRUE; else goto out; /* Tag mark */ if (read_uint16 (p0 + 2, is_big_endian) != 0x002a) goto out; /* First IFD offset */ m = read_uint32 (p0 + 4, is_big_endian); if (m > 0xffff) goto out; if (p0 + m + 2 > end) goto out; if (m + 2 > n) goto out; /* Number of directory entries in this IFD */ o = read_uint16 (p0 + m, is_big_endian); m += 2; for (;;) { guint16 tagnum; if (!o) goto out; if (p0 + m + 12 > end) goto out; if (m + 12 > n) goto out; tagnum = read_uint16 (p0 + m, is_big_endian); if (tagnum == 0x0112) break; o--; m += 12; } m = read_uint16 (p0 + m + 8, is_big_endian); if (m > 9) goto out; rot = m; out: return rot; } /* ----------- * * JPEG loader * * ----------- */ /* --- Memory source --- */ /* libjpeg-turbo can provide jpeg_mem_src (), and usually does. However, we * supply our own for strict conformance with libjpeg v6b. */ static void init_source (G_GNUC_UNUSED j_decompress_ptr cinfo) { } static boolean fill_input_buffer (j_decompress_ptr cinfo) { ERREXIT (cinfo, JERR_INPUT_EMPTY); return TRUE; } static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) { struct jpeg_source_mgr *src = (struct jpeg_source_mgr *) cinfo->src; if (num_bytes < 0) return; num_bytes = MIN ((size_t) num_bytes, src->bytes_in_buffer); src->next_input_byte += (size_t) num_bytes; src->bytes_in_buffer -= (size_t) num_bytes; } static void term_source (G_GNUC_UNUSED j_decompress_ptr cinfo) { } static void my_jpeg_mem_src (j_decompress_ptr cinfo, const void *buffer, long nbytes) { struct jpeg_source_mgr *src; if (cinfo->src == NULL) { cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)); } src = (struct jpeg_source_mgr *) cinfo->src; src->init_source = init_source; src->fill_input_buffer = fill_input_buffer; src->skip_input_data = skip_input_data; src->resync_to_restart = jpeg_resync_to_restart; /* Use default method */ src->term_source = term_source; src->bytes_in_buffer = nbytes; src->next_input_byte = (JOCTET *) buffer; } /* --- Error handler --- */ struct my_jpeg_error_mgr { struct jpeg_error_mgr jerr; jmp_buf setjmp_buffer; }; static void my_jpeg_error_exit (j_common_ptr cinfo) { struct my_jpeg_error_mgr *my_jerr = (struct my_jpeg_error_mgr *) cinfo->err; longjmp (my_jerr->setjmp_buffer, 1); } /* --- Magic probe --- */ static gboolean have_any_apptype_magic (FileMapping *mapping) { guchar magic [4] = { 0xff, 0xd8, 0xff, 0x00 }; guchar n; for (n = 0xe0; n <= 0xef; n++) { magic [3] = n; if (file_mapping_has_magic (mapping, 0, magic, 4)) return TRUE; } magic [3] = 0xdb; return file_mapping_has_magic (mapping, 0, magic, 4); } /* --- Loader --- */ static JpegLoader * jpeg_loader_new (void) { return g_new0 (JpegLoader, 1); } static gint convert_cmyk_ch_to_rgb (gint k, gint cmy) { gint v = k * cmy + 128; v = ((v >> 8) + v) >> 8; return CLAMP (k - v, 0, 255); } static void convert_cmyk_pixel_to_rgb (const guchar *cmyk, guchar *rgb) { gint c, m, y, k; c = cmyk [0]; m = cmyk [1]; y = cmyk [2]; k = cmyk [3]; rgb [0] = convert_cmyk_ch_to_rgb (k, 255 - c); rgb [1] = convert_cmyk_ch_to_rgb (k, 255 - m); rgb [2] = convert_cmyk_ch_to_rgb (k, 255 - y); } static void convert_cmyk_row_to_rgb (const guchar *cmyk, guchar *rgb, gint width) { gint i; for (i = 0; i < width; i++) convert_cmyk_pixel_to_rgb (cmyk + 4 * i, rgb + 3 * i); } JpegLoader * jpeg_loader_new_from_mapping (FileMapping *mapping) { guint width, height; guint rowstride; struct jpeg_decompress_struct cinfo; struct my_jpeg_error_mgr my_jerr; JpegLoader * volatile loader = NULL; gpointer frame_data = NULL; volatile gboolean convert_cmyk_to_rgb = FALSE; guchar *cmyk_buf = NULL; volatile gboolean have_decompress = FALSE; volatile gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); /* Check magic */ if (!have_any_apptype_magic (mapping)) goto out; loader = jpeg_loader_new (); loader->mapping = mapping; /* Get file data */ loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; /* Prepare to decode */ cinfo.err = jpeg_std_error ((struct jpeg_error_mgr *) &my_jerr); my_jerr.jerr.error_exit = my_jpeg_error_exit; if (setjmp (my_jerr.setjmp_buffer)) { #ifdef ENABLE_DEBUG static gchar jpeg_error_msg [JMSG_LENGTH_MAX]; (*cinfo.err->format_message) ((j_common_ptr) &cinfo, jpeg_error_msg); g_printerr ("JPEG error: %s\n", jpeg_error_msg); #endif goto out; } jpeg_create_decompress (&cinfo); have_decompress = TRUE; my_jpeg_mem_src (&cinfo, loader->file_data, loader->file_data_len); (void) jpeg_read_header (&cinfo, TRUE); if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) { convert_cmyk_to_rgb = TRUE; cinfo.out_color_space = JCS_CMYK; } else { cinfo.out_color_space = JCS_RGB; } cinfo.output_components = 3; jpeg_start_decompress (&cinfo); width = cinfo.output_width; height = cinfo.output_height; if (width < 1 || width >= (1 << 28) || height < 1 || height >= (1 << 28) || (width * (guint64) height >= (1 << 29))) goto out; rowstride = ROWSTRIDE_PAD (width * BYTES_PER_PIXEL); frame_data = g_malloc (height * (guint64) rowstride); /* Decoding loop */ if (convert_cmyk_to_rgb) cmyk_buf = g_malloc (width * 4); while (cinfo.output_scanline < height) { guchar *row_data = ((guchar *) frame_data) + cinfo.output_scanline * rowstride; if (convert_cmyk_to_rgb) { if (jpeg_read_scanlines (&cinfo, &cmyk_buf, 1) < 1) goto out; convert_cmyk_row_to_rgb (cmyk_buf, row_data, width); } else { if (jpeg_read_scanlines (&cinfo, &row_data, 1) < 1) goto out; } } /* Orientation and cleanup */ (void) jpeg_finish_decompress (&cinfo); rotate_image (&frame_data, &width, &height, &rowstride, 3, invert_rotation (read_orientation (loader))); loader->frame_data = frame_data; loader->width = (gint) width; loader->height = (gint) height; loader->rowstride = (gint) rowstride; success = TRUE; out: g_free (cmyk_buf); if (have_decompress) jpeg_destroy_decompress (&cinfo); if (!success) { if (frame_data) g_free (frame_data); if (loader) { g_free (loader); loader = NULL; } } return loader; } void jpeg_loader_destroy (JpegLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->frame_data) free (loader->frame_data); g_free (loader); } gboolean jpeg_loader_get_is_animation (JpegLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return FALSE; } gconstpointer jpeg_loader_get_frame_data (JpegLoader *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 = CHAFA_PIXEL_RGB8; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (rowstride_out) *rowstride_out = loader->rowstride; return loader->frame_data; } gint jpeg_loader_get_frame_delay (JpegLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return 0; } void jpeg_loader_goto_first_frame (JpegLoader *loader) { g_return_if_fail (loader != NULL); } gboolean jpeg_loader_goto_next_frame (JpegLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); return FALSE; } chafa-1.14.5/tools/chafa/jpeg-loader.h000066400000000000000000000030661471154763100174260ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __JPEG_LOADER_H__ #define __JPEG_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct JpegLoader JpegLoader; JpegLoader *jpeg_loader_new_from_mapping (FileMapping *mapping); void jpeg_loader_destroy (JpegLoader *loader); gboolean jpeg_loader_get_is_animation (JpegLoader *loader); gconstpointer jpeg_loader_get_frame_data (JpegLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint jpeg_loader_get_frame_delay (JpegLoader *loader); void jpeg_loader_goto_first_frame (JpegLoader *loader); gboolean jpeg_loader_goto_next_frame (JpegLoader *loader); G_END_DECLS #endif /* __JPEG_LOADER_H__ */ chafa-1.14.5/tools/chafa/jxl-loader.c000066400000000000000000000177641471154763100173030ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; ent su: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 oupson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "chafa.h" #include "file-mapping.h" #include "glib.h" #include #include #include #include #include "jxl-loader.h" #define BUFFER_SIZE (1024) GList *jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, FileMapping *mapping); void jxl_cleanup_frame_list (GList *frame_list); struct JxlLoader { GList *frames; int frame_count; int index; }; typedef struct JxlFrame { uint8_t *buffer; int width; int height; gboolean have_alpha; gboolean is_premul; int frame_duration; } JxlFrame; static JxlLoader * jxl_loader_new (void) { return g_new0 (JxlLoader, 1); } GList * jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, FileMapping *mapping) { if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, runner)) { return NULL; } if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents (dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME)) { return NULL; } JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; JxlDecoderStatus decode_status = JXL_DEC_NEED_MORE_INPUT; uint8_t buffer [BUFFER_SIZE]; gpointer image_buffer = NULL; GList *frame_list = NULL; JxlBasicInfo info; JxlFrameHeader frame_header; int read = 0; gboolean success = FALSE; while (JXL_DEC_ERROR != decode_status) { if (JXL_DEC_NEED_MORE_INPUT == decode_status) { JxlDecoderReleaseInput (dec); const gsize nbr = file_mapping_read (mapping, buffer, read, BUFFER_SIZE); read += nbr; if (nbr > 0) { if (JXL_DEC_SUCCESS != JxlDecoderSetInput (dec, buffer, nbr)) { break; } } else { break; } } else if (JXL_DEC_BASIC_INFO == decode_status) { if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo (dec, &info)) { break; } if (!info.alpha_bits) { format.num_channels = 3; } image_buffer = g_malloc (info.xsize * info.ysize * format.num_channels); } else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) { if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer (dec, &format, image_buffer, info.xsize * info.ysize * format.num_channels)) { break; } } else if (JXL_DEC_FRAME == decode_status) { if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader (dec, &frame_header)) { break; } } else if (JXL_DEC_FULL_IMAGE == decode_status) { const uint32_t num = info.animation.tps_numerator == 0 ? 1 : info.animation.tps_numerator; JxlFrame *frame = g_new (JxlFrame, 1); frame->buffer = image_buffer; frame->width = info.xsize; frame->height = info.ysize; frame->have_alpha = format.num_channels == 4; frame->is_premul = info.alpha_premultiplied; frame->frame_duration = frame_header.duration * 1000 * info.animation.tps_denominator / num; frame_list = g_list_prepend (frame_list, frame); image_buffer = g_malloc (info.xsize * info.ysize * format.num_channels); } else if (JXL_DEC_SUCCESS == decode_status) { frame_list = g_list_reverse (frame_list); success = TRUE; break; } decode_status = JxlDecoderProcessInput (dec); } // Decoding failed if (image_buffer != NULL) { g_free (image_buffer); } if (!success && frame_list != NULL) { jxl_cleanup_frame_list (frame_list); g_list_free (frame_list); frame_list = NULL; } return frame_list; } JxlLoader * jxl_loader_new_from_mapping (FileMapping *mapping) { JxlLoader *loader = NULL; gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); if (!file_mapping_has_magic (mapping, 0, "\xFF\x0A", 2) && !file_mapping_has_magic ( mapping, 0, "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12)) { return NULL; } loader = jxl_loader_new (); JxlDecoder *decoder = JxlDecoderCreate (NULL); JxlParallelRunner *runner = JxlResizableParallelRunnerCreate (NULL); GList *frames = jxl_get_frames (decoder, runner, mapping); JxlDecoderDestroy (decoder); JxlResizableParallelRunnerDestroy (runner); success = frames != NULL; if (success) { loader->frames = frames; loader->frame_count = g_list_length (frames); loader->index = 0; /* _new_from_mapping() steals the mapping on success. We're done with * it at this point, so destroy. */ file_mapping_destroy (mapping); } else { if (loader) { g_free (loader); loader = NULL; } } return loader; }; void jxl_cleanup_frame_list (GList *frame_list) { GList *frame = frame_list; while (frame != NULL) { if (frame->data != NULL) { JxlFrame *jxl_frame = frame->data; g_free (jxl_frame->buffer); g_free (jxl_frame); frame->data = NULL; } frame = frame->next; } } void jxl_loader_destroy (JxlLoader *loader) { GList *list = loader->frames; jxl_cleanup_frame_list (list); g_list_free (loader->frames); g_free (loader); } gboolean jxl_loader_get_is_animation (JxlLoader *loader) { return loader->frame_count > 1; } gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); const JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); int num_channels; if (frame->have_alpha) { num_channels = 4; *pixel_type_out = frame->is_premul ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED : CHAFA_PIXEL_RGBA8_UNASSOCIATED; } else { num_channels = 3; *pixel_type_out = CHAFA_PIXEL_RGB8; } *width_out = frame->width; *height_out = frame->height; *rowstride_out = frame->width * num_channels; return frame->buffer; } gint jxl_loader_get_frame_delay (JxlLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return ((JxlFrame *) g_list_nth_data (loader->frames, loader->index))->frame_duration; } void jxl_loader_goto_first_frame (JxlLoader *loader) { g_return_if_fail (loader != NULL); loader->index = 0; } gboolean jxl_loader_goto_next_frame (JxlLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); loader->index = loader->index + 1; return loader->index < loader->frame_count; } chafa-1.14.5/tools/chafa/jxl-loader.h000066400000000000000000000030701471154763100172710ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 oupson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __JXL_LOADER_H__ #define __JXL_LOADER_H__ #include "file-mapping.h" #include G_BEGIN_DECLS typedef struct JxlLoader JxlLoader; JxlLoader *jxl_loader_new_from_mapping (FileMapping *mapping); void jxl_loader_destroy (JxlLoader *loader); gboolean jxl_loader_get_is_animation (JxlLoader *loader); gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint jxl_loader_get_frame_delay (JxlLoader *loader); void jxl_loader_goto_first_frame (JxlLoader *loader); gboolean jxl_loader_goto_next_frame (JxlLoader *loader); G_END_DECLS #endif /* __JXL_LOADER_H__ */chafa-1.14.5/tools/chafa/manifest.rc000066400000000000000000000001221471154763100172060ustar00rootroot00000000000000#include CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ms-utf8.xml" chafa-1.14.5/tools/chafa/media-loader.c000066400000000000000000000253751471154763100175620ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "file-mapping.h" #include "gif-loader.h" #include "xwd-loader.h" #include "jpeg-loader.h" #include "media-loader.h" #include "png-loader.h" #include "qoi-loader.h" #include "svg-loader.h" #include "tiff-loader.h" #include "webp-loader.h" #include "avif-loader.h" #include "jxl-loader.h" typedef enum { LOADER_TYPE_GIF, LOADER_TYPE_PNG, LOADER_TYPE_XWD, LOADER_TYPE_QOI, LOADER_TYPE_JPEG, LOADER_TYPE_TIFF, LOADER_TYPE_WEBP, LOADER_TYPE_AVIF, LOADER_TYPE_SVG, LOADER_TYPE_JXL, LOADER_TYPE_LAST } LoaderType; static const struct { const gchar * const name; gpointer (*new_from_mapping) (gpointer); gpointer (*new_from_path) (gconstpointer); void (*destroy) (gpointer); gboolean (*get_is_animation) (gpointer); void (*goto_first_frame) (gpointer); gboolean (*goto_next_frame) (gpointer); gconstpointer (*get_frame_data) (gpointer, gpointer, gpointer, gpointer, gpointer); gint (*get_frame_delay) (gpointer); } loader_vtable [LOADER_TYPE_LAST] = { [LOADER_TYPE_GIF] = { "GIF", (gpointer (*)(gpointer)) gif_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) gif_loader_destroy, (gboolean (*)(gpointer)) gif_loader_get_is_animation, (void (*)(gpointer)) gif_loader_goto_first_frame, (gboolean (*)(gpointer)) gif_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) gif_loader_get_frame_data, (gint (*) (gpointer)) gif_loader_get_frame_delay }, [LOADER_TYPE_PNG] = { "PNG", (gpointer (*)(gpointer)) png_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) png_loader_destroy, (gboolean (*)(gpointer)) png_loader_get_is_animation, (void (*)(gpointer)) png_loader_goto_first_frame, (gboolean (*)(gpointer)) png_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) png_loader_get_frame_data, (gint (*) (gpointer)) png_loader_get_frame_delay }, [LOADER_TYPE_XWD] = { "XWD", (gpointer (*)(gpointer)) xwd_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) xwd_loader_destroy, (gboolean (*)(gpointer)) xwd_loader_get_is_animation, (void (*)(gpointer)) xwd_loader_goto_first_frame, (gboolean (*)(gpointer)) xwd_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) xwd_loader_get_frame_data, (gint (*) (gpointer)) xwd_loader_get_frame_delay }, [LOADER_TYPE_QOI] = { "QOI", (gpointer (*)(gpointer)) qoi_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) qoi_loader_destroy, (gboolean (*)(gpointer)) qoi_loader_get_is_animation, (void (*)(gpointer)) qoi_loader_goto_first_frame, (gboolean (*)(gpointer)) qoi_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) qoi_loader_get_frame_data, (gint (*) (gpointer)) qoi_loader_get_frame_delay }, #ifdef HAVE_JPEG [LOADER_TYPE_JPEG] = { "JPEG", (gpointer (*)(gpointer)) jpeg_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) jpeg_loader_destroy, (gboolean (*)(gpointer)) jpeg_loader_get_is_animation, (void (*)(gpointer)) jpeg_loader_goto_first_frame, (gboolean (*)(gpointer)) jpeg_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) jpeg_loader_get_frame_data, (gint (*) (gpointer)) jpeg_loader_get_frame_delay }, #endif #ifdef HAVE_SVG [LOADER_TYPE_SVG] = { "SVG", (gpointer (*)(gpointer)) svg_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) svg_loader_destroy, (gboolean (*)(gpointer)) svg_loader_get_is_animation, (void (*)(gpointer)) svg_loader_goto_first_frame, (gboolean (*)(gpointer)) svg_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) svg_loader_get_frame_data, (gint (*) (gpointer)) svg_loader_get_frame_delay }, #endif #ifdef HAVE_TIFF [LOADER_TYPE_TIFF] = { "TIFF", (gpointer (*)(gpointer)) tiff_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) tiff_loader_destroy, (gboolean (*)(gpointer)) tiff_loader_get_is_animation, (void (*)(gpointer)) tiff_loader_goto_first_frame, (gboolean (*)(gpointer)) tiff_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) tiff_loader_get_frame_data, (gint (*) (gpointer)) tiff_loader_get_frame_delay }, #endif #ifdef HAVE_WEBP [LOADER_TYPE_WEBP] = { "WebP", (gpointer (*)(gpointer)) webp_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) webp_loader_destroy, (gboolean (*)(gpointer)) webp_loader_get_is_animation, (void (*)(gpointer)) webp_loader_goto_first_frame, (gboolean (*)(gpointer)) webp_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) webp_loader_get_frame_data, (gint (*) (gpointer)) webp_loader_get_frame_delay }, #endif #ifdef HAVE_AVIF [LOADER_TYPE_AVIF] = { "AVIF", (gpointer (*)(gpointer)) avif_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) avif_loader_destroy, (gboolean (*)(gpointer)) avif_loader_get_is_animation, (void (*)(gpointer)) avif_loader_goto_first_frame, (gboolean (*)(gpointer)) avif_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) avif_loader_get_frame_data, (gint (*) (gpointer)) avif_loader_get_frame_delay }, #endif #ifdef HAVE_JXL [LOADER_TYPE_JXL] = { "JXL", (gpointer (*)(gpointer)) jxl_loader_new_from_mapping, (gpointer (*)(gconstpointer)) NULL, (void (*)(gpointer)) jxl_loader_destroy, (gboolean (*)(gpointer)) jxl_loader_get_is_animation, (void (*)(gpointer)) jxl_loader_goto_first_frame, (gboolean (*)(gpointer)) jxl_loader_goto_next_frame, (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) jxl_loader_get_frame_data, (gint (*) (gpointer)) jxl_loader_get_frame_delay }, #endif }; struct MediaLoader { LoaderType loader_type; gpointer loader; }; static int ascii_strcasecmp_ptrs (const void *a, const void *b) { gchar * const *sa = a, * const *sb = b; return g_ascii_strcasecmp (*sa, *sb); } MediaLoader * media_loader_new (const gchar *path, GError **error) { MediaLoader *loader; FileMapping *mapping = NULL; gboolean success = FALSE; gint i; g_return_val_if_fail (path != NULL, NULL); loader = g_new0 (MediaLoader, 1); mapping = file_mapping_new (path); if (!file_mapping_open_now (mapping, error)) goto out; for (i = 0; i < LOADER_TYPE_LAST && !loader->loader; i++) { loader->loader_type = i; if (mapping && loader_vtable [i].new_from_mapping) { loader->loader = loader_vtable [i].new_from_mapping (mapping); } else if (loader_vtable [i].new_from_path) { loader->loader = loader_vtable [i].new_from_path (path); if (loader->loader) file_mapping_destroy (mapping); } if (loader->loader) { /* Mapping was either transferred to the loader or destroyed by us above */ mapping = NULL; break; } } if (!loader->loader) goto out; success = TRUE; out: if (!success) { if (mapping) file_mapping_destroy (mapping); media_loader_destroy (loader); loader = NULL; if (error && !*error) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Unknown file format"); } } return loader; } void media_loader_destroy (MediaLoader *loader) { if (loader->loader) { loader_vtable [loader->loader_type].destroy (loader->loader); loader->loader = NULL; } g_free (loader); } gboolean media_loader_get_is_animation (MediaLoader *loader) { return loader_vtable [loader->loader_type].get_is_animation (loader->loader); } void media_loader_goto_first_frame (MediaLoader *loader) { loader_vtable [loader->loader_type].goto_first_frame (loader->loader); } gboolean media_loader_goto_next_frame (MediaLoader *loader) { return loader_vtable [loader->loader_type].goto_next_frame (loader->loader); } gconstpointer media_loader_get_frame_data (MediaLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { return loader_vtable [loader->loader_type].get_frame_data (loader->loader, pixel_type_out, width_out, height_out, rowstride_out); } gint media_loader_get_frame_delay (MediaLoader *loader) { return loader_vtable [loader->loader_type].get_frame_delay (loader->loader); } gchar ** get_loader_names (void) { gchar **strv; gint i, j; strv = g_new0 (gchar *, LOADER_TYPE_LAST + 1); for (i = 0, j = 0; i < LOADER_TYPE_LAST; i++) { if (loader_vtable [i].name == NULL) continue; strv [j++] = g_strdup (loader_vtable [i].name); } qsort (strv, j, sizeof (gchar *), ascii_strcasecmp_ptrs); return strv; } chafa-1.14.5/tools/chafa/media-loader.h000066400000000000000000000031151471154763100175530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __MEDIA_LOADER_H__ #define __MEDIA_LOADER_H__ #include G_BEGIN_DECLS typedef struct MediaLoader MediaLoader; MediaLoader *media_loader_new (const gchar *path, GError **error); void media_loader_destroy (MediaLoader *loader); gboolean media_loader_get_is_animation (MediaLoader *loader); void media_loader_goto_first_frame (MediaLoader *loader); gboolean media_loader_goto_next_frame (MediaLoader *loader); gconstpointer media_loader_get_frame_data (MediaLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint media_loader_get_frame_delay (MediaLoader *loader); gchar **get_loader_names (void); G_END_DECLS #endif /* __MEDIA_LOADER_H__ */ chafa-1.14.5/tools/chafa/ms-utf8.xml000066400000000000000000000006071471154763100171070ustar00rootroot00000000000000 UTF-8 chafa-1.14.5/tools/chafa/named-colors.c000066400000000000000000000700111471154763100176050ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/tools/chafa/named-colors.h000066400000000000000000000021451471154763100176150ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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.14.5/tools/chafa/placement-counter.c000066400000000000000000000046121471154763100206530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "placement-counter.h" #define BUF_SIZE 256 struct PlacementCounter { guint id; }; static void save_id (PlacementCounter *counter) { gchar *path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (), "chafa", "placement-id", NULL); gchar buf [BUF_SIZE]; snprintf (buf, BUF_SIZE, "%u\n", counter->id); g_file_set_contents (path, buf, -1, NULL); g_free (path); } static void restore_id (PlacementCounter *counter) { gchar *path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (), "chafa", "placement-id", NULL); gchar *buf = NULL; gsize length; g_file_get_contents (path, &buf, &length, NULL); if (buf && length > 0) { counter->id = strtoul (buf, NULL, 10); if (counter->id < 1) counter->id = 1; } g_free (buf); g_free (path); } static void ensure_id_storage (void) { gchar *path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (), "chafa", NULL); g_mkdir_with_parents (path, 0750); g_free (path); } PlacementCounter * placement_counter_new (void) { PlacementCounter *counter; counter = g_new0 (PlacementCounter, 1); counter->id = 0; ensure_id_storage (); restore_id (counter); return counter; } void placement_counter_destroy (PlacementCounter *counter) { save_id (counter); g_free (counter); } guint placement_counter_get_next_id (PlacementCounter *counter) { counter->id = 1 + (counter->id % 65535); return counter->id; } chafa-1.14.5/tools/chafa/placement-counter.h000066400000000000000000000023161471154763100206570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __PLACEMENT_COUNTER_H__ #define __PLACEMENT_COUNTER_H__ #include G_BEGIN_DECLS typedef struct PlacementCounter PlacementCounter; PlacementCounter *placement_counter_new (void); void placement_counter_destroy (PlacementCounter *counter); guint placement_counter_get_next_id (PlacementCounter *counter); G_END_DECLS #endif /* __PLACEMENT_COUNTER_H__ */ chafa-1.14.5/tools/chafa/png-loader.c000066400000000000000000000100671471154763100172570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include "png-loader.h" #define BYTES_PER_PIXEL 4 #define IMAGE_BUFFER_SIZE_MAX 0xffffffffU struct PngLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gpointer frame_data; gint width, height; }; static PngLoader * png_loader_new (void) { return g_new0 (PngLoader, 1); } PngLoader * png_loader_new_from_mapping (FileMapping *mapping) { PngLoader *loader = NULL; gboolean success = FALSE; guint width, height; unsigned char *frame_data = NULL; LodePNGState lode_state; gint lode_error; g_return_val_if_fail (mapping != NULL, NULL); lodepng_state_init (&lode_state); if (!file_mapping_has_magic (mapping, 0, "\x89PNG", 4)) goto out; loader = png_loader_new (); loader->mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; lode_state.info_raw.colortype = LCT_RGBA; lode_state.info_raw.bitdepth = 8; lode_state.decoder.zlibsettings.max_output_size = IMAGE_BUFFER_SIZE_MAX; /* Decodes to RGBA8 */ if ((lode_error = lodepng_decode (&frame_data, &width, &height, &lode_state, loader->file_data, loader->file_data_len)) != 0) goto out; if (width < 1 || width >= (1 << 28) || height < 1 || height >= (1 << 28)) goto out; loader->frame_data = frame_data; loader->width = (gint) width; loader->height = (gint) height; success = TRUE; out: if (!success) { if (loader) { g_free (loader); loader = NULL; } if (frame_data) free (frame_data); } lodepng_state_cleanup (&lode_state); return loader; } void png_loader_destroy (PngLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->frame_data) free (loader->frame_data); g_free (loader); } gboolean png_loader_get_is_animation (PngLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return FALSE; } gconstpointer png_loader_get_frame_data (PngLoader *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 = CHAFA_PIXEL_RGBA8_UNASSOCIATED; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (rowstride_out) *rowstride_out = loader->width * BYTES_PER_PIXEL; return loader->frame_data; } gint png_loader_get_frame_delay (PngLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return 0; } void png_loader_goto_first_frame (PngLoader *loader) { g_return_if_fail (loader != NULL); } gboolean png_loader_goto_next_frame (PngLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); return FALSE; } chafa-1.14.5/tools/chafa/png-loader.h000066400000000000000000000030421471154763100172570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __PNG_LOADER_H__ #define __PNG_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct PngLoader PngLoader; PngLoader *png_loader_new_from_mapping (FileMapping *mapping); void png_loader_destroy (PngLoader *loader); gboolean png_loader_get_is_animation (PngLoader *loader); gconstpointer png_loader_get_frame_data (PngLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint png_loader_get_frame_delay (PngLoader *loader); void png_loader_goto_first_frame (PngLoader *loader); gboolean png_loader_goto_next_frame (PngLoader *loader); G_END_DECLS #endif /* __PNG_LOADER_H__ */ chafa-1.14.5/tools/chafa/qoi-loader.c000066400000000000000000000073041471154763100172630ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "qoi-loader.h" #define QOI_IMPLEMENTATION #define QOI_NO_STDIO #include "qoi.h" #define BYTES_PER_PIXEL 4 struct QoiLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gpointer frame_data; gint width, height; }; static QoiLoader * qoi_loader_new (void) { return g_new0 (QoiLoader, 1); } QoiLoader * qoi_loader_new_from_mapping (FileMapping *mapping) { QoiLoader *loader = NULL; gboolean success = FALSE; unsigned char *frame_data = NULL; qoi_desc desc; g_return_val_if_fail (mapping != NULL, NULL); if (!file_mapping_has_magic (mapping, 0, "qoif", 4)) goto out; loader = qoi_loader_new (); loader->mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; /* Decodes to RGBA8 */ frame_data = qoi_decode (loader->file_data, loader->file_data_len, &desc, BYTES_PER_PIXEL); if (!frame_data) goto out; if (desc.width < 1 || desc.width >= (1 << 16) || desc.height < 1 || desc.height >= (1 << 16)) goto out; loader->frame_data = frame_data; loader->width = desc.width; loader->height = desc.height; success = TRUE; out: if (!success) { if (loader) { g_free (loader); loader = NULL; } if (frame_data) free (frame_data); } return loader; } void qoi_loader_destroy (QoiLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->frame_data) free (loader->frame_data); g_free (loader); } gboolean qoi_loader_get_is_animation (QoiLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return FALSE; } gconstpointer qoi_loader_get_frame_data (QoiLoader *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 = CHAFA_PIXEL_RGBA8_UNASSOCIATED; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (rowstride_out) *rowstride_out = loader->width * BYTES_PER_PIXEL; return loader->frame_data; } gint qoi_loader_get_frame_delay (QoiLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return 0; } void qoi_loader_goto_first_frame (QoiLoader *loader) { g_return_if_fail (loader != NULL); } gboolean qoi_loader_goto_next_frame (QoiLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); return FALSE; } chafa-1.14.5/tools/chafa/qoi-loader.h000066400000000000000000000030421471154763100172630ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __QOI_LOADER_H__ #define __QOI_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct QoiLoader QoiLoader; QoiLoader *qoi_loader_new_from_mapping (FileMapping *mapping); void qoi_loader_destroy (QoiLoader *loader); gboolean qoi_loader_get_is_animation (QoiLoader *loader); gconstpointer qoi_loader_get_frame_data (QoiLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint qoi_loader_get_frame_delay (QoiLoader *loader); void qoi_loader_goto_first_frame (QoiLoader *loader); gboolean qoi_loader_goto_next_frame (QoiLoader *loader); G_END_DECLS #endif /* __QOI_LOADER_H__ */ chafa-1.14.5/tools/chafa/qoi.h000066400000000000000000000440601471154763100160240ustar00rootroot00000000000000/* Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org SPDX-License-Identifier: MIT QOI - The "Quite OK Image" format for fast, lossless image compression -- About QOI encodes and decodes images in a lossless format. Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and 20% better compression. -- Synopsis // Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this // library to create the implementation. #define QOI_IMPLEMENTATION #include "qoi.h" // Encode and store an RGBA buffer to the file system. The qoi_desc describes // the input pixel data. qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ .width = 1920, .height = 1080, .channels = 4, .colorspace = QOI_SRGB }); // Load and decode a QOI image from the file system into a 32bbp RGBA buffer. // The qoi_desc struct will be filled with the width, height, number of channels // and colorspace read from the file header. qoi_desc desc; void *rgba_pixels = qoi_read("image.qoi", &desc, 4); -- Documentation This library provides the following functions; - qoi_read -- read and decode a QOI file - qoi_decode -- decode the raw bytes of a QOI image from memory - qoi_write -- encode and write a QOI file - qoi_encode -- encode an rgba buffer into a QOI image in memory See the function declaration below for the signature and more information. If you don't want/need the qoi_read and qoi_write functions, you can define QOI_NO_STDIO before including this library. This library uses malloc() and free(). To supply your own malloc implementation you can define QOI_MALLOC and QOI_FREE before including this library. This library uses memset() to zero-initialize the index. To supply your own implementation you can define QOI_ZEROARR before including this library. -- Data Format A QOI file has a 14 byte header, followed by any number of data "chunks" and an 8-byte end marker. struct qoi_header_t { char magic[4]; // magic bytes "qoif" uint32_t width; // image width in pixels (BE) uint32_t height; // image height in pixels (BE) uint8_t channels; // 3 = RGB, 4 = RGBA uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear }; Images are encoded row by row, left to right, top to bottom. The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An image is complete when all pixels specified by width * height have been covered. Pixels are encoded as - a run of the previous pixel - an index into an array of previously seen pixels - a difference to the previous pixel value in r,g,b - full r,g,b or r,g,b,a values The color channels are assumed to not be premultiplied with the alpha channel ("un-premultiplied alpha"). A running array[64] (zero-initialized) of previously seen pixel values is maintained by the encoder and decoder. Each pixel that is seen by the encoder and decoder is put into this array at the position formed by a hash function of the color value. In the encoder, if the pixel value at the index matches the current pixel, this index position is written to the stream as QOI_OP_INDEX. The hash function for the index is: index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All values encoded in these data bits have the most significant bit on the left. The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the presence of an 8-bit tag first. The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. The possible chunks are: .- QOI_OP_INDEX ----------. | Byte[0] | | 7 6 5 4 3 2 1 0 | |-------+-----------------| | 0 0 | index | `-------------------------` 2-bit tag b00 6-bit index into the color index array: 0..63 A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the same index. QOI_OP_RUN should be used instead. .- QOI_OP_DIFF -----------. | Byte[0] | | 7 6 5 4 3 2 1 0 | |-------+-----+-----+-----| | 0 1 | dr | dg | db | `-------------------------` 2-bit tag b01 2-bit red channel difference from the previous pixel between -2..1 2-bit green channel difference from the previous pixel between -2..1 2-bit blue channel difference from the previous pixel between -2..1 The difference to the current channel values are using a wraparound operation, so "1 - 2" will result in 255, while "255 + 1" will result in 0. Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as 0 (b00). 1 is stored as 3 (b11). The alpha value remains unchanged from the previous pixel. .- QOI_OP_LUMA -------------------------------------. | Byte[0] | Byte[1] | | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | |-------+-----------------+-------------+-----------| | 1 0 | green diff | dr - dg | db - dg | `---------------------------------------------------` 2-bit tag b10 6-bit green channel difference from the previous pixel -32..31 4-bit red channel difference minus green channel difference -8..7 4-bit blue channel difference minus green channel difference -8..7 The green channel is used to indicate the general direction of change and is encoded in 6 bits. The red and blue channels (dr and db) base their diffs off of the green channel difference and are encoded in 4 bits. I.e.: dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) The difference to the current channel values are using a wraparound operation, so "10 - 13" will result in 253, while "250 + 7" will result in 1. Values are stored as unsigned integers with a bias of 32 for the green channel and a bias of 8 for the red and blue channel. The alpha value remains unchanged from the previous pixel. .- QOI_OP_RUN ------------. | Byte[0] | | 7 6 5 4 3 2 1 0 | |-------+-----------------| | 1 1 | run | `-------------------------` 2-bit tag b11 6-bit run-length repeating the previous pixel: 1..62 The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 (b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and QOI_OP_RGBA tags. .- QOI_OP_RGB ------------------------------------------. | Byte[0] | Byte[1] | Byte[2] | Byte[3] | | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | |-------------------------+---------+---------+---------| | 1 1 1 1 1 1 1 0 | red | green | blue | `-------------------------------------------------------` 8-bit tag b11111110 8-bit red channel value 8-bit green channel value 8-bit blue channel value The alpha value remains unchanged from the previous pixel. .- QOI_OP_RGBA ---------------------------------------------------. | Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | |-------------------------+---------+---------+---------+---------| | 1 1 1 1 1 1 1 1 | red | green | blue | alpha | `-----------------------------------------------------------------` 8-bit tag b11111111 8-bit red channel value 8-bit green channel value 8-bit blue channel value 8-bit alpha channel value */ /* ----------------------------------------------------------------------------- Header - Public functions */ #ifndef QOI_H #define QOI_H #ifdef __cplusplus extern "C" { #endif /* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. It describes either the input format (for qoi_write and qoi_encode), or is filled with the description read from the file header (for qoi_read and qoi_decode). The colorspace in this qoi_desc is an enum where 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel 1 = all channels are linear You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely informative. It will be saved to the file header, but does not affect how chunks are en-/decoded. */ #define QOI_SRGB 0 #define QOI_LINEAR 1 typedef struct { unsigned int width; unsigned int height; unsigned char channels; unsigned char colorspace; } qoi_desc; #ifndef QOI_NO_STDIO /* Encode raw RGB or RGBA pixels into a QOI image and write it to the file system. The qoi_desc struct must be filled with the image width, height, number of channels (3 = RGB, 4 = RGBA) and the colorspace. The function returns 0 on failure (invalid parameters, or fopen or malloc failed) or the number of bytes written on success. */ int qoi_write(const char *filename, const void *data, const qoi_desc *desc); /* Read and decode a QOI image from the file system. If channels is 0, the number of channels from the file header is used. If channels is 3 or 4 the output format will be forced into this number of channels. The function either returns NULL on failure (invalid data, or malloc or fopen failed) or a pointer to the decoded pixels. On success, the qoi_desc struct will be filled with the description from the file header. The returned pixel data should be free()d after use. */ void *qoi_read(const char *filename, qoi_desc *desc, int channels); #endif /* QOI_NO_STDIO */ /* Encode raw RGB or RGBA pixels into a QOI image in memory. The function either returns NULL on failure (invalid parameters or malloc failed) or a pointer to the encoded data on success. On success the out_len is set to the size in bytes of the encoded data. The returned qoi data should be free()d after use. */ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); /* Decode a QOI image from memory. The function either returns NULL on failure (invalid parameters or malloc failed) or a pointer to the decoded pixels. On success, the qoi_desc struct is filled with the description from the file header. The returned pixel data should be free()d after use. */ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); #ifdef __cplusplus } #endif #endif /* QOI_H */ /* ----------------------------------------------------------------------------- Implementation */ #ifdef QOI_IMPLEMENTATION #include #include #ifndef QOI_MALLOC #define QOI_MALLOC(sz) malloc(sz) #define QOI_FREE(p) free(p) #endif #ifndef QOI_ZEROARR #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) #endif #define QOI_OP_INDEX 0x00 /* 00xxxxxx */ #define QOI_OP_DIFF 0x40 /* 01xxxxxx */ #define QOI_OP_LUMA 0x80 /* 10xxxxxx */ #define QOI_OP_RUN 0xc0 /* 11xxxxxx */ #define QOI_OP_RGB 0xfe /* 11111110 */ #define QOI_OP_RGBA 0xff /* 11111111 */ #define QOI_MASK_2 0xc0 /* 11000000 */ #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) #define QOI_MAGIC \ (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ ((unsigned int)'i') << 8 | ((unsigned int)'f')) #define QOI_HEADER_SIZE 14 /* 2GB is the max file size that this implementation can safely handle. We guard against anything larger than that, assuming the worst case with 5 bytes per pixel, rounded down to a nice clean value. 400 million pixels ought to be enough for anybody. */ #define QOI_PIXELS_MAX ((unsigned int)400000000) typedef union { struct { unsigned char r, g, b, a; } rgba; unsigned int v; } qoi_rgba_t; static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { bytes[(*p)++] = (0xff000000 & v) >> 24; bytes[(*p)++] = (0x00ff0000 & v) >> 16; bytes[(*p)++] = (0x0000ff00 & v) >> 8; bytes[(*p)++] = (0x000000ff & v); } static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { unsigned int a = bytes[(*p)++]; unsigned int b = bytes[(*p)++]; unsigned int c = bytes[(*p)++]; unsigned int d = bytes[(*p)++]; return a << 24 | b << 16 | c << 8 | d; } void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { int i, max_size, p, run; int px_len, px_end, px_pos, channels; unsigned char *bytes; const unsigned char *pixels; qoi_rgba_t index[64]; qoi_rgba_t px, px_prev; if ( data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || desc->colorspace > 1 || desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } max_size = desc->width * desc->height * (desc->channels + 1) + QOI_HEADER_SIZE + sizeof(qoi_padding); p = 0; bytes = (unsigned char *) QOI_MALLOC(max_size); if (!bytes) { return NULL; } qoi_write_32(bytes, &p, QOI_MAGIC); qoi_write_32(bytes, &p, desc->width); qoi_write_32(bytes, &p, desc->height); bytes[p++] = desc->channels; bytes[p++] = desc->colorspace; pixels = (const unsigned char *)data; QOI_ZEROARR(index); run = 0; px_prev.rgba.r = 0; px_prev.rgba.g = 0; px_prev.rgba.b = 0; px_prev.rgba.a = 255; px = px_prev; px_len = desc->width * desc->height * desc->channels; px_end = px_len - desc->channels; channels = desc->channels; for (px_pos = 0; px_pos < px_len; px_pos += channels) { px.rgba.r = pixels[px_pos + 0]; px.rgba.g = pixels[px_pos + 1]; px.rgba.b = pixels[px_pos + 2]; if (channels == 4) { px.rgba.a = pixels[px_pos + 3]; } if (px.v == px_prev.v) { run++; if (run == 62 || px_pos == px_end) { bytes[p++] = QOI_OP_RUN | (run - 1); run = 0; } } else { int index_pos; if (run > 0) { bytes[p++] = QOI_OP_RUN | (run - 1); run = 0; } index_pos = QOI_COLOR_HASH(px) % 64; if (index[index_pos].v == px.v) { bytes[p++] = QOI_OP_INDEX | index_pos; } else { index[index_pos] = px; if (px.rgba.a == px_prev.rgba.a) { signed char vr = px.rgba.r - px_prev.rgba.r; signed char vg = px.rgba.g - px_prev.rgba.g; signed char vb = px.rgba.b - px_prev.rgba.b; signed char vg_r = vr - vg; signed char vg_b = vb - vg; if ( vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2 ) { bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); } else if ( vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8 ) { bytes[p++] = QOI_OP_LUMA | (vg + 32); bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); } else { bytes[p++] = QOI_OP_RGB; bytes[p++] = px.rgba.r; bytes[p++] = px.rgba.g; bytes[p++] = px.rgba.b; } } else { bytes[p++] = QOI_OP_RGBA; bytes[p++] = px.rgba.r; bytes[p++] = px.rgba.g; bytes[p++] = px.rgba.b; bytes[p++] = px.rgba.a; } } } px_prev = px; } for (i = 0; i < (int)sizeof(qoi_padding); i++) { bytes[p++] = qoi_padding[i]; } *out_len = p; return bytes; } void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { const unsigned char *bytes; unsigned int header_magic; unsigned char *pixels; qoi_rgba_t index[64]; qoi_rgba_t px; int px_len, chunks_len, px_pos; int p = 0, run = 0; if ( data == NULL || desc == NULL || (channels != 0 && channels != 3 && channels != 4) || size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) ) { return NULL; } bytes = (const unsigned char *)data; header_magic = qoi_read_32(bytes, &p); desc->width = qoi_read_32(bytes, &p); desc->height = qoi_read_32(bytes, &p); desc->channels = bytes[p++]; desc->colorspace = bytes[p++]; if ( desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || desc->colorspace > 1 || header_magic != QOI_MAGIC || desc->height >= QOI_PIXELS_MAX / desc->width || /* hpj: The following avoids slow decode on crafted files, * and Chafa doesn't handle bigger images anyway */ desc->width > 65535 || desc->height > 65535 ) { return NULL; } if (channels == 0) { channels = desc->channels; } px_len = desc->width * desc->height * channels; pixels = (unsigned char *) QOI_MALLOC(px_len); if (!pixels) { return NULL; } QOI_ZEROARR(index); px.rgba.r = 0; px.rgba.g = 0; px.rgba.b = 0; px.rgba.a = 255; chunks_len = size - (int)sizeof(qoi_padding); for (px_pos = 0; px_pos < px_len; px_pos += channels) { if (run > 0) { run--; } else if (p < chunks_len) { int b1 = bytes[p++]; if (b1 == QOI_OP_RGB) { px.rgba.r = bytes[p++]; px.rgba.g = bytes[p++]; px.rgba.b = bytes[p++]; } else if (b1 == QOI_OP_RGBA) { px.rgba.r = bytes[p++]; px.rgba.g = bytes[p++]; px.rgba.b = bytes[p++]; px.rgba.a = bytes[p++]; } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { px = index[b1]; } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { px.rgba.r += ((b1 >> 4) & 0x03) - 2; px.rgba.g += ((b1 >> 2) & 0x03) - 2; px.rgba.b += ( b1 & 0x03) - 2; } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { int b2 = bytes[p++]; int vg = (b1 & 0x3f) - 32; px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); px.rgba.g += vg; px.rgba.b += vg - 8 + (b2 & 0x0f); } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { run = (b1 & 0x3f); } index[QOI_COLOR_HASH(px) % 64] = px; } pixels[px_pos + 0] = px.rgba.r; pixels[px_pos + 1] = px.rgba.g; pixels[px_pos + 2] = px.rgba.b; if (channels == 4) { pixels[px_pos + 3] = px.rgba.a; } } return pixels; } #ifndef QOI_NO_STDIO #include int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { FILE *f = fopen(filename, "wb"); int size, err; void *encoded; if (!f) { return 0; } encoded = qoi_encode(data, desc, &size); if (!encoded) { fclose(f); return 0; } fwrite(encoded, 1, size, f); fflush(f); err = ferror(f); fclose(f); QOI_FREE(encoded); return err ? 0 : size; } void *qoi_read(const char *filename, qoi_desc *desc, int channels) { FILE *f = fopen(filename, "rb"); int size, bytes_read; void *pixels, *data; if (!f) { return NULL; } fseek(f, 0, SEEK_END); size = ftell(f); if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; } data = QOI_MALLOC(size); if (!data) { fclose(f); return NULL; } bytes_read = fread(data, 1, size, f); fclose(f); pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels); QOI_FREE(data); return pixels; } #endif /* QOI_NO_STDIO */ #endif /* QOI_IMPLEMENTATION */ chafa-1.14.5/tools/chafa/svg-loader.c000066400000000000000000000140611471154763100172700ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include #include "svg-loader.h" #define DIMENSION_MAX 4096 #define MAGIC_BUF_SIZE 4096 /* Cairo uses native byte order */ #if G_BYTE_ORDER == G_BIG_ENDIAN # define PIXEL_TYPE CHAFA_PIXEL_ARGB8_PREMULTIPLIED #else # define PIXEL_TYPE CHAFA_PIXEL_BGRA8_PREMULTIPLIED #endif struct SvgLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; cairo_surface_t *surface; }; static SvgLoader * svg_loader_new (void) { return g_new0 (SvgLoader, 1); } static void calc_dimensions (RsvgHandle *rsvg, guint *width_out, guint *height_out) { #if !LIBRSVG_CHECK_VERSION(2, 52, 0) RsvgDimensionData dim = { 0 }; #endif gdouble width, height; rsvg_handle_set_dpi (rsvg, 150.0); #if LIBRSVG_CHECK_VERSION(2, 52, 0) if (!rsvg_handle_get_intrinsic_size_in_pixels (rsvg, &width, &height)) { width = height = (gdouble) DIMENSION_MAX; } #else rsvg_handle_get_dimensions (rsvg, &dim); width = dim.width; height = dim.height; #endif /* FIXME: It would've been nice to know the size of the final viewport; * that is, the terminal's dimensions in pixels. We could pass this in * if we change the internal API. */ if (width > DIMENSION_MAX || height > DIMENSION_MAX) { if (width > height) { height *= (gdouble) DIMENSION_MAX / width; width = (gdouble) DIMENSION_MAX; } else { width *= (gdouble) DIMENSION_MAX / height; height = (gdouble) DIMENSION_MAX; } } *width_out = lrint (width); *height_out = lrint (height); } SvgLoader * svg_loader_new_from_mapping (FileMapping *mapping) { SvgLoader *loader = NULL; gboolean success = FALSE; RsvgHandle *rsvg = NULL; cairo_t *cr = NULL; #if LIBRSVG_CHECK_VERSION(2, 46, 0) RsvgRectangle viewport = { 0 }; #endif guint width, height; g_return_val_if_fail (mapping != NULL, NULL); if (file_mapping_has_magic (mapping, 0, "mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; /* Malformed SVGs will typically fail here */ rsvg = rsvg_handle_new_from_data (loader->file_data, loader->file_data_len, NULL); if (!rsvg) goto out; calc_dimensions (rsvg, &width, &height); if (width < 1 || width >= (1 << 28) || height < 1 || height >= (1 << 28) || (width * (guint64) height >= (1 << 29))) goto out; loader->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); if (!loader->surface) goto out; cr = cairo_create (loader->surface); #if LIBRSVG_CHECK_VERSION(2, 46, 0) viewport.width = width; viewport.height = height; if (!rsvg_handle_render_document (rsvg, cr, &viewport, NULL)) goto out; #else if (!rsvg_handle_render_cairo (rsvg, cr)) goto out; #endif success = TRUE; out: if (cr) cairo_destroy (cr); if (!success) { if (loader) { if (loader->surface) cairo_surface_destroy (loader->surface); g_free (loader); loader = NULL; } } if (rsvg) g_object_unref (rsvg); return loader; } void svg_loader_destroy (SvgLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->surface) cairo_surface_destroy (loader->surface); g_free (loader); } gboolean svg_loader_get_is_animation (SvgLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return FALSE; } gconstpointer svg_loader_get_frame_data (SvgLoader *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 = PIXEL_TYPE; if (width_out) *width_out = cairo_image_surface_get_width (loader->surface); if (height_out) *height_out = cairo_image_surface_get_height (loader->surface); if (rowstride_out) *rowstride_out = cairo_image_surface_get_stride (loader->surface); return cairo_image_surface_get_data (loader->surface); } gint svg_loader_get_frame_delay (SvgLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return 0; } void svg_loader_goto_first_frame (SvgLoader *loader) { g_return_if_fail (loader != NULL); } gboolean svg_loader_goto_next_frame (SvgLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); return FALSE; } chafa-1.14.5/tools/chafa/svg-loader.h000066400000000000000000000030421471154763100172720ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __SVG_LOADER_H__ #define __SVG_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct SvgLoader SvgLoader; SvgLoader *svg_loader_new_from_mapping (FileMapping *mapping); void svg_loader_destroy (SvgLoader *loader); gboolean svg_loader_get_is_animation (SvgLoader *loader); gconstpointer svg_loader_get_frame_data (SvgLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint svg_loader_get_frame_delay (SvgLoader *loader); void svg_loader_goto_first_frame (SvgLoader *loader); gboolean svg_loader_goto_next_frame (SvgLoader *loader); G_END_DECLS #endif /* __SVG_LOADER_H__ */ chafa-1.14.5/tools/chafa/tiff-loader.c000066400000000000000000000177421471154763100174320ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include #include "tiff-loader.h" /* ----------------------- * * Global macros and types * * ----------------------- */ #define BYTES_PER_PIXEL 4 struct TiffLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gpointer frame_data; gint width, height; ChafaPixelType pixel_type; toff_t file_pos; }; /* ----------- * * TIFF loader * * ----------- */ /* --- Memory source --- */ static tsize_t my_tiff_read (thandle_t obj, tdata_t buffer, tsize_t size) { TiffLoader *loader = (TiffLoader *) obj; size = CLAMP (size, 0, (tsize_t) loader->file_data_len - (tsize_t) loader->file_pos); memcpy (buffer, loader->file_data + loader->file_pos, size); loader->file_pos += size; return size; } static tsize_t my_tiff_write (G_GNUC_UNUSED thandle_t obj, G_GNUC_UNUSED tdata_t buffer, G_GNUC_UNUSED tsize_t size) { return 0; } static int my_tiff_close (G_GNUC_UNUSED thandle_t obj) { return 0; } static toff_t my_tiff_seek (thandle_t obj, toff_t pos, int whence) { TiffLoader *loader = (TiffLoader *) obj; if (whence == SEEK_SET) { loader->file_pos = pos; } else if (whence == SEEK_CUR) { /* Since toff_t is unsigned, we can't seek backwards */ loader->file_pos += pos; } else /* whence == SEEK_END */ { /* Since toff_t is unsigned, this is all we can do */ loader->file_pos = loader->file_data_len; } loader->file_pos = MIN (loader->file_pos, loader->file_data_len); return loader->file_pos; } static toff_t my_tiff_size (thandle_t obj) { TiffLoader *loader = (TiffLoader *) obj; return loader->file_data_len; } static int my_tiff_map (thandle_t obj, void **base, toff_t *len) { TiffLoader *loader = (TiffLoader *) obj; /* When the TIFFMap delegate is non-NULL, libtiff will use it preferentially. * * Our map is read-only, while base points to non-const. Fingers crossed * libtiff doesn't actually try to write to it during read operations. */ *base = (void *) loader->file_data; *len = loader->file_data_len; return 0; } static void my_tiff_unmap (G_GNUC_UNUSED thandle_t obj, G_GNUC_UNUSED void *base, G_GNUC_UNUSED toff_t len) { } /* --- Error handlers --- */ static void my_tiff_error_handler (G_GNUC_UNUSED const char *module, G_GNUC_UNUSED const char *format, G_GNUC_UNUSED va_list ap) { } static void my_tiff_warning_handler (G_GNUC_UNUSED const char *module, G_GNUC_UNUSED const char *format, G_GNUC_UNUSED va_list ap) { } /* --- Loader --- */ static TiffLoader * tiff_loader_new (void) { return g_new0 (TiffLoader, 1); } TiffLoader * tiff_loader_new_from_mapping (FileMapping *mapping) { TiffLoader *loader = NULL; gboolean success = FALSE; uint8_t *frame_data = NULL; TIFF *tiff = NULL; gint samples_per_pixel = 4; uint32_t width, height; g_return_val_if_fail (mapping != NULL, NULL); if (!((file_mapping_has_magic (mapping, 0, "II", 2) && file_mapping_has_magic (mapping, 2, "\x2a\x00", 2)) || (file_mapping_has_magic (mapping, 0, "MM", 2) && file_mapping_has_magic (mapping, 2, "\x00\x2a", 2)))) goto out; loader = tiff_loader_new (); loader->mapping = mapping; /* Get file data */ loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; /* Prepare to decode */ TIFFSetErrorHandler (my_tiff_error_handler); TIFFSetWarningHandler (my_tiff_warning_handler); tiff = TIFFClientOpen ("Memory", "r", (thandle_t) loader, my_tiff_read, my_tiff_write, my_tiff_seek, my_tiff_close, my_tiff_size, my_tiff_map, my_tiff_unmap); if (!tiff) goto out; if (!TIFFGetField (tiff, TIFFTAG_IMAGEWIDTH, &width)) goto out; if (!TIFFGetField (tiff, TIFFTAG_IMAGELENGTH, &height)) goto out; if (!TIFFGetField (tiff, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel)) goto out; if (width < 1 || width > (1 << 28) || height < 1 || height > (1 << 28) || (width * (guint64) height >= (1 << 29))) goto out; /* An opaque image with unassociated alpha set to 0xff is equivalent to * premultiplied alpha. This will speed up resampling later on. * * For an opaque image, samples_per_pixel will typically be 1 or 3. Other * values may indicate there's an alpha channel -- in those cases, we look * for an EXTRASAMPLES field, and if it doesn't explicitly specify * premultiplied alpha, we fail safe to unassociated alpha. */ loader->pixel_type = CHAFA_PIXEL_RGBA8_PREMULTIPLIED; if (samples_per_pixel == 2 || samples_per_pixel >= 4) { const uint16_t *extra_samples = NULL; uint16_t n_extra_samples = 0; if (TIFFGetField (tiff, TIFFTAG_EXTRASAMPLES, &n_extra_samples, &extra_samples) && n_extra_samples >= 1 && extra_samples && extra_samples [0] != EXTRASAMPLE_ASSOCALPHA) loader->pixel_type = CHAFA_PIXEL_RGBA8_UNASSOCIATED; } frame_data = _TIFFmalloc (width * height * (guint64) BYTES_PER_PIXEL); if (!frame_data) goto out; /* Decode and rotate the image */ if (!TIFFReadRGBAImageOriented (tiff, width, height, (uint32_t *) frame_data, ORIENTATION_TOPLEFT, 0)) goto out; /* Finish up */ loader->width = width; loader->height = height; loader->frame_data = frame_data; success = TRUE; out: if (tiff) TIFFClose (tiff); if (!success) { if (frame_data) _TIFFfree (frame_data); if (loader) { g_free (loader); loader = NULL; } } return loader; } void tiff_loader_destroy (TiffLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->frame_data) _TIFFfree (loader->frame_data); g_free (loader); } gboolean tiff_loader_get_is_animation (TiffLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return FALSE; } gconstpointer tiff_loader_get_frame_data (TiffLoader *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 = loader->pixel_type; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (rowstride_out) *rowstride_out = loader->width * BYTES_PER_PIXEL; return loader->frame_data; } gint tiff_loader_get_frame_delay (TiffLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return 0; } void tiff_loader_goto_first_frame (TiffLoader *loader) { g_return_if_fail (loader != NULL); } gboolean tiff_loader_goto_next_frame (TiffLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); return FALSE; } chafa-1.14.5/tools/chafa/tiff-loader.h000066400000000000000000000030661471154763100174310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __TIFF_LOADER_H__ #define __TIFF_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct TiffLoader TiffLoader; TiffLoader *tiff_loader_new_from_mapping (FileMapping *mapping); void tiff_loader_destroy (TiffLoader *loader); gboolean tiff_loader_get_is_animation (TiffLoader *loader); gconstpointer tiff_loader_get_frame_data (TiffLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint tiff_loader_get_frame_delay (TiffLoader *loader); void tiff_loader_goto_first_frame (TiffLoader *loader); gboolean tiff_loader_goto_next_frame (TiffLoader *loader); G_END_DECLS #endif /* __TIFF_LOADER_H__ */ chafa-1.14.5/tools/chafa/util.c000066400000000000000000000122371471154763100162050ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 "util.h" #define ROWSTRIDE_ALIGN 16 #define PAD_TO_N(p, n) (((p) + ((n) - 1)) & ~((unsigned) (n) - 1)) #define ROWSTRIDE_PAD(rowstride) (PAD_TO_N ((rowstride), (ROWSTRIDE_ALIGN))) static void transform (const guchar *src, gint src_pixstride, gint src_rowstride, guchar *dest, gint dest_pixstride, gint dest_rowstride, gint src_width, gint src_height, gint pixsize) { const guchar *src_row = src; const guchar *src_row_end = src + src_pixstride * src_width; const guchar *src_end = src + src_rowstride * src_height; guchar *dest_row = dest; while (src_row != src_end) { src = src_row; dest = dest_row; while (src != src_row_end) { memcpy (dest, src, pixsize); src += src_pixstride; dest += dest_pixstride; } src_row += src_rowstride; src_row_end += src_rowstride; dest_row += dest_rowstride; } } RotationType invert_rotation (RotationType rot) { switch (rot) { case ROTATION_90: rot = ROTATION_270; break; case ROTATION_270: rot = ROTATION_90; break; default: break; } return rot; } void rotate_image (gpointer *src, guint *width, guint *height, guint *rowstride, guint n_channels, RotationType rot) { gint src_width, src_height; gint src_pixstride, src_rowstride; guchar *dest, *dest_start; gint dest_width, dest_height; gint dest_pixstride, dest_rowstride, dest_trans_rowstride; g_assert (n_channels == 3 || n_channels == 4); if (rot <= ROTATION_NONE || rot == ROTATION_0 || rot >= ROTATION_UNDEFINED) return; src_width = *width; src_height = *height; src_pixstride = n_channels; src_rowstride = *rowstride; switch (rot) { case ROTATION_90: case ROTATION_90_MIRROR: case ROTATION_270: case ROTATION_270_MIRROR: dest_width = src_height; dest_height = src_width; break; case ROTATION_0_MIRROR: case ROTATION_180: case ROTATION_180_MIRROR: dest_width = src_width; dest_height = src_height; break; default: g_assert_not_reached (); } dest_rowstride = ROWSTRIDE_PAD (dest_width * n_channels); dest = g_malloc (dest_rowstride * dest_height); switch (rot) { case ROTATION_0_MIRROR: dest_pixstride = -n_channels; dest_trans_rowstride = dest_rowstride; dest_start = dest + ((dest_width - 1) * n_channels); break; case ROTATION_90: dest_pixstride = dest_rowstride; dest_trans_rowstride = -n_channels; dest_start = dest + ((dest_width - 1) * n_channels); break; case ROTATION_90_MIRROR: dest_pixstride = -dest_rowstride; dest_trans_rowstride = -n_channels; dest_start = dest + ((dest_height - 1) * dest_rowstride) + ((dest_width - 1) * n_channels); break; case ROTATION_180: dest_pixstride = -n_channels; dest_trans_rowstride = -dest_rowstride; dest_start = dest + ((dest_height - 1) * dest_rowstride) + ((dest_width - 1) * n_channels); break; case ROTATION_180_MIRROR: dest_pixstride = n_channels; dest_trans_rowstride = -dest_rowstride; dest_start = dest + ((dest_height - 1) * dest_rowstride); break; case ROTATION_270: dest_pixstride = -dest_rowstride; dest_trans_rowstride = n_channels; dest_start = dest + ((dest_height - 1) * dest_rowstride); break; case ROTATION_270_MIRROR: dest_pixstride = dest_rowstride; dest_trans_rowstride = n_channels; dest_start = dest; break; default: g_assert_not_reached (); } transform (*src, src_pixstride, src_rowstride, dest_start, dest_pixstride, dest_trans_rowstride, src_width, src_height, n_channels); g_free (*src); *src = dest; *width = dest_width; *height = dest_height; *rowstride = dest_rowstride; } chafa-1.14.5/tools/chafa/util.h000066400000000000000000000026531471154763100162130ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __UTIL_H__ #define __UTIL_H__ #include G_BEGIN_DECLS typedef enum { ROTATION_NONE = 0, ROTATION_0 = 1, ROTATION_0_MIRROR = 2, ROTATION_180 = 3, ROTATION_180_MIRROR = 4, ROTATION_270_MIRROR = 5, ROTATION_270 = 6, ROTATION_90_MIRROR = 7, ROTATION_90 = 8, ROTATION_UNDEFINED = 9, ROTATION_MAX } RotationType; RotationType invert_rotation (RotationType rot); void rotate_image (gpointer *src, guint *width, guint *height, guint *rowstride, guint n_channels, RotationType rot); G_END_DECLS #endif /* __UTIL_H__ */ chafa-1.14.5/tools/chafa/webp-loader.c000066400000000000000000000155431471154763100174340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #include "webp-loader.h" #define DEFAULT_FRAME_DURATION_MS 50 #define BYTES_PER_PIXEL 4 struct WebpLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gint width, height; ChafaPixelType pixel_type; WebPAnimDecoder *anim_dec; gpointer this_frame_data, next_frame_data; gint this_timestamp, next_timestamp; guint is_animation : 1; }; static WebpLoader * webp_loader_new (void) { return g_new0 (WebpLoader, 1); } WebpLoader * webp_loader_new_from_mapping (FileMapping *mapping) { WebpLoader *loader = NULL; gboolean success = FALSE; WebPBitstreamFeatures features; WebPAnimDecoderOptions anim_dec_options; WebPData webp_data; WebPAnimInfo anim_info; g_return_val_if_fail (mapping != NULL, NULL); /* Basic validation and info extraction */ if (!file_mapping_has_magic (mapping, 0, "RIFF", 4) || !file_mapping_has_magic (mapping, 8, "WEBP", 4)) goto out; loader = webp_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 (!WebPGetInfo (loader->file_data, loader->file_data_len, &features.width, &features.height)) goto out; if (WebPGetFeatures (loader->file_data, loader->file_data_len, &features) != VP8_STATUS_OK) goto out; /* Set up the animation decoder */ webp_data.bytes = loader->file_data; webp_data.size = loader->file_data_len; WebPAnimDecoderOptionsInit (&anim_dec_options); anim_dec_options.color_mode = MODE_RGBA; anim_dec_options.use_threads = TRUE; loader->anim_dec = WebPAnimDecoderNew (&webp_data, &anim_dec_options); if (!loader->anim_dec) goto out; /* Get animation info and validate */ if (!WebPAnimDecoderGetInfo (loader->anim_dec, &anim_info)) goto out; if (anim_info.canvas_width < 1 || anim_info.canvas_width >= (1 << 28) || anim_info.canvas_height < 1 || anim_info.canvas_height >= (1 << 28) || (anim_info.canvas_width * (guint64) anim_info.canvas_height >= (1 << 29))) goto out; if (anim_info.frame_count < 1) goto out; /* Store parameters */ if (anim_info.frame_count > 1) loader->is_animation = TRUE; loader->width = anim_info.canvas_width; loader->height = anim_info.canvas_height; /* An opaque image with unassociated alpha set to 0xff is equivalent to * premultiplied alpha. This will speed up resampling later on. */ loader->pixel_type = features.has_alpha ? CHAFA_PIXEL_RGBA8_UNASSOCIATED : CHAFA_PIXEL_RGBA8_PREMULTIPLIED; success = TRUE; out: if (!success) { if (loader) { g_free (loader); loader = NULL; } } return loader; } void webp_loader_destroy (WebpLoader *loader) { if (loader->anim_dec) WebPAnimDecoderDelete (loader->anim_dec); if (loader->mapping) file_mapping_destroy (loader->mapping); g_free (loader->this_frame_data); loader->this_frame_data = NULL; g_free (loader->next_frame_data); loader->next_frame_data = NULL; g_free (loader); } gboolean webp_loader_get_is_animation (WebpLoader *loader) { g_return_val_if_fail (loader != NULL, 0); return loader->is_animation; } static gboolean decode_next_frame (WebpLoader *loader, uint8_t **buf, int *timestamp) { return WebPAnimDecoderGetNext (loader->anim_dec, buf, timestamp); } static gboolean maybe_decode_frame (WebpLoader *loader) { uint8_t *buf; if (loader->this_frame_data) return TRUE; if (decode_next_frame (loader, &buf, &loader->this_timestamp)) { loader->this_frame_data = g_memdup (buf, loader->width * BYTES_PER_PIXEL * loader->height); } return loader->this_frame_data ? TRUE : FALSE; } gconstpointer webp_loader_get_frame_data (WebpLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); if (!maybe_decode_frame (loader)) return NULL; if (pixel_type_out) *pixel_type_out = loader->pixel_type; if (width_out) *width_out = loader->width; if (height_out) *height_out = loader->height; if (rowstride_out) *rowstride_out = loader->width * BYTES_PER_PIXEL; return loader->this_frame_data; } gint webp_loader_get_frame_delay (WebpLoader *loader) { uint8_t *buf; g_return_val_if_fail (loader != NULL, 0); /* The libwebp API complicates this a little. We need to load the next frame * in advance to know how long to hold this frame. */ maybe_decode_frame (loader); if (!loader->next_frame_data) { if (decode_next_frame (loader, &buf, &loader->next_timestamp)) { loader->next_frame_data = g_memdup (buf, loader->width * BYTES_PER_PIXEL * loader->height); } } return loader->next_frame_data ? (loader->next_timestamp - loader->this_timestamp) : DEFAULT_FRAME_DURATION_MS; } void webp_loader_goto_first_frame (WebpLoader *loader) { g_return_if_fail (loader != NULL); WebPAnimDecoderReset (loader->anim_dec); g_free (loader->this_frame_data); loader->this_frame_data = NULL; g_free (loader->next_frame_data); loader->next_frame_data = NULL; } gboolean webp_loader_goto_next_frame (WebpLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); g_free (loader->this_frame_data); loader->this_frame_data = loader->next_frame_data; loader->next_frame_data = NULL; if (loader->this_frame_data) { loader->this_timestamp = loader->next_timestamp; return TRUE; } return WebPAnimDecoderHasMoreFrames (loader->anim_dec) ? TRUE : FALSE; } chafa-1.14.5/tools/chafa/webp-loader.h000066400000000000000000000030661471154763100174360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 __WEBP_LOADER_H__ #define __WEBP_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct WebpLoader WebpLoader; WebpLoader *webp_loader_new_from_mapping (FileMapping *mapping); void webp_loader_destroy (WebpLoader *loader); gboolean webp_loader_get_is_animation (WebpLoader *loader); gconstpointer webp_loader_get_frame_data (WebpLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint webp_loader_get_frame_delay (WebpLoader *loader); void webp_loader_goto_first_frame (WebpLoader *loader); gboolean webp_loader_goto_next_frame (WebpLoader *loader); G_END_DECLS #endif /* __WEBP_LOADER_H__ */ chafa-1.14.5/tools/chafa/xwd-loader.c000066400000000000000000000256331471154763100173020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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 #define UNPACK_FIELD_U32(dest, src, field) ((dest)->field = GUINT32_FROM_BE ((src)->field)) #define UNPACK_FIELD_S32(dest, src, field) ((dest)->field = GINT32_FROM_BE ((src)->field)) static gboolean load_header (XwdLoader *loader) { XwdHeader *h = &loader->header; XwdHeader in; const XwdHeader *inp; if (!file_mapping_taste (loader->mapping, &in, 0, sizeof (in))) return FALSE; inp = ∈ UNPACK_FIELD_U32 (h, inp, header_size); UNPACK_FIELD_U32 (h, inp, file_version); UNPACK_FIELD_U32 (h, inp, pixmap_format); UNPACK_FIELD_U32 (h, inp, pixmap_depth); UNPACK_FIELD_U32 (h, inp, pixmap_width); UNPACK_FIELD_U32 (h, inp, pixmap_height); UNPACK_FIELD_U32 (h, inp, x_offset); UNPACK_FIELD_U32 (h, inp, byte_order); UNPACK_FIELD_U32 (h, inp, bitmap_unit); UNPACK_FIELD_U32 (h, inp, bitmap_bit_order); UNPACK_FIELD_U32 (h, inp, bitmap_pad); UNPACK_FIELD_U32 (h, inp, bits_per_pixel); UNPACK_FIELD_U32 (h, inp, bytes_per_line); UNPACK_FIELD_U32 (h, inp, visual_class); UNPACK_FIELD_U32 (h, inp, red_mask); UNPACK_FIELD_U32 (h, inp, green_mask); UNPACK_FIELD_U32 (h, inp, blue_mask); UNPACK_FIELD_U32 (h, inp, bits_per_rgb); UNPACK_FIELD_U32 (h, inp, color_map_entries); UNPACK_FIELD_U32 (h, inp, n_colors); UNPACK_FIELD_U32 (h, inp, window_width); UNPACK_FIELD_U32 (h, inp, window_height); UNPACK_FIELD_S32 (h, inp, window_x); UNPACK_FIELD_S32 (h, inp, window_y); UNPACK_FIELD_U32 (h, inp, window_border_width); /* Only support the most common/useful subset of XWD files out there; * namely, that corresponding to screen dumps from modern X.Org servers. * We could check visual_class == 5 too, but the other fields convey all * the info we need. */ ASSERT_HEADER (h->header_size >= sizeof (XwdHeader)); ASSERT_HEADER (h->header_size <= 65535); ASSERT_HEADER (h->file_version == 7); ASSERT_HEADER (h->pixmap_depth == 24); /* Should be zero for truecolor/directcolor. Cap it to prevent overflows. */ ASSERT_HEADER (h->color_map_entries <= 256); /* Xvfb sets bits_per_rgb to 8, but 'convert' uses 24 for the same image data. One * of them is likely misunderstanding. Let's be lenient and accept either. */ ASSERT_HEADER (h->bits_per_rgb == 8 || h->bits_per_rgb == 24); /* These are the pixel formats we allow. */ ASSERT_HEADER (h->bits_per_pixel == 24 || h->bits_per_pixel == 32); /* Enforce sane dimensions. */ ASSERT_HEADER (h->pixmap_width >= 1 && h->pixmap_width <= 65535); ASSERT_HEADER (h->pixmap_height >= 1 && h->pixmap_height <= 65535); /* Make sure rowstride can actually hold a row's worth of data but is not padded to * something ridiculous. */ ASSERT_HEADER (h->bytes_per_line >= h->pixmap_width * (h->bits_per_pixel / 8)); ASSERT_HEADER (h->bytes_per_line <= h->pixmap_width * (h->bits_per_pixel / 8) + 1024); /* If each pixel is four bytes, reject unaligned rowstrides */ ASSERT_HEADER (h->bits_per_pixel != 32 || h->bytes_per_line % 4 == 0); /* Make sure the total allocation/map is not too big. */ ASSERT_HEADER (h->bytes_per_line * h->pixmap_height < (1UL << 31) - 65536 - 256 * 32); 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->color_map_entries * sizeof (XwdColor) + h->pixmap_height * (gsize) h->bytes_per_line); loader->image_data = (const guint8 *) loader->file_data + h->header_size + h->color_map_entries * 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 = NULL; gboolean success = FALSE; 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)); if (loader->header.pixmap_width < 1 || loader->header.pixmap_width >= (1 << 28) || loader->header.pixmap_height < 1 || loader->header.pixmap_height >= (1 << 28) || (loader->header.pixmap_width * (guint64) loader->header.pixmap_height >= (1 << 29))) goto out; success = TRUE; out: if (!success) { g_free (loader); loader = NULL; } return loader; } void xwd_loader_destroy (XwdLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); g_free (loader); } gboolean xwd_loader_get_is_animation (G_GNUC_UNUSED XwdLoader *loader) { return FALSE; } gconstpointer xwd_loader_get_frame_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; } gint xwd_loader_get_frame_delay (G_GNUC_UNUSED XwdLoader *loader) { return 0; } void xwd_loader_goto_first_frame (G_GNUC_UNUSED XwdLoader *loader) { } gboolean xwd_loader_goto_next_frame (G_GNUC_UNUSED XwdLoader *loader) { return FALSE; } chafa-1.14.5/tools/chafa/xwd-loader.h000066400000000000000000000030421471154763100172750ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2024 Hans Petter Jansson * * This file is part of Chafa, a program that shows pictures on text terminals. * * 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); gboolean xwd_loader_get_is_animation (XwdLoader *loader); gconstpointer xwd_loader_get_frame_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); gint xwd_loader_get_frame_delay (XwdLoader *loader); void xwd_loader_goto_first_frame (XwdLoader *loader); gboolean xwd_loader_goto_next_frame (XwdLoader *loader); G_END_DECLS #endif /* __XWD_LOADER_H__ */ chafa-1.14.5/tools/completions/000077500000000000000000000000001471154763100163515ustar00rootroot00000000000000chafa-1.14.5/tools/completions/Makefile.am000066400000000000000000000002641471154763100204070ustar00rootroot00000000000000PREFIX=/usr/share EXTRA_DIST = \ zsh-completion.zsh .PHONY: install-zsh-completion install-zsh-completion: install -Dm644 zsh-completion.zsh $(PREFIX)/zsh/site-functions/_chafa chafa-1.14.5/tools/completions/zsh-completion.zsh000066400000000000000000000073631471154763100220630ustar00rootroot00000000000000#compdef chafa local options=( '(- : *)'{-h,--help}"[Show help]" '(- : *)'--version"[Show version]" {-v,--verbose}"[Be verbose]" --animate"[Whether to allow animation. Defaults to on]:BOOL:(on off)" --bg"[Background color of display (color name or hex).]:COLOR" {-C,--center}"[Center images. Defaults to off.]:BOOL:(on off)" --clear"[Clear screen before processing each file]" {-c,--colors}"[Set output color mode. Defaults to best guess]:MODE:(none 2 8 16/8 16 240 256 full)" --color-extractor"[Method for extracting color from an area. Average is the default]:EXTR:(average median)" --color-space"[Color space used for quantization. Defaults to rgb, which is faster but less accurate]:CS:(rgb din99d)" --dither"[Set output dither mode. No effect with 24-bit color. Defaults to none]:DITHER:(none ordered diffusion)" --dither-grain"[Set dimensions of dither grains in 1/8ths of a character cell. Defaults to 4x4]:WxH:(1 2 4 8)" --dither-intensity"[Multiplier for dither intensity. Defaults to 1.0]:NUM 0.0 - inf" {-d,--duration}"[The time to show each file]:SECONDS" --fg"[Foreground color of display (color name or hex).]:COLOR:(black blue cyan default gray green magenta orange red white)" --fg-only"[Leave the background color untouched]" --fill"[Specify character symbols to use for fill/gradients. Defaults to none]:SYMS" --font-ratio"[Target font's width/height ratio. Can be specified as a real number or a fraction. Defaults to 1/2]:W/H" {-f,--format}"[Set output format]:FORMAT:(iterm kitty sixels symbols)" --glyph-file"[Load glyph information from FILE]:FILE:_files" --invert"[Invert video. For display with bright backgrounds in color modes 2 and none. Swaps --fg and --bg]" --margin-bottom"[When terminal size is detected, reserve at least NUM rows at the bottom as a safety margin. Can be used to prevent images from scrolling out. Defaults to 1.]:NUM" --margin-right"[When terminal size is detected, reserve at least NUM columns on the right-hand side as a safety margin. Defaults to 0.]:NUM" {-O,--optimize}"[Compress the output by using control sequences. 0 disables, 9 enables every available optimization. Defaults to 5, except for when used with '-c none', where it defaults to 0]:NUM:("{0..9}")" --polite"[Polite mode. Defaults to on. Turning this off may enhance presentation and prevent interference from other programs, but risks leaving the terminal in an altered state (rude).]:BOOL:(on off)" {-p,--preprocess}"[Image preprocessing. Defaults to on with 16 colors or lower, off otherwise]:BOOL:(on off)" --scale"[Scale image, respecting terminal's maximum dimensions. 1.0 approximates original pixel dimensions. Specify max to use all available space. Defaults to 1.0 for pixel graphics and 4.0 for symbols]:NUM" {-s,--size}"[Set maximum output dimensions in columns and rows. By default this will be the size of your terminal, or 80x25 if size detection fails]:WxH" --speed"[Set the speed animations will play at. This can be either a unitless multiplier, or a real number followed by fps to apply a specific framerate.]:SPEED" --stretch"[Stretch image to fit output dimensions; ignore aspect. Implies --scale max]" --symbols"[Specify character symbols to employ in final output]:SYMS" --threads"[Maximum number of CPU threads to use. If left unspecified or negative, this will equal available CPU cores]:NUM" {-t,--threshold}"[Threshold above which full transparency will be used 0.0 - 1.0]:NUM" --watch"[Watch a single input file, redisplaying it whenever its contents change. Will run until manually interrupted or, if --duration is set, until it expires.]" {-w,--work}"[How hard to work in terms of CPU and memory. 1 is the cheapest, 9 is the most accurate. Defaults to 5]:NUM:("{1..9}")" ) _arguments $options '*:: :_files' chafa-1.14.5/tools/fontgen/000077500000000000000000000000001471154763100154555ustar00rootroot00000000000000chafa-1.14.5/tools/fontgen/Makefile.am000066400000000000000000000000671471154763100175140ustar00rootroot00000000000000EXTRA_DIST = \ README.md \ chafa8x8.py \ compare.py chafa-1.14.5/tools/fontgen/README.md000066400000000000000000000073331471154763100167420ustar00rootroot00000000000000Chafa8x8 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"`. Note, don't miss the quotes because the wildcard should be expanded by python's `glob` instead of the shell. Although the default parameters should be fine, the optional arguments are: (1) `-Mc ` specifies the number of vectors to sample from each image file. The default is `16`. Namely, in total 16 random crops (converted to vectors) are sampled from each image; (2) `-N ` specifies the vector length. The default is 64 for 8x8 blocks. After this, a fine named `chafa8x8.npz` will be created for the following clustering step. 3. Run K-Means algorithm to find cluster center vectors. `./chafa8x8.py Clustering`. This will create the optimal vectors. The optional parameter `-C ` specifies how many vectors you want (i.e., how many optimal 8x8 blocks you want). The actual usable vectors (glyphs) will be less than this number after post processing (such as deduplication). The results will be stored in `chafa8x8.raw.json` by default. Note, this step is slow. For GPU accelerated computing, see the misc notes in the next section. 4. Post-process. Generate C code, SVG images and TTF font. `./chafa8x8.py GenA`. This is actually a composed command. It executes the following four steps: (1) It invokes `./chafa8x8.py Postproc` to deduplicate the optimal glyphs. The results will be written in `chafa8x8.json`; (2) Then it invokes `./chafa8x8.py GenC` to generate C header from json as `chafa8x8.h`; (3) Next, it invokes `./chafa8x8.py GenSVG` to convert the optimal glyphs from json into SVG files under the `chafa8x8_svg` directory; (4) Lastly, it invokes `./chafa8x8.py GenFont` to create the font file `chafa8x8.ttf` from the json result. ## Misc Notes 1. Python dependencies: `pip3 install numpy scipy scikit-learn tqdm`. 2. Note, in order to generate a usable font, the `python3-fontforge` (for Debian bullseye and newer) package has to be installed as well. It will be used in the `./chafa8x8.py GenA` step. It will automatically invoke `chafa8x8.py GenFont` subcommand for creating the font. 3. Note, the clustering step is the most time-consuming procedure. If the created dataset is very large (e.g., more than 1 million vectors inside), it won't be surprising to run for several hours or even longer. Although the result can be better with a large dataset, the resulting fonts should be good to use as long as, say, the dataset size is more than 10 times larger than the number of optimal glyphs we want. 4. You may take a quick glance at the glyphs from the json files, e.g., for the finalized glyphs: `./chafa8x8.py Dump --json chafa8x8.json | less`, and for the preliminary glyphs before deduplication: `./chafa8x8.py Dump --json chafa8x8.raw.json | less`. 5. The clustering step can be accelerated using Nvidia GPU (through CUDA). You need to install faiss first: `pip3 install faiss-gpu`. To use it, just specify the backend in the command line: `./chafa8x8.py Clustering --backend faiss`. ## Todos - [ ] update `compare.py` with the latest usage. ## 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.14.5/tools/fontgen/chafa8x8.py000077500000000000000000000320061471154763100174450ustar00rootroot00000000000000#!/usr/bin/env python3 # This script is written for Chafa in order to generate custom fonts. # Copyright (C) 2018-2023 Mo Zhou # License: LGPLv3+ import os import sys import argparse import glob try: import ujson as json except ModuleNotFoundError: import json import time import random from PIL import Image from subprocess import call import numpy as np from typing import * # Preserved Unicode Plane for Private use, see wikipedia for detail. UNICODE_BASE = 0x100000 def bdump8x8(v: np.ndarray, *, clang: bool = False, padding: int = 0) -> str: ''' dump the 8x8 bitmap. v: np.ndarray -- the vector (bitmap) to dump clang: bool -- dump C code intead of plain text padding: int -- number of spaces to indent in clang mode ''' 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: List[str]): ''' 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', help='image file glob pattern to be expanded by python') 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) from tqdm import tqdm 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: List[str]): ''' 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.add_argument('-B', '--backend', type=str, default='sklearn', choices=('sklearn', 'faiss'), help='k-means backend') ag.add_argument('-I', '--iterations', type=int, default=100, help='number of iterations for K-means algorithm (faiss)') 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 ...') if ag.backend == 'sklearn': from sklearn.cluster import KMeans, MiniBatchKMeans 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')) elif ag.backend == 'faiss': import faiss # pip3 install faiss-gpu dataset = dataset.astype(np.float32) # uint8 to float32 print('dataset type', dataset.dtype, ', size', dataset.shape) clus = faiss.Clustering(dataset.shape[1], ag.C) clus.verbose = True clus.niter = ag.iterations clus.max_points_per_centroid = 10000000 res = faiss.StandardGpuResources() cfg = faiss.GpuIndexFlatConfig() cfg.useFloat16 = False index = faiss.GpuIndexFlatL2(res, dataset.shape[1], cfg) clus.train(dataset, index) centroids = faiss.vector_float_to_array(clus.centroids) centroids = centroids.reshape(ag.C, dataset.shape[1]) centroids = (centroids >= 0.5).astype(np.uint8) print('centroids', centroids.shape, centroids.dtype) # sort and save centroids = list(sorted( [list(map(int, c)) for c in centroids], key=lambda x: sum(x) )) json.dump(centroids, open(ag.save, 'w')) print(f'=> {ag.save}') def mainPostproc(argv: List[str]): ''' 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) from scipy.signal import convolve2d as conv2d # load raw vectors centers = json.load(open(ag.json, 'r')) print(' -> (before) number of centers:', len(centers)) # [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(' -> (after) 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: List[str]): ''' 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: List[str]): ''' 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: List[str]): ''' 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 _w = (50 + 500.0 + 50) / 8.0 _h = (200 + 1000.0 + 75) / 8.0 f.write(template.format(w=_w, h=_h, x=-50+c*_w, y=-200+r*_h)) f.write(''' \n''') f.close() def mainGenBDF(argv: List[str]): ''' Generate BDF font (it is a plain text format!) https://en.wikipedia.org/wiki/Glyph_Bitmap_Distribution_Format ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, default='chafa8x8.json') ag.add_argument('--bdf', type=str, default='chafa8x8.bdf') ag = ag.parse_args(argv) # open file with open(ag.json, 'rt') as f: j = json.load(f) # List[List[int]] bdf = open(ag.bdf, 'wt') # write bdf header _timestamp_ = time.ctime().replace(' ', '-').replace(':', '-') bdf.writelines([ 'STARTFONT 2.1\n', f'FONT chafa8x8-{len(j)}-{_timestamp_}\n', 'SIZE 16 75 75\n', 'FONTBOUNDINGBOX 16 16 0 -2\n', '\n', 'STARTPROPERTIES 2\n', 'FONT_ASCENT 14\n', 'FONT_DESCENT 2\n', 'ENDPROPERTIES\n', '\n', f'CHARS {len(j)}\n', '\n', ]) # write characters _base_ = 0x100000 for (i, vector) in enumerate(j): # start char bdf.writelines([ f'COMMENT Chafa8x8-glyph-id-{i}\n', f'STARTCHAR U+{hex(_base_ + i)}\n', f'ENCODING {int(_base_ + i)}\n', f'SWIDTH 500 0\n', f'DWIDTH 8 0\n', 'BBX 8 16 0 -2\n', 'BITMAP\n', ]) # glyph representation for i in range(0, 64, 8): line = 0b0 for j in range(8): line = line + (0b1 << j) * vector[i + j] line = '{:02x}'.format(line).upper() bdf.writelines([line + '\n'] * 2) # end char bdf.writelines(['ENDCHAR\n', '\n']) # end font bdf.write('ENDFONT\n') # close file bdf.close() def mainGenFont(argv: List[str]): ''' Generate Font file (ttf) using fontforge ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, default='chafa8x8.json') ag.add_argument('--ttf', type=str, default='chafa8x8.ttf') ag = ag.parse_args(argv) try: import fontforge as ff except ModuleNotFoundError: print('You need to install the python3-fontforge package to generate the font') exit(1) import json N = len(json.load(open('chafa8x8.json', 'r'))) try: font = ff.font() font.clear() font.copyright = "(C) 2018 Mo Zhou , LGPLv3+" 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 = 500 glyph.vwidth = 1000 # font.save('chafa8x8.sfd') font.generate(ag.ttf) font.close() except Exception as e: print(e) print(f'done. the font has been written as {ag.ttf}') def mainGenA(argv: List[str]): print('calling ./chafa8x8.py Postproc') call(['./chafa8x8.py', 'Postproc']) print('calling ./chafa8x8.py GenC') call(['./chafa8x8.py', 'GenC']) print('calling ./chafa8x8.py GenSVG') call(['./chafa8x8.py', 'GenSVG']) print('calling ./chafa8x8.py GenFont') call(['./chafa8x8.py', 'GenFont']) # experimental #print('calling ./chafa8x8.py GenBDF') #call(['./chafa8x8.py', 'GenBDF']) #call('bdftopcf -o chafa8x8.pcf chafa8x8.bdf'.split()) if __name__ == '__main__': eval(f'main{sys.argv[1]}')(sys.argv[2:]) chafa-1.14.5/tools/fontgen/compare.py000077500000000000000000000014621471154763100174630ustar00rootroot00000000000000#!/usr/bin/env python3 # Just a simple tool to compare the results using different symbol sets # (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') # FIXME: the command is oudated call(['chafa', '-s', '80x24', '--symbols', 'block', im]) print('------') call(['chafa', '-s', '80x24', '--symbols', 'custom', im]) print('>>>>>> chafa: symbols=custom')