kew/000077500000000000000000000000001512074754200116535ustar00rootroot00000000000000kew/.clang-format000066400000000000000000000002621512074754200142260ustar00rootroot00000000000000BasedOnStyle: LLVM IndentWidth: 8 TabWidth: 8 UseTab: Never BreakBeforeBraces: Linux ColumnLimit: 0 PointerAlignment: Right ReflowComments: false SpacesBeforeTrailingComments: 1 kew/.clangd000066400000000000000000000001111512074754200130750ustar00rootroot00000000000000Diagnostics: ClangTidy: Remove: bugprone-unchecked-optional-access kew/.editorconfig000066400000000000000000000003441512074754200143310ustar00rootroot00000000000000# EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true [*] indent_style = space indent_size = 8 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = truekew/.github/000077500000000000000000000000001512074754200132135ustar00rootroot00000000000000kew/.github/FUNDING.yml000066400000000000000000000000411512074754200150230ustar00rootroot00000000000000ko_fi: ravachol github: ravachol kew/.github/ISSUE_TEMPLATE/000077500000000000000000000000001512074754200153765ustar00rootroot00000000000000kew/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000010141512074754200173620ustar00rootroot00000000000000blank_issues_enabled: true contact_links: - name: 💬 Support or Questions url: https://chatgpt.com/ about: | Please check if this issue has already been posted here: https://codeberg.org/ravachol/kew/issues Also, report it there if you can. For support or general questions (like installation help, etc.), please consider reading the readme, or asking ChatGPT or Mistral.ai **before** opening an issue here. This issue tracker is for **bug reports and feature requests primarily**. kew/.github/workflows/000077500000000000000000000000001512074754200152505ustar00rootroot00000000000000kew/.github/workflows/arm_macos.yml000066400000000000000000000014671512074754200177440ustar00rootroot00000000000000name: macOS Latest Build Check on: pull_request: push: paths: - 'Makefile' workflow_dispatch: jobs: macos-build-check: name: macOS Build Check if: github.server_url == 'https://github.com' runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Check system architecture run: uname -m - name: Install Homebrew run: | if ! command -v brew &> /dev/null; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi - name: Update Homebrew and install dependencies run: | brew update brew install faad2 taglib chafa fftw opus opusfile libogg libvorbis make curl - name: Build code run: make kew/.github/workflows/claude_security_review.yml000066400000000000000000000173451512074754200225520ustar00rootroot00000000000000name: Full Codebase Security Scan on: workflow_dispatch: inputs: max_files: description: 'Maximum number of files to scan (cost control)' required: false default: '50' extensions: description: 'File extensions (comma-separated)' required: false default: 'c,cpp,cc,cxx,h,hpp' jobs: security-scan: runs-on: ubuntu-latest if: github.server_url == 'https://github.com' steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install Claude SDK run: pip install anthropic - name: Run Security Analysis env: ANTHROPIC_API_KEY: ${{ secrets.CLAUDE_API_KEY }} MAX_FILES: ${{ github.event.inputs.max_files || '50' }} EXTENSIONS: ${{ github.event.inputs.extensions || 'c,cpp,cc,cxx,h,hpp' }} run: | python3 << 'EOF' import anthropic import os import glob import json from datetime import datetime def get_code_files(extensions, max_files): """Find code files to analyze""" code_files = [] exclude_dirs = {'.git', 'node_modules', '__pycache__', 'venv', 'build', 'dist', 'target'} for ext in extensions.split(','): ext = ext.strip() files = glob.glob(f"**/*.{ext}", recursive=True) # Filter out excluded directories for file in files: if not any(excluded in file for excluded in exclude_dirs): code_files.append(file) # Remove duplicates, sort, and limit code_files = sorted(list(set(code_files)))[:int(max_files)] return code_files def analyze_file(client, file_path): """Analyze a single file for security issues""" try: # Read file with size limit for cost control with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read()[:6000] # Limit to ~6k chars per file prompt = f"""Analyze this {file_path} file for security vulnerabilities in a C/C++ Linux terminal music player application. CONTEXT: This is a terminal music player with NO database, web access, or network functionality. Focus on vulnerabilities specific to: CRITICAL AUDIO FILE PARSING VULNERABILITIES: 1. Buffer overflows when parsing music file headers (MP3, FLAC, OGG, WAV, etc.) 2. Integer overflows in size calculations for audio data 3. Heap corruption from malformed audio metadata 4. Stack buffer overflows in filename/path handling 5. Format string vulnerabilities in audio tag parsing 6. Unsafe memory operations when reading variable-length audio data GENERAL C/C++ SECURITY ISSUES: 7. Buffer overflows (strcpy, strcat, gets, scanf, etc.) 8. Use-after-free and double-free vulnerabilities 9. Null pointer dereferences 10. Integer overflows in memory allocations 11. Unsafe pointer arithmetic 12. Missing bounds checking on arrays 13. Race conditions in file I/O 14. Path traversal in file operations 15. Memory leaks that could lead to DoS AUDIO-SPECIFIC ATTACK VECTORS: 16. Malicious embedded album art (large images causing memory exhaustion) 17. Crafted playlists with overly long file paths 18. Audio files with corrupted or oversized metadata tags 19. Files with misleading file extensions vs actual format 20. Symbolic link attacks in music directory traversal Rate severity as: CRITICAL, HIGH, MEDIUM, LOW Pay special attention to any code that processes untrusted audio file data. Code: ``` {content} ``` Respond with findings in this format: SEVERITY: Description (function name) - AUDIO_PARSING/BUFFER/MEMORY/etc """ response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1500, messages=[{"role": "user", "content": prompt}] ) return response.content[0].text except Exception as e: return f"Error analyzing {file_path}: {str(e)}" def main(): # Setup api_key = os.environ.get('ANTHROPIC_API_KEY') if not api_key: print("❌ ERROR: ANTHROPIC_API_KEY not found") return max_files = os.environ.get('MAX_FILES', '50') extensions = os.environ.get('EXTENSIONS', 'c,cpp,h') print(f"🔍 Security Scan Starting") print(f"📁 Max files: {max_files}") print(f"📄 Extensions: {extensions}") print("=" * 60) client = anthropic.Anthropic(api_key=api_key) # Find files code_files = get_code_files(extensions, max_files) print(f"Found {len(code_files)} files to analyze\n") # Analyze files total_issues = 0 critical_issues = 0 for i, file_path in enumerate(code_files, 1): print(f"[{i:2d}/{len(code_files)}] 🔍 {file_path}") analysis = analyze_file(client, file_path) # Count issues if "CRITICAL:" in analysis: critical_issues += analysis.count("CRITICAL:") total_issues += analysis.count("CRITICAL:") if "HIGH:" in analysis: total_issues += analysis.count("HIGH:") if "MEDIUM:" in analysis: total_issues += analysis.count("MEDIUM:") if "LOW:" in analysis: total_issues += analysis.count("LOW:") # Print results if any(severity in analysis for severity in ["CRITICAL:", "HIGH:", "MEDIUM:", "LOW:"]): print(f" ⚠️ Issues found:") for line in analysis.split('\n'): if any(severity in line for severity in ["CRITICAL:", "HIGH:", "MEDIUM:", "LOW:"]): print(f" {line}") print() else: print(" ✅ No issues found") print("-" * 50) # Summary print(f"\n🎯 SECURITY SCAN COMPLETE") print(f"📊 Files analyzed: {len(code_files)}") print(f"⚠️ Total issues: {total_issues}") print(f"🚨 Critical issues: {critical_issues}") print(f"💰 Estimated cost: ~${len(code_files) * 0.15:.2f}") if critical_issues > 0: print("\n🚨 CRITICAL ISSUES FOUND - Review immediately!") elif total_issues > 0: print(f"\n⚠️ {total_issues} security issues found - Review recommended") else: print("\n✅ No security issues detected") if __name__ == "__main__": main() EOF - name: Upload Results Summary if: always() run: | echo "Security scan completed at $(date)" > scan-summary.txt echo "Check the logs above for detailed findings" >> scan-summary.txt - name: Upload Artifact uses: actions/upload-artifact@v4 if: always() with: name: security-scan-summary path: scan-summary.txt kew/.github/workflows/ubuntu.yml000066400000000000000000000033461512074754200173230ustar00rootroot00000000000000name: Ubuntu Build Check on: pull_request: push: branches: - main jobs: ubuntu-build-check: name: Ubuntu Build Check if: github.server_url == 'https://github.com' runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Install build essentials run: sudo apt-get update && sudo apt-get install -y build-essential - name: Install dependencies run: sudo apt install -y libcurl4-openssl-dev libtag1-dev libfaad-dev libogg-dev libfftw3-dev libopus-dev libopusfile-dev libvorbis-dev libchafa-dev libavformat-dev libstb-dev libglib2.0-dev libgdk-pixbuf-2.0-dev - name: Build code run: make - name: Clean build artifacts run: make clean - name: Build code without FAAD run: make USE_FAAD=0 - name: Run simple tests run: | set -e # exit on first failure # Set a clean config export XDG_CONFIG_HOME=$PWD/.config mkdir -p "$XDG_CONFIG_HOME/kew" touch "$XDG_CONFIG_HOME/kew/kewrc" # Create an empty library mkdir -p tests/empty ./kew path "$PWD/tests/empty" # Test running a song with empty library output=$(./kew song 2>&1) echo "$output" if [[ ! "$output" =~ "No Music found" ]]; then echo "❌ 'No Music found' test failed" exit 1 fi echo "✅ Empty library test passed" # Test --version version_output=$(./kew --version) echo "$version_output" if [[ -z "$version_output" ]]; then echo "❌ Version test failed" exit 1 fi echo "✅ Version test passed" kew/.github/workflows/x86_macos.yml000066400000000000000000000017301512074754200176030ustar00rootroot00000000000000name: macOS x86_64 Build Check on: pull_request: push: paths: - 'Makefile' workflow_dispatch: jobs: macos-intel-build: runs-on: macos-latest if: github.server_url == 'https://github.com' name: Build on Intel macOS steps: - name: Checkout code uses: actions/checkout@v2 - name: Ensure x86_64 run: | arch # Force x86_64 using Rosetta if needed if [ "$(arch)" != "x86_64" ]; then echo "Switching to Rosetta (x86_64)" softwareupdate --install-rosetta --agree-to-license fi - name: Install Homebrew dependencies run: | if ! command -v brew &> /dev/null; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi brew update brew install faad2 taglib chafa fftw opus opusfile libogg libvorbis make curl - name: Build code run: make kew/.gitignore000066400000000000000000000005411512074754200136430ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app kew .vscode/ error.log valgrind-out.txt tags *.idx compile_commands.json debug.log kew/LICENSE000066400000000000000000000432541512074754200126700ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. kew/Makefile000066400000000000000000000203031512074754200133110ustar00rootroot00000000000000CC ?= gcc CXX ?= g++ PKG_CONFIG ?= pkg-config # To enable debugging, run: # make DEBUG=1 # To disable DBUS notifications, run: # make USE_DBUS=0 # To disable faad2, run: # make USE_FAAD=0 # Detect system and architecture UNAME_S := $(shell uname -s) ARCH := $(shell uname -m) # Default USE_DBUS to auto-detect if not set by user ifeq ($(origin USE_DBUS), undefined) ifeq ($(UNAME_S), Darwin) USE_DBUS = 0 else USE_DBUS = 1 endif endif PREFIX ?= /usr/local ifeq ($(UNAME_S),Darwin) ifeq ($(ARCH),arm64) PKG_CONFIG_PATH := /opt/homebrew/lib/pkgconfig:/opt/homebrew/share/pkgconfig:$(PKG_CONFIG_PATH) else PKG_CONFIG_PATH := /usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:$(PKG_CONFIG_PATH) endif export PKG_CONFIG_PATH endif # Default USE_FAAD to auto-detect if not set by user ifeq ($(origin USE_FAAD), undefined) # Check if we're in Termux environment ifneq ($(wildcard /data/data/com.termux/files/usr),) # Termux environment - check common installation paths USE_FAAD = $(shell [ -f "$(PREFIX)/lib/libfaad.so" ] || \ [ -f "$(PREFIX)/lib/libfaad2.so" ] || \ [ -f "$(PREFIX)/local/lib/libfaad.so" ] || \ [ -f "$(PREFIX)/local/lib/libfaad2.so" ] || \ [ -f "/data/data/com.termux/files/usr/lib/libfaad.so" ] || \ [ -f "/data/data/com.termux/files/usr/bin/faad" ] || \ [ -f "/data/data/com.termux/files/usr/lib/libfaad2.so" ] || \ [ -f "/data/data/com.termux/files/usr/local/lib/libfaad.so" ] || \ [ -f "/data/data/com.termux/files/usr/local/lib/libfaad2.so" ] && echo 1 || echo 0) else # Non-Android build - try pkg-config first USE_FAAD = $(shell $(PKG_CONFIG) --exists faad && echo 1 || echo 0) ifeq ($(USE_FAAD), 0) # If pkg-config fails, try to find libfaad dynamically in common paths USE_FAAD = $(shell [ -f /usr/lib/libfaad.so ] || [ -f /usr/lib64/libfaad.so ] || [ -f /usr/lib64/libfaad2.so ] || \ [ -f /usr/bin/faad ] || [ -f /usr/local/lib/libfaad.so ] || \ [ -f /opt/local/lib/libfaad.so ] || [ -f /opt/homebrew/lib/libfaad.dylib ] || \ [ -f /opt/homebrew/opt/faad2/lib/libfaad.dylib ] || \ [ -f /usr/local/lib/libfaad.dylib ] || [ -f /lib/x86_64-linux-gnu/libfaad.so.2 ] && echo 1 || echo 0) endif endif endif LOCAL_INC = \ -Isrc \ -Iinclude \ -Iinclude/minimp4 \ -Iinclude/stb_image \ -Iinclude/miniaudio \ -Iinclude/nestegg ifeq ($(UNAME_S),Darwin) PKG_LIBS = gio-2.0 chafa fftw3f opus opusfile vorbis vorbisfile ogg glib-2.0 taglib gdk-pixbuf-2.0 else PKG_LIBS = gio-2.0 chafa fftw3f opus opusfile vorbis vorbisfile ogg glib-2.0 taglib endif PKG_CFLAGS = $(shell $(PKG_CONFIG) --cflags $(PKG_LIBS)) PKG_LDFLAGS = $(shell $(PKG_CONFIG) --libs $(PKG_LIBS)) COMMONFLAGS = $(LOCAL_INC) $(PKG_CFLAGS) ifeq ($(DEBUG), 1) COMMONFLAGS += -g -DDEBUG else COMMONFLAGS += -O2 endif COMMONFLAGS += -DMA_NO_AAUDIO COMMONFLAGS += -fstack-protector-strong -Wformat -Wno-format-security -fPIE -D_FORTIFY_SOURCE=2 COMMONFLAGS += -Wall -Wextra -Wpointer-arith # Check if we're in Termux environment ifneq ($(wildcard /data/data/com.termux/files/usr),) # Termux environment COMMONFLAGS += -D__ANDROID__ endif CFLAGS = $(COMMONFLAGS) # Compiler flags for C++ code CXXFLAGS = $(COMMONFLAGS) -std=c++11 # Libraries LIBS = -lm -lopusfile -lglib-2.0 -lpthread $(PKG_LDFLAGS) LIBS += -lstdc++ LDFLAGS = -logg -lz ifeq ($(UNAME_S), Linux) CFLAGS += -fPIE -fstack-clash-protection CXXFLAGS += -fPIE -fstack-clash-protection LDFLAGS += -pie -Wl,-z,relro ifneq (,$(filter $(ARCH), x86_64 i386)) CFLAGS += -fcf-protection CXXFLAGS += -fcf-protection endif ifneq ($(DEBUG), 1) LDFLAGS += -s endif else ifeq ($(UNAME_S), Darwin) LIBS += -framework CoreAudio -framework CoreFoundation endif # Conditionally add USE_DBUS is enabled ifeq ($(USE_DBUS), 1) DEFINES += -DUSE_DBUS endif DEFINES += -DPREFIX=\"$(PREFIX)\" # Conditionally add faad2 support if USE_FAAD is enabled ifeq ($(USE_FAAD), 1) ifeq ($(ARCH), arm64) CFLAGS += -I/opt/homebrew/opt/faad2/include LIBS += -L/opt/homebrew/opt/faad2/lib -lfaad else ifeq ($(UNAME_O),Android) CFLAGS += -I$(PREFIX)/include LIBS += -L$(PREFIX)/lib -lfaad else CFLAGS += -I/usr/local/include LIBS += -L/usr/local/lib -lfaad endif DEFINES += -DUSE_FAAD endif ifeq ($(origin CC),default) CC := gcc endif ifneq ($(findstring gcc,$(CC)),) ifeq ($(UNAME_S), Linux) LIBS += -latomic endif endif OBJDIR = src/obj SRCS = src/common/appstate.c src/ui/common_ui.c src/common/common.c \ src/utils/utils.c src/utils/file.c src/utils/cache.c src/utils/term.c \ src/sound/sound.c src/sound/m4a.c src/sound/sound_builtin.c src/sound/audiobuffer.c \ src/sound/decoders.c src/sound/audio_file_info.c src/sound/playback.c src/sound/volume.c \ src/sys/sys_integration.c src/sys/notifications.c src/sys/mpris.c \ src/ops/playback_ops.c src/ops/playback_clock.c src/ops/playback_system.c \ src/ops/playlist_ops.c src/ops/library_ops.c src/ops/track_manager.c src/ops/playback_state.c \ src/ui/control_ui.c src/ui/input.c src/ui/playlist_ui.c src/ui/search_ui.c src/ui/player_ui.c \ src/ui/visuals.c src/ui/chroma.c src/ui/queue_ui.c src/ui/settings.c src/ui/cli.c \ src/data/theme.c src/data/directorytree.c src/data/lyrics.c src/data/img_func.c src/data/song_loader.c \ src/data/playlist.c src/kew.c # TagLib wrapper WRAPPER_SRC = src/data/tagLibWrapper.cpp WRAPPER_OBJ = $(OBJDIR)/tagLibWrapper.o MAN_PAGE = kew.1 MAN_DIR ?= $(PREFIX)/share/man DATADIR ?= $(PREFIX)/share LOCALEDIR ?= $(DATADIR)/locale THEMEDIR = $(DATADIR)/kew/themes THEMESRCDIR := $(CURDIR)/themes DEFINES += -DLOCALEDIR=\"$(LOCALEDIR)\" all: kew # Generate object lists OBJS_C = $(SRCS:src/%.c=$(OBJDIR)/%.o) NESTEGG_SRCS = include/nestegg/nestegg.c NESTEGG_OBJS = $(NESTEGG_SRCS:include/nestegg/%.c=$(OBJDIR)/nestegg/%.o) # All objects together OBJS = $(OBJS_C) $(NESTEGG_OBJS) # Create object directories $(OBJDIR): mkdir -p $(OBJDIR) ## Compile C sources $(OBJDIR)/%.o: src/%.c Makefile | $(OBJDIR) @mkdir -p $(dir $@) $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< # Compile explicit C++ sources in src/ $(OBJDIR)/%.o: src/%.cpp Makefile | $(OBJDIR) @mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $< # Compile TagLib wrapper C++ source $(WRAPPER_OBJ): $(WRAPPER_SRC) Makefile | $(OBJDIR) @mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) $(DEFINES) -c $< -o $@ # Compile C files in include/nestegg $(OBJDIR)/nestegg/%.o: include/nestegg/%.c Makefile | $(OBJDIR) @mkdir -p $(dir $@) $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< # Link all objects safely together using C++ linker kew: $(OBJS) $(WRAPPER_OBJ) Makefile $(CXX) -o kew $(OBJS) $(WRAPPER_OBJ) $(LIBS) $(LDFLAGS) .PHONY: install install: all # Create directories mkdir -p $(DESTDIR)$(MAN_DIR)/man1 mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(THEMEDIR) mkdir -p $(DESTDIR)$(LOCALEDIR)/ja/LC_MESSAGES mkdir -p $(DESTDIR)$(LOCALEDIR)/zh_CN/LC_MESSAGES # Install binary and man page install -m 0755 kew $(DESTDIR)$(PREFIX)/bin/kew install -m 0644 docs/kew.1 $(DESTDIR)$(MAN_DIR)/man1/kew.1 # Install Chinese translation install -m 0644 locale/zh_CN/LC_MESSAGES/kew.mo \ $(DESTDIR)$(LOCALEDIR)/zh_CN/LC_MESSAGES/kew.mo # Install Japanese translation install -m 0644 locale/ja/LC_MESSAGES/kew.mo \ $(DESTDIR)$(LOCALEDIR)/ja/LC_MESSAGES/kew.mo @if [ -d $(THEMESRCDIR) ]; then \ for theme in $(THEMESRCDIR)/*.theme; do \ if [ -f "$$theme" ]; then \ install -m 0644 "$$theme" $(DESTDIR)$(THEMEDIR)/; \ fi; \ done; \ for theme in $(THEMESRCDIR)/*.txt; do \ if [ -f "$$theme" ]; then \ install -m 0644 "$$theme" $(DESTDIR)$(THEMEDIR)/; \ fi; \ done; \ fi .PHONY: uninstall uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/kew rm -f $(DESTDIR)$(MAN_DIR)/man1/kew.1 rm -rf $(DESTDIR)$(THEMEDIR) rm -f $(DESTDIR)$(LOCALEDIR)/ja/LC_MESSAGES/kew.mo rm -f $(DESTDIR)$(LOCALEDIR)/zh_CN/LC_MESSAGES/kew.mo .PHONY: clean clean: rm -rf $(OBJDIR) kew i18n: $(MAKE) -f Makefile.i18n i18n kew/Makefile.i18n000066400000000000000000000020731512074754200140730ustar00rootroot00000000000000# Paths SRC := $(shell find src -type f \( -name "*.c" -o -name "*.h" \)) POT := kew.pot PO_DIR := po LOCALE_DIR := locale LANGS := zh_CN ja # add other languages as needed # Default target .PHONY: all all: i18n @echo "i18n ready." # Generate POT template from source .PHONY: pot pot: @echo "Generating POT template..." xgettext -k_ --from-code=UTF-8 -o $(POT) $(SRC) # Merge updated POT into PO files .PHONY: merge merge: pot @echo "Merging POT into PO files..." @for lang in $(LANGS); do \ msgmerge --update --backup=none $(PO_DIR)/$$lang.po $(POT); \ done # Compile PO files to MO .PHONY: mo mo: @echo "Compiling MO files..." @for lang in $(LANGS); do \ mkdir -p $(LOCALE_DIR)/$$lang/LC_MESSAGES; \ msgfmt $(PO_DIR)/$$lang.po -o $(LOCALE_DIR)/$$lang/LC_MESSAGES/kew.mo; \ done # Full i18n update: regenerate POT, merge PO, compile MO .PHONY: i18n i18n: pot merge mo @echo "i18n updated successfully." # Clean generated files .PHONY: clean-i18n clean-i18n: @echo "Cleaning MO files..." @rm -rf $(LOCALE_DIR) @echo "Cleaning POT template..." @rm -f $(POT) kew/README.md000066400000000000000000000205721512074754200131400ustar00rootroot00000000000000
kew Logo

Screenshot
Screenshot showing Jenova 7: Lost Sci-Fi Movie Themes .


[![License](https://img.shields.io/github/license/ravachol/kew?color=333333&style=for-the-badge)](./LICENSE)
**English** | [简体中文](README_zh_CN.md)

kew (/kjuː/) is a terminal music player. ## Features * Search a music library with partial titles from the command-line. * Creates a playlist automatically based on matched song, album or artist. * Private, no data is collected by kew. * Music without distractions or algorithmic manipulation. * Full color covers in sixel-capable terminals. * Visualizer with various settings. * Edit the playlist by adding, removing and reordering songs. * Gapless playback. * Explore the library and enqueue files or folders. * Search your music library and add to the queue. * Supports MP3, FLAC, MPEG-4/M4A (AAC), OPUS, OGG, Webm and WAV audio. * Supports desktop events through MPRIS. * Supports lyrics through .lrc files, embedded SYLT (Mp3) or Vorbis comments (Flac,Ogg,Opus). * Use themes or colors derived from covers. ## Installing Packaging status Install through your package manager or homebrew (macOS). If you can't find it on your distro, or you want the bleeding edge, follow the [Manual Installation Instructions](docs/MANUAL-INSTALL-INSTRUCTIONS.md). ## Usage kew creates a playlist with the contents of the first directory or file whose name matches the arguments you provide in the command-line. ```bash kew cure great ``` This creates and starts playing a playlist with 'The cure greatest hits' if it's in your music library. It works best when your music library is organized this way: artist folder->album folder(s)->track(s). ### Example commands ``` kew (starting kew with no arguments opens the library view where you can choose what to play) kew all (plays all songs, up to 20 000, in your library, shuffled) kew albums (plays all albums, up to 2000, randomly one after the other) kew moonlight son (finds and plays moonlight sonata) kew moon (finds and plays moonlight sonata) kew beet (finds and plays all music files under "beethoven" directory) kew dir (sometimes, if names collide, it's necessary to specify it's a directory you want) kew song (or a song) kew play "/home/joe/Musik/Fridge - (2007) The Sun/" (Plays this album, location can be anywhere) kew play "/home/joe/Musik/moonlight sonata.flac" (Plays moonlight sonata, location can be anywhere) kew list (or a playlist) kew theme midnight (sets the 'midnight.theme' theme). kew shuffle (shuffles the playlist. shuffle needs to come first.) kew artistA:artistB:artistC (plays all three artists, shuffled) kew --help, -? or -h kew --version or -v kew --nocover kew --noui (completely hides the UI) kew -q , --quitonstop (exits after finishing playing the playlist) kew -e , --exact (specifies you want an exact (but not case sensitive) match, of for instance an album) kew . loads kew favorites.m3u kew path "/home/joe/Musik/" (changes the path) ``` ### Key Bindings #### Basic * Enter to play or enqueue/dequeue. * Space, p or right mouse to play or pause. * Use + (or =), - keys to adjust the volume. * Use , or h, l keys to switch tracks. * Alt+s to stop. * F2 or Shift+z (macOS/Android) to show/hide playlist view. * F3 or Shift+x (macOS/Android) to show/hide library view. * F4 or Shift+c (macOS/Android) to show/hide track view. * F5 or Shift+v (macOS/Android) to show/hide search view. * F6 or Shift+b (macOS/Android) to show/hide key bindings view. * i to cycle colors derived from kewrc, theme or track cover. * t to cycle themes. * Backspace to clear the playlist. * Delete to remove a single playlist entry. * r to cycle repeat settings (repeat, repeat list, off). * s to shuffle the playlist. #### Advanced * u to update the library. * m show full page lyrics in track view. See [Lyrics](#lyrics) * v to toggle the visualizer. * b to toggle album covers drawn in ascii or as a normal image. * n to toggle notifications. * a to seek back. * d to seek forward. * x to save the currently loaded playlist to a m3u file in your music folder. * Tab to switch to next view. * Shift+Tab to switch to previous view. * f, g to move songs up or down the playlist. * number + G or Enter to go to specific song number in the playlist. * . to add currently playing song to kew favorites.m3u (run with "kew ."). * Esc to quit. ## Configuration Linux: ~/.config/kew/ macOS: ~/Library/Preferences/kew/ Key bindings can be added like this: bind = +, volUp, +5% If you have an old install of kew, delete the kewrc file to make this style of bindings appear. kew state is kept in ~/.local/state/kewstaterc. ## Themes Press t to cycle available themes. To set a theme from the command-line, run: ```bash kew theme (ie 'kew theme midnight') ``` Put themes in \~/.config/kew/themes (\~/Library/Preferences/kew/themes on macOS). Do not edit the included themes as they are managed by kew. Instead make a copy with a different name and edit that. Try the theme editor (by @bholroyd): [https://bholroyd.github.io/Kew-tip/](https://bholroyd.github.io/Kew-tip/). ## If Colors or Graphics Look Wrong Cycle i until they look right. Press v to turn off visualizer. Press b for ASCII covers. A terminal emulator that can handle TrueColor and sixels is recommended. See [Sixels in Terminal](https://www.arewesixelyet.com/). ## Lyrics Lyrics can be read from a provided .lrc file that matches the audio file in name and content, from Vorbis comment metadata or from SYLT embedded tags on mp3 files. Timestamped lyrics will be shown automatically in track view. Press m show full page lyrics. ## Playlists To load a playlist: type kew list To export a playlist, press x. This will save a file in your music path with the name of the first song in the queue. There is also a favorites playlist function: Add current song: press . To load 'kew list fav': ```bash kew . ``` ## License Licensed under GPL. [See LICENSE for more information](./LICENSE). ## Attributions
Attributions kew makes use of the following great open source projects: Chafa by Hans Petter Jansson - https://hpjansson.org/chafa/ Chroma by yuri-xyz - https://github.com/yuri-xyz/chroma TagLib by TagLib Team - https://taglib.org/ Faad2 by fabian_deb, knik, menno - https://sourceforge.net/projects/faac/ FFTW by Matteo Frigo and Steven G. Johnson - https://www.fftw.org/ Libopus by Opus - https://opus-codec.org/ Libvorbis by Xiph.org - https://xiph.org/ Miniaudio by David Reid - https://github.com/mackron/miniaudio Minimp4 by Lieff - https://github.com/lieff/minimp4 Nestegg by Mozilla - https://github.com/mozilla/nestegg Img_To_Txt by Danny Burrows - https://github.com/danny-burrows/img_to_txt TermBox2 (adapted for input handling only) - By nsf and Adam Saponara https://github.com/termbox/termbox2
## Authors See [AUTHORS](./docs/AUTHORS.md). ## Contact Comments? Suggestions? Send mail to kew-player@proton.me. kew/README_zh_CN.md000066400000000000000000000160531512074754200142200ustar00rootroot00000000000000
kew Logo



[![License](https://img.shields.io/github/license/ravachol/kew?color=333333&style=for-the-badge)](./LICENSE)
[English](README.md) | **简体中文** kew (/kjuː/) 是一个终端音乐播放器。 ## 功能 * 从命令行通过部分标题搜索音乐库。 * 根据匹配的歌曲、专辑或艺术家自动创建播放列表。 * 私密,无数据收集。 * 无算法推荐或干扰,纯粹的音乐体验。 * 在支持 sixel 的终端中显示全彩专辑封面。 * 可调节的可视化效果(Visualizer)。 * 编辑播放列表:添加、删除或重新排序歌曲。 * 无缝播放(Gapless Playback)。 * 浏览音乐库并将文件或文件夹加入队列。 * 支持从音乐库中搜索并添加到播放队列。 * 支持 MP3、FLAC、MPEG-4/M4A (AAC)、OPUS、OGG、Webm 和 WAV 格式。 * 通过 MPRIS 支持桌面事件。 * 通过 .lrc 文件、内嵌 SYLT(Mp3)或 Vorbis 注释(Flac, Ogg, Opus)支持歌词。 * 支持主题和从专辑封面提取颜色的配色方案。 ## 安装 Packaging status 可以通过包管理器或 Homebrew(macOS)安装。 如果你的发行版中还没有,或想获取最新版本,请参阅 [手动安装说明](docs/MANUAL-INSTALL-INSTRUCTIONS.md). ## 使用方法 `kew` 会根据命令行参数中提供的第一个匹配的目录或文件名创建播放列表并开始播放。 ```bash kew cure great ``` 例如:如果你的音乐库中包含 “The cure greatest hits”,此命令会自动找到并播放该专辑。 建议将音乐库组织为以下结构: 艺术家文件夹 -> 专辑文件夹 -> 音轨文件 ### 示例命令 ``` kew 无参数启动 kew,会打开音乐库视图以选择播放内容 kew all 随机播放音乐库中所有歌曲,最多 20000 首 kew albums 随机播放所有专辑,最多 2000 张 kew moonlight son 查找并播放《moonlight sonata》 kew beet 查找并播放 “beethoven” 目录下的所有音乐 kew dir <专辑名> 当名称冲突时,可指定为目录 kew song <歌曲名> 或是歌曲 kew list <播放列表名> 或播放列表 kew theme midnight 设置 “midnight.theme” 主题 kew shuffle <专辑名> 随机播放,shuffle 参数需放在最前 kew 艺术家A:艺术家B:艺术家C 随机播放这三位艺术家的歌曲 kew --help, -? 或 -h 显示帮助 kew --version 或 -v 显示版本号 kew --nocover 隐藏专辑封面 kew --noui 完全隐藏界面 kew -q <歌曲>, --quitonstop 播放完毕后自动退出 kew -e <歌曲>, --exact 精确匹配专辑或歌曲名,不区分大小写 kew . 加载 kew favorites.m3u kew path "/home/joe/Musik/" 更改音乐库路径 ``` ### 按键绑定 #### 基本操作 * Enter 播放或加入/移出队列 * Spacep 或鼠标右键 播放/暂停 * +(或 =)、- 调节音量 * hl 切换曲目 * Alt+s 停止播放 * F2Shift+z(macOS/Android) 显示/隐藏播放列表视图 * F3Shift+x 显示/隐藏音乐库视图 * F4Shift+c 显示/隐藏曲目视图 * F5Shift+v 显示/隐藏搜索视图 * F6Shift+b 显示/隐藏快捷键视图 * i 在 kewrc、主题或封面之间切换颜色 * t 切换主题 * Backspace 清空播放列表 * Delete 删除单个播放列表条目 * r 切换循环模式(单曲、列表、关闭) * s 随机播放 #### 高级操作 * u 更新音乐库 * m 在曲目视图中显示整页歌词 参见[歌词](#歌词) * v 开关可视化效果 * b 在 ASCII 与正常封面之间切换 * n 开关通知 * a 快退 * d 快进 * x 将当前播放列表保存为 .m3u 文件 * Tab 切换至下一个视图 * Shift+Tab 切换至上一个视图 * fg 移动歌曲在播放列表中的位置 * 数字 + GEnter 跳转至指定歌曲编号 * . 将当前播放歌曲添加至 kew favorites.m3u(运行 `kew .`) * Esc 退出程序 ## 配置文件位置 Linux: ~/.config/kew/ macOS: ~/Library/Preferences/kew/ ## 主题 按 t 切换可用主题。 也可通过命令行设置主题: ```bash kew theme <主题名> (例如:kew theme midnight) ``` 将主题文件放置在目录 \~/.config/kew/themes (macOS 为 \~/Library/Preferences/kew/themes). ## 如果颜色或图形显示异常 按 i 切换颜色模式,直到显示正常。 按 v 关闭可视化效果。 按 b 切换为 ASCII 封面显示。 建议使用支持 TrueColor 和 Sixel 的终端模拟器。[Sixels in Terminal](https://www.arewesixelyet.com/). ## 歌词 kew 可读取与音频文件同名的 .lrc 文件,或从 mp3 的 SYLT 标签、Flac/Ogg/Opus 的 Vorbis 注释中提取歌词。 带时间戳的歌词会在曲目视图中自动显示。 按 m 可查看整页歌词。 ## 播放列表 加载播放列表: kew list <名称> 导出播放列表, 按 `x`,这会保存播放列表至你的音乐库目录下,文件名为当前队列首首歌曲。 也有“我喜欢”功能: 添加当前歌曲到“我喜欢”播放列表:按 . 加载“我喜欢”播放列表: ```bash kew . ``` ## 许可证 本项目基于 GPL 协议开源。 详细内容请参阅 [LICENSE](./LICENSE). ## 致谢
Attributions kew 使用了以下优秀的开源项目: Chafa,由 Hans Petter Jansson 开发 - https://hpjansson.org/chafa/ TagLib,由 TagLib 团队开发 - https://taglib.org/ Faad2,由 fabian_deb、knik、menno 开发 - https://sourceforge.net/projects/faac/ FFTW,由 Matteo Frigo 和 Steven G. Johnson 开发 - https://www.fftw.org/ Libopus,由 Opus 团队开发 - https://opus-codec.org/ Libvorbis,由 Xiph.org 开发 - https://xiph.org/ Miniaudio,由 David Reid 开发 - https://github.com/mackron/miniaudio Minimp4,由 Lieff 开发 - https://github.com/lieff/minimp4 Nestegg,由 Mozilla 开发 - https://github.com/mozilla/nestegg Img_To_Txt,由 Danny Burrows 开发 - https://github.com/danny-burrows/img_to_txt
## 作者 参见 [AUTHORS](./docs/AUTHORS.md). ## 联系方式 反馈与建议请发送邮件至: kew-player@proton.me. kew/docs/000077500000000000000000000000001512074754200126035ustar00rootroot00000000000000kew/docs/ANDROID-INSTRUCTIONS.md000066400000000000000000000042351512074754200161530ustar00rootroot00000000000000### **Basic Installation Requirements :** To run kew on Android please install the following applications : - **Termux** : A terminal emulator for Android that allows you to run Linux commands on your device. [![Download Termux](https://img.shields.io/badge/Download-Termux-brightgreen?style=for-the-badge&logo=android)](https://github.com/termux/termux-app/releases/) - click to go to downloads page - **Termux-Api** : A plugin for Termux that executes Termux-api package commands. [![Download Termux-Api](https://img.shields.io/badge/Download-Termux--API-blue?style=for-the-badge&logo=android)](https://github.com/termux/termux-api/releases/download/v0.53.0/termux-api-app_v0.53.0+github.debug.apk) - click to download ### **Termux Setup:** 1. **Update and install dependencies** ```sh pkg install tur-repo -y && yes | pkg upgrade -y && pkg install clang pkg-config taglib fftw git make chafa glib libopus opusfile libvorbis libogg dbus termux-api ``` 2. **Make sure termux has sound:** On your phone, go to Settings -> Sound and Vibration -> Volume and make sure the level for Media is not 0.
Building Faad2 from source (needed to run .m4a files) ```sh pkg install cmake make clang git clone https://github.com/knik0/faad2 cd faad2 cmake -DCMAKE_EXE_LINKER_FLAGS="-lm" . -D CMAKE_INSTALL_PREFIX=/data/data/com.termux/files/usr make install ```
3. **Enable storage permissions** ```sh termux-setup-storage ``` Tap allow for the setup to finish 4. **Setup dbus for kew** * edit/create `~/.bashrc` ``` nano ~/.bashrc ``` * In nano, add this line and save it (ctrl+x): ```bash alias kew="dbus-launch kew" ``` * Restart the shell: `exec $SHELL ### **Compiling Kew:** ```sh git clone https://codeberg.org/ravachol/kew.git cd kew make -j4 make install ``` This step is all you will need to do later to update the version. ### **Run kew:** 1. **Set kew's music library path** This could be ~/storage/music for instance: ``` kew path ``` 2. **Run kew** ``` kew ``` ### To get volume buttons on your phone to work in Termux: run nano ~/.termux/termux.properties Uncomment this line: volume-keys = volume Save (ctrl+x) kew/docs/AUTHORS.md000066400000000000000000000034161512074754200142560ustar00rootroot00000000000000# AUTHORS ## Maintainers * **Ravachol** @ravachol * Founder and Main Author ## Contributors (in alphabetical order) * Alex Hart @hartalex * Chromium-3-Oxide * Davis @kazemaksOG * @dandelion-75 * DNEGEL3125 @DNEGEL3125 * Hans Petter Jansson @hpjansson * @hiruocha * @hypercunx * Joey Schaff @jaoheah / @jaoh (codeberg) * John Lakeman @j-lakeman * @noiamnote * Matthias Geiger @werdahias * @Overionised * Ravachol @ravachol * Rioprastyo17 * Robertson D. Torress @Welpyes * Rowan Shi @rowanxshi * Rui Chen @chenrui333 * Ruoyu Zhong @ZhongRuoyu * Ryouji @soryu-ryouji * Samuel @Samueru-sama * Vafone @vafone * Xplshn @xplshn * Zane Godden @mechatour ## Testers * Vafone @vafone * Nicolas F * Ravachol @ravachol * LeahTheSlug ## Translations * @hiruocha (Simplified Chinese) ## Special Thanks We would like to extend our gratitude to the following individuals who have contributed significantly to kew: * David Reid @mackron (author of Miniaudio, used for playing music) * Hans Petter Jansson @hpjansson (author of Chafa, used for displaying images) * Chromium-3-Oxide * Matthias Geiger @werdahias * Vafone @vafone * Nicolas F * Yuri @yurivict yuri@freebsd.org * Joey Schaff @jaoheah * Agustin Ballesteros @agus-balles * Saijin Naib @Saijin_Naib * Xplshn @xplshn * Mateo @malteo * Hampa @hampa * Markmark1 @markmark1 * VDawg @vdawg-git * INIROBO @inirobo kew/docs/CHANGELOG.md000066400000000000000000001333011512074754200144150ustar00rootroot00000000000000# CHANGELOG ## 3.7.3 #### New Features - Added support for USLT lyrics tag including USLT with embedded synchronized lrc content. Suggested by @gzemlevskiy17. #### Bugfixes - Change makefile and kew.c so that locale install dir derives from PREFIX. Found by @yurivict. ## 3.7.2 #### New Features - kew play \ , plays a file or a the contents of a directory. By @Overionised. #### Bug Fixes - Fix bug with running 'kew song' on macOS. Reported by @spacetime-labs. ## 3.7.1 #### Bug Fixes - Fix screen was not getting cleared when switching views if hidesidecover was set to 1. Found by @bholroyd. ### 3.7.0 Biggest news in this release: Optimisations for large music collections/slow disks and a key binding/input handling overhaul. - Optimisations: Faster loading of previous playlist. So if you are used to running kew all for instance this will be significantly faster, especially for people with large collections. The new method should be up to 20 times faster for loading a big last used playlist. - The library is now always cached. It scans the library only if the files have changed, which it checks at the top level. This will be much faster if you have music on a slow disk. But it's all still very simple, no database dependencies or anything, just a flat binary file with the bare essentials. It's not a lot of data: 1k songs = 60KiB on disk. - Key binding overhaul: This allows for more advanced key bindings. You can now bind more keys and key combinations. The new format was suggested by @jaoh. Som examples: bind = +, volUp, +5% bind = Shift+s, stop bind = mouseMiddle, enqueueAndPlay The old format config files will still work for the most part, but to see examples of the new style, delete your kewrc file. - The help page will now fully reflect the keys you have bound. - Handle both synchronized and unsynchronized vorbis lyrics. Suggested by @hiruocha. - Auto-scrolling lyrics in the lyrics page for synced lyrics. Also added the ability to scroll lyrics manually. By @noiamnote. - The app and readme has been translated into japanese and chinese. Thank you for help with the chinese translation @hiruocha. - New theme: catpuccin mocha by @pixel-peeper. - New theme: neutral, uses only the default foreground color. - The cover is now visible on the left side on most views. Can be disabled by setting hideSideCover=1 in the config file. - Reverted to being neutral in album color mode when no music is playing. #### Bug Fixes - Don't enqueue the .m3u files themselves when mass enqueueing. - Fixes a bug related to certain types of mp3 files, where the song metadata wasn't switching in the UI. Found by: @Chromium-3-Oxide. - Fixes a bug in library view where under some conditions, the position of the selected row could jump upwards. - Made the path validation function less strict to avoid false positives. Reported by: DimaFyodorov. - Removed hardcoded paths in Makefile to avoid conflicting paths. Found by @hpjansson. - Fixed a few minor bugs with the library UI. Found by @bholroyd. - Fixed full width characters not displayed in notifications. Found by @Chromium-3-Oxide. #### Contributors Thank you also especially to contributors @jaoh, @bholroyd, @LeahTheSlug, @Chromium-3-Oxide and @hpjansson, @Vafone for reporting many issues and helping out. Thank you to @bholroyd for making the kew theme editor: https://bholroyd.github.io/Kew-tip/ Thank you to all the beta testers! #### Sponsors Thank you to Christian Mummelthey, imalee.sk and @sandrock. for their donations. ### 3.6.4 - Fixed 'kew theme' command. - Add support for vorbis lyrics for FLAC/Ogg/Opus files. We now have .lrc file support, SYLT mp3 lyrics support and now these. Should cover most cases. ### 3.6.3 - Fixed issue on termux for people who don't have faad installed. - If run with song arguments starts in track view like it's supposed to. - Fixed song loading instability with flac and mp3. ### 3.6.2 - Fix kew not exiting cleanly on android. - Fix update library from cache resulting in error. ### 3.6.1 - Fixed build issue on termux. ### 3.6.0 ![kew logo](https://codeberg.org/ravachol/kew/media/branch/main/images/logo.png) - kew now has a real logo and a tagline: "MUSIC FOR THE SHELL". - We now also have a color associated with kew, which is red: #de2b4d. - This color is now the default if you are not playing anything and are using album colors. - The welcome screen that appears when the path is not set has been given much love. - Song lyrics support through .lrc files. These need to already be on your computer. By @Rioprastyo17 - Song lyrics support through SYLT id3 tags. By @dandelion-75. - Watch timestamped lyrics in track view or press 'm' in the same view for full page lyrics. By @ravachol. - Bumped miniaudio to version v0.11.23 which among other things fixes a bug with some versions of mp3. By @hypercunx. - Code cleanup, improved internal structure by A LOT and removed all globals among other things. There are still a bunch of little inconsistencies to work on, naming conventions and so on, but for the most part, I'm very pleased with the progress. - There's now a diagram of kew's architecture included for devs who want to know how the internals are laid out. - You can now finally set collaped paths such as ~/Music and they will be set that way in config. Suggested by @danielwerg. - The config file is now respected and no longer changed by kew, only if you run kew path \. Instead there is a kewstaterc file in ~/.local/state that keeps the variables that can change in-app, these override what's in kewrc. Suggested by @danielwerg. - Dropped Nerd Fonts in favor of ⇉,⇇,↻,⇄ which are all unicode symbols. While Nerdfonts is neat it's too much trouble for users to install things and troubleshoot for just 4 slightly better symbols. - kew now restarts if it's already running in a different window. This replaces the ugly message that instructs the user to run 'kill'. Suggested by @amigthea. - Gradients are now enabled when using themes, not just when using album colors. It's the little things that count. - Playlist max files limit raised to 50k songs. Suggested by: @Saijin_Naib. #### Bug Fixes - Path is expanded correctly when providing it through the first screen that let's you choose a path. - Don't create ~/.config/kew/themes dir if there are no themes to be copied (user hasn't done sudo make install). - Fixed a bug with the library cache ids that was introduced in the last version. - Fixed a build issue on some versions of macOS. - Fixed an issue with replay gain being calculated in the wrong place. ### Sponsors Thank you to for the generous donation @LTeder! ### 3.5.3 - Fixes a bug that affects the library cache. This bug has the effect of making startup times for kew be slower on already slow HDDs. ### 3.5.2 - Fixed line in cover being erased in landscape mode on some terminals. By @hartalex. - Fixed long names no longer scrolling. - Fixed cover in landscape mode jumping from line 1 to line 2 and back when resizing window. ### 3.5.1 - Fix issue/test on homebrew. ### 3.5.0 Now with themes and Android support New in this release: - Fully customizable colors - Themes supporting both TrueColor RGB and the terminal 16-color palette - Theme pack with 16 included themes - Android support - Fixed TTY flickering - Improved search #### Themes Press t to cycle available themes. To set a theme from the command-line, run: kew theme themename (ie 'kew theme midnight') Put themes in \~/.config/kew/themes (\~/Library/Preferences/kew/themes on macOS). #### Android I haven't looked at battery life aspects yet, but staying in library view will be easier on the battery than using track view with the visualizer. You can also press v to toggle the visualizer on or off. #### TTY problems resolved The flickering in TTY has been fixed. Btw, if you are on tty or have limited colors and font on your terminal, try pressing i (for other color modes), v (for visualizer off) and b (for ascii cover). That should make it look much more easy on the eye! #### Move to Codeberg We now have a repo on Codeberg and that will be the preferred repo going forward. But people will be welcome to contribute in whichever place they prefer. Except for PRs, PRs need to go to codeberg, develop branch. #### OpenSuse We are now back on openSuse, our package there hadn't been updated in a long time, due to openSuse not having faad lib. We still need a Fedora package. We already have a RPM spec that @kazeevn added and everything. Thank you to @welpyes for bringing up Termux and helping out with that, and @arcathrax for fixing the ultrawide monitor bug. Thank you to mantarimay for updating the openSuse library. #### Sponsors and Donations Wanted Thank you to a new sponsor, @BasedScience! Please support this effort: https://ko-fi.com/ravachol https://github.com/sponsors/ravachol. - Ravachol #### New Features / Improvements - Theme colors, both TrueColor and 16-color palette theming. Cycle by pressing 't'. - Android compatibility! Please see ANDROID-INSTRUCTIONS.md for how to get kew on your phone. (@welpyes) - Improved the search function so that albums are shown below an artist hit. - Improved installation instructions for Fedora and openSuse in the README. - Enabled the detection of FAAD2 (which handles m4a) on Fedora properly in the makefile. - Made makefile compatible with openSuse Tumbleweed. The kew package has been updated on openSuse for the first time in a long time, thank you mantarimay (maintainer on openSuse). - Added an icon indicating if the song is playing or paused before the title at the top when the logo is hidden. - Shows the playlist from the first song (if it's in view), instead of always starting from the playing song. Suggested by @affhp. - Improved the safety of various functions and addressed potential vulnerabilities. - Don't make a space for the cover if there is none on landscape view. - Improved the instructions in the help view. #### Bug Fixes - Fixed visualizer crashing the app on ultrawide monitors. - Added null check for when exporting an empty playlist to .m3u. - Prevent flickering when scrolling on TTY and likely on some other terminals as well. - Search: fixed files being reordered when scrolling on macOS/kitty. ### 3.4.1 Adds a few minor bug fixes and you can now use playlists from the library view. - Added ability to see and enqueue playlists (m3u and m3u8) from library view. By Ravachol. Suggested by Kansei994. - Removed -flto from the makefile since it was causing compatibility problems, for instance Ubuntu 25.04. - Removed ALAC file support due to CVEs in the Apple ALAC decoder: https://github.com/macosforge/alac/issues/22. By Ravachol. Reported by @werdahias. #### Bug Fixes - Fixed G key not bound for new config files. By @Chromium-3-Oxide. - Fixed restarting from stop by pressing space bar only working once. By @ravachol. Found by @Chromium-3-Oxide. - Fixed status/error message sometimes not being cleared. By @SynthSlayer. Found by @SynthSlayer. - Fixed playlist sometimes starting from last song when enqueueing all by pressing "MUSIC LIBRARY". By @ravachol. Found by j-lakeman. - Fixed fullwidth characters not being truncated correctly. By @ravachol. Found by Kuuuube. - Fixed running kew --version to show the version screen along with the logo makes the logo have a really dark gradient that is barely readable on some terminals. By @ravachol. Found by @Chromium-3-Oxide. ### 3.4.0 - Landscape View (horizontal layout). Something that was long overdue. Widen the window and it automatically goes into landscape mode. By @Ravachol. Suggested by @Saijin-Naib. - Added ability to switch views by using the mouse. By @Chromium-3-Oxide. - Added ability to drag the progress bar using the mouse. Suggested by @icejix. - Faster loading of library cache from file, for people with very large music collections. - Show the actual keys bound to different views on the last row on macOS and show Shift+key instead of Sh+key for clarity. By @arcathrax. - Added an indicator when the library is being updated. Suggested by @Saijin-Naib. - Now (optionally) sets the currently playing track as the window title. By @Chromium-3-Oxide. Suggested by @Saijin-Naib. - Added back the special playlist, but renamed as the kew favorites playlist. This is a playlist you can add songs to by pressing "."." while the song is playing. Requested by @nikolasdmtr. #### Bug Fixes - Don't strip numbers from titles when presenting the actual title taken from metadata. We still strip numbers like 01-01 from the beginning of filenames before presenting them though. - Reset clock when resuming playback when stopped. Found by @Knusper. - Better way of checking for embedded opus covers, some covers weren't detected. Reported by @LeahTheSlug. - Better way of extracting ogg vorbis covers. Reported by @musselmandev. - Fixed 'kew all' not being shuffled if 'save repeat and shuffle settings' was turned on. By @j-lakeman. ### 3.3.3 - Don't show zero's for hours unless the duration is more than an hour long. - Show shuffle, repeat settings even if last row not visible. - Better handling of comments in config file. - Volume settings now follow a more conventional pattern where it increases or decreases based directly on the system output, instead of being relative to maximum output. Was able to remove a lot of code related to getting system volume on linux and macOS. Suggested by @arcathrax. - Escape for quit is no longer hard coded and can be disabled or changed in the settings file. Suggested by @0023-119. - Page Up and Page Down for scrolling are no longer hard coded and can be changed in the settings file. Suggested by The Linux Cast. - Remove special playlist function. It's kew's most odd feature and confuses people because they think it's related to the normal saving playlist function. Plus nobody has ever mentioned using it. - Tmux + kitty and Tmux + foot now displays images correctly. Reported by @amigthea and @acdcbyl. #### Bug Fixes - Fixed "ghost" visualizer bars showing up at higher frequencies in a zoomed out terminal window. Reported by @Chromium-3-Oxide. - Fixed bug in library related to handling of sub-directories several levels deep. - Fixed volume up/down not working when audio interface plugged in on macOS. Reported by @arcathrax. - Fixed bug with library cache setting not being remembered. Reported by @Saijin-Naib. ### 3.3.2 - Remove -lcurl from makefile. ### 3.3.1 Removal of Internet Radio Feature: We have decided to remove the internet radio feature from this release. This decision was made after careful consideration of stability and security concerns. Why This Change: To focus on core functionality. By removing the internet radio feature, we can concentrate on making the core music player really high quality, and making kew a more enjoyable experience. Security and Stability: It has been challenging addressing issues related to the internet radio feature. Removing it allows us to focus on other aspects of the application without compromising its stability and security. To summarize: By removing internet access completely from kew, we can make it a simple, secure and robust tool much more easily. - Also Fixes an issue with visualizer height on macOS. ### 3.3.0 - Reworked the visualizer to make it have more punch and to make higher frequency value changes more visible. The visualizer now also runs at 30fps instead of 60fps. 60 fps was just at odds with kew's very low system requirements profile, and we want kew to consume very little resources. The lower framerate cuts the cpu utilization in half when in track view with no big noticeable differences. - Added webm support. Suggested by @Kuuuube. - kew now remembers the playlist between sessions, unless you tell it to load something else. - Added a new setting, visualizerBarWidth. 0=Thin,1=Fat (twice as wide bars) or 2=Auto (depends on the window size. the default). Also line progressbar is now the default, mainly because it looks better with fat bars. - The appearance of the progress bar can now be configured in detail. There's even a pill muncher mode, where this round guy eats everything in his path. There are commented out examples in the kewrc so you can go back to the old look, if you don't like the new. By @Chromium-3-Oxide. - Gradient in library and playlist that makes the bottom rows a bit darker when using album colors. - Replay gain source can now be set in the config file. 0 = track, 1 = album or 2 = disabled. Suggested by @ksushagryaznaya. - Logging to error.log is now enabled if you run make DEBUG=1. - Prevent Z,X,C,V,B,N to trigger view changes in search or radio search if not on macOS. These are the shortcuts that are used instead of the F1-F7 keys on macOS, because there F-keys don't always mean F-functions. Delete the config file kewrc if you want to type uppercase letters in search. - Now saves repeat and shuffle settings betweens sessions. This can be turned off in the settings file. #### Bug Fixes - Fixed a format conversion issue with the visualizer. - The clock wasn't getting reset completely when restarting songs after seeking or when using alt+s to stop. Found by @Chromium-3-Oxide and @Kuuuube. - Fixed ascii cover image being too narrow on gnome terminal. - Fixed error (invalid read 8 bytes) when using backspace to clear a stopped playlist. - Gave the last row more minimum space. - Fixed bug where on some terminals when in a small window and visualizer disabled, the time progress row would get repeated. - Removed a use-after-unlock race in radio search. - Eliminated memory leak on radio search cancel by switching to cooperative thread cancellation (stop flag) instead of pthread_cancel. ### 3.2.0 Now with a mini-player mode, a braille visualizer mode, a favorites list for radio stations, scrolling names that don't fit, the visualizer running at 60 fps, and much more! - New mini-player mode. Make the window small and it goes into this mode. Suggested by @HAPPIOcrz007. - The visualizer now runs at 60 fps instead of 10, making it much smoother. The rest of the ui runs slower much like before to save on system resources. - The visualizer can now be shown in braille mode (visualizerBrailleMode option in kewrc config file). - Track progress can now be shown as a thin line instead of dots. - Now shows sample rate and if relevant, bitrate, in track view. - A favorites list for radio stations. It's visible when the radio search field is empty. - Audio normalization for flac and mp3 files using replay gain metatags. Suggested by @endless-stupidity. - Long song names now scroll in the library and playlist. Suggested by @HAPPIOcrz007. - Press o to sort the library, either showing latest first or alphabetically. Suggested by @j-lakeman. - Radio search now refreshes the list as radio stations are found, making it less "laggy". - Track view works with radio now. - Added a stop command (shift+s). Space bar is play as before. - Removed the playback of tiny left overs of the previous song, when pausing and then switching song. - Added bitrate field to radio station list. - Added support for fullwidth characters. - Added repeat playlist option. Suggested by @HAPPIOcrz007. - Added option to set the visualizer so that the brightness of it depends on the height of the bar. By @Chromium-3-Oxide. - Added config option to disable mouse (in kewrc file). By @Chromium-3-Oxide. - Previous on first track now resets the track instead of stopping. - Code cleanup. By @Chromium-3-Oxide. #### Bug Fixes - Fixed deadlock when quickly and repeatedly starting a radio station. - Fixed bug with previous track with shuffle enabled. Found by @GuyInAShack. - Fixed bug with moving songs around, there was a case where it wasn't rebuilding the chain and the wrong song would get played. - Fixed bug with alt + mouse commands not working. By @Chromium-3-Oxide. ### 3.1.2 - Fix radio search sometimes freezing because of an invalid radio station URL. Found by joel. by @ravachol. - Added ability to play a song directly from the library (instead of just adding it to the playlist) by pressing Alt+Enter. Suggested by @PrivacyFriendlyMuffins. By @ravachol. - Added ability to disable the glimmering (flashing) last row. By @Chromium-3-Oxide. ### 3.1.1 - Reverts the command `kew path` to its previous behavior (exit on completion), which enables some automated tests to function again. By @ravachol. ### 3.1.0 Now with internet radio, mouse support and ability to move songs around in the playlist. #### Dependencies: - New dependency on libcurl. #### Changes: - Added Internet radio support. MP3 streams only so far, but the vast majority of streams are MP3 streams in the database we are using, others are excluded. Press F6 for radio search or Shift+B on macOS. By @ravachol. - Added mouse support. Use the middle button for playing or enqueueing a song. Right button to pause. This is configurable with plenty of options. By @Chromium-3-Oxide. - Move songs up and down the playlist with t and g. By @ravachol. Suggested By @HAPPIOcrz007. - Added support for m4a files using ALAC decoder. By @ravachol. - When the program exits previous commands and outputs are restored. By @DNEGEL3125. - Clear the entire playlist by pressing backspace. By @mechatour. - Added support for wav file covers. By @DNEGEL3125. - Made the app do less work when idle. By @ravachol. - The currently playing track is now underlined as well as bolded, because bold weight wasn't working with some fonts. Found By @yurivict. By @ravachol. - Added logic that enables running raw AAC files (but not HE-AAC). By @ravachol. - Added debugging flag to the makefile. Now to run make with debug symbols, run: make DEBUG=1 -ij4. - It's now possible to remove or alter the delay when printing the song title, in settings. By @Chromium-3-Oxide. - Added the config option of quitting after playing the playlist, same as --quitonstop flag. By @Chromium-3-Oxide. - Improved error message system. By @ravachol. - Reenabled seeking in ogg files. By @ravachol. #### Bug Fixes: - Fixed cover sometimes not centered in wezterm terminal. By @ravachol. - Fixed setting path on some machines doesn't work, returns 'path not found'. Found by @illnesse. - Fixed crash when in shuffle mode and choosing previous song on empty playlist. Found by @DNEGEL3125. - Fixed crash sometimes when pressing enter in track view. By @ravachol. - Fixed ogg vorbis playback sometimes leading to crash because there was no reliable way to tell if the song had ended. By @ravachol. - Fixed opus playback sometimes leading to crash because of a mixup with decoders. By @ravachol. - Uses a different method for detecting if kew is already running since the previous method didn't work on macOS. By @DNEGEL3125. - Prevent the cover from scrolling up on tmux+konsole. Found by @acdcbyl. By @ravachol. #### Special Thanks To These Sponsors: - @SpaceCheeseWizard - @nikolasdmtr - *one private sponsor* ### 3.0.3 - Fixed buffer redraw issue with cover images on ghostty. - Last Row is shown in the same place across all views. - The library text no longer shifts one char to the left sometimes when starting songs. - Fixed minor bug related to scrolling in library. - Fixed bug related to covers in ascii, on narrow terminal sizes it wouldn't print correctly. Found by @Hostuu. - Minor UI improvements, style adjustments and cleaning up. - Added play and stop icon, and replaced some nerdfont characters with unicode equivalents. - Disabled desktop notifications on macOS. The macOS desktop notifications didn't really gel well with the app, and the method used was unsafe in the long run. A better way to do it is by using objective-c, which I want to avoid using. ### 3.0.2 - You can now enqueue and play all your music (shuffled) in library view, by pressing MUSIC LIBRARY at the top. - Removed dependency on Libnotify because its' blocking in nature, and some users were experiencing freezes. Instead dbus is used directly if available and used with timeouts. Reported by @sina-salahshour. - Fixed bug introduced in 3.0.1 where songs whose titles start with a number would be sorted wrong. - Fixed music library folders containing spaces weren't being accepted. Found by @PoutineSyropErable. - Fixed bug where after finishing playing a playlist and then choosing a song in it, the next song would play twice. - Fixed kew all not being randomized properly. Found by @j-lakeman. - Fixed useConfigColors setting not being remembered. Found by @j-lakeman. - Added AUTHORS.md, DEVELOPERS.md and CHANGELOG.md files. - Dependencies Removed: Libnotify. ### 3.0.1 - Uses safer string functions. - Fixed bug where scrolling in the library would overshoot its place when it was scrolling through directories with lots of files. - Fixed mpris/dbus bug where some widgets weren't able to pause/play. - Fixed crash when playing very short samples in sequence. Found by @hampa. - Fixed order of songs in library was wrong in some cases. Found by @vincentcomfy. - Fixed bug related to switching songs while paused. - Fixed bug with being unable to rewind tracks to the start. Found by @INIROBO. - Seek while paused is now disabled. Problems found by @INIROBO. ### 3.0.0 This release comes with bigger changes than usual. If you have installed kew manually, you need to now install taglib, ogglib and, if you want, faad2 (for aac/m4a support) for this version (see the readme for instructions for your OS). - kew now works on macOS. The default terminal doesn't support colors and sixels fully, so installing a more modern terminal like kitty or wezterm is recommended. - Removed dependencies: FFmpeg, FreeImage. - Added Dependencies: Faad2, TagLib, Libogg. - These changes make kew lighter weight and makes it faster to install on macOS through brew. - Faad2 (which provides AAC decoding) is optional. By default, the build system will automatically detect if faad2 is available and include it if found. - More optimized and faster binary. Thank you @zamazan4ik for ideas. - Better support of Unicode strings. - Case-insensitive search for unicode strings. Thank you @valesnikov. - Fixed makefile and other things for building on all arches in Debian. Thank you so much @werdahias. - More efficient handling of input. - Added support for .m3u8 files. Thank you @mendhak for the suggestion. - Fixed bug where switching songs quickly, the cover in the desktop notification would remain the same. - Fixed issue with searching for nothing being broken. Thank you @Markmark1! Thank you so much @xplshn, @Vafone and @Markmark1 for help with testing. ### 3.0.0-rc1 This release comes with bigger changes than usual. If you have installed kew manually, you need to now install taglib, ogglib and, if you want, faad2 (for aac/m4a support) for this version (see the readme for instructions for your OS). - kew now works on macOS. The default terminal doesn't support colors and sixels fully, so installing a more modern terminal like kitty or wezterm is recommended. - Removed dependencies: FFmpeg, FreeImage - Added Dependencies: Faad2, TagLib, Ogglib - These changes makes kew lighter weight and makes it faster to install on macOS through brew. - Faad2 (which provides AAC decoding) is optional! By default, the build system will automatically detect if faad2 is available and include it if found. Disable with make USE_FAAD=0. - More optimized and faster binary. Thank you @zamazan4ik for ideas. - Better support of Unicode strings. - Fixed makefile and other things for building on all arches in Debian. Thank you @werdahias. - More efficient handling of input. - Added support for .m3u8 files. Thank you @mendhak for the suggestion. - Fixed bug where switching songs quickly, the cover in the desktop notification would remain the same. Thank you @xplshn and @Markmark1 for help with testing. Big thanks to everyone who helps report bugs! ### 2.8.2 - Fixed issue with building when libnotify is not installed. - Fixed build issue on FreeBSD. ### 2.8.1 New in this version: - Much nicer way to set the music library path on first use. - Checks at startup if the music library's modified time has changed when using cached library. If it has, update the library. Thanks @yurivict for the suggestion. - Improved search: kew now also shows the album name (directory name) of search results, for clarity. - You can now use TAB to cycle through the different views. - There's now a standalone executable AppImage for musl x86_64 systems. Thank you to @xplshn and @Samueru-sama for help with this. Bugfixes and other: - Added missing include file. Thank you @yurivict. - Don't repeat the song notification when enqueuing songs. A small but annoying bug that slipped into the last release. - Fixed issue where kew sometimes couldn't find the cover image in the folder. - Better handling of songs that cannot be initialized. - Removed support for .mp4 files so as to not add a bunch of video folders to the music library. Thanks @yurivict for the suggestion. - Made the makefile compatible with Void Linux. Thank you @itsdeadguy. - Cursor was not reappearing in some cases on FreeBSD after program exited. Thank you @yurivict. - Fixed slow loading UI on some machines, because of blocking desktop notification. Thanks @vdawg-git for reporting this. Thank you to @vdawg-git for helping me test and debug! Thank you also to @ZhongRuoyu! ### 2.8 New Features: - Much nicer way to set the music library path on first use. - Checks at startup if the music library's modified time has changed when using cached library. If it has, update the library. Thanks @yurivict for the suggestion. - Improved search: kew now also shows the album name (directory name) of search results, for clarity. - You can now use TAB to cycle through the different views. - There's now a standalone executable AppImage for musl x86_64 systems. Thank you to @xplshn and @Samueru-sama for help with this. Bugfixes and other: - Don't repeat the song notification when enqueuing songs. A small but annoying bug that slipped into the last release. - Fixed issue where kew sometimes couldn't find the cover image in the folder. - Better handling of songs that cannot be initialized. - Removed support for .mp4 files so as to not add a bunch of video folders to the music library. Thanks @yurivict for the suggestion. - Made the makefile compatible with Void Linux. Thank you @itsdeadguy. - Cursor was not reappearing in some cases on FreeBSD after program exited. Thank you @yurivict. - Fixed slow loading UI on some machines, because of blocking desktop notification. Thanks @vdawg-git for reporting this. Thank you to @vdawg-git for helping me test and debug! ### 2.7.2 - You can now remove the currently playing song from the playlist. Thank you @yurivict for the suggestion. You can then press space bar to play the next song in the list. - Scrolling now stops immediately after the key is released. - Better reset of the terminal at program exit. - MPRIS widgets are now updated when switching songs while paused. - When pressing update library ("u"), it now remembers which files are enqueued. - No more ugly scroll back buffer in the terminal. Btw, there is a bug in the KDE Media Player Widget which locks up plasmashell when certain songs play (in any music player). If you are having that problem, I suggest not using that widget until you have plasmashell version 6.20 or later. Bug description: https://bugs.kde.org/show_bug.cgi?id=491946. ### 2.7.1 - Added missing #ifdef for libnotify. This fixes #157. ### 2.7 This release adds: - Complete and corrected MPRIS implementation and support of playerCtl, except for opening Uris through mpris. - Libnotify as a new optional dependency. - Fixes to many minor issues that have cropped up. - Proper MPRIS and PlayerCtl support. Set volume, stop, seek and others now work as expected. You can also switch tracks while stopped or paused now. Everything should work except openUri and repeat playlist which are not available for now. - New (optional) dependency: Libnotify. In practice, adding libnotify as a dependency means browsing through music will no longer make desktop notifications pile up, instead the one visible will update itself. Thank you, @kazemaksOG, this looks much better. kew uses libnotify only if you have it installed, so it should if possible be an optional thing during installation. - Allows binding of other keys for the different ui views that you get with F2-F6. - Removed the option to toggle covers on and off by pressing 'c'. This led to confusion. - Removed build warning on systems with ffmpeg 4.4 installed. - Only run one instance of kew at a time, thanks @werdahias for the suggestion. - If you exit the kew with 0% volume, when you open it next time, volume will be at 10%. To avoid confusion. - Handle SIGHUP not only SIGINT. - Prints error message instead of crashing on Fedora (thanks @spambroo) when playing unsupported .m4a files. This problem is related to ffmpeg free/non-free versions. You need the non-free version. - Fixed issue where special characters in the song title could cause mpris widgets to not work correctly. ### 2.6 - New command: "kew albums", similar to "kew all" but it queues one album randomly after the other. Thank you @the-boar for the suggestion. - Fixed bug where sometimes kew couldn't find a suitable name for a playlist file (created by pressing x). - Made it so that seeking jumps in more reasonable smaller increments when not in song view. Previously it could jump 30 seconds at a time. - Rolled back code related to symlinked directories, it didn't work with freebsd, possibly others. ### 2.5.1 - Fixed bug where desktop notifications could stall the app if notify-send wasn't installed. Thank you @Visual-Dawg for reporting this and all the help testing! - Search: Removed duplicate search result name variable. This means search results will now have a very low memory footprint. - Symlinked directories should work better now. Works best if the symlink and the destination directory has the same name. ### 2.5 - Fuzzy search! Press F5 to search your library. - You can now quit with Esc. Handy when you are in search view, because pressing 'q' will just add that letter to the search string. - Fixed issue where after completing a playthrough of a playlist and then starting over, only the first song would be played. - Fine tuning of the spectrum visualizer. Still not perfect but I think this one is better. I might be wrong though. - Fixed issue where debian package tracker wasn't detecting LDFLAGS in the makefile. - Made scrolling quicker. ### 2.4.4 - Fixed no sound playing when playing a flac or mp3 song twice, then enqueuing another. - Don't save every change to the playlist when running the special playlist with 'kew .', only add the songs added by pressing '.'. - Removed compiler warning and a few other minor fixes. ### 2.4.3 - Fixed covers not being perfectly square on some terminals. - Fixed playlist selector could get 'stuck' after playing a long list. - Code refactoring and minor improvements to playlist view. - Moved the files kewrc and kewlibrary config files from XDG_CONFIG_HOME into XDG_CONFIG_HOME/kew/, typically ~/.config/kew. ### 2.4.2 - Fixed a few issues related to reading and writing the library. ### 2.4.1 - Improved album cover color mode. Press 'i' to try this. - To accelerate startup times, there is now a library cache. This feature is optional and can be enabled in the settings file (kewrc). If the library loading process is slow, you'll be prompted to consider using the cache. - You can now press 'u' to update the library in case you've added or removed songs. - Faster "kew all". It now bases its playlist on the library instead of scanning everything a second time. - Fixed when running the special playlist with "kew .", the app sometimes became unresponsive when adding / deleting. - Code refactoring and cleanup. ### 2.4 - Much faster song loading/skipping. - New settings: configurable colors. These are configured in the kewrc file (in ~/.config/ or wherever your config files are) with instructions there. - New setting: hidehelp. Hides the help text on library view and playlist view. - New setting: hidelogo. Prints the artist name as well as the song title at the top instead of a logo. - Fixed an issue with shuffle that could lead to a crash. - Fixed an issue where it could crash at the end of the playlist. - Fixed an issue where in some types of music libraries you couldn't scroll all the way to the bottom. - Fixed notifications not notifying on songs with spaces in cover art url. - Fixed sometimes not being able to switch song. - Further adjustments to the visualizer. - .aac and .mp4 file support. - New option: -q. Quits right after playing the playlist (same as --quitonstop). - Improved help text. ### 2.3.1 - The visualizer now (finally!) works like it's supposed to for all formats. - Proper clean up and restore cursor when using CTRL-C to quit the app. - Don't refresh track view twice when skipping to the previous song. ### 2.3 - Notifications of currently playing song through notify-send. New setting: allowNotifications. Set to 0 to disable notifications. - Fixed an issue that could lead to a crash when switching songs. - Fixed an issue with switching opus songs that could lead to a crash. - Plus other bug fixes. ### 2.2.1 - Fixed issue related to enqueuing/dequeuing the next song. - Some adapting for FreeBSD. ### 2.2.1 - Fixed issue related to enqueuing/dequeuing the next song. - Some adapting for FreeBSD. ### 2.2 - This update mostly contains improvements to stability. - M4a file decoding is no longer done by calling ffmpeg externally, it's (finally) done like the other file formats. This should make kew more stable, responsive and it should consume less memory when playing m4a files. - kew now starts the first time with your system volume as the volume, after that it remembers the last volume it exited with and uses that. - kew now picks up and starts using the cover color without the user having to first go to track view. ### 2.1.1 - Fixed a few issues related to passing cover art url and length to mpris. Should now display cover and progress correctly in widgets on gnome/wayland. ### 2.0.4 - You can now add "-e" or "--exact" in your searches to return an exact (not case sensitive) match. This can be helpful when two albums have a similar name, and you want to specify you want one or the other. Example: kew -e basement popstar. - Fixed issue where pressing del on the playlist changed view to track view. ### 2.0.3 - Fixed issue where sometimes the last of enqueued songs where being played instead of the first, - F4 is bound to show track view, and shown on the last row, so that the track view isn't hidden from new users. ### 2.0.1 - New view: Library. Shows the contents of the music library. From there you can add songs to the playlist. - Delete items from the playlist by pressing Del. - You can flip through pages of the library or playlist by pressing Page Up and Page Down. - Starting kew with no arguments now takes you to the library. - After the playlist finishes playing, the library is shown, instead of the app exiting. - To run kew with all songs shuffled like you could before, just type "kew all" in the terminal. - Running kew with the argument --quitonstop, enables the old behavior of exiting when finished. - Removed the playlist duration counter. It caused problems when coupled with the new features of being able to remove and add songs while audio is playing. - New ascii logo! This one takes up much less space. - kew now shows which song is playing on top of the library and playlist views. - Volume is now set at 50% at the start. - Also many bug fixes. ### 1.11 - Now shows volume percentage. - Fixed bug where on a small window size, the nerdfonts for seeking, repeat and shuffle when all three enabled could mess up the visualizer. ### 1.10 - Improved config file, with more information on how to make key bindings with special keys for instance. - Changing the volume is now for just kew, not the master volume of your system. - Switching songs now unpauses the player. - Fixed issue of potential crash when uninitializing decoders. ### 1.9 - Fixed a potential dead-lock situation. - Fixed one instance of wrong metadata/cover being displayed for a song on rare occasions. - Fixed an issue that could lead to a crash when switching songs. - Fixed issue of potential crash when closing audio file. - Fixed playlist showing the previous track as the current one. - Much improved memory allocation handling in visualizer. - Playlist builder now ignores hidden directories. ### 1.8.1 - Fixed bugs relating to showing the playlist and seeking. - Fixed bug where trying to seek on ogg files led to strange behavior. Now seeking in ogg is entirely disabled. - Fixed bug where kew for no reason stopped playing audio but kept counting elapsed seconds. - More colorful visualizer bars when using album cover colors. ### 1.8 - Visualizer bars now grow and decrease smoothly (if your terminal supports unicode). ### 1.7.4 - Kew is now interactive when paused. - Fixed issue with crashing after a few plays with repeat enabled. - Deletes cover images from cache after playing the file. ### 1.7.3 - Fixed issue with crash after seeking to the end of songs a few times. A lot was changed in 1.6.0 and 1.7.0 which led to some instability. ### 1.7.2 - Introduced Nerd Font glyphs for things like pause, repeat, fast forward and so on. - More fixes. ### 1.7.1 - Fixes a few issues in 1.7.0 ### 1.7.0 - Added decoders for ogg vorbis and opus. Seeking on ogg files doesn't yet work however. ### 1.6.0 - Now uses miniaudio's built-in decoders for mp3, flac and wav. ### 1.5.2 - Fix for unplayable songs. ### 1.5.1 - Misc issues with input handling resolved. - Faster seeking. ### 1.5 - Name changed to kew to resolve a name conflict. - Fixed bug with elapsed seconds being counted wrong if pausing multiple times. - Fixed bug with segfault on exit. ### 1.4 - Seeking is now possible, with A and D. - Config file now resides in your XDG_CONFIG_HOME, possibly ~/.config/kewrc. You can delete the one in your home folder after starting this version of cue once. - Most key bindings are now configurable. - Singing more visible in the visualizer. - Better looking visualizer with smoother gradient. - Misc fixes. - You can no longer press A to add to kew.m3u. instead use period. ### 1.3 - Now skips drm'd files more gracefully. - Improvements to thread safety and background process handling. - Misc bug fixes. - Using album colors is now the default again. ### 1.2 - It's now possible to scroll through the songs in the playlist menu. - Unfortunately this means a few key binding changes. Adjusting volume has been changed to +, -, instead up and down arrow is used for scrolling in the playlist menu. - h,l is now prev and next track (previously j and k). Now j,k is used for scrolling in the playlist menu. - Added a better check that metadata is correct before printing it, hopefully this fixes an occasional but annoying bug where the wrong metadata was sometimes displayed. - Using profile/theme colors is now the default choice for colors. ### 1.1 - Everything is now centered around the cover and left-aligned within that space. - Better visibility for text on white backgrounds. If colors are still too bright you can always press "i" and use the terminal colors, for now. - Playlist is now F2 and key bindings is F3 to help users who are using the terminator terminal and other terminals who might have help on the F1 key. - Now looks better in cases where there is no metadata and/or when there is no cover. - The window refreshes faster after resize. ### 1.0.9 - More colorful. It should be rarer for the color derived from the album cover to be gray/white - Press I to toggle using colors from the album cover or using colors from your terminal color scheme. - Smoother color transition on visualizer. ### 1.0.8 #### Features: - New Setting: useProfileColors. If set to 1 will match cue with your terminal profile colors instead of the album cover which remains the default. - It is now possible to switch songs a little quicker. - It's now faster (instant) to switch to playlist and key bindings views. #### Bug Fixes: - Skip to numbered song wasn't clearing the number array correctly. - Rapid typing of a song number wasn't being read correctly.. ### 1.0.7 - Fixed a bug where mpris stuff wasn't being cleaned up correctly. - More efficient printing to screen. - Better (refactored) cleanup. ### 1.0.6 - Fixed a bug where mpris stuff wasn't being cleaned up correctly ### 1.0.5 - Added a slow decay to the bars and made some other changes that should make the visualizer appear better, but this is all still experimental. - Some more VIM key bindings: 100G or 100g or 100+ENTER -> go to song 100 gg -> go to first song G -> go to last song ### 1.0.4 - Added man pages. - Added a few VIM key bindings (h,j,k,l) to use instead of arrow keys. - Shuffle now behaves like in other players, and works with MPRIS. Previously the list would be reordered, instead of the song just jumping from place to place, in the same list. Starting cue with 'cue shuffle ' still works the old way. - Now prints a R or S on the last line when repeat or shuffle is enabled. ### 1.0.3 - cue should now cleanly skip files it cannot play. Previously this caused instability to the app and made it become unresponsive. - Fixed a bug where the app sometimes became unresponsive, in relation to pausing/unpausing and pressing buttons while paused. ### 1.0.2: - Added support for MPRIS, which is the protocol used on Linux systems for controlling media players. - Added --nocover option. - Added --noui option. When it's used cue doesn't print anything to screen. - Now you can press K to see key bindings. - Fixed issue with long files being cut off. - Hiding cover and changing other settings, now clears the screen first. - Fixed installscript for opensuse. - Feature or bug? You can no longer raise the volume above 100% - New dependency: glib2. this was already required because chafa requires it, but it is now a direct dependency of cue. kew/docs/CONTRIBUTING.md000066400000000000000000000042051512074754200150350ustar00rootroot00000000000000# CONTRIBUTING ## Welcome to kew contributing guide Thank you for your interest in contributing to kew! ### Goal of the project The goal of kew is to provide a quick and easy way for people to listen to music with the absolute minimum of inconvenience. It's a small app, limited in scope and it shouldn't be everything to all people. It should continue to be a very light weight app. For instance, it's not imagined as a software for dj'ing or as a busy music file manager with all the features. We want to keep the codebase easy to manage and free of bloat, so might reject a feature out of that reason only. ### Bugs Please report any bugs directly on codeberg, with as much relevant detail as possible. If there's a crash or stability issue, the audio file details are interesting, but also the details of the previous and next file on the playlist. You can extract these details by running: ffprobe -i AUDIO FILE -show_streams -select_streams a:0 -v quiet -print_format json ### Create a pull request After making any changes, open a pull request on Codeberg, develop branch. https://codeberg.org/ravachol/kew - Please contact me (kew-player@proton.me) before doing a big change, or risk the whole thing getting rejected. - Try to keep commits fairly small so that they are easy to review. - If you're fixing a particular bug in the issue list, please explicitly say "Fixes #" in your description". ### Issue assignment We don't have a process for assigning issues to contributors. Please feel free to jump into any issues in this repo that you are able to help with. Our intention is to encourage anyone to help without feeling burdened by an assigned task. Life can sometimes get in the way, and we don't want to leave contributors feeling obligated to complete issues when they may have limited time or unexpected commitments. We also recognize that not having a process could lead to competing or duplicate PRs. There's no perfect solution here. We encourage you to communicate early and often on an Issue to indicate that you're actively working on it. If you see that an Issue already has a PR, try working with that author instead of drafting your own. kew/docs/DEVELOPERS.md000066400000000000000000000210111512074754200145700ustar00rootroot00000000000000# DEVELOPERS ## Getting started This document will help you setup your development environment. It is intended to be beginner friendly as we welcome and encourage people who are just starting to learn about programming in C to contribute to this project. We also welcome senior developers of course, but much of this document will be redundant if you are experienced. ### Problems If you run into any problems just ask for help in an email to kew-player@protonmail.com or in the PR itself. ### Prerequisites Before contributing, ensure you have the following tools installed on your development machine: - [GCC](https://gcc.gnu.org/) (or another compatible C/C++ compiler) - [Make](https://www.gnu.org/software/make/) - [Git](https://git-scm.com/) - [Valgrind](http://valgrind.org/) (optional, for memory debugging and profiling) - [VSCodium](https://vscodium.com/) or [VSCode](https://code.visualstudio.com/) (or other debugger) ### Building the Project 1. Clone the repository: ``` git clone https://codeberg.org/ravachol/kew.git --single-branch --branch develop cd kew ``` 2. To enable debugging symbols, run make with DEBUG=1 3. Build the project: ``` make DEBUG=1 -j$(nproc) # Use all available processor cores for faster builds ``` ### Commenting Please refrain from using a lot of comments, and make sure that they are in English. I am not a big believer in comments and avoid commenting as much as possible. If you feel you need to add a comment, please first consider if you can make the function or variable names clearer, or if you can structure the code differently so that it is simpler and the intent is clear, or if you can make the code block into a function with a name that explains crystally clear what is going on. If you used AI make sure to remove comments that aren't strictly needed. ### Architecture ![](../images/kew_architecture.png) kew follows the above architecture, so that function calls only go in the direction of the arrows. For instance, functions in ops module never call functions in ui module as there is no arrow pointing from ops to ui. Please make sure your PR follows this architecture, and place your code in the correct module. If you have doubts in where to place it, just ask. ### Debugging with VSCodium 1. Install extension clangd, C/C++ Debug (gdb) and EditorConfig. 2. Install the program bear that can generate a compile_commands.json. This helps clangd find libs. 3. Run bear -- make. This should enable you to develop kew on VSCodium. ### Debugging with Visual Studio Code To enable debugging in VSCode, you'll need to create a `launch.json` file that configures the debugger. Follow these steps: 1. Open your project's folder in VSCode. 2. Press `F5` or go to the "Run and Debug" sidebar (`Ctrl+Shift+D` on Windows/Linux, `Cmd+Shift+D` on macOS), then click on the gear icon to create a new launch configuration file. 3. Select "C++ (GDB/LLDB)" as the debugger type, and choose your platform (e.g., x64-linux, x86-win32, etc.). 4. Replace the contents of the generated `launch.json` file with the following, adjusting paths and arguments as needed: ```json { "version": "0.2.0", "configurations": [ { "name": "kew", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/kew", //"args": ["artist or song name"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } ``` 5. Save the `launch.json` file. 6. Create a c_cpp_properties.json file in the same folder (.vscode) with the following contents adjusting paths and arguments as needed: ```json { "configurations": [ { "name": "linux-gcc-x64", "includePath": [ "${workspaceFolder}/include/miniaudio", "${workspaceFolder}/include/nestegg", "${workspaceFolder}/**", "/usr/include", "/usr/include/opus", "/usr/include/vorbis", "/usr/include/chafa/", "/lib/chafa/include", "/usr/include/glib-2.0", "/usr/lib/glib-2.0/include", "/usr/include/libmount", "/usr/include/blkid", "/usr/include/sysprof-6", "/usr/include/glib-2.0/gio", "/usr/include/glib-2.0", "${workspaceFolder}/include" ], "browse": { "path": [ "${workspaceFolder}/include/miniaudio", "${workspaceFolder}/src", "${workspaceFolder}/include", "${workspaceFolder}/**" ], "limitSymbolsToIncludedHeaders": true }, "defines": [ "_POSIX_C_SOURCE=200809L" ], "compilerPath": "/usr/bin/gcc", "cStandard": "${default}", "cppStandard": "${default}", "intelliSenseMode": "linux-gcc-x64" } ], "version": 4 } ``` 7. Add the extensions C/C++, C/C++ Extension pack, C/C++ Themes (optional). 8. Now you can use VSCode's debugger to step through your code, inspect variables, and analyze any issues: * Set breakpoints in your source code by placing your cursor on the desired line number, then press `F9`. * Press `F5` or click on the "Start Debugging" button (or go to the "Run and Debug" sidebar) to start the debugger. * When the execution reaches a breakpoint, VSCode will pause, allowing you to use its built-in features for debugging. #### Finding where libs are located If the paths in c_cpp_properties.json are wrong for your OS, to find the folder where for instance Chafa library is installed, you can use one of the following methods: 1. **Using `pkg-config`**: The `pkg-config` tool is a helper tool used to determine compiler flags and linker flags for libraries. You can use it to find the location of Chafa's include directory. Open your terminal and run the following command: ``` pkg-config --cflags chafa ``` This should display the `-I` flags required to include Chafa's headers, which in turn will reveal the installation prefix (e.g., `/usr/include/chafa/`). The folder containing the library files itself is typically located under `lib` or `lib64`, so you can find it by looking for a folder named `chafa` within those directories. 2. **Using `brew` (for macOS)**: If you installed Chafa using Homebrew, you can find its installation prefix with the following command: ``` brew --prefix chafa ``` This will display the installation prefix for Chafa (e.g., `/usr/local/opt/chafa`). 3. **Manually searching**: Alternatively, you can search your file system manually for the `chafa` folder or library files. On Unix-based systems like Linux and macOS, libraries are typically installed under `/usr`, `/usr/local`, or within the user's home directory (e.g., `~/.local`). You can use the `find` command to search for the folder: ``` find /usr /usr/local ~/.local -name chafa ``` This should display the location of the Chafa installation, revealing both the include and library folders. ### Valgrind To use Valgrind for memory debugging and profiling: 1. Build kew with debug symbols. Run this command: make DEBUG=1 -j4 2. Run Valgrind on your binary: ``` valgrind --leak-check=full --track-origins=yes --show-leak-kinds=all --log-file=valgrind-out.txt ./kew ``` ### Editorconfig - If you can, use EditorConfig for VS Code Extension. There is a file with settings for it: .editorconfig. ### Contributing For further information on how to contribute, see CONTRIBUTING.md. kew/docs/FEDORA-RPM-INSTRUCTIONS.md000066400000000000000000000022161512074754200164640ustar00rootroot00000000000000# Building an RPM package For RPM-based distributions (like Fedora, CentOS, RHEL), you can build the package from source using the provided `.spec` file. 1. **Install Build Tools & Dependencies** First, install the necessary build dependencies for `kew` by following the instructions for your distribution (e.g., Fedora) in the "Building the project manually" section. Then, install the RPM build tools: ```bash sudo dnf install rpm-build ``` 2. **Prepare Source Tarball** Create the source tarball from the git repository and place it where `rpmbuild` can find it: ```bash # Define the version based on the spec file VERSION=$(grep 'Version:' kew.spec | awk '{print $2}') # Create the rpmbuild directory structure mkdir -p ~/rpmbuild/SOURCES # Create the source tarball git archive --format=tar.gz --prefix=kew-$VERSION/ -o ~/rpmbuild/SOURCES/kew-$VERSION.tar.gz HEAD ``` 3. **Build the RPM** Now, you can build the binary and source RPMs: ```bash rpmbuild -ba kew.spec ``` The resulting RPM files will be created in the `~/rpmbuild/RPMS/` and `~/rpmbuild/SRPMS/` directories.kew/docs/MANUAL-INSTALL-INSTRUCTIONS.md000066400000000000000000000112031512074754200171450ustar00rootroot00000000000000## Manually Installing kew kew dependencies are: * FFTW * Chafa * libopus * opusfile * libvorbis * TagLib * faad2 (optional) * libogg * pkg-config * glib2.0 Install the necessary dependencies using your distro's package manager and then install kew. Below are some examples.
Debian/Ubuntu Install dependencies: ```bash sudo apt install -y pkg-config libfaad-dev libtag1-dev libfftw3-dev libopus-dev libopusfile-dev libvorbis-dev libogg-dev git gcc make libchafa-dev libglib2.0-dev libgdk-pixbuf-2.0-dev ``` [Install kew](#install-kew)
Arch Linux Install dependencies: ```bash sudo pacman -Syu --noconfirm --needed pkg-config faad2 taglib fftw git gcc make chafa glib2 opus opusfile libvorbis libogg ``` [Install kew](#install-kew)
Android Follow the instructions here: [ANDROID-INSTRUCTIONS.md](ANDROID-INSTRUCTIONS.md)
macOS Install git: ```bash xcode-select --install ``` Install dependencies: ```bash brew install gettext faad2 taglib chafa fftw opus opusfile libvorbis libogg glib pkg-config make ``` Notes for mac users: 1) A sixel-capable terminal like kitty or WezTerm is recommended for macOS. 2) The visualizer and album colors are disabled by default on macOS, because the default terminal doesn't handle them too well. To enable press v and i respectively. [Install kew](#install-kew)
Fedora Install dependencies: ```bash sudo dnf install -y pkg-config taglib-devel fftw-devel opus-devel opusfile-devel libvorbis-devel libogg-devel git gcc make chafa-devel libatomic gcc-c++ glib2-devel ``` Option: add faad2-devel for AAC, M4A support. ```bash sudo dnf install faad2-devel faad2 ``` [Install kew manually](#install-kew)/[Build an RPM package](FEDORA-RPM-INSTRUCTIONS.md)
OpenSUSE Install dependencies: ```bash sudo zypper install -y pkgconf taglib fftw3-devel opusfile-devel libvorbis-devel libogg-devel git chafa-devel gcc make glib2-devel faad2 faad2-devel gcc-c++ libtag-devel ``` [Install kew](#install-kew)
CentOS/Red Hat Install dependencies: ```bash sudo dnf config-manager --set-enabled crb sudo dnf install -y pkgconfig taglib taglib-devel fftw-devel opus-devel opusfile-devel libvorbis-devel libogg-devel git gcc make chafa-devel glib2-devel gcc-c++ ``` Option: add faad2-devel for AAC,M4A support (Requires RPM-fusion to be enabled). Enable RPM Fusion Free repository: ```bash sudo dnf install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm ``` Install faad2: ```bash sudo dnf install faad2-devel ``` [Install kew manually](#install-kew)/[Build an RPM package](FEDORA-RPM-INSTRUCTIONS.md)
Void Linux Install dependencies: ```bash sudo xbps-install -y pkg-config faad2 taglib taglib-devel fftw-devel git gcc make chafa chafa-devel opus opusfile opusfile-devel libvorbis-devel libogg glib-devel ``` [Install kew](#install-kew)
Alpine Linux Install dependencies: ```bash sudo apk add pkgconfig faad2-dev taglib-dev fftw-dev opus-dev opusfile-dev libvorbis-dev libogg-dev git build-base chafa-dev glib-dev ``` [Install kew](#install-kew)
Windows (WSL) 1) Install Windows Subsystem for Linux (WSL). 2) Install kew using the instructions for Ubuntu. 3) If you are running Windows 11, Pulseaudio should work out of the box, but if you are running Windows 10, use the instructions below for installing PulseAudio: https://www.reddit.com/r/bashonubuntuonwindows/comments/hrn1lz/wsl_sound_through_pulseaudio_solved/ 4) To install Pulseaudio as a service on Windows 10, follow the instructions at the bottom in this guide: https://www.linuxuprising.com/2021/03/how-to-get-sound-pulseaudio-to-work-on.html
#### Install kew Download the latest release (recommended) or, if you are feeling adventurous, clone from the latest in main: ```bash git clone https://codeberg.org/ravachol/kew.git ``` ```bash cd kew ``` Then run: ```bash make -j4 ``` ```bash sudo make install ``` ### Uninstalling If you installed kew manually, simply run: ```bash sudo make uninstall ``` If you want, you can also delete the settings files: Linux: ~/.config/kew/ macOS: ~/Library/Preferences/kew/ Then delete the kewstaterc file: Linux: ~/.local/state/kewstaterc macOS: ~/Library/Application Support/kewstaterc #### Faad2 is optional By default, the build system will automatically detect if `faad2` is available and includes it if found. kew/docs/SECURITY.md000066400000000000000000000012761512074754200144020ustar00rootroot00000000000000# SECURITY ## Reporting a Bug If you find a security related issue, please contact us at kew-player@proton.me. When a fix is published, you will receive credit under your real name or bug tracker handle in Codeberg. 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 fix for the latest release. This fix will be released as fast as possible. You may be asked to provide further information in pursuit of a fix. kew/docs/kew.1000066400000000000000000000106261512074754200134600ustar00rootroot00000000000000.\" DATE .TH "kew" "1" "9/3/23" "Linux" "General Commands Manual" .nh .if n .ad l .SH "NAME" \fBkew\fR , \fBkew\fR \- Music For The Shell. .SH "SYNOPSIS" .HP 4n \fBkew\fR [OPTIONS] [\fIPARTIAL\ FILE\ OR\ DIRECTORY\ NAME\fR] .SH "DESCRIPTION" \fBkew\fR plays audio from your music folder when given a partial (or whole) file or directory name. A playlist is created when finding more than one file. It supports gapless playback, 24-bit/192khz audio and MPRIS. .PP Typical use: .PP \fBkew\fR artist, album or song name .PP \fBkew\fR returns results from the location of the first match, it doesn't return every result possible. .SH "OPTIONS" .TP 9n \fB\-h,\fR \fB\--help\fR Displays help. .TP 9n \fB\-v,\fR \fB\--version\fR Displays version info. .TP 9n \fBpath \fR Sets the path to the music library. .TP 9n \fB\--nocover\fR Hides the cover. .TP 9n \fB\--noui\fR Completely hides the UI. .TP 9n \fB\-q,\fR \fB\--quitonstop\fR Exits after playing the whole playlist. .TP 9n \fB\-e,\fR \fB\--exact Specifies you want an exact (but not case sensitive) match. .TP 9n shuffle Shuffles the playlist before starting to play it. .TP 9n dir Plays the directory not the song. .TP 9n song Plays the song not the directory. .TP 9n play Plays a song or loads a directory one time without changing the default library. .TP 9n \fBtheme \fR Sets a theme from ~//kew/themes .TP 9n list Searches for a (.m3u) playlist. These are normally not included in searches. .SH "EXAMPLES" .TP 9n kew Start \fBkew\fR in library view. .TP 9n kew all Start \fBkew\fR with all songs loaded into a playlist. .TP 9n kew albums Start \fBkew\fR with all albums randomly added one after the other in the playlist. .TP 9n kew moonlight son Play moonlight sonata. .TP 9n kew moon .br Play moonlight sonata. .TP 9n kew nirv .br Play all music under Nirvana folder shuffled. .TP 9n kew neverm Play Nevermind album in alphabetical order. .TP 9n kew shuffle neverm Play Nevermind album shuffled. .TP 9n kew list mix Play the mix.m3u playlist. .TP 9n kew theme midnight Sets the 'midnight.theme' theme. .TP 9n kew :: Play the first match (whole directory or file) found under A, B, and C, shuffled. Searches are separated by a colon ':' character. .TP 9n kew . Play the "kew favorites.m3u" playlist. .SH "KEY BINDINGS" .TP 9n +, - Adjusts volume. .TP 9n Left-right arrows/h,l Change song. .TP 9n Space Play, Pause. .TP 9n Shift+s Stop. .TP 9n F2 or Shift+z Show playlist view. .TP 9n F3 or Shift+x Show library view. .TP 9n F4 or Shift+c Show track view. .TP 9n F5 or Shift+v Show search view. .TP 9n F6 or Shift+n Show help view. .TP 9n u Update the library. .TP 9n i Toggle colors derived from album cover or from color theme. .TP 9n v Toggle spectrum visualizer. .TP 9n n Toggle desktop notifications on or off. .TP 9n b Switch between ascii and image album cover. .TP 9n r Cycles repeat mode (repeat, repeat list or off). .TP 9n t Cycles themes. .TP 9n s Shuffles the playlist. .TP 9n a Seek Backward. .TP 9n d Seek Forward. .TP 9n m Show lyrics in track view. .TP 9n x Save currently loaded playlist to a .m3u file in the music folder. .TP 9n "." Add currently playing song to "kew favorites.m3u" playlist. Play this list with "kew .". .TP 9n Tab Switch to next view. .TP 9n Shift+Tab Switch to previous view. .TP 9n Backspace Clear the playlist. .TP 9n Delete Remove a single playlist entry. .TP 9n f,g Move a song up or down the playlist. .TP 9n number + G or Enter Go to specific song number in the playlist. .TP 9n Esc or q Quit \fBkew\fR. .SH "FILES" .TP 10n \fI~//kew/kewrc\fR Config file. .TP 10n \fI~//kew/kewstaterc\fR Dynamic Preferences file, ~/.local/state/kewstaterc on Linux. .TP 10n \fI~//kew/kewlibrary\fR Music library directory tree cache. .TP 10n \fI//kew favorites.m3u\fR The \fBkew\fR playlist. Add to it by pressing '.' during playback of any song. This playlist is saved before q exits. .SH "COPYRIGHT" Copyright \[u00A9] 2023 Ravachol. License GPLv2+: GNU GPL version 2 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH "SEE ALSO" .TP 10n Project home pages: .TP 10n \fI\fR .TP 10n \fI\fR .TP 10n Donate here: .TP 10n \fI\fR .TP 10n \fI\fR kew/docs/kew.spec000066400000000000000000000021021512074754200142400ustar00rootroot00000000000000%global debug_package %{nil} %define _hardened_build 1 Name: kew Version: 3.5.0 Release: 1%{?dist} Summary: Terminal music player License: GPLv3 URL: https://codeberg.org/ravachol/kew Source0: kew-%{version}.tar.gz BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: make BuildRequires: pkg-config BuildRequires: taglib-devel BuildRequires: fftw-devel BuildRequires: opus-devel BuildRequires: opusfile-devel BuildRequires: libvorbis-devel BuildRequires: libogg-devel BuildRequires: chafa-devel BuildRequires: glib2-devel BuildRequires: faad2-devel %description kew is a terminal music player with a customizable interface, playlist management, and support for various audio formats. %prep %autosetup -n %{name}-%{version} %build %make_build %install make install DESTDIR=%{buildroot} PREFIX=%{_prefix} %files %{_bindir}/kew %{_datadir}/kew/ %{_datadir}/locale/zh_CN/LC_MESSAGES/ %{_datadir}/locale/ja/LC_MESSAGES/ %{_mandir}/man1/kew.1.gz %changelog * Sun Oct 05 2025 Jules - 3.5.0-1 - Initial RPM release. kew/images/000077500000000000000000000000001512074754200131205ustar00rootroot00000000000000kew/images/chroma.gif000066400000000000000000107155341512074754200151000ustar00rootroot00000000000000GIF89a\1&4!*9.2 I A .P w  - $ 2<'V(I(= X';;6L(".*g']/5_@Zju!v!}!"u"2"F5"p"q"#$$YG%%O&~&','Ne(=(N6(eX()L)Z6)\),5;,--./ ,/"9/$3/l1 12D1R9?7@g1@A_BARA_AAAB B/BBCKlC]bCDt6EF^.FmG<2GH+_I0IJILRJ:6JKJM7=MnMOWtOzR9?7@g1@A_BARA_AAAB B/BBCKlC]bCDt6EF^.FmG<2GH+_I0IJILRJ:6JKJM7=MnMOWtOz $߆~@!~Hhb^!(r A E0&_-@"{k@£t'C !d@pUp20;AN kp9(i28.0JcG34Anl83CCҴdxDq̖$/,pE.47t#:Эr+4+Я+ .k)Q+0@`:J@‹@_1ܛE. YȢ+<0K@, ޻j,2,@)lL " 4$l<#Z+ގ̑ 3,9Q|@ @)P%!=PPpYoݵ]A`5xGAxM4,Ap!- (D-Ѕ05 =q8׊K_NS83?`Z^>㗷^,ud.Çƿ#PEAڢ~DNP:O<׹3?}xFW%8  p M0 5h* B sѰ6E`r+J Ҷj ^ABPt?| B"ps+(:&Ή"B>ъǢbD9 @ "Q;ꐌh*{H\ z|  DM#:bERNEPP Щ 0 j ] ‡/z 6֒p6.b $gW*i̖U,a46lNL$g:Mk^Aڼe<%&i,hW)u?TM+Q]rmAc]x$V|/" ,hʥv|O<_s.Q۽*r]}׻|^y{J@[4/= Xsw]6 {;Nxvٝ:G ף8}/pm'R 'u1H[<7VO:R4@N!@,g!D4B WC<ղx}/ak1+aͼff]3OIJFz}2{ևfiUK>!|"|;dlZd=BiB{$GBR96y,pZE'Sr[hv H1l%(fvrq@pmNGCD%@Ȭ7 @i _78׻· W!298AZMHi͒'^@TK w' ͽu_G´BkRcΨ *7"-:n7oƚn ţDf(~S1Hk\jXc{sߜ>p"_/{I޻W!hD:$ՐEFT|~ϋܐ9} sp7(/ѣZ?ׅB޻x~A'bz@pЏO{E3 #Bbx4! hNF)Cj [P@qeIU0 14`OA RrUU1 @{!ߡ8Qhpaj0f x9a'8?qU+k V%UX 3h R5 g5VW#rAX!XF0"b@ Ɂ!X@ #0o@ <#(0SEZȅ]('@ ExU0Qe}&(p`'}'z"(b(IItX Jn u(u c }ep Q/.D++,Ţ,,H,B,b+Xf(0+ X-08-T"c$1.t.$M2..{<1ٸ3"xMOlԤPzZPs3B Dٖ ؏0 P0;2/{]sKtp%V6y6j#6!o#PsÑQ6:5b<8W#@\;3i|<#|8;c;F=ɓD;;D8A83:KS34kzC9;gdFqt,(&'u cbGhvxbWx˼6vix'wp=7w||\7 lBkT6zc֎BF4?j=>:;y>N;};]<[ٔaI=bm9 \i|؃<S\ٜ]jE7U3a_ ذm{@)ҽ I+XÙ\Bq<죝YQJSY޻ɜ+EJDOrJdQTRDkD]G4;!'vWF&9μDI@i rY ͽ '{ j~jzF= U!r8Lg5 K!Z.P3t9}+Ҍ+m['},k/zvV IA?5nNF- ϛ7G6륦SHNֳm`Ȁd6 P@W3 l@ #$ZJQ>ZڂJO[*aZ밪Xp*cL>RIp>?wW̫\2a^ǭ\ľ%+t=X]kKa1b%Z|y;XCeZz_ZG>;#9NP'+:&f9&`^KKFv緀MƳk@m0 3[M״dfUKFbv\fsfqP<:s* ~fN̪f&{{ɰ4`Ͷ0l 4Z OGJĊ+mQ䐻Y:-dnCzgWRMul6J0KA%A;AA3BTY9w>bd;Njr8q{s6g#Ɵ75; Nuj@SڅDG87A|\X!G<9/9{sd+f;:<@< PB >QD-BDg =~RH%MDRJ-]7 pSN=}TPXTRM>UTU^ŚU֞ + V9>P0DSB1 (ˁ)~8;1&A xB/| # )EB p7޼ " E\mճOw X0akOHŞKC&" Р" A&x:DBAd2$&% VŅ`</+ `eH$ ȃ#XQJ`*G\x+5#"V\[hC8B =04>q/j/dIA,d"` M z@@^hHj 5 H2,! l!559 Ԉ պn%TUd55Ru}Y57}v QSM? S># \bЌ*jqAZKP44Ti=7zN~UQG!] Y%stS:%T)QhjEJx+d!3Af 2qLɐfezȆh@`h&P`]E` f& q\: zHl, D즗vjĻ!(FPZ )E@!q ܁x7r Z]]'t8  tg7goS>!?X9"h Fgr* 8 F>yB@-\y2iro~ `T_H@} t @:=C* pdA.^t> ~(\ms $7Tn"2t}&UބT`D1q l*nz%b<| Į2p"Ǻ9*@4-jQWU8EU AD Y7я䣮&e 8B@Q2 )%*OU6q;$,%O䊧[+eBvA)+H@etC[ I(H@lLiBN w1*]T*o= jU*H8ABcK"<`sAP;8d:Qy* I)w9]"h6NJE Mnf΁EDuQmeΙqMΩ3dm Z@\RC>m  6H c\U2BX5]T0`L!J`A@4A~"A$0P`:Ej xHtЉX*: rfθ.d1:P>TF4Q:!VsAqz@^c.;[܎!-t|mcX<*ۉ IWsCH!RA4}F*QB&$9Al:!Z81iaW̗ +Yr@DtkBa20iEE"`ULv*-q^Gnį85XDz{]p7xvF6v;Ϙtl|c9 CNG ɤ;f9; Lys(Ht(dZxh~ ڑxdVFbU6(e@IWaJ!4W/Y+򤣃K& =EhQ/Y^ăG篮].%* Tβ"}bwཐx#C}/zFRBU_(DE#![zMԆ'i &NS(75U{VSl~thFѭoU7.$}XR 3}Rf߆۴#8iƂ4W[uT5Z-Ԭ508h`ߪZdCVjOu-MJ$i ,1SH MZViN "{s @ĩǮe;ҁ/ݮU]ۅH71Y3br=(>.YxA<OVz+0s_o6 2=7}VP_&jHc=w?ue3.tqW~}w?~Gտ~ ? _8!EPa[#F1:,\(@TQ4@D@%[tYӪ@z \ 4@ ,m;4ӌ V(i[ 0x'[X(8X,|X 8??0A`Q  @TN04ش R)0+I >?41 d@ BdA A$DikxYpIv#+RI(OVl)(nIjiQq/lE\EZ^En [+j9[ {2錕 ש3s Y[2؉ *]ֹ<]]{%"裈۵1ۥ]10"K>T3 ȱqP鬜E%V)P 02[2X]%1>ӌ3yp%1BRi1F )`ρ0 رթ` ࣸЍ N{Ms5CUX5vH۴sD5@ۿڠ5W#B})Z`U{4M`BJsZ,aX1FIЄK1^V6VaNz8)87c08;ss:IY \t@ETSWmG[eG]7C{&kdcݪd8SȸӑSIW,JqKIKIuZ*}Xe^>A`㗈k3͛Pa6cEbrf <#:PQ M pf* g (!řlޜHBq:L " (NH[}&{XLcnVhKhf(>h ݋K i[2v.#2 #c%^Օi&6FVfv꧆sjmjA] 8\CA-Ʊ6kEkZKXk(xk6x? (AL 08XFHLE@2hXʎlEFL &A'k)lAL ?:0cCC6ƪ)٧-뼐؞hÛ1hh:D~n:n8D#1.jLƒʔ8MlփUSlEƘ(x֋E=]PiFdhthyhip=R7'1Y[cvim " m(D쉳¹ ^u /K!a.`2M젛u+k !' 0#OlDZ?!K%G#5R!$"2\_Qo y;͑QCr"hUѢ'"v Pu/?Qz z}ґH=b*࢙wDy {wO):G*$ڎ"һ/:.AE0R;Q9X6c>)n69i.N)|T@}C-~*ԕ@}p(gŷ}TNt\uwmGWe~qI XjH~\`GGrRP]dN = ak^v:|n=v,`'Z9_Fs.Bd/ "9.8,i$ʔ*Wl%̘2g%mY3J>̹& 6;p@%F8UQQ;%-R;Œʵ䃯8L}:93Uetkן=w<(8NB( ALt 4ҷF()ug֗PkF`}F Lg8d 0 #*ad6"p8V*'ӽEJ1&Q~kJG7Bd#&PB?2) H @L !8 $D`)@0]!8"%@ݙ@C9/r^8%ݘ8HґKH2HIJ&x6U5Ł'qХ8AtiUU>acNԏ:$[z~H9xoitꉦIz>#lN@9%#h=PzyaehTժ덿AV%*lLW@w(@Mx(@-p7E 耞|M Ntqo*n}v_hٚ  -n@*)@ ms`*@TXS0zψ5Jp 8'Z~>>DžFy%s\SE ?yX[DFIܩG*B9ѫX; TN %QwĽLx )'> =P;^vGV90#}3>>1AU̾?a)o·#Kgy"[{Yj+du6(>h6پn @ LQ$egYЀu-j:ޕi`\@͂0L@ P߄84X*T$*470*8(Nh ഁ* DPD@ WI 0h٤E & @142b=Z! 4UL2GPRwxAP<:\] Ԁ8iI@Z``{/Q`y~a45%>xؘh ,!=DV anᏖY|Jۘ͊'(9 yOd IC2)LIt@ i aAh$qu@=]*᱅χOQOO蕲R.j @* [jPj$5e*Q AeLꮠDZZEQ*Į ^eUd*+.XPk؉++d&.4.+͈ԓ\ʿ,&.,ök3a2IU H'Xy\vWK/Q2aWyS b5MAI5Z6+Iͦ,5 r}$^Y`-Ѧft/t=@>GY}k@ +AmFH D0BiKKeچJ21xImݶmF-ۦBFm"BH @$df<[E>5RJZ/Rǂ pzuLYY@Z A%T06A,~goFH(%Vbb%>54nD'UrS ^0Y- ْf&oqMpo p6$JPWHW/qHAͰ>.zy'ȗ~ 00T`L4Mp sÅC 0p5006uFX+\&,cC.:Z8vcb2SVX;nx4-+fhp`Q,C @3G]"2\s,6B{ȑ$Z`џd[rM$E$_JZ )q zڥȫ``LQ6JJ D`ʏȚpګu$\Fz}1%{HwH`@@ ʳ)e%]ٞMsǵ9s;&Be=Vcqf0v]<~]K1^ %,\:wגwiC%,B u}6iC֣vlvk6hg nAHno7p˗6]g_0=JwtMh0ތ5Ef}^ x Pp"P?)?S<@!".*>wiP;i>?R|jI[@JX5@hH8T`@C!F8bE1fԸcG0|RC%rpǕaƔ9fȈ']޴ˑ;5S(6LjҦw|sOI"M*jS7V iâ]sTm[oƕ;n]T!j(J7~ncǏ!Gzu%D§F40eHfpBA`Fy (}Ɨ/@(I%:ID"(b !va\,!(. +hP@"``"ȃCAֻπB `>rd$`e& A0n LBE\X[20`PXbdM5FIhNhSM<|sM9  %+`%x!?AIP!0$4 ϡ;#ϼ#̔IMtJKR-ÒJlFXi\ve0 I+Ek)B\\,r4*dq$(7/e\]05!3ؖ}x$@<>Iv_4=xc>ST`8s1j2l\@iB@ My0e4jA!#T 1Xq!6ZYi* `^\`4.XcH'TwVƜe!;@ikYe4TYFJ,V7\ס5w_xc`i}i": [l Ej7L`mh t uJ`o< `s= [l9+(V| 8 i~o*ph$0%(``JF䐳@P} orA%}?g(P$0JTb?V)!ā"DAq`h:P@H(  @9DPYɸ1^cŲƽ8Tbzr'#4q@8!7j}_ =`C合( P{aE$>D6FwPy :`I+yBDNdIҕ&H!d)0mɔP @YrْR)PHVR˃@4HO !:y`%D'=N '>INz: Aɂzd\I9 iJ(.J fT"тLH|Ń>(*(@Cc2K=&2e0Zî0r;deF2]`6[* FGJ=eo.G9?a¯H vM4>hPQH_jђWu +P/ԉv1+m3th=5mІ}WFث<ٻ1=,e/Yɾ#`+ Z6mg,Rݣf rx+"%\%|/iola絀k Pza֡F{Ё'~8@*8$ Ћ̥$bB'f"*V!ɟ$(\`!z$`Eț>blH`bjD If C 9ą #E BB p!P<Ĕ"!4$|p*& 'p$* 4b B ;4"'*6 Qgn}B'8+Y.("M,>b)$"H(b)b~%0%*8ja#zQ,)Qvuoqɱa"/ܧ//|ޱ[ܱOK|t oC3!>cE4Q3,$4#b!r"" Ne!I$M$7" b r) )0@Qr'y'}'2(%?=xR >=HRVSV\e? P)88GDC2,!$$Df+&+cUn\```F& AN2.gf d2V$J"M<@ZX(WWW%`!Z.AR@LLP3N$PH31݄4$5LNEP!1/J2s3ǤLDK!0!Rpe*c<+SESB%4C=S#@ӢdJDZ 382 kR<`.ԼHI(%#vFPje= \^@t'MP`ula"bDc4b0FDa:T2DQ!deΣet`g[ `Aj`g%LPA4 A``ft Ta@ǧs&ytA*p̦Wa]&n&HKq&oKA&MM'Ma a&lԆl`lcHH tiv%P)rjEcFNJ]`' @G lmIU4J+! ܀ziBzyzAj(H5|HIu,("8#:h*hZ)ڪZ Ĩ\@ \c$u@b2I}X+_+`&B%';荂Íh"64!H6(c`I6$ `IGT~\SB>gW gA&6B8&}4 & >)fiXBbVb&disU2d2*bI$DA:|VzmS !bʣRH %jKv K,IHƞ *$נi6wr/7IAw` pl_ɦVi.He]pop!vI꤂àd/Nx62EmS#S[]StezǪe9|QIDb"nF GJj&8uIx&Vypa$!z$&r$K8KϱTЂ!B9&ȴN jfzzK8zj  AP70f z]8X. U8"g@.<+9Y NCϐb"-9(ژ-/NeHl/%cL&@,т.;Dǐ$ҎN.ˠXYX9ȡg.G`0Q(`hc ܋lFAgW-S↭Wsb1^cIfhd^BG_'$ {b,҇!:`V.傈!0@tx "Z" ްޤh߄5I>f+H`RM .͢9">p-zbB"HBVZc"YT<7z>Gh.NrxFz#.aTl`D󂳒EInyt"J9h,.kPٳ.lad Bo{':p.qk.g{vF7z/`n!h@ `/1r/7zOvK{#'/`]4~Gfﷸ;@FVjxXmKknF7&Yࡥ׍))BQ\B*"t0@xCCX %l}~ȑ-eq z+$"-~+jۜ"l)=ۡm_1٭ͽ7(t)lb=e¯1/00> ^1BcB !>B!u5F!U!5#I9rvV#0('(~%~Y^(vX}&&unb<[~艾(Q"Vf)me^SSbc)l ~D2 $tr,R-˒EJ-ٲКs~E#>D2a!G,7K ēK΄P&LFP<41_66 6sjJQANX\_7wQ}Go_sJ4 !M5 0Y1J$I맒@땓9.[8C0^@@1A@{%%3L>/4 _S"^V _j F iXHpaÇiLa0A_dPE JFIQRr(@Z(t(ʛ9wĩs%>*A 0DY䔔$< Eի!XbA|a׹iP:X\ E7(\lDIڥO 3Ə#O"dɍ3+B8yN3Hu>R(6Ai`M ` {0 VU+iۈ  ]giEBl*p#AYV% Vj?} 'P@c0-.004Px !L dgHG:%ACp6 =JN b.c2Hc6ވ#@{p@pAp`#PP!UF"Fr~G"* e+x%`m9gIf u$ J`eN0Et0’FcC#iR`@mZ}@A?`*rjt{6e++R mRVPy$y 0[hRP(+G&@R#(F0N2ldB֪$ NHr&r0:@(TP?UUZE2;P;l3L-ɥE*ߨPG;]gIO ]M@\R%A %iP] ¯%lPj7n o|aî]a}8l㊷ Ujܓ P4<לP-c+MDq?jwzʞn6v)x`k~?}7&@E-󭤑EAiEu}I}^0@KA8=SM(RdSK}%@)K;޲(x4`;.jH2if0'*Ƒ ЄK*! sQ Y)G>b(\@9A8D<4Q{(EȄ)Lsѝ=l^Ü:"F(D?铏>*q^U<.iPxd H6#u\G`5街Y#$"F`WV X<@V8A:hCZJ4>ˆi@ p q3HJӉrF: !TĩN_*BE)O !@THE`LD O3-(QҗHU8ye3 \k'J [1'IKV,t5W뙈AJO' &i g?#=kI: J[Ҋ? %TX+]Pn{*}k]VlQGOdPft-i?ʮMe*Gj J$m:iL$y^b.NdݚB_G{`ζQy<-mkS;= xXPꋤi81"5]ݪx) Q4{t K2ݱzVCDn0 ;!'28hP@ш#H*UTHE3/ یzFHjRғy&LQ=t74K }C72;}L6' 4[ A/ڱ x`{1A A`0 5V!a AB)|af.p9|"+EdbxH";q,ZI6q!(3m؎&9$(n7>ۏ9]2!'rq$'JZ $s:Iok}v T7x@XPi_"G'^ *:;"5`$IОulNڀxp|xDǗ[*5mI$P Ꭴ@vxm c4@s, w@[ x $4׽h@ # k&{iMaT]Z\«?6c}/@ҷ&e'J;ȥCWH]-5h\U_;/3.(n,J+֕A(CHEX$ՠcG؄ĄQnM8N0" "cz+T@`hȆb+PgDX~u{ȇ}8J `m % Ȉ ֐!pAaGX >r 'wh"6cՋpVèZ$0Bp`y.SZ)V3f33&]uՀ(Hh爎bDU 0C V55J e%S`(WDLa M@MKJMnr HV# +r M PV%5T*e # Xy|@ `JO!I M&ЏR|@ĕIM XM)pfA tx!e`PCy!0`p ` pYq (|ٗCYU` U Eh 9?ih5QP7Bѐ?Y V5T}hx֓7gX"acb y Ch8pWX $WPLJ>I POA >.ц=@p4B9Hp 0" o%`̡ 3-z/p1JP?pA|!rK!0 {qrq!Vj!|rJ BxqF2AaG([!; !!Ym`  @H  "(x#@Yb(F$<.]pb$M/*'K}+bb&3f&e"(ƥ'zv&t@2K#~B(`r(L(+2.83|w'$*R)}$^,P(]0$YG$/b++/#$="+()+2)24L}ԯ.1pb+`P*@b$ `P"%2$yҪL+hr\ҫڪ;BbLB/I2,"+.'&+ [ )O)j)5/33#:59;U;Q{3<);j/B/-$55VqT'`Khg2:r˵7;R[79%87t7796pzF8F7{3y89a#7~c7l#|r#<۹9~s{6VʧCfdVjdv۠᯴i63UhlM݄`ҖuVmG@^t#mýkm.P$Bnlӿ rC T 8p/,,j%|Cq*$ PC!TrܐI9|<|H-'oB6\G\o$!|X{17s?mWY#?fsa b彐H9 ` m<IHvAxwf7ww~Bvkvv7ާ`C8v`gmGvzW0](|v<6N|{kGyyθG:%y'z^y yz6{yz'x˷~vg}z7Է#O+A~{yrCGZ#z{Fh/HoJ/0UU(KoB*x̅& x] 8j7QJ_kOB vǂ.:hu_3؃n_,fƞHU!_X !K[h ]8zR|,D8g2T%!pOEmNߊn XC χea"O՟2 +vH"9-a"?#?(]%aiws/o ⠠ Yy0X(4BM! AAQL! tB%4OޜYӦN)vÓOHR;~ i9@q ŭ^B& 4̫Bѡ ЁXP ZB_h>#gu@y1rQ6‡ؙ }}fuo߿ ֍+* $J-o ڷ=hDp `$ jdUZM2=psPۣ4?b~: VK"`W q\@ lPX# BoC;CCqDK4 HCÖۘhFl FP%aLZd$c 3ȅ>1i+rq>Ҥ&a(L%TєYd  х F= 7AI !?a4jFH@{Z G .gx IG?Ze XW}VbiXUWcU Z_SXulg?Arj@pVmi\Cfl?z "(s)X9 im#\76@yJF$.Ǻ B6YwFA zLGo' \F|u(@_&>t-ZxD$Bx>&$Kl"40EQ|6%bw'( 96&(8}0=>Fo'H\Q%@F>d$6F+~`A#c~`[78KnNy|\F447mk<0nyxwu;&b F.W8Jlc@׻pT3(Il5פ!`ǣ(~@ ooEm@ rPk["I3|#qc0d~CE)#{'~Yܒ%˞Hh3{˧@(zozC>=~X p@ݼh۹z0x%O_7XGʻ[Bhۨ@؅BF@"jDLB ҄)hk@ktM߄ԔvФE\NlN|NΩQ7RdH ̇bMst%NyE8qz0L Mzݤpn PP-Pl :)I#J!c0F Q-Q=QMQ]QmQmQ VW (a&3!؊ dHPR^`:0 0*]p P @!@/%h0XӇ8] ?ÍE20 0w b bH(x g r( LTN=HC[20PླྀSX 䀅BUFp [ ߨSM"RTV R0," 6E#lP/' 2@6`a}QM"YqQIwݑj(Eᩜ@c#H^Ԥ-2.b;iD XB)FGY WFXY5xeٔI؆?p2o RT)S'Ī( ^QygA$V镩ZZ`*UPz~1[ar,`IxPz۸{ w !C:QAQ3(,X֢ )yIs(uOKH[eM!@!9 hőx! )!R͜ u!؁^܁! 0Ѡh] 썝Q흡A r굠 z^# ع饀LA a]q)+ҩPYAY` AӜ_'ىJ!qȟ]Baru]2Az^E]q; Y: 5U"EBiZJҀC'+*?3: E#dX#2< x㻔'6&6xj"l@,*dAn&n&+:$bL"^&^MNNHPY$[RXS%$xNdKR&_A&[~3^Y&ZޢG"w6v ?s:cN tzdc=iDBul̤Z>BZGf0|] ]9 Vr H X%(iF\RAėDꭙX wq.x66r"]q>*^QX,,В`p$QYqCnTk*RViZj0|R++*dN!C74PhΪi~j̊j8l,Xgg\ܒ:\ ")ȍ)"A _\T511X`=a33 2I2)(Ӳ#k21X1]?#3D{?X 4h16o89K1 Iv594O85xǓqVrp'rfMkȰֵ ^KaC cd k6ݷc6l ssx# 7{6v}sSr.rj#;10Gj 4t37K\uʉ#j88%>˜"A*{'j;Cdks 6ҁ[;uӣ3k7%r̷ u S5q\B01̵]ǻqZ: 8^`T#'ȫ<@+9;.܉?a;;5<=u{>Àv=ozK\">Kò. ñjܣ{nBw>K4 # S=C??5]/k44JhUAMsP 8+C,B÷VXZN=>#|g0^@,QLzn6Wdz2bhTkA@ΎD9nr:עlG7E?t; Igt~7jlF^r~#~w~w>n'G xɘds,^J&GJGy$픤 X%H % J%=8ˏ~($Ht)ӦN0'УIVpѬV(RBڡ=B+w.ݺv;֥|ˡoT@%'K5qa+Gf:Xd7E覔/yzg՛!V[ӔKE rڸMƍ4fƫn8u [֯cϮ};޿Eg ⫍0ϧZ h` ҕj?  GAE! 2ؠBҥ@p RSP \P $ ゅҤnCՆ4h.Qأ?CYd^=@\_E+ BPb!\ Ahhgk٦oF-(+Ɲy§,F} F"(J 4L(!ф` D L` / ʁ*( 9p&# + (P+ ک좍zPk'((0D­;ҺmVm!&B"Jq%@8`#-  0K@(#аK;s  -3hLǰ yʌ`,FڮL1+\Dщ( DE@˕hMbo'!@ZlZ= 2I4;į~2BdJY\ ы !  {8轰pΈ{@pA :$L>4.$E-40`T,aJ cزy_G+!Lkys.L @S ٻ@_aO{ 0@|+ @W 4`u.hp?TtFVQ $A`\AaC / yx  aaK7Η0 A"0g 'LaYh:7l#up,X$#``W! (@QA4dQ 45^(`Cd_ ( RC!I?jeDY#\W)OGƐ  |qU{ `R*zٔ $FI (t(sP0 Bp("p%1O*OScÁ'qv dД&5@|N"&lSFY(>@q6t@ {⛣p-H ј2H x!En?@"m6D] 0-(sVCo厂sKj{}nomTݺ #"={r^C@Hţ.AWY!&2yy~/01KQ2FLa/XA/p?ţЏ>P<Zs#LwUe==CA{BEc}@l|(U2UVe,RM@}6Z2,Oh=C$6&Y'E^~R'qO \O,Ex# h@7Kxq-#D| OLcM@ aqcx(&YbxB^IPX$Dc|LIDyC,@p [CTiJ@TĖnMl@VZ JPDZD}ERgEW șIʝBqYW@'SF|FE`4j ƥ~FgDtF_ n*g!`ƝRGpa|sF#FEr8tx*Td!cLFJdت".2k><} :G|G}>VxyD9뺲k/YzÉXC:p Hk7#|`īQk*2lLq}E n"O408H~:t-Q@\&&$Fa~:ؔV`JUI5T@ᓋT/OU@gGMUDu5Q(Op]M:@@ztI4U Ds/?X-tB^Yt5Y4M gTp G M!1QL|ȄA!ԕ2Yh=yBYa:)FuX":iF2uaVaUE>ӓС:s 4nd?Rr)m ilxYpPE}auD9}]v2Ֆ~ŵ[~xy6u c8sM{E6sWl5m>XRDA$i-)uasvbmv]XEvDLX-m׶A(6c6ztMc4>e_΄rEr3ŎrWT6P$YAWd'=wS|/[kiZ $H<\5L܁;:{p\ɅO+٣i=yڸ3ܦg^,BwؙH\M͙~YY. l۵pCñä-8 βy[ gEbfK[qrpxqx+l>([>PWP2l %C:TgZOU_%25SQL''GROowe}RI \ \4d<0D(I> ٩zأ.r MYz}?:&d @ $L#1 n)RJ" %_ Xe$p$}$IM;^5J$)eX$һG"@$,M "_#MIؐ M]_`~T" L% `OH{<ϛ˜/: w+ j_H)5Urz.cհ!շv3W ơQԁ4 bVh̽kda՛qd!'W Ra'r _3}atf`Nd(O}V)kط="#Mu=$6s^Sj\l ңkma $|GaN9MNCK֤{`ʨS!䭣TjO%=R7-#@$@#x]\4Nqq{;F{FBt_ ^B r &ƎCrHa!tQ"K+ ĥ@)#<`@@D*h TxAlXcɖ5{-XB8+`#A"Q6|qbŋNf[ H W YƬyU5qј,z4zAXkG#u,.B"ARM0 ~^7uZ9g[ nA g`=xMdgp_LAzOc_ߺ+.=E\H`f4KVǴTMwb yXH~ }`pṕ:#@ $p(f]x1|0e grК}4gFd!YzqqrӆkOK,<L=kФs<ܓ>ǂ@ 8@x@ *`B EԁOK4QL+HT9V:uJ/U Q8]GKPII60T^4W]T RZ\'HA?U]e* Je27SFE\^K06vc v_7WF\tx J(\@v߅T`E>ETPB=҃ U-U \ϖ hRgaf35g}h?0jS=hS{#ZCYYz*Y ꦹ감vvY˦Y뱡>j~r7 n鷑;&{[ ;Z-5߼enh=B@{bh{ j$t?a8ܥZtӁabVk@v\H9ꭿ^1Hw79,!* q{yE{ė>}ɰRi }Q^@>1 -S!UBAguB ĉ킵jnALMVyCAXDpp  ` @QTt.,,GD@ 0#^q}5эpLp `q|b#K(LHGA(P4G1{12̀R2M`ЄpQ_lL@3 ,ό1D0" ,%(&5Ɉ`0id3Nhl:IEbqAැg6SsQ"ZlE PU"X%,PX4Qt4,$QI\`* [nl PͿaeMkt Y 2]#4 JJҴ=-ފw%z2 @ɜooEV>a/,+Tz$O/BX]JGQW0[;5R.R)^O9lE)s6+nt kA!/Ģ;JTnի>1\g١NGIly,ECeEOH)y{.6sbw%~g2h1ᵕ*2j5, {U7 A6r'T%˜4h{S݋U s!*v(Gӄf4Ok+A$ǹO~ZvzXQ5U?gp#@!DEnF@a@oBoDDAOAZDEXާ<4* (/>> r’cOb0`0,V0@L&$H?쳎/18"D :"$b$@(@.a8@!*0 ) >!$vi B)P2 Q MB Kb " 7$F!@xdm+BX%z(`G` `%ZB/a%`!A1=Bʠ$`) &0911| t 1$0%vB  :qUQ,*:A@Bʤ(  a ȥLQ+6+#=A"9R-P1p2/T2ke[:(ДFHRХRnʅͅbD4(Y<&F(l`\k&-<mr*UU΍(0fĦ%&K$e*Bp2d+go$&gc-''T%gi҃(UW%Ym&GK$q$Y~m2¸+6T + N%I<K&u %#%$XB ȁ%p#PP(`0 ))q)78 @88bIB 6'P9' )`B-f 0+$.BK9(:/sA"aQ'"q#"p"*Y1@#%?a?B%D$ޑ'(P'<".RP+B #OE_-)VtmZ󇔊J@HHTOIaB! #<(zA3DLcJaBtO4CKP[;210G0 G:`hD#:L$.A6 p.*5S%G`:5@D Lh̕B^LNH(]cF`jX9]TS%ST"_e"P6Y+Y^&]\.(5'W%xZfEe[¢Y,P.`a:FY.`B`YU`yF\P&QaFE`5aH^bEc]bb50EYbT %-P^olPxM#+ [,S5eUgg`WGogle֦p*hilj q~0vi+/jmk-2-vl͖akp2nEommnֈ :ÿzp[o',yNsoWsRWqywx¨bt0tvgrr{tH6tGts0^svkgu{ FÀ܁tܡw{t!s`w_ ezdw0w|‡7$FWwf~4WyvW~]&YR'"^~#&jPB(,JZZqĢf%8!K'n痃;؃?CXGO IH|M(iH蛚Ț)i2.) 8@Pi^A@IHɔFTɧxVhil `X}銼iz؉ixډɄ;iȊ=)h& jj8󸚎8  jjOr+R.,ި2jL*!¤$j8.f h)"mvYճ9vJH4ϰ˖HuoJ22A !yݙYh˱` 82>8NʤH˴ :7l6j붪!jKa4k*zaFʀ!@^9 kXïh`5 Sx{[*y%z2!4 `9'XpɚZsYK Ćd՘ 7lϖ-@\-zlzc,l֊L;g.[+$ɒMU MLv,̍Z(d%)HNJ-zm|m]M :{֬rrWR/w2f2ZrV-Z4p*Y4Mm[;&elhdΜϢ%mҠG9'.쨎X"4m*<ݎNTnn醦.Ņ&O^EX`!.FJ\(\4w@a_7*Svb3#Zo@dEDu/`G$IM4:Q OdMo4VpR| }E֏;joOjlU qNZO2RYRd3FJ90ʽnQ#3C?Bݭ0+/ )B Bq#)?ҕX=m 4 P ^Qۑ "AX7^2! e^y>߅'u⫟I!2 ?) eTDJ+EO* RB$v3GK0&0&m&S2'-&3#h2ն0N/,i:b(1;'%m2h`(V-[-a*|23*?nViBw_&em h1#S43?@ @rPTORJp.@f-R,:kֻ+nRq V0վCU@)8.>{Zm +/2,<\±F-)S H4 pT*%ٍ3 pu%pwR@#t*Pl D x@$PIsÔTAnD/@cv(ݠ TЁ@\ A:Pƅ{ o}u*z@xW9[;|݆0qq;Zsȸ} m$X 98L"F:$h2h| F*yLz ~Z@1F8F8AO|e&7i`-]b&%-XZ$-7y E`ep ( ;1>1 !((` WQ9өpS9xS5p1aI_:*/qPB(@$\ 8lD0 6Y&pe%=X/+7OOr@P'(tBlPS%"Uէh^*Q*VTu*SJH`6jUm7vSiUu` 0 Xa8SwGc 66k:^-PӦGiaZĺR01`HNd6L#,E " S 0Z X} V…l0ٛZ#G&xCI%;ClC+p[iTmwڀ Q2Fev]piHiBX v3)l)/8c?;& 6Fn957UE-0c(82iT. KQ@6S8p+:ф@sk3eÁ7?i^s2@ Ϥ14ނUhB; h(эvs,(x- @p (8":Cp: P )@A=~#t9pQG&W `=8< vkW:Xݚ@lQ`+i;\~5`gfMCApӼ` X6q[ aKA#L{>!`P@4ZeitEtYg6V <Aj^5 FG:΋Ggt5Sw]& D}qdg\ׅGhZzԡnsqIVz;b񺸜3n*kך6Z/tDtwnwi@OՒ`d.2}9'L֟डuh? C/g,Di*5F0zn(6JW"b%n7AuP&&E(Pp(`OBU!u` 8`h ˗`jr")8 0مG ]A> @'QfDeP#UQdFx65cYFP(C>JePse#J)FDr*R 7{2i!dцp EpSiQf! k)eЇkex0q t teJEX U3 00 ֆH @@5Q@Q )1Q#0= #65([ aPSPU%4VX F 0QT*BV:z{Ed$VOa rVkŊ 78IUN%Ds4c8xs37sGry%)76 ْsqF5i7BE/T!35I? 07EXjF760~3mniFxcnoC7I<攃E83 oD98{m]0v֔v9mZCG^9b42scvGghٕ}$9,<)dSٕ7r)ie1.!SqlH bH/(i95qxwaA"FY!A)S:okAjQUbɘb .paрXȜYY)!>9 91WA#*ss"|P"%w <ɷ|  Vb7W~}!,}* wbw!'^!`GW#C+55&b"x"!@%4*|a XeAH2(PAi5#`@"'„I(5]PЄ8e=!9I 1TS;Ru1â.37,;'.",>@:?s3Brs# w=s3;2/!sFZ+5YSJ6aD#(r Ø22C+1r-B/+ .vg1Iӫo50z 6M)CV5DJ@Ø ꧠc8S3l9hEB&Q85 z_;:c#8#2_8}E}T3dE eF>XS C>'[>?s>7<; >s6dV?BdWx<ɓ(SM(?JPR5tP ]z_ $zdXkBAH4DtD*TDDIt Ķe&B$/dt_p<_/ҧS;+˜31ɓ!0dG{Gn3FuF{#G;HGl4G[+[fg4k78Rۺ;kIp JwJ4 JdKdI5KLKKL;LKLiOtM4R254%PNNE1HN4DNM758M$P)vJѴI`p q 0M RTS [N*{YP1#TtE_U%7aUe6clãp)7ҬWI.d6dV&eW) WDJTElU. pG3sA2CslЦgl2gln6V0vih!7xfijj"fVjr?sw7-4-3F-kvhݲh`K=@РF1pfƟj&pZSHwkUoӱ-'rCzr )Ef qvm:px]ȚFrY(q} rF04m:BV77wxwww=wms]3tR7wTGڢMwtUwڥso7..0Y]W{ev/q7wx}ڐ:u\uZ1y`uӫbG8ezTjy)EfzzG{ޮwֻ}ۑ}@}SYPŷ->R"j'$*j} ΢'R$"7&܂92b(f)%0]X&Pא&.}3~'"1 p'3a@j"*".œCx$N&6\͇$] J0S(cF:REe|*NԆp PQ0 ߨltnsN NΆqSXN A2`h6PONT i4iS ĉX蓈~ȈSsь>!p?  Ll Ex{0|WS] xбXce TU-kG#3 )(8?yc&5)E9i ?7qzyj2CV96}(_q GAB@%BniWf&kz^?ǔs2".ElQ/CTc~mBrqxl ils߇ٝ%_ٟ>~!Uqw5w/z8ouA9QNNWr J^Og/@ $j=|0^ǟRz ^ᷢ 5ޢOJQ[NLzG# )b1*\"="#aѸZRC VU`EӋAilx %MR$,t @@d, | 1>UTS'VRyfN]͞EVZ4xXxă1\okp<[yKLn5tfѝJ `덿dA (V(?3TOA8Dʩ C DD@d<A@")c 2"cGzj2Jh4QJ+2*' (-K/KZMࣚଓ6<A,:9 tBݥ93KGvD+TPx5TQG%TSD n*IdQ d9S R]uLUYsWU,Ta! d[uCYhUFt'(Zc}Y46]ue]w?ݕ0<+:W_8HNT0,>@w5Frئ&c?9dG&dOF9e2`\Xh8h( qeg`9A!fbF8:i>nZi@Zi ib2:bÒQ2nE^8%iFa[ P X 0 V@PW0`ORP`@vax9&CO1Kڼs abp`e(dgCw[.~F'p Yq#rP,Wu77X$“dl@ m@X^, !M p<;P 54 K@ #B(b%ɟ?~+ItZǷH+!K\@܈2QTȃ .ZId>LGD0bB,V"x)vb8PɈ$Ë8 .g n .,ԡ4`@1‹mF0 0 pp('"W‚D@  [4h/ᑑFpC&i"=FI!#Lgѐ1GBRd14ēpApYr ` I ^r}J' q aв@ I p<𠐨]PC,VG"( 4ž~jj=ȓ<Xt9*I8@<rЧ)PДP @@*(ru@ԡh@8 h-}(֣m@ P(m^t ЁB9ec8X`1yTYD%@ZFh(" YePQU5?xX9R +9[>yQh?`n\jv7!(PE{ӂv«M8Z ?ARWq Jh5UtI)x?+vBl\론L0+ gO5zJ͘B0a-$1]c_"΋S\_3 ̊5cŮ1L`/r%[L^r ԧ&SA5/ޱ[e\c=[C!}f@ 0@N=:3BԊ) M Y.ay8)}1`逆 yB-B - Aq$3*A£t zBБ)ȀJG|zD 6EH}/=Kl;]KE;;[Ž3Y;`4A<5YCKҋ{?3AC0k@CGrFp4l>[Kq6h678I68m 8){ x+7P'yK%+9xJ7Z7tXІB3Ӧh+L{K{@EH'J":(7ʮ,VzK3LѰ( 82  `4 d ΄ ϤM / ˘  :hPɍ!9 HpM2Ђ ݐ%3XͿh ڼ8-D,lL4<%!C%0PH )4%B5CEDUEeԐ!vb8KԮњTQ էRݚQXY8yaiM1șA^2()h!l ѣZRdUVb}^ W0>z^׬kUIVsѽ?(!93(xZGDG Y 2Y5P @'U!p֒V:'sF%'Beе$|H'kH"1J1}T@2Z#83#g"8 = `d Hc_$ 8G: pbk U1@M2HJZXQw'@Wjܦ[|SHȆKY2\H=P[L[aJnc==U݀% L\ɩ;;))֖JꨔBJRG39!t)x(7uE^'#[P9)d7*K] Y+*z+: *x3_Q+:`9*"r+F .$3D0.: XSJ- 钪>*:C0:+㰪(-/^ C/IJ-*t,*>9$.*ȗ&bc3A# 6۬Q*4/y pAh zA7.⼮󈀂*N.2(y03M0ޤLdK1c1ON  !Ce5 HU2"-<L23^4) :`22 2-s 2 hsee!c7{-cF_NI4jsVuvF+F 4yN 5@|N0 gr> }GHW35O 6\}HS5҈6h_`UL6CX8@A&lR$F V}[K: xCʚtd+x_}"q8d=K~۶n@ic2$^mVK& qkuS7^:#^~) Kޡ_(A9Hg^ ˺>(x1yܭsL6;b0>U4>YHm^<\Lmcſ+Fd:c΃0^U?@3HN= x{QP.=ۣHnn低d@س=ͱbnC;&_!=l43dAcwm?n@3V= @p7 XpnXp TX ,xB: -B&)BiB)X/B܊ʪq%gD7-T/CT/ O(R@̐|*#S6\02җ5 ,C:R20gCYBCaBEDD Y/J4A/0FD Y E q)IEl\|&mqXLmY[ŶCFXuZ٩Ƥ @ #m|otYz$P8hh'tuHooG7ZkG҉H j_HnGf=z{>͡dž km=qQnw(xhe贄pINCDnh~r["s[INyɠT+ʑކ 0녊P$n6m@6ꖨJ jyܣ~{l&:̄z ns۬Nmkyʡ̵~:Y0j.L U$G{ʌdpM|p L4бxTdc h#;T M C}C6ݠ$EOkE PN` 2xNJ}T ~ZOGϻ/9qw' T!iAM Oט ~)qQ#q5P%F/PРh77?P~,h „ 2l!D,8"A#r#Ȑ" "hTF\F/Fk\ (v{!#O@{$j#rq$ԨRRj*֬Zr7btc)AyihE W/}Ӻ0_3n1Ȓ'S"4BPC:ki:|Ё"`~M2ܺw7‡/n89s4˃9a]><,Gf*u.2ː>Xܫg{_Rd--(&5q    4QtC|`@*0(0Jx 43pBAL1F '#d6QxB#AAvFF0<'')xc08%("iȡ&#Hfgrhe&3@AXjYVE'DDE,KAiA,FAmVz?!$hAF6J0 勼xb+&7,G N(N ƾゲܠ`8.0 $c # XÖ@dl34\끱*+ .4\G5)̴ub,@@pA@X;4*Ji"'~Q˽+@VЀYCEpE.34h ]P2 dL@ @;E%.|2i|B!30k|4qev`M,357T54ALtAD4BC>2fb@p@8d4]P Nׇd =~\?O9hhl |c|xq,7MtӛfE@i2{&1IMď<ws Tg77p:  g*F3.ZĢA ч2]AP^Д&$cj(KE=>U#Y8NᯟRܺ@8XcP@9q.j*S]VjVJJ^mPpR=\mh`ie_mPŹҠ1,[р5TB-d3Oj8+e"He;U-VfZ[YbVK@Ơ5Vcm`K՚°E3]6h1 8v̵x܈t HdwQ$4թQ{۩gm~kݲVFoXA;a,{ r"!( ؿV8\X"), ؉0!)@ d02XlBФ @1$0@QZF K'j%BWRcEpPݜ|)"bF"3\$.{82j`)P@%g@={NcU$蜃:h4'Hb!ud@*M:DgSJPwD(b~EJ}l&#QcW фtwM+{3/L`kzvHiw7j}e>F]t۠@ t<ͻ[H;q[Oٌ|]so#mF$ۦۗr<ٴf-k1lѸ`p34c;1|l`䠉. M [o|p L_  6aD{ J\~2C 0'tGpf+v"b`^8zU~oxWf˄WՎ^ҳ^4U?8|oi)IVSUyV֯JVPVUlu+\ öAUm_ [W}oG$Y$dۭpaVkBjMWWjSԞcLVVY`D$ FLf E $h) HK *Y&. 1qR R)Ql!At2YDcQDxT@ T@ 8H|r@X!GD! ! US`fakd@"NN~VFD!jDrZ@yWx  ba"*qIBXbϵ=NTCTm(”A%mE*U+z+Ң&Sb.-$RbW"0 #sD DPFMN1ER6.1~#88BRXȃfY_NXHP^FX D=c`?#@nyfpBéRDfE@fFn$GvG~$H?.GsBj6)/(MMHM-LِL(&W08@܏᏷xOeO @ TD8Gfd@Q@uLO" @XF@#.8DFl P&Aʑ7@ #mP !eƶү9qYPݜE0='>CkHje|ƐrZ:-5&`,RžRž Thlt$f/9* &i1', ,fi,K59IQ9QIВšήϪTJUN:jҒTJ--GZ^E+1mIzSf l`df%K8V=_VES ŽXg=4.=wU28CU/L8_/$KLKý:y@hCDhcѩ̴qijiqM`h> @W#$)hTQ Po|>VR #r=s spmUDB4 6|/lj_:\0E؀@XYUuB"@$d .WE@EH5 Œݍ+.L D@ C Ӑawp8L(Zf%ICumzY$+vq J[1H1٬Dq)Y?s׭9J#vA`Fĩ)U@ Him&`Bڗe}ٟ0[DYi=5N ͭ]$t@¦,O[‰L"b2-F0F\ A@*R͜ڛ2e@<6ǫ0_1\5'2ǽHW@ͥ":u@QNmm7K[a2dCAÁy]сTya$IK١]X ]0N4I GHssW;h`ޟvsGQPzI$BT>X*c%6h4 @&Twp0VW"z0 #Ó{PC% 0 %0@&e)J;-\欔eי GZ_ֻfj"oftf}&j",jB<$`\Y.>G>"ΒhO>N忩 #>ע<AY)j iWKhNhnƬM] C2tPϫbO4dlOڡb3*oF D -j"ɐEoЮr@Dx@@@p<(AD0ppAD xh8@$K$ieJ!P@8RakL&ɚ4O(k"ys O ҌPP(&Jf *|1ؖ]*Um[(0 c9P>)pkB!GPR@ ~FHGگ!k!P " @4vs@ދPYly' 1'VT̡qXؑWi !=IA!:z9rVPPub`t\@vh`ve X&8ѡ8AK\>@E 2 l\ ILl$+@5qi5 %@^kۀGmhq@svH>ґ t%1m{(!?x& "t+w,PRtG.ؑ9F##!II{ő$*Aj$ `F;&Lgr4&0% Nj␧5`uB(e)LyNԦ@JRr?hIX4t]&E/+_ÿZPJ8%@P%H_JJ6"eLd`/7% R Fp8 &rie9|!Jd?h\FKN /HA84Uu ýg Gndl>Jaiv[P'Wڕd'3:h%`jY:HJ[ |FNG]umq[2 pC:h) pÐn[jus]/YT2 pw^񎗼5yћ00"J 4.^Xo0bxeFkP0 fsr,  QTsZ @M D/5?r#E`@@ D` 9s@.2 is۝G$GaU' %Gf2=X$ cFI4$ |f(<§#Y3Ilg3yϑ병 ɦ`63p)!,a a`J`H.M/,wI} s(S*!?# HE5F@;"H{o`9*V RG) \nQD6Hl٤di-`A΢š@0(P&Vp|;*<(9|~ ;aBѧHD m#⩊j@C%^%@&(if[ϗ$W,꧹)kW-+7 ROC̽^-&HL#@B} "/t3 5j4555c6d#D8DF"C<"=J;( &:&dF^l¤4fx xVP`kZhƼf#Fr,h!Bm 8")@L?#pO &&%ɔ Ј r@~Rstr''2t+&E$S ] @j@$juT$eTf,>$`xa, "$lle>MfXI6 0a>#ƒȧ+4Ȩdb'4(-:J,P//G^h0,,:,z(;2:˄R)V(XZ5]36[s6Y3ZHCL-aDZG5;b{L³s4~<:`!!`H`ኬhcē  R``bSf̏N##VoFh}+c ` @7 rOD U=Mq=Gғ.o/AHڰ: sGz闤H찘PJ-% 2F"1IlnQM&.e:M&D$ qFo)^HQI1zDʦ@jGH`]NYLJNE%'*jPP;9h1czsM-)&()!)vH C#:R*4"4*³BQ#:u2VYuX5 W;RՉRK枕\JeZ5[ub@~k鸬\5] &)"`|U]^^5_u_˿ 2` l4@k sTxr kjrlf&m$iZ%Ԍf!nVDgs2tR>҈ʮTwa&@4 Na+~`݋|_$lr rDֱnmrH|<̱ԑe٦!  `XvgFT8 6ceTǺF?:pl蚎ygy1 ny'N=B.Wb{ovs{{%|=D&new7Nv bgy)enb2|BEH'ޤt N_D`qBry :7ABh0o0 (O#N"O1z.0ZI(T=%"6#nF# @&3,X$zȇoB'xxwB:o!/O/Oo = fxj88+"B/N/-B0𘍃z49j:w4@M7c6d#DMCH #T5f< ap5RMG"2V\7*Nstu$45! P4*9A“c<3 h<L deLDR>*2emNOǮOo^f,<[# mk?s, J(\COHF/2 Hl #Ƀ-%7zGEzNsRzSV:vLmI'ƌfwbԵ5@$"ez*I!՛p-~Dqљ}2>#!RMNtW5⪬ bT}PB;ª4dV=*ܳ~&ʝ5o4@b4T db4æ7X%+VU~g|p;FlHJY䵕 `濂WVq,R :^+|`|dܣ%@\Y & F·꯾NL ^!پkF ܫ _Vqwa(`G/-~b1A=!L,etjt㦻 f} nffekǬgʪh ic=ROc-Ei;J ip&e6MMQc[x*}gJ׈dM9mL7cp">2( @b*C+Z1ƍ;z2H $`. C U-_ƜٲJVg!Bi|S)hT4DEDh @X.[Q.]#_H>01䉍W<J..+ , Zt%=qk׊rיg_[ Ȣrc8{8Ecۆ5҉OmU8Hȃ,\ՙɳ9q Jˋ`  4|a/縠33PUG/.0GB%m8ANTL2҆ܠ0R&b*nDQ5X#!8}@ApTL0!-%c!`=AkqAq&Tw$TXEEhERɧyŸeujʥY(q2q Z&%$ )k9p@Q@:fj^Zo (DХ >#QAtPv #pP@YP(c:Xdnk%L @󾛯8Pi"oI%hYdZ8 8'Frb @bV$2*rlr?e/zZ97\;2_s_.o1ҋ-L=4X>PxrVA 0Q_nh]m wr*qA*QU#gqѻf]|t]'v,~4ix{om?}̓$T |K;44`4oFQTPやv$, +"+!B* @ Q23 `D`x`B3 FH X҉M$JbP7zGD7#C'd*.]"H1 (]Ÿ 2/"S$i-gW ` l XcY?x %19Bڅ x-J`v` d@@a`($܂ &8`␆Gfwha|ωN ,GT#ikET9@KƜt.'AcEֹ8(S:h.C*2A12* I-R= W;H=:LzQwBt7ph=USM:hHkZ2Se*KpRRO2LN팓<)3ѰuE1 A }xóPj-YhX@HxDd<0u%_iURTdlbi(2 ;`\ER:d~ -% q-՗_ڥ,L& BP@5z r34\0t vbP6yo5\a-g9=@G8σK7oy%/rױo,_"fI} \ 4t"\@$( X`XIP.x?N KN񚈻~$Pq P, h`|כf̥ OFoH(%R 9nO$IMOb2G5P022Q]ג<) LfPX8  j6:l0כZlؐC. w ڝ*TAvIqДhOI Ґ^I1P{c_SEM$ю,r_.# / C+Ta@@ Q$HjDPx[|$# @Z@u` Ti .^c$cFHq1FAZ ȁ$GဣQb>q=0(?3Aaf;X Q?R! .@88hf @C8=  h @ |U1qY(Na (@ Hc wu P ?Ґ@  ` CҐAPPp ,`E"11"lB)k$$@$"-gQQ<2@*@$,ɒo(RR{'8).Uƌ'"(G(%t(R+(3S))X+xTnSR%'(8)X!+R%uU`+gbRni #(-hU&G-!0Y#&oP#Pyr\PR#ep"Q.pM%%V0Bc6/0k3/b0/ SiR2.2q^OYBM9FN14LCc3G]0V34 C4sPUst[ytK6#bjk$kY0D6Cy5g,`bSc,rh5I?o6oSg~x~7&Q%q7 Ր!8Bı_/>0vB;yC<^]s__OafUi=KFSf =(ߙ ٕ9$ph^t@m 0 ,Dh  p @%h 09`:ay@MMD@$DDdDt w t`CtP|A|Vs`&M  3 &EH\  >02Fg;utGJѥHWDIHYtv<0 xg(ij& tElI/IYPrUJz*ETC uDP>DaHT `Z=pZEW0LN }Ɨ Ä, @`—$ P|dVR#aGb$6O!oPT OV%oWU&95jtjZRP:RЈ%T &h6(>5YSz&'$h(m::(fU#fOOI%OOE T%Z%o+#(&=WWsUWw%M 2k ,dw%qxvDz.JZ)j5YY(DKxy>EZO;RP;JKSl:ZZ::veYg:5T{$~BH{D${ 6T{(B䠳ٰV0 g%\FKX ^@i7pYSSu Si[i# ˝uU浻$ѻ镻E8i,oG׺Khk9IP$慺! 뀠 J J `Z7B,7 c[X%2Fc6v %0,#0bKb02cz2˿d\c\2Md\"" fv$b%nPv(<&*f zf85nj&2>,#1$+bAńVᥬ Y\hi3j2imin'֫ݺcohi{ Nԭ$FƇȉ%d@2Ր&ɝɞ<CLƣҐ[mK ɳL˵ u5P%z& ,Lc5sPz4{ {.s2PzE G `ͺW{ll{~w`e_:\AB5[guYWd,\|.muLuc:HehjǴmwxp:zjw|zѭz|̫Gs!=ҭށLKTZz$2mz$Ms#ps9z<ͻ{p qWqՌ !0('r$q!w!aQql DHry { P~!umGiPc0 xXB;'Q:(H#AƃPu2<Xw9G$94(<2=Q=^RSq膽݆Tq9l½ǭ۸{G Gȷ }kC=i 4pxrv8g !ذ~wB@2@4'+;5!"TB)$"h2?bF$odO&)%^Ž~bQ4I5~&jLۍzØxTa8)('%(yUؐK)WΐD3+IB9XN.g%in5i^#hp)#o-gp1DR"Т|^-z`-ђ:#='2i-({ G907y/55&f05"RU],3W4V YL1D43lzd0xYm>~Po55vc`0gwaC{)^6f<)Ay0@Y6^6.i)w=9` @}k}c@=ڼo1<˩P<{ҹkɻe>,2{9 OHg}ݶ]Oq8?iϟp}I1 0 8!t!+Y uz\A J"C*ԣA DL[Hd/{K7:d oSfaGxvJJa:Ycetwq1xD_q[.0 A wqJEq; 0HzDM F{t%[@D>.r1[,B0Mlj'걅⬚,`P F"*88p DC%NXE ZtĄ 0 `I(` & \xP`3lgB i2IG 1#*hЫH(P$O>+vX&H1*`A*%g"!倄eA0*Hٰ$58\8e7 \ȫz )u e+~_{SXdcWxZ6VVde"v i T0 @< ڠUzizIߌ&iNʚi8G #zɎ($þj.-b[6pW|qw\iFc sC}tK'LFtq!Xp_kvs}IW\ދ7xW~ywy裗~zz~{{|7|W}w}~~P$` x@&P d`@FP`-xA fP#, PѨMaD|(&@uhpk ^9A $/A,'`5XNl-2#`I樋Ph*gt5 hhƗAA\jg (SP^yH py-; tc@z*@cX [ 1q@08,&|2$ @2; #IaMӭ=mn2$(`$崳-a)yPHfm}ۃx! U&Wens\B `<ڵSR*t?"#ȴ <`XƗ} [X 7x+0LUCbH._wPo7"oW_4%PR 9ЀK3r`V5pWdlx=&<Ђe<F9@^l!dp`j D_k-7rdn" 3/A h4|GL pb_X8VP`s`5 \sAg1Ls@3LG=G #> ]Hvm-Z0#& Rg1Fe3e,@[ xDn=.$@|p6Ue`H0o \0;o"h9Vnk/6Irp4!\V$BHUTq~r\+gy]r\y|] va3'zDD0ntC'r^)#$]A,̠ܒ€b03n :",n@PT7&Ѐ o'X /p_ a plmniE dtd0Htmp;a4j(Ƞ#cV@n%w|[]" Cs >!1ttmȆ?o98/!`gnPj8f`<55kc K6o K|:H:l ; K?XC=:4;@ãA飈loȉ)B 3<( BʷC;`;h `d[o;!z78nh>f8̣$%|`8  (l`KDK2^X{$&* lȁ;C7|. c ۃ/ p#2<"Tc "8/x(J2;J;:A9#E/Ěؿ@;d``E{E8@ ;Z :h(EoJ27 628:l 8]@34m97oH2_(@{K!q Ń`z5{ЀqȧcG::؃S iF0ch8h&pb`'jӆ#'L:_079n DlPˤK@ xne @n`c[6HDJٜ["u0=ٌHZ/"!Z 4#XJ[HʓUX.aHK uŭcpJ"䍆mC`C(VoHST٭]]E^_ EU8mZ]׍]YE" g1YHYvP{1Z|]"]fz;;ay_Ұ?u4uVzZ0Yrm݈")4(bˍMLb "RK616Hbh^(ӛ45͚<,!Ba2^ װa`Xhq- b`4S'pl0BN&?gnxS!_gh&6hoȳJf f&f[]4XfHdkV;_5oj=w`qX`֯E^:zZ K5ߥڑ0F[EzAv&(l⦎FY>{-8#6ІQ0† 7H^Z4gXjq6F(jꧾkIe~5x["O~0₨4s>ݫꋫlE48lnfu;Ƚn#aN`mɾWV%>cBmَ#mhh^ -MaC1-zp   ph%ޭ,˦}Ymn魀4q٦mޮmXȹvXv nco iE!p "8ǶDI#b툹}'[3"9`$جqH^*\qriJ !o@@O5۵CrC<PYW R3 _gr 7 \S<4 (LoDs$Wr&wE_rfhrב;B]6 ]q!pP-SʫF_XjgV%D X&[҈J0Wv> 0WzmC솃RAcYkms&9*@m-X)f[u|uPO`:6x*nWu`MuwZFiamxpW&+Ηp |B.nUUy0 P]y]54.4R*bm4Ś,g ;^2!N#9Ayz`2͚lS'Uy9H,N㥒Gg `]4{DZU8u{(J/jPid|W||||}}/}?}O}_}o}}؏}ٟ}گ}ۿ}}}}}ŘៃQ~77?/.~_??~׮ß/oL'w~WA. p "Lp!ÆB(q"Ŋ ąq<? )r$ɒ&OLr%˖._(36o̩s'Ϟ>9sfТF"Mt)Ӧt*u*ժVb c֮^ei,ٲf?MrFn Fvxw/߾Fb<#VI8ŌCkKL/[<# z4˘j=.VڶW~Ӗݺu@'Hv̟Cq9t&Aљz.~〱Λ|`xc/y;iKz59(EX 8Ia8 u( !3bDb'@xP hb/4c5cN:@B"ȣ[aJ&EdHRXeJRbi$Z倊EGbZYHY(#C :IQf)gyi5M~Fh6'6}ٓ=J&J#[^d]ER }:|ڐZ GijV*͚P `ʺNk!}7>,ӆ@, k题~KҴҒ՚lVrq"zaKffE'K$K.\j; qNoͣFgDž5,SgɁ0b_ z[ĭL36[!gR^rZ]GK4K;QKAUO}5V5yM4X]uRtL ^ -WIuHߝuamt_4bMvfvj/vn A> [eG 8ޢ 6ߤ:ݪ#N3Cn9E 鼏|ʧώ}}~??|PS=ٯ}ݿ}?ן/z_?'(H? گ 8H@>PaI8AV0qQ,B#95E|@3̩bٸQA;uc?sCWP6BڦI7-r:rpdd%1yI7oc4&%JJN2d'XK(R.赑$\c/X7zҒL&3iLg6Ӗϔf4̰L$ü*!JYr&'yFsԄ&;NeS tsԧ.K? vs AhJMBD9O(@.IKjғ!(]iIUҕ'L[z8Avӂ4PkZTIeRaS6QiO:U^ jJӬ իDQɊT*LUS UJTUUju \kWUկclYWo%YVǮmU_%WΕ ]9WlmaIX>Ud5[Wþmf[.gZVnb;ZUnjZֶl[[޶6np[v؝.w[wmz获|׼E~_׽}`XK70)_ 0!l`30GJ wEU< o#>_<C><+oc><;σ>=Koӣ>_=[>=ko>={?a tS!7> B8;{ ϒ.} V~A?v$0j0cAv" Ti@ @T }@=@ @@9 @1 D_ A BT@`E|`K `&@h@ `Yr@T !M X`MT!E@V ^aj!}T@ `YA ŀ faR_ @B}D`!!=@A<p f "f" =9!>@NaBC<!ڝ$>>@#L>@2.HFa#*_Y>#3c>*@M`9 !Aa"+#A V=>dg0d=@DD>_v_8&$bƢ:@HV IR 7RȌGcAJ!B.#P/M /.#Va=c aᵘhO*DZҋWff8JX%\$^rVj$F`S ,.]~^!}"<T^`2`SD@Bcƥ aUFf_6f^Nfe^@ 7aS:*d,fP`;@pE`Q:ZlzA c762 X ba~P&$"4gu/R@r$Zf! Z16+~Yf'['Ap_ΧLDzaB@Hj$ F('Y@T"4R BP& vf'}HIb%r&zb(VpApc7+"Zphޟ`erS"D"B"@t @dZ:!@@L'Z6n%Pč:j@&cPB_)"2)*)0鈮 b*b$A\iΐ` )gG0j=UZ5j?DƩަ(r Lr*^@ `d]a$S6Bd:Vi<ʦGp_w>/jfq꽜簎\!ơPY}&*6Z |K -@ wh U3[gP("Df@%BUO A*$Ē-)APiD*DUO!z:uXu0d$D|D̘UI&ԁON=YA!e!%ŧIPs@UURh0%Abyho5&Qr$dzYtz*A䤨DXA ϜIE` (QxP5'Chvi*@gTEt5¹O'ܓxiI!\ ׭JĮ0~j"TAzgբۮ@%;893"jܒܼU$?RRC­t;c0`+bA_7 )T-P  Mh1 4arԔ$st%Y Az$@H?3S4\%p#2Aӭ Ւ=T_($1h[Kd%@ GTob+7J)Q I{! Bw]AtV,PF$U(R"BXA 4~A⧕AGpAػƓIL =B /TBptE'P@x*y@'~S _@ O!Q攐JDY x@ JTPJD.fQ $ DP`I)4,[Lt fP!7EX\u`vhMRQ@yW1%R"Mp8 LJ&1; !!vDkH}bUʮQ |dġ dp9"ja ;+@~ͥh.!\#mZ 2pg"~*vi}{rpnb8Du, $\ L00*)A `-h Z@ h fBGJ \"%!yʲUZA$P1I B`ė|PVwA_IB*61!hiA&6TDl(I41:eJpYQ ľ:5o5YV:5!DC5AZ:BQ9kaSz hG DHaUlgKͭnbP"]ɍ@ y HwMHA-^v{JKjT@x2 yX F=/YwRKoi3*w ^cE.-@ƃ,6X*̯B4;et q&h! 88&!Q8A/OVͷ ؑLG չ\-LA ]~*H˲ ՠ\ԧDoPxmkb@G>5Rؑ ыN   8Bia3SfTPoG2tP% @#> )@^aG+OL I!.{Pr5Ta*J!(jBDzHe-֪||ZwtfVF?0d! GKl܂9fdw7gn!L6#7,.S~;:7N 'yұAI?>!, |@ 0 P<ѳ'u,/H;ƹd6?⫓r~Kn׿i}u[#ES]ZLx+W(IpAUn&z¡44'sA+'E,ȗ|DR*J,=E>4Ers*dDD-`N/ ,0s10*7-' /XEi*Yt?n0{p{5|C$Mҷvx2INjVWiHE>@e1?YQ-7f °1ZW^cDN@ fUtXVXH;Z@e'jPwho\;Ҁy\y'jXxI0z"TTk$"MSA^<9k?OEk!SDAgQ!T)!aJQcqIQU55"p.&P`u!UX f] G1gb"R~)GY"W pq!V1 !b4ceDJ"!@O4 h@b7\fv%WYFe0uyY 0UwP>oAmFjq#Ka'* i  jI1u!S=TA^# 4wY)a sWx* b v'd  Pg8 9ѡ /"z0)`PU U`8Hhe9+ЏVb%ړ|MWIBp6*M^Eeնvl:Fh)Wf2MĤ}-+Den4**rgTВI*UJ,!ok:$o-!Cn)}o(Oi+/R!B(6>р"4R7*DZ=`N2~yHNI:"0YXq-&'sg(gA?uX}@7tsB#ڪA55jǦLtjQt;IwqoPRr ؅%3A  RxE%@ BTcw'< r/s57K6o! A92xK[3v%qg&@9-{60[1 #҂@)vgCP\$Z!)z4)nGK(:#DyY3Py4 dz=B%ʬk݂s}-<I/}#G|1~Q|,VAoA <"Tf# ܸ0bq]..1d*a|`"8,V!:Tn=h5(F8[@Fs!0C/X!g$KdfH+tD۰,+!0.Rq0 9:]x!I"p$=tnݨo'UT<A:ZoA(KУ> #j n IVnf}";c(\Imq[Y+O;8>¤+S+~DV+V x+m W5P U)z6 ivSQ * ɐ2a|Itc0QvNIMO`p9e3]qTXro5HBZHSP"S! ihNsUVF" qkSl0y!em5qq"S"gyR!3|k"u'(6ZBA1sɈt WaWa:Hs$5((*.m,&It{js!Vf5YJ)}Zp n {!b@`3pG%CA<@?P3uӲ+oL@S3 =]x$cT@ a&" D @gaX=Z-$PՆ aYIa`~qR0q&>5"5v OTC2801{]tcz9͵PQ -E5yٺ+ {P$pYjP5q4Urc8K_5tL)Q 2)g -^c֜=-THeBKYe<>R5 )p616d 4 3  4 ?SAL !I P-3#gTp!943 UYڀi\,  *:I -*66 <d L+@&LGEA9& 9 7bqҰ8kA71<ͩEhm Fm*Co"MG']"4K&o!!lsZd.RjbW %HRq*&Cmt P98q`+s BǛ&"Ғ&k9D1ib_fR/H&|rVN Q-О;n+|+ÈhZOG,{~Ӏ[~kp@ /WWBѭ.Ѩ,%Uq# S/s%Ut "f{B^``|;/Q%B!ߏ%Q2?/7Y|Z{NTCpfx{MJ#}],#Lb"5nPbOf7P ,x{1= .s6X4D1PT!:W9R^5DuR3?5U@OU!l+:`n0dӝ0)+!ě)NZ. !ffa_?@ N9 ;7 w!qcd]`$/U(,PO?&,eA=(HPC%Hba6RD,B,DPS'+Gֆ 25Ĭe©ZX&3ÿR h(L",S zE0 *H]~5Xء "o>@7x``-XGl"d6"rH"JPC"2dHmz` h֠A7`HA&7>H[ZLw 8B탡;V>ͿR+DЇ>/R,2'B / )I z芆b EbؤnH$+)*+D쫠 5>"TZlH8"[9&@AL*>Z')~1";+@2 0Έ\DØtLNA%TPEݪ dIGS#t#L"C$-3"H R ?I5Q@iZ6 z"B %ׂx:#*d߆-:@rjx+$ʱ k-ZtjVc +iHo#:o+‘Z2T(h/ c2^&,P4 $ ZP76x*o(w28Ђ5c*aەW'[KabN!b6֭ *KS?hJ,@Kނ|$r0sF;٨n۬m9_Iؖ®7vL-*}8Bӆ`WKEkc?[ƁU0-ڲ@Jk{6,dqrmq*Si.An0a 7@פ#5 #`1p\&4=XQvK* 3"b( /WĔ3N3\q s(}I"2ʂbYUA<5HD_͛#rid]PKe崌_`': e?}OwєS O}D]g :C ] wew<< JLKP%ͮ#B<4)t!^C4," l* (#("Iqс\!.Fb\0}, 8U?`> 2rGaND[a/" Iù-, J1 Ġ [EpJ$c!#Hɇr (QrE.DZYHh3&J. 1L. /g lX, d DWWdOW\(8fF"eH5'Ѭv3x _.y gNSk>o*4&0V `h5]a{_H4=S喍ƁD3]==$Ģ>?|v$O|/|1*HpD Y{]c|1==j|10UBZTߍ߿0?ʽ'Dy3+ӱG ! >8$[&C*c2>x/ò2H =@l#6 y&Eэ<3rʆlQA{`2#'PPi"$BmᴈX0? S@ ?̭5+ Pe.7 ؀9l ӠA0` )Bɩ`Ɖi5t;JO ź0D GL/| Xkȳ ƽQ X㵲Q?V@Qiĕklmnopq$r4sDtTuTsGdsǸx5:!.HG"(d !"EZE3hx*0a>$>ȍi7X0 *88;d;P!= *R@ $Z"v#aڷ mH(!2K!?9h8K$dʥÜjT#k*/7[ *BS:̣ H3 ы5pEEڋ$ hZ ,xe<h5?qjL_a$!hQB W@0-Xa^``N N_:ȅ\aAV*A-\ҥL2_"Laf qnҼ@m lDk2ú@#za95X & o Hz#pJIcSy4$R%5{X+;ڨ5騩 `5 Ѱe:,P -FC*+ р*: ڈ,`8:+x  hQ&D0Cy @*& h,͢1h=   (N된q*1۪Ž[/d :ZT QƁ `/ E*ZHL:bhʛ`.Ӣ0N GRW[M9h0C$ |>պ<#ذ(TRY` Tʰ*RUR]S=uw2O$;3S :+[mY3 YP7؆l-pfpJۢJLlPv0=PmM K*ʃwScTWߓ`õw \(=%b\+paLMX4Ho @ʾX.88-Bl.Ȅvq6ﱷ'I} y𞉛_P6ݨ݈h!v"'PPʉ% ihjJ ڑ1ʡ!aɱ꺇 : հ0QYQٯp:@<3daXȣ[$<([ hRP{Bȍ T 4E!++TZ ߀aE1˳\)k\؈軂KSNXXP5A{ʑH>M6 2([0f  P@h8aA3l^%Ҩ涩!i~1X9QJ-M C t*M/seLXHw'piˮ$ `!;d"m!ݐJq < 'I'PI<*HIl#5$}cPA@CyU"[%V*̤`HXõdHj4o'-΂1j0Vp֔qLz2O%Mgp`%UovY݄YP7hN _%>(o hlQ\'&u1զ{.&&4H*Kyޱ[rl"ۥaZ;`ؤo 0uif`pfH[PZ #okH(B$F6 ԫ`=+B B4 摀T:rҧ8a*~ُqq(3 aG +AÑq+ az%, K1Dfİ 곢ȧj o 9H-8lxT$xUq^} ? ,a ꇋ޲5b ᎊej} h.im m}ء؀WqD „ 0? lHЄ # &BNAT0TH~AGOX>QNI0{)UvJuPuG?zPWhF1EB/[(" eP d/"DxGƻD\n+P( =@R>16T;k@W!P>0oK04-JR\80\%C% )c> $ DIW\R١:Y{NP)Baح1@]4jL54?k *,"D1pI? CALfm>?%LwpZ"$D.t^BK@F2!K.59Ma6pkІoxFyvh"]lr#`(iaP\$P'9̎Rq1ϻÎG>0@ 7TBiuEXcB(.vLޘ0D]PY@!H(Bd%2@ >Ղid8`[m#H bP6%&SXDșDa PH l)Ȅt2 qATY 8B%dGdi$s YfF'ryxdN!_u<'CI@MҡF=*Rj.vSSԭr^*X8@ Eva}+\*׹ҵv+`!‰*B (BXH AJ~p8r ~:$t&$>@b%T [;H2 B :`E7@j"B D@~' 0Hz9 fG9 iDBh4e.BJ8.\D`aPd趵"xm[)AN=("ްV y(`?ba"`q\ 0 W ;r,tқ ZaL~,@ 9 Y (.lկ-6J$4V5ȂMP䘬ǘ52B!JIH  i97ɍ*R8PN@ `/5h_‡R<慓D0VOE XBh$z!X2 CJf5" [~(09 EiV!}Q1{>p# P3:n`g1paSF+rQ@ (!S $0S'A$\VLL7Dcס_&QH"0rCm8O @\{ PHN3 xT lh{m6vW`2L$CwW$|iNGpf8\RLj\*^>"zv֊Kpe4#@I@rG Vd){LkazMd peyvZ{e !@s xM(/jR&H-L# '姂5o[AQv‰Y¾"L+BGW D% 靊8 1S i)ƒ , UXE0858B ?QLYB[0^/+D.ĦMf*iu^9%RD^Zj92T۴ Fa5@`r?ZBE DQB xyywa$bL7$< l)/@`+_PSA^zC`H`mBV (WD<5(iׇ0IO5P?ʞA-TA,D̜HI BABL@DyI4V̢,:N,&dTNa'~ X"U?a,N^4 |@m)n&d1 pΣyBܥc3 KY $\OK = @}`uMW =7 GMAyaO=?M>A]@!` KLM"LAd$Z1L1M8Rl DdLIbx=EU]SMUH S$ΑA DxĄ)FJNv1BɆLxD&JLF]\8E݊@,A5IH<_8Σ(±lDel5,< )!Do tLEeE R ABBh =`BA.ŦE +@d?^A'T ,q@$Ca@ bmL=*@JV ,@]aL$ᰅB4w e!1 -p); -X'-HhՕMC&MP@-BHS-*D3Ew@D%$$mD`/FPcH%@]mD E@q@!?0$h88|ahCN? D+ -^/Ev!KBe4x2@,?y :& 2w\?@[8Nh`+)[@AVDZ9IXYaP(p|\A<[T`k@lZf(>C l[N`[!,K!ɭK b@T#!D *q쓻d đL\Th R<@ DgFqECX ܨd@uʼnCH@@D8K|@SA+1 DVzy8 N=PQ@+Pz1HMC#4H\L!\W8 DDdA^,]O .(NAb4j=1B|.@ P笲1Ո4gsL; , q< 8qR5ԧք,X@ D$YJXGHT(;FC$ID CGt),)3KuH/Bpm"M쾰@h g̳KmRUC&D@C 0%84!B`d\EmD`kPPdxD LEQD@A؃CrC8C9X[`Ta/"n؀godEPA`Z"7cDd(qOL:Я:2| `RX(A?%%N@fpD,,lMZHA@Ȅ@/4B$̬gI%_R4u&xD8&xIDX0@&waBB D!`u$CN`Vk.dFIcd'. B3lBm`]\f@)3ΆW@@W(ԎHC),T{SR)DPH"A&%@RAQtR!C׋T@Z?>͟dƌFJ͜A ˜T$u>4K$AtAT3G$3/d 2EKKP$@XslTL LFDMaQ+f|1g %Dh!xPNhe P1W?0@~`r4WSh׵ DOx'$0&HBA6Ϻ@#^KHD^j,7b"KϦ@8mrMOuL g܀-DAS@!H,H. wi#DcE[eFBAË:^ <2Ch}z``va719F+-&`Q` UK Hvܭj[S`lۆ:Em'nsJLQ.ՋG 9ӸiE $!D@ RGEHl@ME@D Ly'ŘĝB`BgB)@B|Q<)3AR\~˛VL6&E@ xz^RDqy3\L=i;E:eu*A(5CCE&*@<`A | †  L࿃E(1‚jBQTeK/aƔ9fM7q_ U|` BP}"c(nhD:fպkW_;'1 9 @Kp^ K߹wM5|)Z%qcǏ!G< ? !"~H"D"]͔aǖ=vm۷q!uJP?F N (%jP(!1m/'DPԟ<@[hSA@)4я iJ҄(DZ X@s02)) TR`@Xd0 iDF cƠpt陂!?"nDGt2f1x(bY2$PȤA8z@Nk!O ⟒$0Dji!lJE0& X@YKȖ BRc˚jT]BK1"8p H%ؑP+Xd `^g ]2"WSW0N!d(8tJ~A'-77Wc}!a 榴I)ij^kJQeęg8=^fǨe"b ~ }jP@{hdh^C? H7ilJǖl-2Փ8WZ%W0V%hTs);"?f7їa,Qiiꉢ0|DXzZG*S, (ČA% HL*Ŗ~K33+=k4:hxK #!  :@"H݃ "( _|*@ 8z*n DP-Pgy0A> u"` @@zu @+@,<t:cIRS5!hR# "̀H?lN3AHh #= $ ߠh|ɜJ0XsbcM$3P,.&NM`d!I2` C6pȇ "Ep'L?'A[ն$Rv c(B`#*RrZ1j+"CErd#D*7fJi)h!nf,rHanm [B RRMK$/.{eamv|а`& L KGT,H=('A1 dN[6WyiB`5L"H,g(cԙ=f̃ p2cc4.h2g@VD"C8Ħ$<ȃYP[qOq!]ATpJDz4 J6AL 8BOf9qX@I0 ]a:*A0 4PW9mX?u%F.AK$4L p6V{% cm#$-ma> lC %{=lAL8 G R #6/.0C$q H85 !Dg" %eeY"6tv=E$Bm]f, W !ia7YVerm] WԵrgF8/K^zd&pՀ( .+dJ>D- Bg:$&_[c+  ́ʁiM3/,.$ #ҏ |ɖ"~|OT !&U>bD!i8/xG4ĨH@`6 pM?LOr 2/~+!ˌl-ɒ'fm, "BԲp"r!-8!>?A>1TiC>%>> "jh r! ? (0!ps7{.\"!!\`Skj$9J00@c $-K! *`8x3xG$ %pt/%" ؏baN3Ђ.J%B0)eSeЖZ ld:"2#0!f%hV2cwVeB "ci.HhdE  Vrͩ"Yk3( -Gjԉ 3ȱ q8gt8ʬ(TaI)΁ 9!joOn!( w8Xp:F7JpQtIuW킁!%obH%kGQVG9k&hw ^0K>K?ogNc ²Xbh{u u1`01${RZK͸Y8m؇ " x1 Z$!IMTNwXu9X}X"N *`.FK2VwB(À/XDEcYz*,:/V̩!PF5A2klϖ @ tajw#2`oBZu ׮At[ڧ(f:*et!w[x7)J:a @0wq֡>6EJ#(s.!]& `X("[#<`L" ȤJH%:J "j b d@36_ o;lhJ-l$!%[# {e"iP0pia{ Q"9MzP" , 2`'b›1b!w2 3g ی/&+2Y@w5B$ [ТE1ʐW#hƁV* YBGSbʼnM93> Oĵ0x|W̭Va/8ܡ˙FWvq$nqI=@qCqB-.$WPm"5 )@Z0{G:M.tqN֩ \}u)Q%}=7&Mă=7C43t3|'(LX[ ۆ}HmҨ"~i[X=_ H6AÊ $n:V"O3}t%Ѓ^%Cl3+ A`cO:t:zAȄ9C:@]9beRID j VGT^0H bZ-Rd3cBQS5GD",]P@V9%h`kbY"1#Ha( B:À*#C t67צ!!ZAfö!e ~!1Z B63[梜^ %Uʆ?omD&@;B sjnj%TB`gN4r f@Bd e>x_@ F>MFt>.@ݳBt EaIQsPBy iE1CjA13O.*hlJֻ A;h} ĉ+Z1c":DC_:R)Pf DIQSb%+v(*B&L:)TR5Y#ҔV@iR J W@TY?>@@ĮU5@R4 $sP igvJLO 5uF2 ( vnE!01%XĢݡ"ġέ7ȪBWTRlQI;XRz*TQGWTY Q:YYUb]O(MTX1UB U09eR'FȡazmmsA)PT 0jԁTRS&dVU5$P!?&HSIt,j$m bYVwlai"|٠EܕzܔA`f!d?nPtKPݒE28HDJ 2XRRÓQO?/8?RTŘ!lEjZԀÇ/L HE5DO8ZDLϰ?B#?sO3=cOYkP{N3?BNCF|5ѩ墯uoFV0^D.7D+k?s-鐰?D4-A- R78?5"B7\]5 Ir-&@\uFۍz,JwTacER0f!0AՃ4M1%7p= "a2? H?HR c0]7i&NQ/=*:508/p44EYYT=O7KdHPBǔE;?"HըadT@E@ {ZAS FR|- P"!Rs"H= Yh/$2֛HD UDx2xX!O:t$$TS<'І^d"XHbbK䑈d)^ 闏E(M9 Ts?.Gݐ$t!|Ȳ?@lʨպ3rk#]"9A}K"8ɾNyAuL"fXĪ,a:}Uc1ۣ ac3L.< d.iB+),'[["7Y:28D 掛 DO3e;$nI-ЂXAv{:@ְhPnPnT"2船Fĺ)KIYH$qb^`]{h7$%DL0&s''f"[7%iJRr CS<d`/Пs)B\݂؎MbFT`bK6Êm.R~EII &۩B {vڠٲLkrHT1RNgqû=EJJ+Y٣ #_ldJ!r$)PV %+zьn*y# EtUPOD1ڰ>X5dn٣_ Xˍ̻oX'ҖHQJ'H !h4Vg5#>^7t%dY5pύn_Q<"bĆA)R0M(8D\>1$"i5mt+|UfK|x 1D8.0XXO`#TL@+{(2XDh0>x+h `P#+8"(L(C qN *i %@HTXE OH/PYl)iD8@d d$HP6P fy Y%:@_KMX]{< z!(D9s\~#q{*b~y[9ַ7 @$ ES[ Ol擁;\ eX=~\).]5a=}IanF`睽ewmD&Ђ,! ~XvLD,P\NrbhHYT8["յ%sz1h!>}p`sz%R?s2ܦ+K/i+PALM6W&|VP3C*P@,;XТT=H)ͳ_^ b?dG QaW%_I+~&*`0%&KYD 8$4 TSH+6ф`RY| 2d⧂2ёOpSDESa #6̉fZb ܅.vi l'JS$2͛c.E YRP g]#Ȇ2;ɉs>9! tT5JdF "AG4AO BD5QOV&X2YdcZI^L&x "Q%ZHN, 4O*[2% i| 6!& ֺ$x?ف@"&(I 8)B%Ȟ8sfKϵ,%:ô \Gfc6̟'ZZ~Nm*p%%OM[FjM BbrMiXrQ&P-_KF9)f`~r1NmR :` )+UZKcJ*D)eZ [rH*t՜+MQpMu+ /4~QZzWU{k` ?6'֠b_XFVle-;8b.inPRU uVe]ZV: jFKA[k{\&Wens\FwpaG>" twWߦB(hH*0Dt@oсX@} R_ܷ%T$@ u\7|p9` WW" Hr($>ѷQQ{tHA& `@<D0ЀW `E[ tC5" H~?ް62 (S"@,?%T ޏU$dR }Wp& Sk( q8[ [ok@̗$$R@S p. $J1 ݵ"8$5tVkD:> p /ɃKVktFNh[+`DmW5@ XH~') >~=x ؟=<~!<| ]B t雚ZS% D)]  q?;&=)Kxœ1}$@ xr(n' Cy/ /&{:Öxßmz u4CCC nɊQKB꧘M"D{D &B+2D3ЈL\x?E]? xg ]cvϰި`b`!;ϒ2ꊢ(@u#@lj+ H:qܶ2܈b( jgx #9$  UcV)ʌ(Ҍl k9xnЦIعpF+ƆPG@؈# ;Z JEUl;h kK<~ңnFgĀ1 Dd9[IsɎ c(6}SShFk{D2\J"ukpGt و ǜ!0!  ];xX"H9r㰒 J8UIL5#mI1"_듬:2!>b$ʙ |1,(L|(( %ĒHW%$S*}[!Qș 'lʤ :( ]t$]]RC+'W"g")il!'w]}B Â&2'r 8PB%U '݌=ͧy*E5GD.W4Y|=D݅(*j`] u)B5*p2*xPҩB *z a5=aFŕRJ.aA})2e:a"`aR)*'`b.ƫW>+k8XP:n2ƍASb:c;cZ7Աj`=~c !Syt AOeT,&+31V2-%E`'IhqKk0 f_nM:aVFȷ@03I'bȋ 5/@q/s_q>/2㓐(+w c@h)mxfΜ分H0r_t(LpxhgCB(Dqf9;"/ G8I((bB{`bJH84Zi-98,U(R 8j%VIjj@ |u(a@ ;`Z78t#5qv@7[9-jcD= 850:j6Ƀz @I5 xx Xnp臂~eXyЦn m hm^m^{ ~~HpЇ,p0pkc{m@>i2hˆj; kD58Y0jJkBmp*[iJ: ϻ`  >A=yˣ?  qq{I嫚] H ׅɐ0;!&$r 1i;" ?XC>@ S!I9V @S@'橀 '!8 QQy#q=?+BޛkC';quѠ< 񜸿IŜAˉ4! B_G\*8 QPdDQmQhGrEu(4`꧁) xҙ6G$wA|D>_.<88|+i tحߎ°&Ȗ%ϑЕ4$b?H v*aX,,Txښ k(9g=-tx P#:ڻ ȐɆ sl]K? (hꈈ7 فfƂ Umb׮X)*srH[>DHgiq~ s0/}9lXi(pi(|o ɜ@$H ,ȉ谋 7d[X oߧ-`9U= X7ٓaaPLIgMf( Ą:@ L-0_!G#!Å B @(Ł$"$)BT<&"JLj5+^,hDÁ)>\I0p%ԆPҿ 0X*ˁ>%hqkƋ/P7 p\)V"$ X>}(K>)`j߄xR5թ Î-;6s<w)2Bx_Ap]D!Cm3hMn͗C}ouRu o6WQqԙGQe\}-n=] Ѓ=!qEB8 BFChsRe%܍ma6OȐv|dhy h^!\?z#cG \qIF]$ &Bkj #Ee^xC *1d] )Ctn?yX&<jOC>I :il:fK%SYdd#lJ3BdBڬ) TfVˎW |"4AXcSV+" O j IQ.Px6jٺ׏ M LXs ?MB^P?SLsȀ+ dɅ?2K AaJLO<1/P"֬B!LF.l+?D5CB Q4Ϩ9awCBBF#j8,?F/OBK瘍Y>}Ӷ=]ӏx5>0>Q4 >5,0M=8N=Oo#\dBj`4\zXR2Ef aRk7C#zsi?%Acqoݩs!A 7ε,3AFP AAț%xY`Bs%t C,Ђ!Ā n"R De>Q p  a#C& B.!wBzA@>@06@$ݣE*AHIȐ 0!X\ }2\QgS"%Hrd!qdH6M&1AxfIa3&dD{m%RDtT"$ґt)8 U#)ߕEN0pצR! 5H5Z֑!;_faHi8B,c1U 1ԡ ,BfD^ \';1. B9ل|gIO~29g 51Ātck C2ʘG eF!>I1!PS])K[K$Ӛ? N| A(@QPP VFR'Njիb5Z*Wկ!fJ)6?50"b\lBR' [rc#1NUρJ63 MRV`MFS*ߜG@@M;:$b7Vԩ|< 66B"7GgNm@}?$ 9=0CK k[@םj'OOw4/B S  Jyw'nRT/˻o鎔ue:::H7/D6sR+1m"eA!'FP 3 E=zgΏ7A\+ƽdtJ|x,J[ x ӥH{to*+&jmnG3rqk𞏑-a^sF"p$:>X?_alX9 )h1zq_?|0t\$.HlMCUCYʹIlT(C=$!D7 \BРQؠ & Jֈ@qC `2<H;D0C/5%9[%E5HA/|Bj5DB@0Ĵ=!!aVBBĂ}eNIX.U̡EJw"Mt"  #_,aD"PGaP#^DS Xs_,1wimH&VBKL<$D+`QLdJH4"DRX"^BXH[4*R!D5@,0c_x¿ I=N?,EcXl¿L]K]p?؅!ac'Ty`TeTḎ݅4\Dho$?$EuI~LA=zHenh~@(-Rh@j@ I\Ȑ\aOsXs؇{P{$% ]&UIyQ a^]D sMk(y]a"MR2G`@G^^*_"UW4bZ WHBIazGTFD$,) ^J$""&C6&KɍRg* \l|l\ ŧ =gatX*hI(̊ ±"^l P|+DPF>D- GB = %,5XXn-1ԃNC/Phw1(YD5 -10 78/7ğc? KȄ ̩Op!$\ n9'C&:YX?&lSX/!&KNPmF&fM$o"", 6 &ԁdn#l0(CpckPHʟ@³.쑣 S^AjkeBr@ZS?b#B.tH .XV&$DZC&4G9zHI@+Ig B[܀9S@%$B,$#Pb1P#]DDgpxy¹Ox}He]hDm4ң̇[GѦꆼ`ѣoz4,+ (%;? Fs4oh;ETGokuF^KNr{G )e @zcmJKHEKh>3b&BHT\lģ[5:?l˾;hB/ڊO/lD76ʨz9z!ũf,O%,K&TJ$c:Jw#v hF;@>2,RV 7zw_7l޴-;#:Z s:ڬ-~CFl tTy)C8D=8to #aW4w/%Ûg)MU`B@&IT  ܪ@{Y 6ujTSR @O2)?kPa~J)](jǴj\sMDPݮdH) u!p.G9F*8?)9T~"Q!6>}@\Og?tC B 405l굏(p*J (PmùKC>+ "Q.BOEvkjF4FRb14ڨAB1%ܰO1%5LE,-ܒK.,E4 Mٚ !PQbtʚ &nKgHK#^^RO F"!9k@0A 1m v@'RM=nRթiA|fSGZjh:굩ްFS4nE٦vp%<U#b3}fr*tm㰃 ;Qb6KR=XTQ6`SH}P;ܷap,-5ޘ=FFA*fbHfg)\RfJ9 T(j)Khec6$th*QE(2#5Zr(dҞ L*H&Hhd6P>""@1MH(0RӁh:,zD[$c(ȢX e2h? *~J+"?` Od.٪R 8)Unf 9~h)r^h@VM ٕ#d)CNJd ܨ@E,W`؉V2.Dl@`iJAyMRh`Y5s PGcU 8)ۘ x7HxK.x r<FFA#E 0W" `==` x!͈ R ;}pS ^`O|t *E4 I!@B MN,;P=*s@*@Ѱƕ%0#<SD\R"J\.(eӁ9SO1 `;TMR@j> %J?LadWe O F <6bn2I*_y@,!̏DjyRNVŦ"¼)KT"(} Lө3̞,H)r7λ tQrTNt@Fvt 9@qMsDf/G^h?>/$-+?!ՖHw)T݉T +K sª1Y?Ρ] njA o1h +js()p ?h1M"a5>r ~5bCG pU(5@";RZ|# p-ҷ_ ĊRG `W,j8fBrh'Ԉ)R8!,a" !%ܲ?1?2ON#ky,1q3*фan-ij 0# Ԁ냒L YLP&`~QkJv.'#P3E$ "q-?<>j8@! HA2s%ICo0#Y;@ BĀ(BZy !>OuԷD!!@Sf$bABm8!Ո/ƮviƨgYѰ9ȱQ"QP6%d܎WR"UY]:N2-vm77*kI&mxj0T͸350hcw@ۉ yg-iJS1zXn9&/N2y_Kk4ER"G.Ăc2f(?dClԛvV)vGJ5IQr(Rq`]F@qjSb3&ڄfsiuev.S"plu\EzzN{ժ=HC`˔s&-#٭L.DpR *-\B9:-%SԠ !(s$ûK۔)w{;׾߀~oqDHy"`)X)"6(& 2obvja+BE2H)e6 tFGN.&1#G$+{JTc:B>A#35#3CÃ4FP7>B2jB7,`@8b#dzZR&?e&cF @@&M anL$>9jnB֐E4CXS>L:$.dGM I*0Vd7>9$DxDgrEBKDN$*1 c^fA&`eQ )^!@Q*MIO,~,Q)W`ϪpTtE.tF))!j$a#dɍϱ$p=lT%.2?eɫqA `m%<Q !R!!aBPHle>"hƚD!.$KD p3HFeL*W l>A|ujT&k. AR #lƦlF5z" T#&GRap2 Ls*,?HsH#Nj b6#< A.p J a.)?"A 0 dْ)xG\jL (h0.|B5~bH!>"$(btXb0`&H!@ XƊ"1`j^@> >b8ȔG|%p?,~a!JA"L&"b4C>!`X|a~`hZ "(Ȃ )lJ(( `;@H<+l bt zAהM "ZIR?FImF?"FgT5@lEzJdۚn;oč:eB2G5<@ ?JK(PI4z\qhh4C#8 4 0H,`2(wR@"$J("%)*Ա$̩P^$(Ԝ#*&6dRRiᒣeH.%#>@"2*8JgE5&U,)F@ʘNiuEp?rǮNE$Y3PBr$BB5<>QcR^1]TBSΔB䫠h)I_>BR&7jnTCjRD-k7RTA : ͳ-cR)F \/Cw\IJu_qN 'eYH¶"H#XbH"X"L1$b6\%J >&l⢹+n!0@A&oR6/%hfRnB8#>k*VbK)B-D)E_):Tb02G#r#az5)pPj$ƀWI"zf;6TFm-($"t )L$Ww q$?rI<$ h& p )Ҍ@C|YHԂB4~Mbz!% `*7"1"hP(sT-(X$ dw|ϗ5T05cz%.ZAixdS(KZ5\:Gbc\1.bF$E;4QD*SdK)[T]^[CJfu{,Rcf[K>Db!fDO89EP0Zb"ڸVKFDڼ98; A-^%4onaB)1aF[ܿ?)GKc""S#ҡ&F*,Ɵ"#f>rlZy$}$**4 P o`A5'k@'"v?buqƹ'g J!|2r ^"f$3#2ȕ7ϑ|#r|6#&`'qY//zc$ 'S3X3AAD)Z)` +4]yG1Bz0HBj84 #5MB)^a5:P"x~"R%ޠ$o@'j%h(4=:1b>T@D;: vUy+, m$%'*hrGVܽ!]9!"n&t&Ɲj)b3:@=ψ.TC]lbq.)/'}E A1:6쎍d $ @in6(jMo;Fxwu\5D`cLIg5KSIOK#)eT#\)2ObM)52%  ˗2GbF^!J1U3Xt*cU->"Xə2皐I$@:"TfFV-1M!@$/<jF_6H3)BI E@VoYBH#*`d` J X @<`ŋ-qAjgà ~`8C`0L M@/Ŀ+3"Tx‚;'B.SEw "W]-";kYVӦ֨j' keܻx^'"+TJ` TĕZ 60+kMe|1Vj_<3hǗk;Exe5~*csmQW߉X7|"*,&S0.̪ۮ:|JEe>ʎZ5a֗o"qIEPAR\$Ah]Nj5D $ L4b$LOnآRzhZEse s=@4e ?iPaPCU.yP 0iPdMT"UKdGv,s"S& eGҔ\D )Lp("$p&S@JG s"BLMWD]zT JT^g )Ga*gDuCnb rAf(BF -XMO!Z.B5Q3-?t j#jńw}kyiiA 34]EǝDQ;GϨBpQEQ !u#'<ЁQAlHT$d\iryUhQuB;uAH-Q41Yxp? @7-P!DO\QVTy=ZzSTGUg>Q4Jt?S 9ɜ6?[MǥDI&%K|՜ٹЭr peJGA?BDV. OtCQI ZE[[ʹ?IOg0n3|Bj/LwL 9 y*A=#L="e"dF1AXˌ08Mj=ML<"AB qs Ձt#Ta97.2" Q3p*O~H =^0,ZY :ܑTI*%J Wh5 ! p -B ?͑DLPM2.@"u !A(p?YD(! D(xG `)m,['cfG  O)qb|,rV/'bMt&XaXiB9R2{ \ZFNGh @&9h.Q"3s! ŤG̢^\rHGǓs$DHC ;2S8(QQ˃촧)diMl1PTJժZ5aPR,heRQ:pSu9ZVLR^"!xͫ^׾S#+I'˓*g Rȯͬf7z hG;ZY6> ,iD:XT-"ʑpaF,20HPqFxL2!8+q,"p"V Q B(BRPE0 : N>1 T#"6"D$v5*EB QJy{6EQnEYjEWe Ŕ@`\ǂ ů'zqWP qDȢs @d%C)HB߹L1tpɜ8G(2K4@ Ae#a00ePG BX٨ 3NIT`eE<#c?R70n\BQB)RH$F`+t 1 LqX`Ao?`jkGrD|lRBqXITquxk?1 硆@m(q n`iS`Pr %<7ORrQ%/}G@3r?f!!WDV)fȈW%FP Nr )%Pĵ`r!&HYQ;\*XW 'f,"-b(+H{@vw50% ?hO1c&2%*li*G %Ԃ?(b+&#+?r!)b2p  R-l33X~ 8$䤋h7U!!= 4pt%bFczUR|}33QO 0W72zx=3xC?S2?61#5W2sC15XD3qZxw#]PqM3940fqw3|o3{mC6yy$w4%!!Y .yGT1OuOxQ )1hAPq4u0%0: hՌi1;q D\r;s#,,&'a>(@ _֓!ٚ8 Xjߣ4 =<3s2J1r> ziT=s䜤uUcpjj@ovɔLoɮoױBk\Rrc_k EqbO Bt:ӑ :Nj!TnF\ y`02vvuqz?Q 3P[M QL s'atP}gw!-iy@v%"[Wqx 8zzut59y˝:~.}@G6BӓӰ"wgĚXGz'\VlE}='V^i7!tb7Q)d1Tԯ<ZTBJ-# 8@ %fC.zQ2! 0& 4S/&2.gE $p@b As}RaQsi*z'x5<1%–fK5@נQܢ Ý1% }6"QP3 Z@Ƙ d%Tjm#hkӱlrZpܼz !5sooD=4w lz&.2jB |8 rV#`EH87!"t CIX, Q-Ô 2,W)3{%s9297}k0A%2vG980)S)1*iYJ-ǰu3CR3,O]Ք9eM:wcR)zs4-ۢ+1Ap8>8i4lтաYu#h&IW# $+q;s& |B +ҠP蹻茮 &Z_ :1@ٓ| jyVU9X?-zhCjy?={C(LUCJ$t܄ r]݇1 ːf[ݟQh`p}]= +R,Wl0^!.|E NǩjWJߦP"yI^ x=|Iol6 0!6b{06:Ф0C /!"l 7$Ú *!z^ tJgb2H!$H#D2INhTiz(cr#uȚLI3D3M5d! A Ű.D!3: $PCE4QEeQG4F$ id(E0P9b0pI0ZIABy!IiXgudU]ymUlR8mD@h=!dD-(+yO,@H6rk`d <9L e# ؤτdf`$ 'ԍjވC+>xI9LQzA7ҁx h$Y:6ƁM8! xO( H`㔗2Ӂ&"@g"6ˉV< CdV!RgӄaDkHg`b!D` hy-ҁiܩa-kpܝXq' 07G'__y(XHBf@hEdUi:8JXKn6: ]jXy *O \6]t ;H< ;7n6b < WB`4@BB"%D0XB/ -  `2?'*d#`8E PX a+,WvFܤ5c QKMwj(  ! @OdRdE(%} X:5 !$4@LcX  T$R&K%"J` IOڡcLfK |-TnQJr[`) N83,K@ X22i&idp ib%wإ"x-AM4"ASr4$*el *Fa _A>2%?%TJ!#'s) ~ d(g*\_̗H@9YYSȄM/3 1$ 8)R %ґxXARꦏ 9 nP@7p#xF@ n|HG(8"k=V[ܖḛ\ X a?Hbgo'/Դ($y:A6 [ j,WGD(7!mB׼ ~ dLa@Pb-Wb& A2+jaPNgB4 H,$, .>d8"tXHkM,f mpVYz7bjpv҄' <ëqrP[ l_mKj'0))X" Hp3!^8D &, 9@RJ gbTuĐi3Y!aPI`d $ 3N%<"DBwW)93=?h@ e XK BQ A vD@)dJ02kGqfC$MpzNۉv]zaO{؃@a(HG> fO*襬CROBϲ4u $BCl>A_H &[&L/#p!S$C_aݳ뭡;3tx儐l/qPMR>5!2 &Ņ%P!J0B|Щ@|,hkA/ѥ/xVY=gf[gG1Z\ugKdQ$pA}e(e)$m sII8и,yb=C737vyYe?!@CbkLZ` G5k_ r/ثc!9m)%g=k?WU&d -$IЃ^DzHE{ EynZfQ$B=+P/胸KP  H" U[?% Xh 30 ܐ<6a'yy; 6`C#_*ꨏҧ3f+h7C9 ZB5鐒ػ ɴGa.(xUFPQ$ő. ;r^ O$O,YZE7`8? Ba$b4cDdTeTF&JọLX0\@'X`&"h8  @d:SH+f1 !JZh yycyD% 0@陁밳938 /H ψ)ٙLܠ q)4ٴPQpQFTiEɲ-a$T+\/_xa"7pj@h2Сo9 8LHr ˪qPbȜڬHA5XQ'ІXт^HЧӞ社 ׬Zx^8䑂A *]08* dN<䲟)  L-`[`g`][p˓VӀ >PE@I7_ VЀ(nXbb?u&֩'*^Va$GC.$Ft7.~X2N̶4d =IЕ@l!"f!7xP KP1 7"4C !h% cx#7VC)XcKhW3f@O" gSf "t=0*xQ'R_2p<6 )z)=1q8zm '?6 rmjޞ3*w(pj3 g(Ku328|ZjHzBTe  X]Z/5>ypFRg'xvڈ`-*-*)Z. r4.; bџ .eN 2#Oʏ@.L׉ 2cvM[ >pPFV:`rU{7 0)]YR@NC) +8ǡצV "! ,AaO"6L!%@l?o"7s73]iQAIZ#Ղ' BO*GyzY`Ydv0fL0 n%U)Wʿ:'HXUBd&/F2 n>wDI``XQX`&ܺA…+%ăm}ࡪt !h( P jo: cE#(OB3廛\2uUAd߁%XRG1J=A?ea_Qs*^GPdDЁ)Ȟp4N'5q 0M4LiG[$`RG[>tŰIDAAbTOEjyDGPPzy$ :(z(}1vbA&bW i?lfO BA M^S+d?/<0pUZ̢T!ɑHCrD U%-BPz.骻.* :JAʴ (8S*ǔ 9Bk~[ &`W sZYBԠL-ffQ}*^!X)-~|1A =4!RpiP'#WXR&AhAE\{>6Ԑ ̠RBdtM~}}7.Gm^ r )csځ[~9kn @aM^%?z:,PP59~;..)/F7-]DW<+9{; ) JI4L)r$Ylc64 N],2ў9Iu$[YmqE\aW$ :H$r5=p eI&Lh֛3.}X@L4J@֓U7@[("̈HN@`#GP,6jU@ `#4$:*jQZBBWW)v⚓Љ\b oA@2w>56IRؚ ߐ/ J\%K'Jl ndp)(2GYh3vAFl;s64] J1u9}XNveU,zz|y*N\"օsIYsY46Gakԥ< zp `wD7֬Rřʑs^U4Mu'mЅDZa1 .rCtrۓTP' ׂH@>IJVY Kvi0L j/Pu& AL'C=4Y[D&ȡI#ޢIP $֐!|bYrrׇ5u( B`*x8BMHCHF0B8tLx@WG$M5.7d10\% 祳fg)SC"`5yGwފ@)KH}_ϡN!_| UDSۊe\GN֓٧M:vD4MmCկ f.o5$R~HG|Ɉ` $A\D@(T `| ʢ .!DDB΢@=PCLiQE%kJ,GҰDC^TTːGċuaAٛ?YQHS2A4Sp29 ,U!!DƌλTUPTIeV,/pՃYҭhҺ1Li< GC4[A×ßM˘\Ĉ` LX lP]EXb>`$!JP0"65 O߄N7ILqDȅ0xm$Y]<@u l5ĞD~5Ŵ %T`RUe:J ̗_eMT%Y@X"x%@ %\ƥ\e"M%Ď$BA?(lBQݏ\PFQdz$|GD@hK0HE@DAEk,?  6E  e?\®m'@D@/G'qVsoY=FdtBT-4AQ?0T'NXQ5r|B,`?QmA+X8"!hQ C0LC p8/CÅZaC"RA(!=òD `$dAR8Þ%e&0R?+Ai_E5DIDBAA$ GC,l9iQ0^@?Śo}ӐijPGqQ^|D+MV*4˳G5*h5h ߥ@]hȒ.!)E,GE+BX?-I*)?0K 42L>5I]?R8CR1SI<ASC@<@? 4ɬ|t}TGO4g^HTx_MG8_xo)SVj9NCUTh 5墖X&q1ŀ1IZ$hX*e!?/F*DԢ,B9J>iP@ЌI zRl^0E&«4g? ۈzDaAYQRQ(r+KPj-:KisJjQZ",;a"F'phB*t1(Q7T5C>R8CC.PR,= L ,k%? \H  i( ?^]QhxDcl@ofĜJ,)Zdb_DQP}IUHr$% AXrPprP8Ɍ-ʚ*=*'yScB, 0@( F&.jTQ!ѼhCA\AL/? CTWa#dFC<idC: CH\Up ƃV{WM_ dGV..2*oߛtU r,HRco{xp5@.m/A# 7}\+zl68"08k &~F,L&-}]/xA9DY-oSPSP[W.7zQ,TA*>T@CMa…@H*WcAq)UB "Ὲ#0dpLO)j@ VR醚n\`'[v|gX|h0Y i?ȉhڇ^l6r?/\?!h`-ջo_{T ÿ+e|9ya7rii|=떼-Җ m<>:@ ȣT&/gbb[R [fƫ8S@ƫ$ܔ6I+:d0"d.!?:K*IԐIjUfrEf$}*gXajbFpi)-~ۊB.fW I!GCD8K"#E`RdQl3y`׽q YP_x03ҋ{HFͷ36q ,On1r!r@L rnuGv NIsʀet9%əa%8'5i"Z \_?o$w )2:Ɠ u& CT2]~EplZC)IK PbRi@z4tfQP*t^h$Q^G֡/G=O%Wլ+_0DXF%j=Ajw /ʈ~=C(ȅ\֘xnEc!X9Y~AD/mўڼ@m ܮ 5[^ζ-m+۬-ܶYEnt[^F  w\) וX.#,UCD":t]hd;Ƙ ,c:^6a?J@l"g,Ђֶd*2%-^(CQ`Ȱ.mB/x>*X?N<ThO¯hoJ@V2pYdFS TZ <|7U{8+rNSR:g9zqӧ3flszgCITK*s7&pw+Rϼt)3ʔk.!#=gQԥ6QM@3lI`XQ-4恵kc$ p |vle/ ڔCޠ9(5v=F P;v}jO6xto^ 5Co7K, /:HcA´u-Q `D)Be?*pbHA9!OKppyfW& i\]%DA 'NOzOa>P:np-E@@ ؅uE<:Èq @}z;뾉L@H !  xA4 u*Pk!>Ϸw"FXVV wBXp&  ޣ" h"gEԀ}`aHP<9OG1cd+CUȅ ?n/7%^֯%^gd f 0d#"` dZb $/,Ё"$eNڠLF tA"f/ctƢ &<",$"<D$LP@ %Z T  [ p /~!~^`0!A 7 OZB]b$0bb&jB&'t' '\,ha|^ '_0)D**ZA.""Nb%Rd>%F366>NAz?RH1Ĥ%1"H@:̥I6,"1鞱EE>EZ$Y A: Q6j"'#I@H(8 <9D? ]Md6N0*`I @)2&i%I:'5,ҪcRM%~dM,6P[OT)/ Pl7SN`WlZ1-Xk,eC|Ŕ11Y,U#[U0 )?X3W%XXj0-[CYjZ 62O%Z8%9r/,7]+ =XFe%/T"mZ"a%9a t~# -HaNpt;[S:`Ff3f*`&&$x i| @gopAP&oۘ Ő  nfP& `<~ /nn!o&Koo'À%@:8 A`1D+rH,G(TrQ4/tJG x,jDnD#b*yfv&HKC5z`'/cGp"4`ǎNjCn~a#NHX*lbpb^GV'H THTkTQ8Hd'>(4*lȩnK85/t5$/X!U?@UJMEZO|&vlGTZ" p`xh΋C]5ba@ _F! ;di 61j 6@x29D:CgO 2W 2V&b}1d,/<6ɕr:z`cRMlfevDtl4FcR)$#g[N8C=0R_kCfe㏨Gs;/^5A(Uv,wsu+@=27uB%`bxjjbw\3A][]ٵ%uP q>BC /pB[P:{i8KгReStf|hmitnFDЀ < VTE)T|MJTh.Xi8@8/ >OF)"A'`0ADJIYQIɁIADXZ%,`Z:V]\PE/(Q7~ۥ*NX<( ~Xԅvz[+3]6.E8=4T5,c1\3/L/jN8/L+4M:JXmɌL[`Ȳ6[8ɾeɜE88J-ќdفpo?n@-V80 XzS74_I 9ꈖyS+#8E߲:|9yV֬!uycj=aʹYMڶvA (RM])[ߍS}qԭ|O=|ڱH=U/"ǚI;فu=؛}٩ٱۭm"٫ׯ==/=}jNq /%޿] ]3թz>76uwa;~-%aN~!QW~$ޝۃ=&/DOHK]NMa?'^}Y ? ;-??1[MI_/V?_?/b{P?Y{[A9Zybj}][_?O?{y7_E/?ɱɕ|MA??bX_N&_] ":|1ĉ+Z1ƍy2d>"K~`ʉ+9|ҤI2E)ņ8{ 4Ѝ5El&A} hȣB=Aխ\z*$G~=0DZ魣۸t9&D7޽zd7xwŌ;~ k(+[l@7o :Ѥ3NcǏWfzk~=ۧm׷csċ?`r˿6\ԫ[>P];w1tNc?>_?{/O?`E'`b&` .Ё e NHalsanana&b*Ȣbb28Y! ,)~ H Ç0?L ӔŁBD$EI-I8  BJ O? S΢˂R K<@HB"& RRZp+HW۸s ,͂^-RM"A\p1ہ:O HS_';GʪӨ.B֩ccū5} ?!xj ZfR ! SdkݨKz!|6D] J&7;AP0 ja,}@?XP ]7}ŐL 1Q4$@asp {I$#AhaP}ݍvJkj'l<:uP  @"@P8UPAN2S)fu4j =זUcԁTG]pU$1MeOYUsA}Y{E{AJfU>)avA*R@YP0q?$@)e 9uA& MeڪA h;zמI+r{&UěAR5.DTE.Uf~YŇ:B ;BtJ s8j AωSE2 .У@A 5 ԲW#ur AzaA*$ ?TIAThVr0+PBGc40vP@ +; M4K-QOǤaUOl;?MIPZ tW1n6@!t$F$A!)&чR5Q] CCPUMՐFqp]1ZTCCxA/z\iO>R_7z?^˟dVoVEi▝y+C 4oz>aֶj@) ҈ɼ8-H G06D ‚Mի "Q7\Z4ff 2!C$aDv+@j6x`HjB]#) @RFԘ9YtlfP;bᐴ8B g ´8 j@4&QOqO1 xڀ`L it] P+1LACVH 9ZB4h632@(\N2b"`lG8$Wi8k9Bs! _|udP_>nN=;eqsMBІB IO =:ͨF7Qd l_GGJҒ(MJWҖ07ؓ,Ж M XJXbByCTMl~q@"0M"xȫ<@x8KiA|/"$BYOD wH]I!k4 " !"Ha#/JO`/0W<0Ba a | -Z7"Aj$ג^YV Lr%iܙKK(X^Ԁo؄H$(Tm"R(h."Uu!1QAp[EF3ц@4m~&M(8A `MP [A,S0`A $ MJ Y!* wN$CA@  !;p 1MQf2pL/'jmLY ~bAgJUδ"EDyHHI]B^>ZRa υ%N&)`jjɥ+ICZ$8KNd/=K +|4a" zjU !-Z ɳٹb bML%FxALҋL{ֶ.1vKdSlt$X_).Hq '/q@aX&o-AVv0Q e2  p|*6m1]AַER@Z7a"{֮[Eb2MD"c:?C-* @KkAhPu+& @ @LB{^黝mw޽ȑa):D>}K>"e/;+ 5TZ D@CZ b uҶMЏD$Z7 ap ߷Ajls1bA@ړ 5Zl1 nq[K7qFo'Q_bp$pU"fp4V&mI'h&=%@mnbJbD6PK_wPb>>ã&dTӄGbMG#*Kb-.ۣs !?hezO7X?W-A ? wnbwÆNA}Qrh~Q,҇xؔt8Xx CC;`'a1 YQ zsIUt5xUTuX<'Z%v^t4tmA-7'm9=p*!14TfA#Hywf8m %A!R R*XXpP @#z p0@"0SAP )G WH1"3jR 3e30 @dz pi : Q 3Sq SQD PtqR ^s]eyW'\N: {k qi2@;QaQ~%9'-%o >252IsqA#7`*a(9Gcfaa"pE@ " N /0) P +RH(鐾) >"-UI[ ]j BzQ)bac'P)4/)@YRVx'm@ 1X&ZHٳ;F'%\Qh!-SAj'dL'MÆKujpgb" h\ !?@+=P*{a1K%=S*mXzQ{d@Q0:~1 cdRb L=t n ]V5 wGX "pqm(=`(I 8; ʮI7Y9R5Q˯ {) J$Dr`Vr 7K,`r~lR<wuW8@'{#L۲lu፼bĉQj0 AG@&dO_ Ddc+>*'gL^N0(cK6[p*aBT`wU3ceb 1M=>!N bHդXG[پBAxɞD (rV"sI"S+S'WJw>SaQPÍ#pab4\r6 \#244d"C[" v)pXb*a2np j v RP ܫq±a :G 2Юx1ba0O*nB~a"MR/0߽hFVZB% ws8ϝ3FBĈC#g0 y p,+[>΁j/xZ;ؾ!Z?:0atzI+2wNq)S{;6Aն)幒#isYA?*z_$NfoVz\;?uT{A397<K@4`XB?@I$ Ŀ,Mzb1J ZHҴqi7^y祷^{7_}_8`&`F8afa8A[LP*HH3OBo0DІBވ<0= 9Gt NH`(& b烁`('PIFE40YdZ%:G㵆!C,4>1F.'Ȩ:`g!   'DtCtFtؼA\w"FwV^zZ76zLj% ڍ۞(7|&ж Ȯ_R*HnHA[i`AWAVRP W^!>x05\pIÊW H`ߐ` P ! Hʊ* ,_}bH']`pCD צ;9H~4$j5 7^2)p .r``8aF0"l@q,]԰ z[/0{O:󼛤D!BԐ d?@GP# K}S2H ʠ;&<|q ~Wl@E΅+|kvRn*c? q<!FV7S{6ƣA0f#i7H>wE YB\@`WD$ 5:x I^KKB.HA_C>Cs%)<0@AJ!KHZ_TjO 36Z\N&$ HNPJ=Q:;F&M{1u:@?P. )č|K*Dd"Ύ4}g&t!14=O>3)U*'AC,-q[$`Hn,8ɯkD.`I K`}hȹ{V)LHZV FZO2lY"Y_qo$8AJTⶱZr ؄B+ć 10Mk|P<9I\BP!˹Gm.D5kh^~2\ʺeuKoңnPZhۓaH}jec`9bZQc) a-k72!dn#e#mh!\ :1܁8Еny! P2Ɉ/ZW8Y~xA|/fǗEh8Aj(- JR`I&E- p,QpV \ TD0$Qc:@CM豁Us9"l cوgMV( 8'X-XIȎ8l+wptdF@3}ё$,:BbH22s, X^xkIjDli*ȁ`ɏ%ry-\_P a7p. x Aa^k(!Uʬʁ)L.HjeHAJʟ'XTItBq`>z"A٣ 7m#ZCi'&YfHp lX!|M;dWFz<f'nBK<,$`Oȩ= (C`sgYr ЀeSX lJ %`eO@^%AY@RFPL9܉ Aa;åE*65HZ2 b 0Ѐ @ ŨKrЀo:P a \+5+D8ORRex8P9X*r2&0<+@*j77v3-SA=uk;W8T2?aH$;KAS,mH-9S9ɫ2ꓯG򟁰슼!g ʁtQj( rցsPc lF֌xkؾU݅څ TΆ7}⋲BZAҞmVf֦D1> 㱠m  Ӟ:DؘĨ8*  ƃDv1!L0FpPEA `Y,bP)p[XSoBm"(lniKl HUA" q9tTH'n)P0P⑃LeLh`q\Qw''hؖ4D4/+C^|_E‰"I-=<&jVφHD3_m*K+@>eӪaR82܊/ ʁJ Iz%rxlI߈G'C`(ϘyB\҈ZrS#yPIj ``IbXmڳZڵU%G`\6e0S( ( Ԉ4 (P%F5-哃@B 8A^6_ƍ:dCSg%*S$P% @۔ OSԣT|x"+\Ӂx*!jh,Xm @ HUXrۧh 1U[_н0X AX;́=-A(ؿvP0a5 Y4O r+^C_,Z A i%Ѐ-EAA@&Ԝ4݀]#vtV =" R - py J>(HtD+,1?I$.), ˉN(RFp)$pyC?>gm*vTsgPY|p)Eplmfg*gc)u@]Jf P AAZ y~z+fZ{ζr giĆ馽~ ЪgEU( J΂g'fn{7뱥iyj+& 8UT@E&BE%$ >3+PVDԑEu9sTIP曯L5ݔ-YP eK RM%UQT5ָj5jaVC|$Wuq#D^ō_ KV8VjEMm8?TP( 4j %AMJXV%`QAd@(&xHPâ@5(A rH+$[8`@!ՊPVaL~@ EAIEHj7\O77VQ`BDPD-d@I0Fd` JD6T=J&z`QJEiu< YiTu@N!tdP*ɪБ2e xNwiIA|%,c)K% x> JV(_%L,e2|fk "|L7l!E)q},l~5!K"9 X D! 8Ă{6HcTPTH?<6mWsH y, zW`GdBx0@}z \͖\Z2 '%LWDĖjX AT9Yd!!:]+q$|ZDL.*6J 9]ʥ6FˡU~** N55%TLf"M B5F9Ƭq䁠DFŮZjBhCHp;V(A5?聇C(X lBC[ 6XLL?}0D\l`BTV&l(W:T9H1@+FCdf÷k aVM^!itjc,]YwHTIaE\;L<4f-O^|y%oSDlf;qZcl"/[9rHth@x@ Ȉ,qpt M[BU;aFд?$Fk@Vy@xpM @@YZgܮJhpAj=6A%8}[̉o d1T@v%D@\,?XelHoƹ*#<>FD*~cj 0ٜ%Uԝ(.fD֒1s$D$ $CBBBU:B.WX.Z(D0XGCDWBeB&?9DgD fA,=$89FBJN%[PfP@F"tbƧ 9=T=rj !?T] QA@(]BL6G5HBʘ8cDCntNd1i6C6}yAvX L-by KR1ԝ&ܯ8puD!6wCRMз91'Eh LCd8CK j3v;zP<LNPE` HL.,9=C^LCh4ttC@HEWl\\Y) C4~3ttP;WHENPeBx^H=$DQuRgo=X;XE2Y5IZEr:gtq Jt"JԀӆƂ6 '@ i b' [{ǔg@-\h< I [0rPhOT$jc$Z P/SlJ|It. ,P?\F)ƻRw-^2'85v8Ʌ[Җ"* 7W)%/!huޟ>=҄E6;&>DmFmFxQCϖxs­j-wC:;0a , 4p 6:>`8 *țj{BƢ.  KJ߂oI{ ]"Y 2`* n}_Z$y\ѠvB8٠݀3a`LओL"YE[>GZ6?D`%P ~Ah"*Hrv"[q V8$b}c dQ Еm 06Q6D!E-="UzA[0V 0F=nZ nD2IXN 2Fe}p SaqPu*SA(ɪKBrTT?*T +q(UT[9rA\_D&b EEV.(-ABJ" +l*Aj3D ',H(sDĥh6t TYʰ qQހ7h>PpSh–G#Cv"'BΧ@Z\oSkXVA2^}Ϲiky?.C@a#aHl72d>ӟrLZSe;)>P`"U)2b!D$8&!Ŏ :$h A)'JC 405eJ!v@! oԝ*HSfRt+DT5*UĢ- EbʼnH$qH^ B)=aUdPAf  H 9`ÕdK<I@ۈ:cF6rA<ꑏAA8ʔ"-HsL2!$1 JM~6AQIS*Qg?d2dRHDx_" YBiL ѻ"%B@<~&5rjXG=2)*@aϵP #WH0A;PA})k9Cl@o%hCgm.|Jq!P) { @{(xt:UpH? T @ WcA2tk"P> !囈 N`8xe·'=RDe,$2 yH^ K\cIwnގe܆d?ڛ2[oz-|;=XoG(ֺP%|(k>iw 8c/ b~4EBP4SUMLe|]c!0A|l"~55p9%A&  ]vR @emNl]\nDn!a/ln$Ҕ b`.L @q6Fu hH*h<#xt w,$ka$| {8k1q '. "*f 6a< "1-#s*.fNZ E!$) J InA>.RQHA#Z!" ރ1^# L54 4$B)!*v# 8LĈc:". pFca"* ' 3 b(,¯6$r#x>Aq" (,K0a`(C #'2b!IC> !D[BE*z. zD` 'oe̢'׋'{aN(o#H&n DjH$0 L2ś@(b7[nE0 Ua΁):P@%آ@%0`%0 !!Yc&x(,8&S3'AW$"3 FNBXX[\2d#%*/bZ "ClcL;V ZDrӌ0 ꨚ0tf`lt"ua4g+|")C">@$F)G tq+T:|4fFnRb":op$ bj;3cL gzN>o#Pot*g HZ&72P8U>uM籆^6BQ!RNI }~*J< db"j m"n5= pR@(75&@ zr)r"`M NNZ93Tz""*VFIE ЅS0):Q9D"b\ ( maa!0@ rP;D V%~I_#@5E]\ 6b N@dMffjB, @ Oɭ( eHO7Qh*\bC?J" *n_h`0@C `D @`(aBq^`)56jfQˈc5PfjnA1 2# 2=$"s/w"pAls"pc&H""-Ί"(f@+J^ A+1az783*6&`!e*M6Fs:dV,(.^Ƚs'beL$x.@wTPifǸ.~p7 5'7 w%BK SkjFz:@$n4`g>-h 6zOE|Zt#bFd! d)sBO?ᴬR1HpPuwP) l*L0man"У[n%0ː" L& 2> !X< Bر=_U+9"%Bw! -3>PJ:ẗ/L bW'WB7% ZuE"9!X-)R`^ ] ^ p !u3 T !8-AU%VaLfbb% ֊SYZm0YTҢrZPpfmg!9gzl< s*B:j(⼢%J0(R~Lf":b+LykȎG((B-NPn[:+:#t8:rYBNt|jbV<%0RT'g"&'d4 vNJK!, TI/Te|+ok*B9dTEFd`w&qO(麼fBSeh⭵'4Q횭1;,M)`!Fxd!dD()R] !Hm p[_^d3&P~o) *P"p1~$6bh";'p'&bJ3oh P}gB!bT;"iS}?a Ri {Xl;k"N{&B3>`bzl^1#@]ӥ4= r݂  ])@"ʯ0y_n 2,W"vsS z؏ ziC') 5d2-*=$%S 2,_Tv+ioNH@ m7C ~0 WE(MQ2%brD@ P}Eh \D@5Ad[TW]+ D2Dls#^CG#JiCa˞\~< kjF @Dq #t  @"¿"<@¿  \GHPA#,[| 3KBX&K-7|x%|  D4,U KG O+X՟M %m-@e1[`%藬̠4w]UU+*v"q՗x']yv%[P SS>fp` j FG{%*[ | qMB@@WW?,/%-_',S$nLJ+M }XC2A8DN?B#?-q2+H=rl=D+8[&QK$7)H~p?JSvG03r\r+"DbUBkJҙ,Ϙ/"+R%`L=B80;/(` ! ACht*NJ=1RH cHt> RDA'ҭ?RǒTPKKjzCH a?  \I\AP3:&mZ?RѬAhp?D36/K$gFA*BB,^qS9G~%ҧݞ| X(Kа0asP،J"Xj h" @! su֣Z ie#QbmNuWX4WH,ܠu]U{hIW "h` @CIES `%G9vڬt9P9a>SdVV~ DW_~sO*UͮM(Ěd_E: t~QY FdTQÝ^K1@bLGD.V*KI`0`YTM@ ?X,{V HlM%IEA$XA$k%јQ1NcCK`ZQ6b0L6\&Z D49?# 2Yt @0&]3`5jaxpk%PDC0Ak\Dd:9 ^Biy`A-a?!-N% $HBV!xl(FN101Q0эVr&hE瀥K؄ dHx3Fc(CPjI %rD.N-6Q~r&mf IIO|pA QBBOP/,JU6@+dt%0 d=CD %)]v6X%4RJ iAJQIhYDJ@%E#G LE+OuNhj#{ځR1`0,)0j Ӳt%x)xO 3X@wuT@ +yRHo<&ݪP1 mJ7PƤ=[kZ Q3nChig_i=5΍htށ(;\3&%m`[VEkϋw;|ȀOp#ؖ%dI=Tkc)gLz&{ Kx/|aBʦQCd(0]2*EH{~a8:ʘKabx< +RɍR!Pwt H[tR y\ 0sb.V(Dl )B be &D(gBYp" ЁPT @!ZDhAg'B#xvp!0acAL," 0aa]QR4̻Lh5$;Ė9?(%-t|K_RغAE6 RAyZً A:a$-񄢥%dg {!Hyh۔' j؋(ug"g.-@'*ޱC@%@jЀ %.R2",9E~$ td^ĈIC+@tnAnp?0`ǒ.ŮU y gDAD@xD2 K"B, %& BBnIx|$X9+w]JAuسؕX,Zآ.]&^wՎvܽ߀p|Ќ "hdx)@R"$3'NAd6/`S3omTbATd$1Bpd @>zG PlaVS>0"F-EFp4kP#*$p=!9*P'TF1уVK'4A'h5HX|q-7xH,R{b1V0ecDS80U5A:\\c$:]Af1YNA66AaVTf9eBxX|!8fRgEQXE⨎??u4PpYF?vJs"Ћ E P~{/ 'P.tZvN~d|.y`@w,{qDR+mg((Rs|$1 @`  ,Q F F:vG/Q#rL#~y0+yz+e+ d VU.m3MT$>$ M$nS{%gO. t20 jBK"b .'G',.O +Ғt~j 0* qKf0*1ir*,!m@ -1APb"'9,Eb4p3Ro-V+q42y-{-- GI  I  u " *1 "É1m@,3# 'K/*t+2j1؉-XcE2&H5%c `E0L5))b)D:cG p(30'z -ɹrr-4, @f5QzP3 OZQQQb>_*(FKJ)6PZӅ: !\c؃G;.L n#7]O$ZűO%مfQ,ŨVБ]!_1b 5}m:ӠS q,sh#N8Mr>CAG V];md~tZ,Q?1ʍQ0tm#9aAixQxA5+ 1tUR/QG1R@<#5uHSuNC8La[0~bd1,{ݓ4ݞ&AnahæwEUwz2Z.X!n.H9Xg6ii@Q=Ł:H>l={j){!JڝrP8UQq s os"o(5Kb?OM[G_X1359%>@ ҵAjUj 2!M"DZ :"tbY E(+Ap s$w?##ړ c'T *jHʗYO.VP0tHTV0]-U lE_ϧOVe `.C",|@`/L'- mׯ_o j衔7\-]h]z]v뽰j&)5nWޤ4B65Nk+5 Tp*Ao 6 JL>{& /\ k“o=uFǔ! D@,:|iA=Bv!B4KL[ee3 S! 6 `HX?z7H! -zYUQV5PFsa5$'3` b:8[bE0&%Px^ fAHǖaFqFiaG@R$d! mV5Hz/d[A7̣oE ²>R$e)MyJT>a2`Žy`8D P!e*}K`S$f1yLd. !i",H3ETE[@LH Db˵ljY( (PH(CcLpɊdX?1*4B:'@AZa YBԹIMD+jL4YTH/?K02)3bҔF#C9SXlJ9B!@TpBD℗>u /xB҃OI!V $^(He)^ îk"P P*Q  B! g(l}L7pC3xQ:F  0`Z x8` !PC*@y ֭$ uEA0 Aw?rQQ/, 9/n7THt\qEW05$@ VEG`*2)$!?[ؒr4hc<e\wK0ȼ!B3B`Rx }o) b5hy r`-LWPC+R/} . Е+,6%+J/p" /+`A/[6Hϩ+aE H )@du lc!@2 OIQD`>uHo2:%H)w `D_#vV6T{(k(`46*VXՍ *87[*l4E A@h)&M%%Jq"˻IoJ86LBPV M<Zx[5&/!,G q)I/NrTW!n$`k(LB2g*ʆ((PQ?IPSD:8Gi RԅI{Hԙ Tx(0uQhq+39vz=tCJn|,ċ{Pߩ2)7dX9Xv^nG3< F)ѿb_ZFC& q?a Lz#1@nl퐍x!j8>u*,R 0:ʩ0ky>zMa@1kь3a22P@\:ne`99KIj{Y{xAX{1vHMY@Z[x[7/.i4 >I:+C]P8>t1H CMF39E,OrV^̔EʈMΤK1 \Nr,YyL ,r|4}4(H1$0G ndĩYg.IZ&SEA0pQmSij?S$S huw243?ٿ~QԸĤcb l,܋ A-].]|0 Vpx9914TQ!š0 ӆx m>ქ` m/ܤ(Å0a*Q9:ÅUUCCT hV  ؝0 H5*j ;*݁WViu H ՀWD߁eѺ {ׅ K\30ީL",ԑ#dѝ _m KX5" %%$zV' |ČHJ%ؠ+CLXj%4,XZ+Y xK 5ȩ^Ȩ^)hl( `Fh*(+)*'觚R0PnP5(D`)nè^E0*߀*_/Aب,xy` _V) D(xU&9toBGtxnV*aRqPta\zDGn4j8BR(2Pΐ*ŠԫгCZ.6Ū>΂ ,^i *c-%l S$Uk 9dk ePvzu Pv(2iU0?E&}0A(,_hg)Ԋn0?ְ V/T.#20X#TAVfs`Q"敳}9grsq.sև((gxVgqg0|upp(`zwxr ~.z"-5C =:4ɤ=ZMB{*ԘhYPxBs-i 5>98ư ( X H5 iS ƶ+p OC > x Mp#3p+9*y9]Kl964C5]C JJyךB:@H/j|i+ (b=b%ShX=]⻶`hkn}p E kЋ> ԂPBհS?rPx(e#xd{d@SB-0V0ܯx*?3]?JU#T@7o8hX<$rM x3 Tᘫ k|Fwtlh(xHgt~sЇ{9>hw gfa.}7L ۟}"]J6biO. 11) yih+ȦxL= Ȇw  a Mb];\Ԅ'MD ̡V_"\ (H\CCP+8(9{-pʢĶULj A yKDQAh4d[nLg<莥C i s"@䘢zl՗ P: B 0;Fq9Q 5Z^#]R!Vo Rc 5$j' lm'4l L8jBυ2L ߈4G˿A'?C "ǎ1Bxa\"G"v%˃.*и$isH͜v9?I yf)Q 5 J#M ɞ%۶nJ(R"Qw¿CN>+/Bv1㇬CVjH62fU *Gt Aٻ|y6?ݸ} ~>Nʥ;Ek^L?ߐ^UtN`'JZ$X:17xM__ `sA0>h N=s4/3wUa|i%|78A%Ԥ?h1 SGF(P,n&&DpZ\o] mAItP? D.`YPBX`% !t)=?XdulpP'@APϐjz֖xVru in@= cY o’ūA2eh P t A @&Hv 8Ch@HA нPT<_-JjA࣏-'BPuB {@n㰫 LEHiܑutUYZ<uLjAdZ){e#͑EV.}tҌ%/d5im]PA ȑNS+oKYt"BR uэucCωBXwp3޸7p5[ x ТΨ-;=B>{# wHwQWS`>1PBP=bCwځd01 lAw Kԭ&B{pwF{ujcJ}  h"0 \ blD *@qCW$:V\!IP4B$8H6(dzA*9dbXČDmRH ""pCtŖ~D1F R}!BT 8Vr\ و5C+?>0@Y7h dDzH PdD PA@pD0I}9A2i !]p2$䀎:T?X z J8R !4@ztCz>$zsT@ Z#GID0t?v4@Xm3,`x)>wKI?U*">7(X!pj;  7^Ѐ2d(D9^IԄXE l D$VU#_UY$`ɇL PC@] F?zRKx5꛾NUl0kW ؁Dî Įp!"8d~VWX%"v$K"@h-8jEGhrDUyUS R+wŪ(kOg!@-t}_<@; ('l? ,6=/ˠSL@ $  _z"A˄gZXj(y@`@c 2:!ъ#@ G,`bzF6K65d QƩIUjB(Yuߍ -veI](t8?ljdm,f|K1Wťȑ6@D3G=7SY72!sbgN&zjjjqŌft"3*8Ј=5џpC> qRP %.wZ W^+:tra+D@2ٰ$dAdf57]u@5pSCM{#7G*ax`;C!X!DD  WKWȄ@"$8B+>0 yБKtSAIS lLC/QH>8I3H0\"RL)J #-T|y.U!D,d8ZCd1HV}PH"@%jaYnR7M@ktL3T^񖏤D=ERHΌ&ׂ('X}LA]N")5% ўژA, Yb=9AS0Sb=edp\%)C/c7ggb=ԥi3(7OY &ug 1ĬLݗø`-Vȅ4@08<]3F/DRZ'2;Ų Dlt5,C-hB`cf&Ȟhm{ nƘ[QpP**01T9'8łô@3K( @lMN&=CQEu4a!?BAP) ] _B$ ؓ BԉaBS); @9&l[7jCmEWH4@Q n%A  @PGUYPC! Hn@XL@͉\`@qp*V[L%jVUpLQVOoކiOD.Ð< (h\ҕIdW[bhHD ZvdSn&idA,߅r \T@vt@0hfex۴Lbk84dÄuIf8!=?EtA,X[AQ?t@]JfLPT_Ų™1t,$S-\MKRtG-M *6DxD?\ǥ8ȉ(@DPU۠\eDfW dXdG G|E,9BU M EsSAXC)vL?q*N""ڪ=CHU2.CXr.`(()52vܲ#sb2XCĆ@D[2EZ +@<27Ec+6xE㫸E@: lIE>S0I oHOD?djT\ZPReY7h (<ɷD͠ߏߑǔV^f|ϟό\m&ϙ6[:kC5Xh&B:"tZ@ Cܦ;ƣ #}ۓ%ܳ6uzeu"|Nr[PybrxrN^KgI*D)^X? rs(pOiEdO~~/F4D:0G|ZTGd}Iʐ Y`..Z{#L/0EVMUv@߬/" ݼ}#` ܽP0oV@|VA =Yv\3?2Ts95VVP"5 T@S Sp5l [C9B]z@:6ш ,H$z+Z."{2H F  GFBE$" 4%@ x  TS"@$"+ H*3% ȷ*@T*hNSP > :1耂"0$*$%EͺΔHԭ U" D * @z4b3MEOzϞPuUU?W;U6#JTU?+WnO=Z%XQ\rէp?JZ Z"P3;uLp >U6`$UXu$ӰXL$ϒV ;nd yZZ,iCxB %R H>xSpw^jgڨoY k,h%知gzR]V_zׇ8z䵋[6`_kfLeaL{&{qh%JcpM"(MTea΍Wl+ͷ.s ='G#@шnNX9E"*fl7n[4"c70z q'BíB9z.^p w *̙LMň&*p[Rj&+0|:pLD(ؓRr~r\)cDG $ $xQFD ' FC e)40n@B8RpBHW0Q p\*8(Z IྷhS|ɒ&@?p=H#JS( !WXWIXejV+@Vh;&Y`F;9+)A`VG8A)JKwR lIJV<@_Sނ4 Q[>;֮V!ΘA14^ا9KmJXY+,L<]t0b0VRR#?0!hC'i q$A "H$!" %t^x/PN% VtEP q3m| 1GƢ@̄xz *p?bRĀ8eKm+P&*)\%Qo,09Hr,:׼tk9h:Gu2҄0htDQDl[8(xaԣyPi/WzOsem03LmZ%dE$n?-Z{囦nK8FkHBIeZanUIluڬ&;Q  Ra@l5ķ1M$%%׈e Q&( 8k<`0Q?ITv_X*W ڴL53%>V,дՙ徔05{GoY_&g8[_wc-gkiOxR"x[x.X#D6cil s-$d `sX3gXxoqEv]X3xRH$x3MngR_%(B*Ό$3aOS0$79c (`?q5f5È}|c6jG#'?bG"4 v D@*:CCc(,ȯK~d.ÖF -L$,`` oh d!Le>b.wPBƶ/,pشm[O"b2VpE)D+ `G ܄l%eO6&P%R mZ%@$@R$SMN`hZNEeE/Hd$-$ TpO, )T  ņ_2ũ<*4X* WHWL#:2l3(&c(Hވ OD^6z"@ƛ6'b~} JB]q,-c1aSqɭfDaaFjpfpkId jT pql̜&ߢg9αxNyq I&0"n+/:-KF0e"]-28t'3rH0\+$~iN0~(l/0!XaVCf{JA"N\#̧6BD/#,t}92,N! D0+g'||'OeuN';ЇOxfv $)G%s(Dp+ LVm, BE`fI/ȈzPۢ$UAs!d,L.PS5EQˤ(,p$0!4@H)T("D` Af.BN %"S$! ĂɏnTn954B8?ꂪ8P"B>!5d%hb:`lD@$ZML*H)@r ,C N,՜ g\4GkZfFVm~ G;PUD`PlDWBnO@EX`hMTD>Ɵb# MW)!<)sSȥs~bɢbTDjeG+22" FF;h d:HB4eQ0A*R!/UJ/U"rJ"A" ~*|9 pBaOnh:L*,,nIJZޢ4:XU#jT $SX"3,jV*ྵ/H(r#7 $Is/w"T,j2!˦'&^X1P6gA"vR"X+?U2,D΄)* /Ȍ-rw1v0Ĵxj0l ܈ ^3!tl6w{lK!$nK"WyÂ%c"((a[~~Kb2`Yf H1t-BW1O#X'(aPn$b$4>3`H".2p-B"4,VN"Tnj.""".B?k"$#B\Bdx2xI$ 8NZXp$ vZ ̸@#=}$0B#t`=O^"&bB6(<^nNK50c(6>j8n+]7Pr@d@*dA lC'0kiG| 05j nDc?^Z;b9yv`(yv./v@4:gyl=(ItPBÂȆ-f(@dG榧s7E>2D +6!%m3N"dEF"> V%b4UZ* AQ0/gV&~n 9Iu%~P%DUH%^If U(6i M'PR: MIZ0Dô%6R F"v_$Be)6j4F:n9*Q*T cbݜ/cjALcx ZڄTȜVgebufE Db/E"nLPr#5\+GF#SBfnolD)k^*FTֆ Fn2]4*NN8M ,-/L2>'$#rY"`(RH(~^%%3&A_w5Fg6~,` z| )b%B*2}5x+@aa-alj砇g,|g}n'Aabɉ40szLsPD&2 gaz.KNՏ!NB`(0$>؅8x8 u+>7"@`kN5d8bU>S`5?i=X#'Tưd;,<a(Q8Tɑ$#tOaX=j"nֽh]6Azػ6b[B/|dj|\{- YطD"Vfu< ~_âJIssL2\Y^;y(`Nal||~>D?(~̫K\fjUZ"$Pfd@//,be \Հ?; tsK$ADjXߋuIɓ(S\ɲ˗+ h"DIɸϟ>4"-@(ƑJ$-i_U3QMh ÿ aW:(AB7UXÆF!XpǢ'@ Qɼ=!B8Y ! G0AE5/N ^[-h,L3HŌ!ʻ[vbZAFd'դ]掘C>zE_{QtS? @TCD,Nϓ?W2EMbIR5YBԍ_}ElAESAYlhTE$=©袌6裐F*餔Vj@DT`rQ9Zb#Ah"sɕ @4?,B?Q 4MR H2Ū65"! D:"")a4:'\%'"7'uEٽ?_'GJOpAHDoCd1DG$?$~FI@"~hH Q0y-0I΍-2'IudE$@?' `/`QBXOU2y>B8>S2h̒ET-@;-`L=/1).PF:/4L6)*J ?j|| 3QTԽĐ@xT#?O&;N7 F+/ B[| \*`T%1@&t Tȫ .[H ":)0RXq:ȑU"hQr D/BBE!z G?ait?L29MǑ"!f( ,!2pDԁ TfJi4,/z$ !?SJaއ*%K.b\M"Jl3AbS,KZ0g$sSfO &"-pM>7λ@DˁiXD$IHؙJ0od"4! ӝcugFE}UcDEʣhIl˜Pl'y> ""B|& F׈H8t ~(<{:cWjDD[85"UHEdËP* !Dn2F|hUQEݑ E$b%b wF x'[$un0"^ *V8)а56"#NDT"EDG} `b\!"(Y!Ɓ,L, "À泗!"IH5RpBH#Є4T &ɰ  K^&P.lfŁzp~ tOt(4Ev"f8c?؃!lZN Han ɜp72 xILCE$_%$Kҳ@ƦP}Q  Znax]e[DnBكp}3PFj&0. ' Cdg{Ea aTSJEP@=!F?>SǸ)I!@ 81[**0dk̳+vs( >ihI"pI4sN'?veX@9yYR ?Bx)b _BL!_p&Mo& `8 _<[@=.EhD{dW&nK\po`$rI^úHo`7E?Gd3KOʽ]\8z>7nJY"&^CUB?V*eeZ C`\ $y_;;!,g'njtXB$z y왘^ ?fG |h)'*"I-HD^D )D.|cۉ ]e^J,X_0%h4O'@g jlVT6`x &Igmٙ(E w(pDf`*@kh_ X0ӷ|L6{2!V'Dk!UAW)za_W_bjh]a&n;j6x9r-1i!{AرSB!q&cT)H}p59pW$ f2#ar#Y3Yr@:#hj$ $78]h*7tul8ұkMQO)5X؉&)dSa0   @* *)2b핋,Ƃ,u[BB+Ш@P eH/"23z[@634"B1//,4`M.3"ByiK/7/FSYvm3@ey80d:Lbx 6jes6-$ .m6 @ "N cG("#c3  0 ?ehl;:VR503Y{$, @%/-- `1=pXB=S=$sgָ[t鄟@=e=<X`PAcX d << Dkܱ6(gA\N"B](b 1ݩ1.@ S`ON!p -zp` ɠ?t[sxCZG aàSlNW24*P2бldvEWԡ+6TH#dP+Qr#T'X"fO`Fua4'$$ItmF&G΁&nmƱb1nH{P^QHT'X1|plhd=!"kuR LʴahL0tNnX1tJbGQN!Hq(zh?PgNwNudj1aX S*.J&JTʁrի>R!!zQʩ nqTQDTddԬ|S,1T<Guh p9Y &pwp{%Uu%%5`%p/ p wHR" p9p=5{ %v,!E9Գ[][[!׳\rgY9yXz x YV;ZR>0 = [cQ=A!_pkbacA8ɐ%D;`!TUz`]!1 '_M W/1  ̒p.W `R`pp Ǻ/P``{(xK+"@;y\/@Acݹ^Sq$ZcB6P !q[b+ni3l-<ʤ) i%Cr3Ik  +r g~ PWN&5e&hH4WV+d'GKJ\ZanaGv6iDAmqm^j 2Jm'BܱFM('iQHk 5Ӛ 01Ԅc&L fQ1ASBZؚx$U+lŕoBs  0"WΨ Mao" <𺷬 ̼pko.`Έ(#+ɻ4:+0h(JX't*G'PHw!G7^6g0y  (0}c#OsÔe%#O 0 ۰RR;@WpUpYvq,4;$k(y6 !.%g\iAF"X3 [1ZYxՑX_ԟ8{[Dz0`\+]zz!֌{/_A}ѧ=!ȧ0zMֆ"a{T,lT3ɱ1yS>FP|thJ聊-aۛXKTj SJ hM֑ES 1 / U 6 ڰէt{QB.Ɓ"J S |ic$$]~ጢ}n:#IS;B=7=sӗ.0cj2'=(z4FA|-D^F~HJKRa o"0N0 JN`Ș"pO%%&N -! P6..>.mͭAp3y1Ј*N K085qV§Y'L ;Q3N44yYg^v/rg$fWB }-1N 2ЮM9"r`.$7 v1o #$v Mðn 9n * p, P) [pӛ=|zc'.K Ju~[c?/)^0O$GrO]8h Y4ٝr%% L2pRBA+X 2 -"E/D׏"|P B ?B=ѠT2M:lD|#*EČ$`f/pFFZlI{I)GsbKuE.EKE5&{Dam'6hhJ*/&kpLl1RzF ѠaIqz%34rydA,oL1J g4F *K|o"!/q!⟇ ` >QD/D)r^p,H*ZBdM/@'s^$a IRUPDž[" `"VЁDJ \p%h.7X% "[,`w%hC!nrkQ&ClЄ lsb֭CWM\Bj׋|)jaFlc6E_3;SPۋeEP"bHZf bHD@dr2H8i! 'B;@^dh ] +[ѵd!]d`1 QESshj " Ç8,]4(^ F* D@(.d!2n7V!_ ঠI J?'d' :0(|'0Ds" M9VԠ'&!H%b @&aCR 8D:0"IĈ] m\,"ڶ$ۯRt$ܭh"Jɢ"|5ьLZf݊<"a>Bχ$Hxa&XD!x ګ V)"P=]M r# C²薖zࢌT3b8#9ؤڠ "B:R@L v:( Á:\[Ɣ ";6ʋ< 񒷣'h-~ h($]Dx4l6o((%2 .@y(  X"a 6BeTK8H /)0?('H/ ij Jq2tWxmUBw}NQf}Qp7R+539?H$H +"(/*!@ )@O{MS5L\$y*;@,W5o)Y)@+ d6-x(@LHPlKmc6tP (R`0V`g0UЍR.DiEz (`􁡢=- `Rȗ@ēf De0h` 4| C9'1?^k  @0y+cW%STEYTč^55MEq骊pŃAC PXJH 8 1]Q9$бLTx.C*ѡ 03:k9H8*i ٫9ۃx4" xJ'ʹ'2ኧ3)ʨKHЀp  GY C# GX08T؄z2rAC0bFǂ˸gaFHlFEL$K)GHG`G :+?#ؓL?п4/@p2x6M/JؔMYD;i6B!*r;, B/tC+ ^ h mo` 4ـ\C" 躔Hل,pq?7Ra*P 0ũ%w{E*:ɗš%*ޓ P PȣѓМ`ОzsrEkpQ3`y xEʽ+!e&uҢ1 z)*R,0({,¬, VpՊ!0O)S'+S,hgphHJ`n"{H%k[72/BeK!H>O?sfQX>Ӡ,95VsNKI$XT4YP Kگ<SٶjY4 D6-S{a0"a3}0b#ac xQ!+5>hGYQ")#6w}-7 `7QB+ӥ%}A'j 8 8/HI+"ر/RF@iCA-ժگq'0Ƈ0THhK` 6JWժUNDM\ThS/Vad @DN\D8ChSpGO]IV,p^fT >USEy0&z/h8ݰ Vcm#6-@L90 L4Lb_ЎmmxH%D!ڈvx\ X7(S`PxZK| 6ɂ]_?K*o;T#EPDڥUZ\`ʎD.sPc45 !l㪍@{ nY??slg`'baÇ J81b ~x01Åh!)RD`c ӣ7'xLhѠ"D| A"DB DP!e+уC21ۂ /G2_=vXъ7v0$|CpAlj-FQH nz僜 yc[r^۷E \!ecKlĤXoqY<aCǂ="7h e)?@̮ٳ2@(:Qx@ Gu@p3 dQCBK\W^@BM GًC8T-M&C1 XA'"@j.BBQ>0BuEsi6RC\TD$*dDA'(7@wXĜElQГ艓RT17?e411A7B>lG(Bm.,I&IR.#Ѥ?I4i#>@9dGxM_fUdyJE^A'd dwAr&XL]E1.y<OΙt&SiPRM~=B.1sƟ39 {@LByNBw;Wwvд,Ud#I^e= 4gal/|ON^)Ii #0dq!%BƅP H@"S% dʦ$ mHɍn[іF# *A0FP\"` yЁ$DXH(A@$0IN%#-H*Iҁj Xr|TO&`˨dhC)`(1GJ1ȋI B3HQ/d:^`Ʋj:Y5yC$*޴N$*UԦ>HW3Vuel >68ڗε/ҎEx)},d#+ٱ& ְQ%B<̉Q]v" cO+Ҷ-`H%`BMȬv\A54 BD X$ 1೅5!^.]GEE.',||7jrt`?HQrL @YT~@qQk"T"XrǸ#`ۣ3X,Q:YJfdle1Ct`B-D^ , xp0(!MAba<0ևbP_B@hmk\CuK>B P0<3j VH$aV< $S 0@++Ɋ+YƖ  xC.l0*aiW(`@ G? $~@? !qʀb@ ߂ $\:#4)I(T.{DO(l`Ǻklb<&瞣O: o#KHARʬPL!Q':Zʹ({ [?3K^OؑLK?dGƍDR@NihWe[wIE T[DI^CX UUU DAySGX}(yIlU;X?`@0`kE(POXu"ր\DwTmUwASL5#v[ȡ6KEbx<@XG}IrQ|tXhG̔d7T?d*.h1@[eA.-\D/AD$# &8?,c-,@- pֺqX(a)E?TA,`9^qD#;̵#5P@EБ9.8?L 8nPD#9*(B-2ʨĝAA 5+*TC94*tÂ=?8^@^$LV^?dA ?B XClˑhMz0o$'ؑD!BmRV/ [YBL' l5P،XdN(aELVB\ XD fE`.)8?H@$ÙUB-t<_]_~H?|& B$iBL% ߘM]A&D\*hQɳAҖ#\CED(SQ `$- Gz$HÂdJ,MN,,?4MʤNFH^@"@Sb}<EO(!(^TFeYZEy#wlK=T@`JDY4҈:LVQӂFC<],;U6]:I*r{D:BIT.qF,'[(Zц{k\aUU,\)OJpF[<2!_Lܧ?FXEye-ථ %@(VMe-Do?85oCno=5iݖ5HBԙ.VIn!HMJ2؃E(o ?(>ږ FX^/֯swa.D\cpܯ/070 l.xFĂB,d^LAlQB?[[LmdQDLrM;R A$?DL-MAHb Y|P!P٠YMZ$ږZ(T@A(<?(ͣEZ/)&( TZ2&h4񛩱ر3/#@$W2C1M/(.d- 0}Ĩ텩5HFN~Q(D#7A4QQM,ODå ׄ&Y&C(VcAQ`(qN  LX`eHAL=QQ\Pa_Df=eF1lKs ,ܷlHHPQlO>dM˜i(ُ>&@kJ?!,,*&fwjPNN7߬A$C#ACЦXfoC/:ҏcPy'D<^Uji5!0N,džXPI0EHm7%(C|S,nJ@@!:p"pDxqТbE",(1B"J>LBD2! T! '1&QB˄'>(!ŋ_;@$lR$DHD_X%}X7k\kbřU&.V1T蜋*heUԑh{TURէwF2'6|`ڠ˂0{Z ^% T.܋ -%`Y R%23",ࣕB0 pu~ze[pϴȎ< \}$E(U؆60"\bd \SDŽ6jngD+&N"Âq-髱Z~[ƁʰW ".!;Bvz,Wbz{dd)Ƶ\)1\胗sc_ͳȑisĚu4yTq]yx&(P97lm}[ I%S0 \R("P>__$b*6юDZ.I`"("]Qs&."H DJ x/#a^3M 'u !''"*xA'NDRD`:(E:E\ $X`" pB8qX E #hC"IFl F G8HG\>Ѥ;9Gғ\Oqp0ae-3!lD[E (>\!/@b ,l~A_9ZpaZ '] c#rC9hCYpEnE *4 DEO` DTMAt)Q:P"L)DH 2&٢E y4@ VVl)KpC+^Ц[ A5+;B"j\:.3B`[ABS\N/Mu'_N֯+U0W3PjVXZ,.xE8 !/$I !9J*gx:4c#{T;@ !! L:P$$.Hd*A E$a/ЯbAv HιH$В|E  |Pd"JQ> @J> B]HH`$@@pS4".P4P T@^NyL^{~p@ IY Y"J-P"%C(`pMْk]Z%@-"Mȫ$>Ols"@av)[ 8R0^!AgVU,J eӖx`0E,hY [H;W;dN6wd@+bxe BZXN]/\/l15"h[h6zʹl`1AC8001TKԲ-\Nʫ <"`Wtl( pZA,#"gHZ4ӄ3JuU*]Uܫ*">9Y!L)H1Q0CM˝>: 4g˫6zfN{uU(~h5)NyRH$B+Α.+T-cEH\" 8.yI8 Xl M(#1D#l,+2=HxzDЀ)KD7D@ހ |Botx7`a3t$?x " pA >Ã+"`,)2/(w"$\)dC*z‚jB"LF ZB<ڦo+0-[n eJ1"(#pC*zlo&&нn# ;'+!~/P&>c?cC 3r,@&w`P~]Jer Bj8(8zDp&:H@IlaDC„J&-"."+nN.%U)f+rPj)}(-bZRೞ3PR.%A":S4xHDL"^ G,dآ+[DjM~q%þ"E,@2f\2f,eM](^ep##5f^L6e3a:fW41- ;ve%8h8187ţ/"XmpSfH %f;d "ҍ#L % NPf-nf&Z:m4| c2Gvb[s?&m3o )?1&m&@ # q#t%sC9C=C%s>GF"G/"T9u,HFtII&g|[3}*G IKK4LtLɴLL4MtMٴMM4NtNNN4OtOOO5PuP P P5QuQQQ!5R%uR)R-R15S5uS9S=SA5TEuTITMTQ5UUuUYU]Ua5VeuViVmVq5WuuWyW}W5XuXXX5YuYYY5ZuZZZ5[u[[[5\u\ɵ\\5]u]ٵ]]5^u^^^5_u___6`v` ` `6avaaa!6b%vb)b-b16c5vc9c=cA6dEvdIdMdQ6eUveYe]ea6fevfifmfq6guvgyg}g6hvhhh6iviii6jvjJk6kj)6lvlہ@M vmնmVl6l6O;`n/onÔH!pp7ކoVnwl3okWlsrÖm.2!Crs15wT]tO^;sA"<F0TQv ^WruEiJעZnAnaxx7yS 4uw?wUU?R,y=|7,A,V@aUnzp]W{0gDzAz-b|||1wBS6~id=Uz xo`u{q`px}5u)xsT 88 8?X ](Ѓ„VxA`x*$M 7H@ )4POC:Rx T@й@u@pfۙ AD TOfjx0SL:=P t B^@A A@T xT1Oq tS{.djおU@k @fPpA=tjPbT)ODլ!pYAnN@˒>u,A;=%.P'x (*q :ui0B{cxQ<LAGIi B$5ӳٔO|rCF +>l= ?_7*L@@pQO q m?;wUAh}N}#Pm>yXЈ0A D"eMPp-P)$Q :5%OV)PU+ܝY޶]@\ jA 7B7 3^_&"P6<7$Re@ٍw/+|pleVb?sV/e:Y0sI{",_>H?U9U'ThE.4 |b0!xҮ ~qixr!QsSWMeRAр}7ݤvi~ß^,u#扈nQXgN OvuցfNidCF :_C'L`Qz ElʢIro2{Xd-hK\)mJA |P&1@DL @A$4(3%A(%DA 瘚@ġD+2QV/H @Hɭ @ d$H@B} Һ X\U ǤeH7IfGخR{c(MC,`B%A`LD[ 2REb؀}BMX]M U4 II*ĬIkA6Vt \^jR6_i(S ^OhS hAOۅ*YZ#^5Ǧq %EzzKP$;D3NDExdKe"䲸R#hCvSA5nKu fQ!FZZͮvV%ۮxKMz:Ljy@ECOFv`.NNNV`7AړON!Kx&,)n  <kd,qaB. )@2 $z`@H,e%hFёPd/)PTBbza ϸ\$a`H rcW?0QHAN3YL<M-%}a4 ] !vdAę|ɀ|2!Q j`|ЊTN],CEiQsU,F-%$b K\$@xR!7lTN5xI>7];  Q?_ SD xnW |Z0\aRI"ȭK%j`Hj.SA9 xX<օUrN jĂcJmD2:Eg(O7ʅ x31AF-5kEI:C)$GSH{I `&Z/w۽ *(*S@${я (|,jC8!dF $kT A'~L-z-%<VgVCf/ߪ\LAmo?FÙغ{6Qys3kކx:ql(w?HaaT!0p Nhl[W`U`%m"01J&N(bX50os WB''PRb1yqm!pA  @@$0$+"ZJzP3,'%sSa6AfWa~KTihA,׆,2F.sq+ 7@xxY@m!Y 40fQ#v%0nt7K}E=TE!+^TERDT1F~Qt2PHtx.;.u2H{YyD{p$\Dt]t!tM6,DTH82EAՊ91r61AxaJvKDvb$tq*tvX+@g`bU8hh$ 9h#ROt; }}b}3!t bDžQE ΆD1+ HX\S&bn $FY<)!XQ`p DT QD&% :H3!|4(i?,0]1dc+"Ba{`U6#s0PH\rxU)Q)T) \Rq]'^&5)8ŖY]YLUHyZ7iHFuzZVS}Mkٛ 5qy<ٜ9ԩ]pa ^AV@ *epI !{IY8k V!^a4}a wltVڗ,Ivdt sp7+ʺreY$/]r9G/DHrSD{]CJe|1DA1d[+Y' F#̼G8'uaWB獌qL*;4v"sCϩ4t~F7v*&ZSH;tO37^T{BB TKdM>Q#2}_B+'[z ku9Hz ,eH;d{I"$ Yq|)Px5Y P3# 7;jmsQ$˳R&Ned HH~c Fr Ւ-8MS M5!@;E曙 QB1dB4 pY[hu "6?헼› k`$Bql;!Dk)ǫ)r,Kci%݊E8"ShՒ4%ޫMU|iG"S[bl3EzEu"}^ !c>^~)0aGI+jP>CQFRkpXh0^p>!M2QBrevfeD^@NN*Vb-a=d,_.9`I'1 N3 k~1롦]>j7 (cZVeAR PFrkPU@F.@e"j<ŖۂQp'pq6܋v a [?+n /iA5 %6m,) @@xck!k:*6k!_ǂ p ߠÿ`n]PyV5IBØgGd@ H @]_ /PY@,`\qx=t'SvoEՈF@{IZG7+ռF70N=g?+21H4+w*t?u/b;0ߥO0eLm((V_ rL³Iv\GE.݌jCfWA"bţ/B*s:(|bbhrC?0tQ:rkBoE&%3+̎M4_T@(PB pA>X -|Ha,0(H& n9%BT4o`P8g S RЪ=! V< -QZP\Q,FEm>X$X`S^#E Ҙ)@gᄑH87$OM(V^dUhZv _ⶠ*9U*[/0Yu؍:lWtWG·Q柌=Pӂ`J@`%1,n$`@gfI7vÖS8:(ҩ_I!_ɐt!='G2Il"QMq"0 +ٖlC8[m@[ $&(6$p`oaˁHp*I&"5 pΈ< QC@?*P@DHMں|a#9S00[`':ǐP?L:"PzRRT(MHI) 0==B.%T%Q"HuLHૃ&( iDGF!]PyE w]`BQZ!UL@@z:Oˠ@ᨎ b%) ּr!'&H 2l*.(2:`i3y@(jڧ XɁ0䧝L* J쫰|y -H+G$ B-ALg-/K,j/i3»bƭybFsJ\";G=uSTڂP%PR@Tv'45OJU) ^) %&@(anjuQ#!m/u\szM&5IL}>XWxRCq@@ a @6!Ђ.T&8`AӜHT $=Yh"~9 H!6$Mp :ńLiR?VT-Q)Z/ءF)@YȰdH!|@a+fH@H 0(dcQ01&QOhGh`iI(&ȄbpIA*_LB($&|hH Bܧ c=FX@}ZIJ'T`S }[&iJ"JBILf6OuJ Z224)xʴRͫ)ڼJiLb@F13Sg>O~ӟh@:PԠEhBP6ԡhD%:QVԢg6?L=B(8U%!KLEⷄ@0ciBbZ3u입O$o248 @Pt @"*k C&D0,B1fD 8VÈ^J#13*Ҫ "D(&fC2RpJv i`ǐOMHOr !EEFR ,S:ZW*UCmC+-Sڊ CDhE[^u;n"2M+DƣIM&eK"!|"nS)ңlxysQ7TP@nE T產 07T0xF40 k *1 Ʌb&! L|rrdsw d?N5^dž%@AIqy ^(Ay ÔTA!<4WTe0"yЊ<CE#a5j3LoPAgW52ܢ0yeƑ&3U" * 2`*PocSŠTf]0GZS8O78%X8yHWx,@kW# `)K= % \H6Bt=%'3ѤBzG-ҬnƩ 9dIh%U N\dw{CTU+$JO t(?,S׾s\NΩO&H3Gd 6W ΁[_-#^_kSB3 sPp>+Ծ`7$1 ;?0:2pɀ  j%2Sp zʡ   Z`@$$A_(Р|(`ˢAF̀dM $:XD. @@DD*  `X  {;@J ؘ`  أ4Ls@ =@3,R8DWJmR,cQIsry,!HBYN[6 FE `-B5TH(@H>@1hG=IJTYGȢx]@NO݅R5SEe; TYZ['"ڻ )Be8@-bb2AjSf &jKJj1#*-jR`"b9pP+r0+"֕Zʭ.E(+\@0`5qsM&|j'З"@V<*r ..Z4Pyz.sM Lx0(CwݰFPv*'XZ޲Y@hb0H/H_0 %bQ[I/x$^!T3[8 PݸkWP`0Iٴ>@0۴y>0>%>(!PMsZ ѐr6k&+ HM[[x*ˍ M2PY`%=, ؎a A 0aX 7Ix]ke1{saݓ4=g*ETV;${59,`l{Xg,HVY (8`oӵ|Ӏ wcH() 98SzD ^* 6c @(* ظ Hɡqq6 y"LU6@8U" و g g+c Ƌ Ѐ:|y  b ?efO|;H; $3d+7S - U։n%S%ͺY#75:W;&&: y< e#hK c'%!Rf2PR/8OB#Cg SPDň_4MM0׈`uMk >1[H90"  xMĊ^ {N34BDk^ jn}^h_>p0M?'Kvj @zVJV &පue/!vYƶ,.:=/,"ʆ8ł@ê9.Ȅ B$` s 9LJ{ķA>QZ?B$eL'W++QIߊRC8.0̄EJk-Y Xdg27-= ch l0ڬ8R~RM |1nb 9dj-^ˣǼ6=ǐy I$ 6U _h$1X.tL (8uH) x4 (B\˅̂7:|(Aʂ(Cx^C3l$L[$\퓵)j V\}lFm8Q"Gˁ0@ʶ8r4-Pn{9}JX@?tO+xR z6 "*E#ktŕࠅ^fh͇>ߦF#! NCًMN&Li\\S?@SG+mj ѝt9J%C>rk6 ѧ Ѕw 攰RUF#mSJ= # UR:V2&   +_ 5Wy*Li{@EPi'Z{ _F:kM铑G2S3& q,u pt'(GRgtN>@('|U0IXUUn/ɧ[mU xUͩ'ʪ /[ 1 O@y[KYXCGP XqAL  $"Y(g?&N(+ dA4!IGBفQFe]ȄF) (#"4$| Tب3ǰvg9VQ׸j_3qTL,bU)*iZ7@ H x@Z#Ԥ1]=@$4 (vI HYMO6S|, fŧEg+:9,_`@֛*j2L 2VZWa= (dbF."0zFra)ydz" <ɒI (@*- Cpholxc0?x_8O nY-*Ѕ/bC5@4{4<5ԃV(:;XL\JLYHSVeĉIa,D|eHKNZNH L \pL[@ H@Yx]T@R\eK LYeԌ ,5 I KP $Z~ky@G%_D _ bR` beP8ը̠lտxΦXeůKv[Fx_2K0d~FzhA\ dM!rx~{D$gM ieXhFR'dbF`:/8|++=R&xDS>'qkE\ekvu+YkD3ֹk}Ck~C $Pg$A6`4h1mUp\R<|^ɕ>ǽ%cxJmLUthcfU L^S`KAտTJDJRT͉g _}OH⑔AM٘SJfTZŬ[T쌶UJ@|MgԨ(^}KgbyD2cZY$qb@_ŀiMnCĀ%{5ZF*YWZ4a0)G1,NE9G*oR$m&ԁA3H\a7::AcbCL<Ԁ\;@A}H0 1C1S?ԡաE]b 4hOC9587[Ճ$]3[_\II:(Y k @_$a|ZY%RTZQ/e6hh6ψZ@R/E6$3%gq!vF _Fl'FtBDD_D44&hC2Bt 5GHGvEM,Es+3dYZy>|C}+1p-V uQA&'uDI Z>4`̙KLHe`D6ޖ %kIJnLy車${$PnPvOcZJh 0,\޼eJPp2 /UY @KWP xdKZexvA2,KJȦe8X9 e9ŋ{hJ2aHT}XW˻HtT[%MYnHE`弼tIvUԘvmWM{J==j3!(ZXڨXZ0~xj`Z(r$?GGpY܌Z fh gŀEgל!\gěA~E&lNSLh$kݍꂕwdmH<#9a`B:/?̷0y=X;3䴝B:pCR(Xх3 %:@7ſe@(a /?qjH8?VZSMB: ̕qҿ]eO@M/$BU94i俟ir E]Ys`g|pD/Jl2N?|/LWջo߼Ij& <0֬"\00c6 A*a9=޻Rhǿ'EQ=K6X EP#MdO(+4Ô ?Eyb)1 3xxt5e bi$Pl  ͇@: >(h5$@Ģ8` qR@&(dIABLJrM!iRpM+EӬ(ftR Ӥ D4zʻ",73+ SO5;:MzM <?A7X@h =ZKtR@NHqz'Du"OO 4SKR5 ORR4%XmSK 2>_2@UUj-W }5l#}SʴuM8ʼnMN#tW b |WٲWށ .zQ Q7#A"@Tp.|H(Ld.2Y7Q!)D0!L)H>TBZȐ> @DM|6 Ģ6)Pe~hHĘ $:28m2)4X^%)ȫktY!ǒ-(1t (_ܩ!20Ɲ`Ɲ`G hI&>Qe`bŚYjLs^Gw~yBX"uJ2I5"&~'J*~VbةM*(Z'gE oC|p+HR0D-lq X(8.+ܕ: h*xB(0pLH BEm>C7VceZl @ 6f!x )2V6PV0ADP,S`SLx1%BEU;P-&E͠PO!$p?e@ՂdP4ML@"@`; " -m%%.[eKa ,U'S K!SdUZf7OUL64L7s%鍻 ͣt2/]_(6-TkʓӤs?P% x j&Y:fhћ^.5F(^M%$tCHps_4MqSsoiQ">A1y8!XI &e`j ʀ7\? Z3QXi-5WWU/S*eOQ5i A+XnjPԄH Z''LFJA$n r 8" @^g_t&A@CcC!}أ 7.Ɖ ,|(!pH, 0Aa EMzY?3^G 7jja@}hjK0 qۉRP"?א]~_@B%⠈ZC^ތYDtvsV&6G 0"A…_QY

6) QxcP/p`*0F(+OaRКSGr 3s9Vc%E@ U "8 Qܙ9LkM| 8y਩?Dȕ  } s^"$ϧ/ Xa(f!A-/J- B n j jq |A Na:HAfS@&" rvn F/P 7<3v`Np fϛVPH4P#2(Q01HAFLf|`S>M$Baf3TD/AK^+bl!qI 7!rDHH^@At6k z7ztDAi\&;CmDkbicC{f1 c2k"j %+$r!vt zLF%jFJT`t--5@i.*;zЪQ)"g/+Mo&Gpg B 4a G Ӑ!La 030 v"AqG&-U11$D$81TMy*#vA(BV˨/ nJLV"@--Q2*`|O[ ;l,`ȅL!'SM&@F%I0)/D %Jt*@*u?JbDj}^7^wV@ҁbxsv}wGq7Ngaxx:ja8 `cjG8UxY\c†"$D 4b($@ "AFD@i%i X[<v >!+>kt`a@j@B" 3`>d>Gyd T!'!5A*k  FM/b Faz ~fZf@Ȍ* lBB<F\mSB6PZA Z! ALX kE`X!pN@! WB݋/`>>DKB<"Ǥ<g" ( !&_*?r# I1G<@n&011{{jR"ytSF cO vFO,8?ɀ %![ڀ?-/Lb !BG2)b>jD`r[FdFvUpX2D8d?p<2`?<5IxBrNԈnQp?m3)/])FC)LLkR# ?"3,aK Xy׬i"cۤBӤ%D*BFdL&HnFX(S¯6$PF~c)p 2eAIPSgPl Q%Y T 2A (1o"sOurY #Y!2^&HTL%LlBBhχR4Bc,ѹ&K#$YPSiDAyq4paqRrU`UA T ?00H$M٭8^8u}RI6EF #Q2=eÈ2!ceyC&;좃_GoC %p<졹Xn$iDP9?A9" )%" @Pp*R0ޥb,ψ4ql#22B1DXv3.GY (PHHwCȁ,$FBЀ$:EI#a@E 8J.F}su_E@FґrEC@w* [2\D`4?P+EɄ35EF>I0= [{OggP]eMIx۸ # (?k ʷN`"T!fv^!eH3W3RWc1ja;:0aH°EtxM3[1ep$G":P09DBFHu$Er$AB Q@ SuHu!WJp#q&!+4őM!NeD/T+TXRuTuiG0! pQrB!v!.xzX p mdQ17PY$zR07@Jp07"h>0$V@]qd[AIAuAG8O#ndg1YC#F#Ahт)X;HWQ܁ G8! Qa(܁[q=InQ!ׁ"|ԓwvzQT!`T.+ÉFPm,5B4VFL7|SGWH`7}=w!wAwWt$o X0x7(*xx}"ye"ya&9vE&'* z-g{߅z{H-b"G`2,Cy}U}~E~i9Xǡ%~SaSF[kSH0&0f0cɍ%.$CE| ˰hoBا& [ʠ$=׀>#{ Ta>& " +3K5 nW2 :81 jkBj1󁾣;"16KUkW[5]3 ܢOgODSVDxtPQ 4#pl@IIZ&U JDGS1S%PaJta`dKUq 2lOxRSbM(r9eN$PTG"JpNoKF{B PU%Q Rss "(K /01P p:@ IQP~  !lPL˵XEXܲwυc{g{Vu5^`wP &CVVn70R%uLVvs5+`Wx|ew~TbH5 a$*P\`Xօ] ,r%XBY([>ir Exa>&cZG[^w  |% %-Q)aOB|0 $I"p$ir` $$>Ä;I\!R s_: D ,bF" j d Qt2tE/l^C{FrܦemDQlh/cba0U(sA;% rQ40$4kfsb-eiR.vkjac=a!h c$Da Gfхk (R$x6tX!jQ8..]s.& /Bd fbAl]a/C$AR%m|f9gj@kz6=N01Td,rn!ncdo8P\u rPnt鶥0Avr?F}j87qq3#"Gb\4E9&>25S7sÓmnר9ToWr\W0|% 0 .Q04ҺpI pvvER$QJ"tP%v M/g{W|*w),B) W AU bl' . zGea>./iȅQg|͕$*lŸ\ |u oal$QA,+Q}WHh|L}wJAD@ /RStZ.agwL^̿_UdC-N!u( rHAxDev;Y`P ʼnцoJ1 ЂxaGP@. 넃ȕa (+LO QxY( eXs.ػ_7PԆl#r HcIYD@1Qsu1 .H&RQql?"3 ISωޔCS 1P2/=IƝX%П']8ؽ/ R`cWI %WaU2%* *"n* #o^jԧÇQ o!@pPP?;T<%.S /|!g ($f6?NZ]> & BF (HFZZpm[jZԒ>g@ *" X 3l0AI;TP!? "ޥ B9mh0]޾<A?6Զi"%F auC`u2* `s"6tx_5ƏIK )c(p( #>Ȃ ~(x!5:P4  0MT#箫EsnŽ.̲\Z#r=*aұfqB``,S\Ц#>+ YB " $тyO>M,!ZZ::z!wR4 gCP~ i`ZE>X0DeM.^ЀI+E`È~l Yق@B6"SRp0jXRZ!nXO S cxE 0xNR `OtP4( @$ x3ND0UXcU Fq# ?q D(?T𼗤$,+ $CTIV ĀHLX ;lft8h#dQ,Y$!0"MPWȓ  eA9DPl?tV"z _Nx=`I P#zVB GB=H#H. h@D_L * .j@*, iFT'bV4?rx@<0z@Hc>/\ тx.%?>@jI<B T<^PG -/P"HfJq XiHd0/%JP86YWIi$oJ vsIjg$ܓtӭv2Rn#@l/M]P@MRl?ȺEL粷/JclڧrZ,9?d#RbrJ|JD82~i,H.g/Xb{R1. B&.;Qz&!\G:"Y4%6c.݀u>|pAP7AZ7PYy5$7wCsH`$rʄt!VPtkC{5@7c~Xa*`/s#("'A@=pMx@7i}zp<|mTZKY)PZ`\~io9}Ar3}/V@rU,_0MD T/ ȀueLnNK$ɴ&aqW8@DMBS<@W8"|J(3?MZd~h =ѐ 18H!` P( "16 *a̛.r4p9D$ Ac[@ h#HBc9y  h, +B 1/- $S: Vi-W1K;2#‚A{ EҳE|ĹR#$|1BL ?@EVDs!2H &  *aPx48383P363n)[<"A>,20m*A{724nPZ` JLU8V4 EO+T#u V+X([2[ ]YP`ȅwˠ+iOCq8˘|Mq PP-Pؕ6PyiK5Ke!Pko1PPrI*$0͎D<.xAϠ QQQQQ53XX 9F&3hhDB(q8A0 PyIQO)!pR3Sk"5"%q8 X\"6Xآ7T!^E *9G4FzgȈمY#~XpD XLqk[nHP~XY #y""$RE#!U:%Xm "]RV`/'t!{ЂY Rl!&Y5xH`ݽp=H` ̖h̬2#0]{\a\r `(+  (:5? )bDܪ8 ~ )_+$yY|(:9>(=Xj- '!(" 2 :r) SQB**,qz⋷ ,H J_ r3  Tcb: ü2;N Ш9Os,3 %tǰ0#’؀RzH/ pIquJ ۾s N.U Ջ H0#Oj~0^@M>D V>T.ⱂ_)#)1('#Pgwd]Yg xuxI(2GW'r+%뀶.1 ^ƅ.B5ZEI#~ Wh(E[%5ccv&ڗ86 ΅KwǻЂ@ǚ $}xP{V PspsЇYv F\+6a㉤Uhȕı LhYppdX3Y*Xk@3 3y:9+:@lH:\ Mi88@.<,D?ڌb'w-Qan<[߀,1d߰>nȈ ꫏1M(rm8֠t+P'.PC(' ֈ:pԮ\B̷6tbaa), uhaA> 7(k4Ɛo{sCcmkD҆(4PXEx-EH&\;.x)80%?' M?oLg>Zx>bG^ۅk`(pu6`HpƇ Uɂh@ȇ#]dǍ.Ţ*5/}628gs$%&jxvTwJ2{4iej@H$hI̥P/Ky*P[Aڽ]k~k&j !DNߌaX ^Vp Ma!ݰ˫-؍p0X  @p:J y.!}?B.@IdQЋ hCܠŌ]Y 0. :P`A  >!Ŋ/b̨'Xp?!L AJRS#͚6)x΂~Ċ 4iț'#(}hJ=lUT&TkQ@~=yӛ{PոE(IJΖmziE';,Y-Ǝ nE;)ǖi&̼Rͼ2ҦOgMk 0Vщ3Zm0vk N-|8Ə_\ƚ zrk-cϮ.]7pAL'd,Du &wEgcx;bbATK  Tby` tT5AEI$AAX"AP@JƠ+آ/~1Xy.=VN 2L,@$}:F@")0,9ͤ ?}fĒ%9EבWP[֤^T)UA-mr]~- =LB8p >M }XCP$ L| Z+/``0l O T?D703Us3?CdΚCGLGFAs }jbHdwAQ4EE4JC2Cwd8ԓBӤnЀ2,$饀wo-2ܤ3)(G)`3@-tJW@*"? XA͛h+ETh IആQoX pWA֧ H#X8Id L<t F-Axԁc r!DMH @`J<DH !@  xdjH`<;Q?ا  E>!N (wB$6!#,"h`B'SS@p"H<p S#䧌=@G<0&5j"e,I"TP?Qcx D $%@ I*ؑ! b*Df! `nS+HiT(-~9K1#@QA({(89gW%"0ALp'-<"ƞ\%/I;͌$`15Cx9IR䅤؀JSt(A`j@#C bzAIZ !@TOydF axMɣYE#T#Cp }Dg&&E]VWn+0,7m ZAhBm[?~dPAXm8"QQ 1 !ELfXqƳQ2!Yqd|x!@l7N PEEI6iUh] ^$$F:۹I8`hE#iJkP /CJ2D4D$M[9PFM?aA/Y]dqH,A ?xTEHej1\?N8 ( LW!`Ԍw44ϴkP3 CKЌrb<֣-bA؍tiDKVL,_+<+,=KEb@q TM)\XLADK 7ݔG4A0Ulo?nJmTaY曕aP@ՆTW˦nCՎ)9x?5dÛ94*0FzQ+}A.3| LA s}]V@jMrQREfwC0BNAg 2q?84f_v t.L0.0 pe^WǍ/4G`~~)@Qd@HpR0ςLRlE2Cr*dם0q }`0ЉeY<\MIިM .8EIFhL9a[A(?(tD~E̚ӎ i@’Yʫh\J,Y!hOAhBh4ThB2lIMK'KP&PKsAH2cB&GyDE9M 4h:,LQDD3%fÖƏx~ ISQd0DͣB~.d\=ASEƅVP@DH8;"8AeMcgꄀ#VUoL#ƹy{<dO$KDXSR)tMpҝl\ PNF.J5 a$K%c2Lt6g%D]$E LT^KdGyf,;EhEQ@MM]A# mEzjJTPvqu|s~B?sbXvAlgA4wѲ8wA$AsQw^g_}2441DcRP2co.`= +>9۩AB U㐖a%!xpѸmeE0ExA< cVA0b̓,XM`TZ8hC7?CA1;IqBjw`Utdq^y(3|}xQR2ر$p$vErS!7?;yLT E2:!STO/?9NG?HE!Eԍ-EJOSIyʄyQLU h SE-*C!"2nJ/D#C1D ??HbQb4H`"d@ itWΦS$ f{C)C$7DӈVK?j5_Raf|EH8,‹B.GĂAtބBOtx@.kL(xZ;c:Xt gs. K%T ( BAV8 D⟓Vw paH,hHGP#T(;?x `f!yH@3NG.e)ԨNR'_H5@* rz @T "@^.2YHЪ:  .ꠃ @X{WjBmcjM·Ҡ٧Z3>sGPXbPԬ*=ʨ;6+MBj)Om?֓f"{ A{ff+M*48ҟn^JJ' r*ڠ(e\Tp~Z*lG6  B4NlnB 'Oe;& ',+mT(oG*Z{fEO5hE1tGatϒU!6Ԫ*T3c~&X!iS͖?P2 I2ђ$\B'VTdUTn@%Ac| ;S%xntF1Ÿz4DKN9#IhCXi]XZLB/pX49g|ܣ3k&Z_;R]䝗]`zD xu/iSъrt%mLJ__|WԹ2w>?2Y="L,5fEN/% ^G#q0"C,.#IA Ihl"d)`9( 6i4R A p;" jK|KA7`;Bw"8b $tBB%>X%$6%TEhT p >@h*UtB /f t )JM ON|(4!̰3RG 3a,Bt2b>c)&!^a#ޠ1-$3&5\6B@D: L'@8i)Q^kE ) v1.j~ZBll BAj&AB|!@" Cc7Q U7tb;dUZDEpo8ocK ("& &' s@* EF"PpXT Sn%_@]RH6- %B4)I&P<pb) ,m4 +@EFIJVTe;JF\@ݘ[I@"ۆc[nJZr2i%@V:&3;,c@gRJ:QVOb>.Q.+tHFe ,6$bfdDMlpF 'l'*8 f c7ukьB-8'A8Qp|Ff!)䤦*s$GR4]RBP-x)1\9tRG!"|ab `N@,Őg4xA'1uX!BC4zt7@'Q fA*d(c<5#ll2BOEC/;(4`slSzFfs>cENoU' oկ0$q/D6B+P붒tZbV 2W5K uk(YY["Zo J` Z7•bU'67W)rv"i pVZϚJO7!P%pz'l,, +$ * xlR]Ym#f0גmddoW;Alp%hp!$c!(AXA1Q'NS'Dâ$7M!3.ٓ3a2)X!Ta\G{(&tuBEB%}qD4EW4ƄJw>K 4ZdG#)h1*.r88j(@4##h\b(ȂT6|ŒLFx(|(TPiRt*R_<l9j^'bA& ,^)Su(*㉏T`XN('bUB"Z7ZI3ȉ)6)^&2t+?f }")jAtFѲMP_R0I`\=`|:[H UFx0Œ<<82up$ ~cFUѢD'f܉PE CoÁ z)Ũ]!DX5Ϟ9"m]9˷_lρH?htLrh"&^`Βvn $M.t_3O:S$n>xDI eHF>CFcZ.';)" XjQ:?>x g^+MDC(dDmOvYF(ǝDXAG|p9Q?5$?#ъ u#ؘtΈO51̊B$83!7)vZvR~qK I a ,B5RsJ?Bټ0Af)cz `Hb )I& '駠 Uޤ*!lvlY%edDAAN†A&%W%eA aUOjh=KV0l) F 'Qq)PgDXޫBQymY SNpDSnF, v@Aj7Ŕ&9UC%W쒜,ɓD2WUD^=D??>$< !A%\"%xd6DMD ?@me`Nx>'n@0 uqIgH GȂ& z:je<;dZ v `obRAܰ !ahZq0ZjEgTN8i,a`T|ə@HV$ ޴*i(tj?yQ2h H6)V(4RCH `Δ⢩zl"@llt= e ?p5$=gD5`L0B3@^|\B/ h]i"2TDL I"24@$Tg#ΝMID T$ #>E-%y@2cH|*@ u%RE`z>h@$*Ah!$Ȍp )H D- "hK"I PӮ<J#,8 0e\oG"Ԯ%a#-:̻xMP!d$t,|,\^=$0MApQ뒺|<99^ Ha1/HYJLk[b0Jꠕl@'s/E B*O2bF-B2xcJ'Rc1l?#l%1gF75w+YÏ\̂vKe(c9E!iEּ'p9Ys AtqtN ^t T2]GvQoaB9"wb! <7>mEcf *!Hۡ6J2T)jjjA -kpXk,an"o&lV*4}166mb XQd Bnx &Uh tQţ)Rq"(t7ݑ5&346Y\ֵ4/ jS,^6Q!ވ#qc"1h>/Yf#h@.1v.25%~S.㒏3bhI/H/` [f8*!-^r-9৒, Q2tF2ϵ`21xq"*?s4i!2g~:SP4$YUN 0kQ1ѕJJg9l3P` @'Tir9tYL#LdY`7 $W?ÕWgh٘yq,s}HQg\Zy.'9Yy"Zsf }> '"r0?  Лi * ) ' r1q Puuh1%r rl2EUbH]H$$EHgC7TAE/$B(2D5@ D: uM 7DDN40 u EBE $vH]G{0^i+8p24#U0b UأU6PT"'C 0AGtxt?$a L9) #ajJ "I"i)]p q:u tzף \æ=/p`" P5 0?+S)2eۦVdOppJ5p%p `8Nql41&EQjVQ$= pR/ })Nq?8X$ʏBǏ|(Y6o} "^P^l!@ZB= B;Y *V 50:qP PERW`YYuyVheׇ3[@Wq\FEPՏk"eE5!$PA r.{BUGv{ĵЕ_Ac]_PF3+ED2ීa\g{aFPSj9b 3aB~H0{ 0^'|`[Wf^:h6Fr !#/.##d8AF-Pd1R)E&82g>ANh<'~3sqbzg9Ae5[bĀkhя14[[3I#] z Z}vpi_&]Ih`Q)VtNcA¢sUK0$ d"DB" xB  @T<:5\=Pr5~ڨ "̰ :y@ WlOpUm4mHOo '2'vƿJoRh6n&g*:9!)!* /GZBm׫l $jB(t*40"%' m 5jp ^,%Z"%{Q "ފAs ] 6U /<͒֬ *@͝ ϼ  #zp9'$@PX uu1`avUh{>УG C6ss2E1 ĞxWwv"cz0pQT1~<<19 \pz lA yЉExYQ~xuf8y1aR&:n}nYNQw\^{0`xMǑAs0eh|+"À:$_W&sB;,(ĕFah.>sM {},jU KG0Y[?NaPpP/ qԹ "0Ԡ? I@pQ$rHxL0`RL @*B&О,':". U~F͝D{B'E |F+:HO@y 0$R%x᠄%D"(+aр7 k`Z n\`Ve2YUYͪ Դ- fXeV Vz8J=R 0J/T` 9Tt]/|zL!R*[5,\j&i&G KPgѥà %=%cH"@  qG]c,~ !'+yaLܩ_ vk& ×0xC&ɥ ބΡ>J  V@@1R  *灡<ЪZNH":` xQA T ܧZ ĭPo ξ}ޭ+{7B͉Nhp? XnY$g {zޝ.y8ϙM؄1L㙿1iI.ck28{E!BnQPA5Hllт[@ xjBGmC:t^`+O|Bȣ mF&a^Xt!=AЦ+/9p>@hD/^H:KqqO҃?$AJ `o 4:9Bv{f'8D!b$:E˞kHE PT iNQ$H ɨ*P $  $"ʱ+R iPՐ@qm@2J5D-kdŮVWU[d֣*e\ZFE<<HgqL gyђZlPXji U@E \"-#@Ua P Ϫ}"Du[mo֮ňk'1`DdZQXŢPoI %$HG(ͲJ2I4*Xr <#$!ՌA($Lq =҆H !E$H@<<M@*zP"M&3N5VoB\dٔbB s,zꀟ&CI R:pɉd?hxh 3WI8HFL L_0>}™Ll/BZ؛0a@Ãy TR#CFmh6 ?CtIC$BJ *Zqq㒝Fݰq NȆ:_C?I`  c+RWFW Jx/y֡[SZ㬃$\:(h"rSz4ֻ֮0侩ڱ%lzܯx>p5<aB&9d@l'/^5yw} k>&م 8e?{=hbjN7=?u{G~|7χ~5?3Zpdb X",DYt aX%/`7C1迡n0O(2 %H):r8D*$,1&OP33HAD'$4 ACyq҈PB 1‚208 p?pV$'Bq0 3XX(0a>#醈C kBq#02yHؾ U 7h@h2DOh QxQ 10]Փcٟx縂KHٛEٞ!Y# q5"85 S,XK5l>5r`UXmTO]zU9RMDTUeUѓ)*.d\-@* [qLX3[I8.MR||މ=;KhԂ)nu뀠K$0j! EYZ7Pݼ.-­-\pI #;:-CeӺ .<_漶e>#zXYڳp1Wa^mnWkR[oxyxC<{^U+52dFgz&hfv臆h->P L(;&Rhlr83v " -,k5DS}򊍐j-O ցP ]ƑA2抂$(4 )D[ ]iA'(((DN"/\0h\( 08h4[. Z\8n; i:l |xB tOm$zF QpGmnk! A8>ƄT aPȃy,p\ H+=HS8TR+=Hp11hHY ՘OH ?aiЇ| xeҕ #Ti 3vӡQ `9.2Ҙ؝ :2( LLJh̞5*Jk6?={i> sx9 PL)-!]Ddtϲ,v˅9AzXClЀO(Vqqrͮh{hZvœTDjJ<嬯iowPIjvpzYz4wEBv,9l'9=X, ' m) =Q3[ fD=D&1-B~ܕ.5 \Eu(oo5yXSTG#OT Y"S1H:!XA5yƣ10TFTM{5xŃP`ԹT}|U8 8wXTx$1T;TZ%[e@4wp2 ^mV &k&|!AXfy@5 WةxU-Wt'e<l}z{Xe."5 G%(@~Jw&Ƭ=0pc:# $C (qxmHYhh„ 2l!P|,"c2Ha?P2eŒ8*Ԡaa g!?N"B::SM6\pEX1FX`S.% TKD)n5 X̸UPB&|qV6 Qc>>b?"9 ‘qX&(E=` xPfWq)*$ue-QB"$ou)BǬ,&̦@*Bj-VmpZAm Lff%{)Wu]d+D^aTJADWY:\?l)Bn40ƒAh`T"/[21<35341qYXsB2 M C-Bm3QK=5U[hL=1&H+d9uּj y1jY6mvzہj{-y7} !tIiD@0?JDа pz#&''U&Tz/ .$bB{w }=|"@S^TP|0w'$A 7G?:/?oSTqM@N,: EP0HP9'(aP@00  0/#cC阞1"PKAa `zBX ڡ'j@U,!l,jL?P!!.4҄fd0B?=!|L'€)b+)_?d%-DTs$FdrBTɄ42Mi$I#%zF4Dtq\ޒ ,Er(Pd P(0#+/fÇat (ΨATa#eC<CO{Ynb#MPYY; *P7 G09X7?zf"$C6Z!Ѧvu{JG@Xb: Ӯ `I&Q3}ue1CP l @b*ǜ(KYWՃ0\ 44]nìY)RPJB|`#(@DЂ@$-OkIג%g@1H$:C~! {|څ5%KU(YVGTHoܾ mqBy!XjBv³t oXU?DDTpK/`($Yj_uQ5l2j1aW)1'i!F+0Ɛ_.1n G?$J_`2shfd(A#a=^ȏI=6&l%Hr^1r!T-K-벍/FP(X?0P`+|4\<xXqN  G?%~h=n4Cl;bz)f$ B6с Cs `CsJP ZKB~]8Dv 1[u'{Zꔊ=eu{9iXZAn(Y|'U!bD43_/DÏo}{d޷I}0ʴy(g4 EMp%zgsIx!2с\]?0$ &t4j$ a0+Y Ɗ0Z9aީma:_]`DXXmV-]3?=u (MZ2uF5HƄ+XrJ^zCT! Jmc`!KEvO#ӴI1j!_vbHEWױp_la]#w}NS/~BzM'ABJD>ryu8mܑe_%dy7Dx¤$2乼Vٮm=ə` I)I. Hٿ*)$!DaŞňSZâ5=PZ!D :ȀOY@@}WˬtA,lKcf[Gˑ +laGta `!Jz<@Tͮ PƩ hJS{D^W E e–Y1C ,4$4".NPdh6#>cWDB@>&B.$C"A]D*? BA, B ,N ()\eVZ`h%WW+%C $SP&ZBPxKyeS@z PB@TViQ?( 8,tE`$A&܆ y@ F 9RD#?P40E  }2Cl";̆1É CC-X !-Q5!Qn?!?Šϵ4Q w NQ|{ Q 5L5I݉-@`I>h D(AgHP 06b:%D)D0IPh$YhBtR*S)4IIX++ępSq>(J9f(B9*tg9 `2tl ?1Z;B FA(!EwdAm<hI] ;eĝquKٱDide<ETixG"Ju đDƄ)XjҪfīnF݄DF@$DlF 0E fT%S-DB"F@;ՖdTٌ4?EQDTTl,fPO"6TKDTZQA c,[#ͤ>C JVtVAlC^yB<|"(vYexW-Fmy DEy ExKǔ Xx'XtnQK-p%׬֢x,ҞlCW@PE̗XDE}B=A~m "B XQmSJ?6%DtƒmBRCgeZQ@Ͷh_(aA.M (qB?箙Ŀ;N\rGe]'h9w_lKH+RQňw-.D0+жx.b'DPmX c1^>̆V[7>./9M7&bp鑼A20Z9ȕ@21փO`9fxT7hG! VD`0GPIE39#0 @?ĂЬhZJBeZn"D(UNJ"0Pr@ Ke$ лuʻ1DM^[DG㏪O#Q(!T=UB(tA(XO=fd>f@dP=4&ph·;Pd.ӯ';5&SW"l?X~y%X?PBMPW{W De$|tPX DL"!D1&@}4}C )ƜQXM/7BH7}S8?5$9葋CmlB=2yh 9DMGJD)BkT@'E}\,Sx@T¿ &p0a !D Pc81H-BZ$D F&O(̈pgˍ#%ARDxA"LeA %;H)CUR#@X!(( A ت"ۂl`*W^}0 Ic U0yD*0@ LGWP[C$@ V$y1pdJEw>z1ch0 CLXPO0گD@7#5 @nQ҄=K`ns , $,/+ *b(6 Ljj= pp*%01C# ;! « C Ƒ@! ;$3G!yC&ɥI1;LhgRs;)IU^f(^N1"q@CL`^JJ,jAQTR[INz/党`V*dmi\_h)*I4!VQ-Y"MH0lSFRC#1ɸل}2VIa9_Ia_ܩfIA5nI{5 8=eVgigZibҶ1ݸE (wPWja(_dhW\v UQH'ewFӠN:i$ꂮH$p 385iCDX@D`FO.! ӹxa5zӊ OF"$& \<"%LGZ@C,萑z(Lh !!a> a Ki:@dD8@wŎubeo ?|:? _7!#]M@HH@nhAX j t!ЈEl(A 4c14'Ji-lNYr ! 4[V<7rLsA)OsNij21< Ph`ڔĺI0R`'dtSP0gyPD<HC {8,1@2c- @ңxR5%iHoyG2:Le\(B$P[H„ BPb?\4UӅB4Pc9t&Fw,gA-ўtb@yĉA9)<}yc8[;R"6)mGH3&48 "> q$DI JꖐhP$!i1P=Si'KX xb+xa7p#Bf2`kXbQ4TD+R[Hd5AE,ת*jped 9W _`ìEl` Ul 0AD[B-R;,#.V\f`A3Jg~l8SZϠj&%E6`H=Qh MF,P7Up2Waif&mo.iD]ׄQAw > .OZ 2HT ;Ɏ;'F-H?(Pׄd 8nbۍ@DOd E6j&%0Q.h*xT=̑/tNj<"6ψac折!OFph',\lq]D:D~rS|H'5MgW6ele(1]f19 /jk VLAy@>Ip9thA xC5yœQ10?[*gBoӝDn*11eL1ZgvU2akYϚֵ EAD*@arbL(/? C,vLOq^ $oyb gg60a`nZ??EY隟z $ 놈pB>M, 6"|x+4@U߸8ƑHB)@!RBh$ ?r}'7B&!B+bLd:~ AwC>W; CA4 "c\ _.P1OW+%1F9߲4 ݊EPX/Pa(E"jEI06'aHqY5!k?\/W_{W~[UV y 5w ք$XTgn.+#UD/ @ϣP!C+֭t^C7"E_P1]~a^!ROd!`R0cZc/coTOVƪc<0_80Q( oP|$`*(&$R!!TFo Bo2B9MTa"%!!*g*M`lL$'Ԃ+(C&I 6:솈L*dܰ ""$"DE0Bü. E&!~**~"Dl,"-I)qƨ2"B~L`J; . 6p $2g:\ǿ BR #habMTf,SL輦YacR"A6Vtz@y YQZajbJ8cO\?Fn+dFޡ`A rsPH oA$2F8F%5Oc^s0s@Fd63BN&R$o'@N$dN .+Rh!agVJ"PY D ! b AպLm Bx|؜l6z c br#fr*&$ l.r.k2 @?̵'dfȺvKWl[SZi2I" ^L!V̐_o9yxÄA BFb ҹ"L m,m:~aaF"gC*͜Y 4Xx9zBHID@L9̨#H> A:EzZ Zb?tDT)n$P$ b礃 Bv¤E"uFb@۴mH[<-pܤjO8>Ǎ&|bb^`yj!zg|lvwpmn( / 24 :N^ ' $W$aWna|Į-' ḿ@@dzR{ذ׬T$ b-A@.n=?6ƚ!J^""@ r"vElE?ӛ a% į o;KaXpT$w*M`+ +er`wpԅud&0dPLFVMf?fNF&5֗9pGFNSha_fdP$=#eg|0O 9VjA"i! bǯA&,6*)J @5"@ nc}#,h6-#*1& DX/E3^{SkV|g1^@,]tH~D 2")N1*=q-(C) s"7hI>4ԣ=D~4~>`!.q#P*! c}!Pc0_C" SQ( 7G]& >tc.ֱ':C&X $s#ɤ1]Q~*5mHƄ"#+% <1qLAb'%G* XÃ]2& b+I2?DJ,#>ʞF^(i7M˿n>H&/$w)Ͳ*m!B",cdʒDJm7$X!R!ܞ,F׫$R9Y6 [cbbX9#!@X9 \pR! :C;kSK7\}!#K0#eR>`!<Eg|M\M "r[ɜ/bϋv| ?> KoEkM V+kD:D,[S2̙1;.7QܿW͡D9_%cXЃ$XHп- Â:/(lZp@޽|iJXIhK)4*PۘV*%V=HAÎ-+>6yPNYHD#/!qȜI`A¿+:x2߀BDӤM)N@7 +Tn?=}7h)@.$sIRˡ"?K<՗L $&i)TWBV2XEpO^ TS@SAd$E0d[< uBD1=3nɥL:4RZRْGA<^\Lv$KMT?(דP>h\@E2AiXZt*B"դѹX(Bxץ-IVQ[Yʥ9nYґ8ZJZhKq9c;͢I]NkUQQ5}]p#Z1MحԞ⁵pdxfk7VBp#l[)ro!Ox- T 5Q'حǝwDzmBe{QG/jrL[Kɶ)`@an@I ]?]lP],&Ѱ?3/t4dREڱLpB-D un@kNP5KLK 1vLd"dGB76K ɀAEM(A$Qč??#Dԓ밮pyK ՀK Q2~Aɿ|-1[G+@?22e48?Q#>BXQ%m?-Dd4 AL =) H``$-@9 dI r$<,<0`KXh\n6p6-L60p.s"ͰHJh)/ [Eс_T[B%İ8[>؁)N(!*M`A(R_"iY( X,"/yI# PQd-d {$QDI&0)/ۚ|Zz33xOcOz߻=t7~%koʏ*|C_ҟS+qV>J򸇲v [l8rT>Y?4JeMS Ȁ (cwL8Sh}D밁ȁCGSHXvH*e^FyeOv-xWzg5|;8S=&/EptHJ( фQLER/lXh@5/4-! ,Cl HAcD\Ȱ!C\1 4q)jD8ORσCR@xIM^S`!:7 ZTzb@?$tT@6?[Bc $O@` nJ! KvIo--f]uk=ͻd6 Zi,їP;C 8)MU8 Y Z`zk/U*QK^A78t N8tuB@gށj0|AࣛV>ì&",'`{7g?- SҼ)H0A%Ԥ&q$#dmhn?$HP†Ҥ-2 )@d Tj`$h EPxDNZr R efAKTY|p.䦚p1] AdPg4 jSrIjPОB-cX堤.̤'UPD b .=*A>EjX'hbI>y=}6B|o} 'Hfk4U̦T.";n{@~lVB|/1gmMLb ABkEr@<$LtPHVWнR A <5i1 l4 WH,P=MPh O*-q Fb7M[%?u5g  o-TOH0Dn/A,EG c}Ϭ0Q)AQU V^l*@^%)XDޭV )H;;=`+R˝̌.ּNFA^ EWA\8ͩNwӞk5RHMR  HԪZXͪVծz|Sob01t.exk"9 H!v_)`'Nh( m xO+[<ж nWbXO0}iIhAp$̚+Rh(t@jV!zH@r[\7WjH28G2WDd2L@ AL$P`D؄. 1" dR0 :A0آ (n4tuSx  lqp^^;Tu$P8R X  pgAS  TAU`(8ɸ!8a#(8^pIh-R$!dy_Dk(Fqhu}rp$2# @AVl^ߤ4J6)0 (Q-'ʓRJd8!m25޵n''g^%zOҵOuq Q̗S?P%@tm,? HՂyplm'$Fibpkkm=hBh)i&mh QC.veUfHQH}Dlj"YpA[g55%sQ?ӕP&1qt{BS-q 1uUW; ma7vlbvgxdIbH_4awcZ^Gwx^=cyGK$ag\#e2$WQB$gy^e8g$P2TL5&K;VH`Vpx;PzN2s|5P%7}qsjP&-wD;4&I,q-sVmbK(؆RA"??Pؒ/:jC/x=c@"l&Qj@ғ+@vr'[HJU TĔR9TT( UU\ٕ^`biWNpdFB0\ 0( Sgy 35X^aX x'h,Y\CcDvUZQ YVeEW $i2m1P"2Q=@ .1nUpX"-$#! " ͹;@ q"7 >P p:0$ 97O a"E(jtw,>1` 7 p y= X֣?6/(6`J '88K /`> -#IaBbfw Z I_3a;e "Pe_9*p3c5Qz23Т. 2SJ6N [yxfgZz[z zZƁ t70Kd7 j pP *Hp J@/JVRi-Q*}FoĘm-m))Rk7j'7ݢ@?8F() ,Z7c2fkB톭ֺ?a* `gm9)UnXKQ&"oR@1?;BMun0n'lP+u2$+/!' =o,&)sA&ph'!kHg2q rĖmWF+Qy݂X2h?A`0E+iD;yF 1:V4(9s.<P)3!Lކ\P7)dt(=K=X=Y2?1o 1 aHA54 jLڴV%аjxXGѢg53ZwbgEDe5v !#TWwWJ6`z u v{0P+ٛvd#Vx03j{ @3 v6RO `Vzaiy)aV4Wq!8b&xI(9g~-aoF]TIrrS"\b~:c;lAч7PRʓDXIX?1\E:&$J_\ _ 8 [@3ܸI ]L|dfåp.'++s./@OqQ[(0JXRZ>vHJ$R4!0⓬AA0vi 7b)mFDp<=F*)*.*2#jYƋ@Zs1OcR tY]!Lu0pcpEL mty4L|R;3> Ĉ͐> sǶl=1~85IxRC((@5,0aۣk/?KV<&]}='QGU^L0 Amă>K|QҙI ^N 3`Sff`V Bi =(`AB(=@Y XTu"a@ OIqD ]i) p"Ox 2fIW!>7N ԢA#zA#`N>$r_t0F3m1r! D>),@H&+*[1_ ZvV A #P `.v )V;)j`*P;Rg_xbRbرbO >Vz^s'IJ?"jqO }@<3:Q2 GOE*hnRop-rj2@,hϘ K*Q)=rq&L|qK1)SI{RsKAc1L;K;D/ a?cW0mSh8Btg40!I8LaFJB GDReJ +iR%Rj6RD@) D2vRiC C"ظdCU.Ś$,R$S(JW ώ/|/'a ]!d nV '#eNU8Bb?)!CܐrC- XjNńH&Dnnh Wv)4a1v5K h#6|c\kok[E;+qj^?Bꄸey!l a`/)T`HP0%* PKJ*:R0ʐ|EE:!! PjfLƋJA@:%a&c N$ Hϫ9GA?KB4/CYS6`& !6y` ZIZ*xB] F&oO@q(NCQD L`E 8Udꪈإ- hkeڒ2_ @ Yb!nMJ]]!"k\If4Jڥnmh#ԵZ1@1!@7@lո*uR(*$+7]mMNҵ嗝  "/k:h^JY!:PŒFSQ"!R;QҨݙ膎.)gBo, 䃨&!<F ԟrRn2{)惩؆hZMUҰPks?W**I6!Jb T/ u(YP) 6Dm[;ELۢ"_XC.a缒njslk!B>sK&(H#V |fF02+hY B`$ yHEH5D`]Jεj~.@ 9RĬ$1HTHa؀ [a R"Q$,EъW"2bCU%K|,EYPW]-kD ֈ\q(:K85bяd 9HBҐDd"HF6ґd$%9IJVҒd&5INvsI:!4RႼHf$+c X`@TBĸ"؈UpjB hRp34 ћ &hfCEl 3 k)`Jt̄X B#ZFh*pJR2NhC">G ".} H/ѯ hF1E` 3w53(M {t(O/ʖ7,%VB5$BSNU"6jT0".dXEB@(vJߐ``& 㰂_?N kt ` `EB @^# H24 QW rj'WeDGV=Cn@ "4A A:5!Ki:g.0 ܼ/x\>ttc]qmvYXZZh`U,2X*Q̲!pS!+!"$ +^ȰPʫJUf ]jP!@V( d . %#A\>"1VI{e@2%0-:0#R xHU{ZqA?,2ǹ<2 ùo"ڇF-eU,`,:kceԸ @r+ZG &xD =$AJ0()Q1$ Q#WU5y]i?Z m[%H,蓫F\= "Zf ,n\b7 cDg$F+ R5UvD(xBU#! O @D[=Hm$X$.Js -Rҡ|wyK\沗tto8 K.~r@ar_|{Po21t(m"Q%kr㼍!O!Y-֕VAIDTK]!A%R.\*ET7Tt@B hSBLPR|BAԔ~%P&(%(Hw-hp?4PG|xt#a;I0$uҍΧ۪NCӀ"|wW g$]hɀ nz;>ЈyX,Cp z@@kA{ PN## a. Lln9")`@])j]I6ry;D6[ CȽHp8D KKCgJÐ+h.@HA@ 디P`W1'佒@ψ?xHƒ1<c#HkG>P8ts("bKY(~; 璉:C)sc [lʪp΁ʙIк:Kh9H-A H |;E J8҄2hEj€˺:;ƻ;vtǰ۬3I2 Q̤ H .ˡ+ h  $0T |i b'Z 8( \+KȒc!II@I_  y"P  <" 9,?P3HU:2[1B#c@ (r J'rN\i07K!dt9K_!̷LLi˰L&L(xq>0W̿\GȮ:y]ƆM^x ٤Իȡ` ˆ/܄J$4D"L{ Y2i@dZXH`O >9ԁ )(˄^)o'q&t \Bϊ=ODo X0M(mHrP8ূTU 8ԝPbڄsf0p&hE 3>FXoJrbsxz`Cz?c1Ev+N- Ȫl*L5uӡpN SpN`4M **ː؎R Ί( ],@xY ,0#fŔT^ajҞ,FuTa@_ptН( 2. ڃ[!/*(㷄0U@Tܨ( SbcVxث+r},{+( H2#2ۏ4@jM E T ; 3GB=†rT{ Pi,cپn1X(C[@y (4DA @0; 2ayَ"CB3(01Sc0ȱ} `=XQ[8-N|YS SA0d c6KtK^ r]{JZ=7] r2h gK S3aM]rHe \{Յ%^н5[yȍ Ɇ?u+ЭSX*e{*S$>?a-ԒHŨe w_FԄ@R^Ÿ`9ddH3`w٨!:C_୆ƛ  0:Q ,˅X,WԈ8VT=V}cW庰6 A)* @L8߷KC/*qq<#C آJV4|C\(VcE\ԓ=ӄ"ĄBHRL.ʛVcITWf'1;viQӈ$g= ʄh_pw4~ގ^T sT8N88]<ƋDu~rD xVhlӢNlgdsl!~aS8a_V 8c dX!kFBKS .X\hhHL饕3]!5 h^ 0}m a\KtɄȟH@7؀V+^!]S2n z ^X#[Fz1/B]sN2i9AiΕXhpm.JUr^ѝKl@=|qI>(W, /Tqr$wrCZ|YtCMr]'(3g$]0rp2|D/5m4hr<>MXQ[04}$N7?-sbwX$T I(O@Q&yцxO;Aқ<'(ha ~!OgWuS`} (^ Ql7* n*3KM0KRKb * PiEUsd ;X{,pvHJ]KmkQ `'R4Zz LuXXk!hpw=8 ~.|-7jנyRha.hM֘sxS {,j;2Uarp,g _)Vrzza\Q}h}HXY7^Km)Xɯ/433R ܇ }uzٔrί s[_q4mY !3nD_ C$c.^dȰA\1@@ (PBH>(ac@09F';{bɃRj*֬ZR*u+X-^ @lU jknuj/޽{MT(_RMܭR]+cd/x^%pd !8} wS76պw> ik3g]3pEn2ڪOoՂ .vI`iUIL YQ7 zXBETE-q qrLKpd J8*\utG5\tNvBpE.^`+xщ\Ïr CSjSCd`E TT cKURȑ2ׂL:"F.*Zh d.Oyb5?9ud0LLQG[Y Fu@_TA,GS}*uV3TiX85Upt=C|Ѐ\CcH>r""$pe3 -L*,E,ew0$U",??"|tL|; ^$@!<%6O%!SWd>Dk! ܰƆrT4!BaSM0,UnTgH, 8T^G@AI,lEp-F:fr{TfJ%=C-XkWAA؝BGD0+fx0PH2)@S ͵keukah㗂48g u9ddQ"vG $ EF]?9;!!&[GD_TVΩRxP_yy?>{/F$ekUÒLU IQU-UDѵe=PѠ. # v# vkkK$Ѡ577P`*f^eUnP/3hLqD)4>j Ew-kK bV"! TmX! BPU|$BD.\}O"J"qDFzь"&Q" Pj t5Bwa%,qDK` 0iPEzAd|q+xa:N@PxLgPhFF9 [CH tQmr3&4I4S+G,G`G0eoSKJe,fhqd1)f dH=)"a"2ixN#)"\5 T" "xT@ G8q26qQ?g 0JMH.y&Q(̞@19I.lCL<)sϩ6¹-r^S)V ?*2%=S,be_u}/|+ҷs҃O`**G& d6TYB|a7QD}z*&xB!T0q@6On`=8YA)&!"\dDEZQ7'B N00l".bKd'T89gy(TҡIy3xt2lba0hPpL6c!EÅTs\b-NEd"dX\ &СbgAGrǑD:A28iL$|3Ul9T 0a~ Bn;ҹ |45 a)cld#XB/bs"ص+BZeZ\U:^_$ͫ@=.)v@P0( L"EA*bDI:\ufyp88bqe#<~Ȉ. ހXH-F$UT)`I"@ ~  Ke8MEP=\tzk$/p5iԣ;h%X0<5]~+^_'z:'T\Xž`iM`ž t"x !7Rn%dV jP ̝{`5J d* _ IH4u"AVnD =d$gav t#H U(pz]%BCb $=tTpfE\@LNOxQdD@@@$DRѕmf2ISNؗTFlF+-B@s<x,ǩLTUlhKmxmEsdJENFg$ VE^jEbrXT@Fpi|b<\Uzke[lK|6m[| $<  &?0@=dּ]0S\(#\DIw8n<ԓ=G8ELEC=? "C)qDt{IEI0?,E8bD P)&E?  pĞ]=b8 5|]`>xح<5C !Z@U?>fM\8TaE@#t Ec!E$LDPE@FVP T\E0?d\. (vK& t¦aB]th( \Qr0KUo5G@$A-Km ݙ22}`C(̟UJL|hU˶L ̴~lC!h^h R\ncW dTeTE UhyR flFZEԍή\Fe`NdZAfet0EjFotQDFf^ SafEU偀pTyr{XUTЧUf`QMDGX\H@$CfCHIzQJR%WE`AMC1PF`iWL5اP^D]hU\%T("4HLKW Q YdX ePhU?`fiC!S f \aڋb'4͂jaE&~ad@TaU X&0]$PXbĉ! U[r{,>`:+Cr%**"UbT8 )(2fU<#ehTX#6ZC\1<7#>9~?>1EAlsHa%C|$ `]Y6Un\'Tt*O4GNB?l@>T>rN@qRK4V\&f~b4EfTEpN@Ђ< k[Yf%^VجϺy-VctHxtXk$q>]8a7)UR,/eu˿5$&Q.Pݖ \ɬqa4UE4EDoT$s6]<@ùC (2p `$|?Ђ:@(C:plDFHKgaoYGdwLld-OSy'Z[v=7@D^(@T\لQўP&UGED^_TVZEE)R?_REQ{v]5V^+}PpIdU\ D4MYܟLH55FD@UDf66LeeN̬kPO@+5-/ ;lH [B)S~`2Jup&Zna˽QZPQx!ۨ`:DD #b'WJ Æ[FٻteZD`, _ûk z$P. 4譈P )p/ , $(D$3܌AľZKz{p+ljκq z0"pG w\ǗQSDC R-/5:wjjcLcAi܁hzp8Nć#?i?I#GyTRƌ cixG' UQI-u3\26,h]@}Y"eG[W^ũ$80.V|o wK3jG+C:;u^|@K MJL!K!AU1X9." :Dڄ 2Y#HQ@ R$`VP%3hgH!3z :C#HGP#D/'(H$A_IL^Kt娑T`hf>ю34  @0akr\):?$h$ *"Q0ȆfI gE (-Hc R ZN|ɖk1$#G DX Ro;2*D= [4m j8GsI ḅE=ZЀim~? լX/H p`#"5 R,<8r膢^>oz  ‰V7yBlH"9C@_"*D +UCQΥK7nd$!g14$K& K bHb0-@$].6HIV5Pa:TuCrI"0H& Cb[dDlbbA([@*~h\_l!8;V+g AZ/>P?p@ ԨN!P"H ?&Pq Za8(qӿ51-G L,Ӻ$@YP $ F]+&AV E76ug427?lQegF@ $ p[b!^"5@tG0ZDN;=hu =-X%&% n%1pf{ (k!]GrI2/pf* ؎¯AhXќk~zb $?̀<,9 R|+CȃEk=s g@}m:F iH:A7=NCכEMjt( tG#ZZw9:l: :h0dpR?Ben 4Rhz8BA 9ҽhDSrq,&Z'tQ@& Z|'zciW lb @th R $nG#EQ4|6 ;Fl J8be xA$\ 0d,"@}> x,.'"vp z|Ȍl!)h0)b"0 !0GBb/(b HA؀0z~ Fh 5W:%CvgϊIih t^lF ,@^ : ?sanBmSoFo <nz$&oZpo+K6 "N/B p*`yN =8>C?8H,yR0izeV*8&dBAA 3=s ZelG4m7lmx$,8+Dlϱ.tDF]oAڀ,4菰J $tk܂Ow@ (G܀0p*dsAŚ4'@ l !BJ,e$gB vh. '-i ^  XUU_5VU B!(h ˍD悼Ga- } } "PZ5BI)VLFO!oB8N=0M BuVRBL*"*dFf"..hf咕._ %t $$ j/V)bꥠ:V:^u@J/:B ScC0Cp./`sj_+50*+fE>/OlBZFJKf޲Dl. ʥfj`M6n!@f8 @꡶Q%B"" ^.<1z: 䪒-6CBQ8DS. pm:,#bAJ+ZAzLwcH0$r״` $S$H+@1EE (KU%/j03 (!j0xT T-O |ث!A^0,X"T"SPáJ)u āAYnb5 BD1ie~=I+oh6%*VGrr晾>2{t ~) "wzl,$i ܒ@aOL+0tE@ZgBi* BH!@@/@7$꫞8'g Pͱ\%E G$|31?S>%8o:.?稇?ٚtp q @ $'ukP\ `idiRBBB8I-4Tw>3 _`n"r7__da7[!m7#;@sK:KdMg ~y?$ $Tj0Xz D x ʿGvR?6D0 ߪdZP vpU? o-T-"W/ ժ-,)QHA~hoW~20Qe XkPoA H% Î?& G@r|E@  $nqn6)0f$2JD p%rFs`D?j*U3h[EwKE7; ??Ź=sf D+_6}?CKD|+Rlqw_OQ]dPr H's^-ZUnOR4X / p?8eZ(tXPiR8%es6")A7^ȃ rU1؄ r?C<HBy`QR8#' _L 8*dxbL ,DSUA!'DĀ b"dʚE8Í Q l,!ZB8(6Ke#l#d `$)@ B!,Q$ ( r& `f,ڑ'?Bё,mb 16j 85*;˚H"4Q62j`"f63#)S[ G՟SMCiPK식i'~#٧XA2@S $}g"mybh-`UDE4ϛp2蹶i"H>Gk&?O/NHz+?vd*jAY " SDC2cQ &J- CX xH:MkcW9EPwz8" !U*Cp+52%y"gPVFD@B0BazMH U! j= DI\@ a-iUŮ$Od:bm o] EkcD֑ u#ZA7ob\ R 0ηٮ#"\"'݅/|P'c!R53>$8&+RA CS 2dKU{)@{Y@b;5M]]Sخ E2{-.q쵀~tM.JpeHMZ XX|S4LAg67&x@ JTa ;o z1 ! 1U c_zuEw"S0E]eLQzq7Cp?AS7<: C# A {(f-Q"a8hb[PZo(Ty/"*&6ʣJ*tt 0dHG CG9!G{JKVK1@ §=yˣZRBBqH%7 C47GPC]G I qwW yG# 3}W _ ) G&zZqUbFLFC:4zCNJ1KFe AC K~74.YzHbtR+LZ *[4 "<@R01 z4POo96 .RGR"&m@(U/0z"%N*+' +& v+P@V"[Dbu'P $NfeNO$O>",flɴ-Rd"ěTJn\ o.My!0%64>EOvYY{XX1HyZJUfH% Zf4@uYTGCan )⬖}$Z_Q XqWvsatZ2DqJ`tUK%qA 6"d-j4 Y?c-;7#aȥcrF]!]Ӊ\HqZȳ"  =;`@wX^}!:@_e.T% l0`y#MZa stcQ /筐bf1HZ`6(A([`c`hp DA˓!8npHT{UH+.IvR(RB,"phPCt=t~uJ 5@2;bvxv*n Y2PER#L{b' TSH!Drd:f!%vKbO*#R9!!`R'Wb5KWQb-vjg"+YtN$ E fGL/jk/c.A#=癴ϓyɵXjB/"Q.Zc_B7Û]#P4i&Bْm3\-kF$fiբ2Oh1ԙ[wX` qӡf-<:B ߠ p*b"bq*2:_w'nGw)P2 k<=8 j8"x"} k1" -)  "DHr# + @>W QG/-ÅBQP쀠"F2 'rI18qIP-s23<2!1`72.QE 77y԰RK5&%HT:7iXm6Z LZU+h s!4K8"!#[ h΢U3ZevhV.5\u5:r o5,w!ʄڅ36jآi7B//hhˢ c#0 Td5w?&he SyI@Qh\W^s>aDQZ@2naZZ(k6%@ŪSW^ZazcY S|yrZi{|gn6Aߍ$W,_x!Ugs77 rÕrtV7]#5Vrhf7TA 3ROI#sEDȲNiYZI$ !j?SџB)#]:K"ᘟM~M9$$? e *%٧XҒ$(g V(%/yLd&s B >LQG /c@dJ3LpS$g9yΏ- PGGDg# cmp@C|a3kyP&T e(9ULh6-CbDMA % ԣON %iIMzRT+eiKY*x "rher€0 dqD@YpS զX VsHn u!h ?Z0(_-jSE@O,UͲnD)4T%Ar$`Y u!8#Y,\0U1` A)8&с AD2¼IԃOd8u@~ -dK"?LTh`L}>o: dATaQf^TDms _ʈ찐bm!H2@ pᅸ"#{&i֨XLG),'("' wm +RCWqDB#W5Š" 4\C#;RA:m Bh(!0Kh n8&_LX(?. q@ f$ HɄ 2L"|Bz#ښ)"It!8uHfaޘؤ$AAf MUdҁ@DK@݈ЀLЁ4Ȍ 7DBQu8_]mT@o~HLPhT!n X\w{6T@f/ycȽ{|@C^Ժd.*0by88YBz" !up" )죒%&xz!6/#1:) t>0; QI EĎϗ}EoDC&O,ypE}$ҧܓE6(ѧ'OCo#ч"k ䷁T@ ' ۓmS9 XW#O=ɥ|?Q? s?SB gMLj37Hu܈2_Z 0P@#s[  [Y Ȳ`@a0#<ݨIY8yC|sY 5l8nHg;#  [L!P_Ih4HE+l9X{K5hh@DC5BDXXXhX$JJSCXx.P0 6\1 A8`D#혘+hŽ  ؋Pӌ0 !XH (qayB㘣:jPFY$ JH  ȂdܪJQ#);L:HY( ?0ɍ8jG?SK%(9 &#Pp"cӍ'@$4ڋ ( t x9L'zP8qLʐ%Z ]⏺1ɳH .i[(IK'(xp u35ADHˑJ%ocK831&ZL|,XHMIt \P Xh9x  ijŅ\LhNPN]9N6AND D8Ix,hX,L7ǬMtǪ؎Hq~HRlH< HLN1P Qػ?Bc E??Xؿ@>Ѱ{001"ѐ |$,Y;A$ӈ1?y !"ɑ A"<xpB>P8{B1 397\C;-,=픆xAMQTTGDx1ϻ[0!bL"!">Lj2bȠl!Ҡ*a׻VW b<ՅЀI埋xTMhhZ= GkIT*k%!( xh[!8oC>io оYz˕*&>E7"~ #J"=$<8J2O[i%`R9&ȷZ؟xX2!MRZB*3B2E[x%KOZʳ> 5ʉhh*=Zc@TVkYwSZݔ]ڽZKمZT{x'hێ&knP;-[ڲJ[u( d;` \}\ȍ\ɝ\eb\,܍FP+JF 0تPJPFzDɰA[,pV@%!18^Y…+`]Mwc@ir|4B`9ϟ]0]}]u@r]%VȀh*P " R]i M % e -ʃ V2}]"hrֈj|@Q\$Ka) 32AQ`L + EJ0W ~qˁϹPXɦ\`a -@ =>@-N>&7GnP0j[h`-8U{Z=۝9aRΏ@Y]e5,H0^F_xݹ IpJH6[̂  9pH) e ` 6#d +N pwn tk=I=)x9~c8z#޸o8@bsl6 ts:dL396a Jsٻ嗍;/ Xč({0T<VEsd` > ?>yQ<_nP(=UePWP ?.؂- { sO%1p1֑,Rb\ !fqBƁyb52k>jm@[.ca-͚ [3z"7@~Y--AA@vATJ执R[=eH9oPgG Yf$84ø4n XSюStF0 08p(qi(ג[׀X++JdA,F "W͊8# n<YjyFr0-P]l @Y (H<{-)!?؈\k舌j楽̈F/pokaKIZ;XI)JUQSzK1HJSV0D/y-G |Q($:LyXX/YvԸʃˏnh >A `'ȜiwѠ `.C`ircv2Gv܈?P'qZ\n rrG#ؖ+$X1Nsk U~\`z8\(<4U%^n`z`y#  8Q dm፰7gڅjym 2nUPdn0[!z_dQond2)En> ioM3[&wȥQu'%P=$: G&! JMUGa.ɢHH!걶7B(֎p}8(}"!x{ Cօ؀)Σ)L5!**}X>&kI -"¿ |/ !T "ǎ? )r$Ɏ$PÄ+[ 0Aaǔ%o@" !{(HPI7J0jȦKu֑ؔW$+Ԅa= ;gǩSZ%5\Da½^m ,xpNZŎ$Т1w2 +ΜW?9 𥰸3Gk5VKwm޺Cέ{7oRsyigU먌_ ^TW#:!I]k}~=!3x@px|k ?o&>Ё =Q ZDB^e[9ȟRXbrءzfG &4 Q52p Q,vf@ "1QB>)AB)0`GmEF  DR`O, p¤GPP1 #}& MJN"PT" da]2è t0i ||f(,@D(@cIc1Uz" QGCc -ό!ABs4GbG>n]v7GSG؁+a3-$TtnBs+]& gLe ? vv(F E6҂G/1G T@5@8 9ئ6I04@#8? 70Bo$ PoGC@3Yo#D֩1LqKnpp%JTaH.&$@(Z$P.TDmQ1wyxKB5]@'D?&@UdzBf)AUD ,TAฐ@ÿ@T"cV?}F{LUьADӟP,@;1KR(#P9D1*,'ԃRԐQ,<RA  T!@>@=Tb| aj>`E'F ոKZ"I ?dC 2RV  $(^Y "#g L",YGĸR@1 C "!ɍLHX$M<<GHF `Va#qSG:_$0Eqqd_ْ٥\G0zŊ]xD0xMs%htM@(Br97X6@y~+!.U"G7:gH?Kflaܕ C(@>yZԞ1R}ԤZ0 Mah)q$oOB&dR ʉ"Z)GY\Ω 0@FXd@f& !?.NAh !IJ>O$Tхc}٤AŲ,:pKkǎ&%l W14nt0}NH\"Z65N,\ Hm= $6a!D6G5\u@"tS޽aS{BWFY ږ%ntcRIXMhS /R2\ܫ!˴oL6}l9! cU;=sW?Є p'6@taH# D FZ sjWS^ ,jE&163䧓sԧFƹ],M (Tb\~ [d~,V4GyA_J@?!Y}`S0xFGYA7R ^zBVH&Ŕ E %EHAB@-aZy Faa@DlhHJ! .H"#&<"D$?Hj8_ ?hzD!?'B;J"&dB6$hCB, 0#BZ" ?lJ `!A )BIK@Apb PyJtcVp@4VT(7&d B` | M4GԒNįcL0'TUGE4g Qe?\?OBc|F 4??,=8Cτpu5GKN@ (SˆDxM 7Ah,p@T)  $@eTF (dF̰[e 4-,@*e+^:L *`Y2%ej0\>RcW fܨ)`BBci,LԞEH.QQAREXBD@A㐄T@K,S$`XTP@tYS %,T<|x@?gOTJ&zfJ<|DHE!ҳ@ia|DVDٴaЧD$t@T@@GO x@RP]c  MD4?PݝdP0DKD WpEegNEГ rgeAD)VMRF eRtEV'"՘"vN*U]DHDƕ@@H4?Z*-PRPEwf-@GڒGܨE(ޭn@d/=,g`m>HB9C [K+ GXڠ_fa, :T`u YL `@?(ԭRܿG]>fMSD"SZSeʀltfhz.?CfQ`ie`FU eF;CBhT,*PWaC9G,H@ɦƉ0ɒq-W)^uu?BEcfYL›VFRGL@ d5&HCr &d>XˢqeBkAϊy% @;EӺ-RUzx)KnlV-z l!|fQ[QxU8d lNHʗ>WG|nGH[P[~FMED(OI9X[)Nm8َz=ECĨċPL.ŎD@|̮h^OmgAyFBсZ@*__>zƜHhCEphË*H(`mZB_%AB\mevSD ?@(|DF ,߫ FzԪ@J/kE,\-p)yx#pB'v4r ЭEPqG-$EC:aZUhd?*p8p bG@tI%ɵjp5)uyB fvDXϔ$XNYӵ"lkl,&^\߄r"ZDX8"$n v ?ȁmpdh"(cD,b5cB Ej q+ $$$OCɞXJulnu##3 5M"D@b}?8wS;_*N'd>%$5\Ds؀h;(phxQ>XBwGEEG<΍zU$1%5ԱE8Cy Dc?eBKG!@M 9V ` BH?ejM.L_ Ä?xfgrZCMêiL+,| 4@L0L/Ki0 #9b&)5-p-ڟb@  tdnG$ W,Ecge7m#5xR&GHz0'c?@$'U„t\B9h07G D6ЬdDQsD[DH(ynTf$U)`@X,)) hVGL(B@G"P! @E@ >DG B\滀B@UB`,(%E26ͦ0с'`uI$`~EuD;KJ)`.8~nEşFD )é*_VCK'D^(-@Q}nwoNDIp]fƧ&Db)F4Sw>үˢ}B?x`0%Nj2ʑT׀+˔mZ8)SNyd-yM+8TZ E+^T?rGH}^sH D_簺o':#;9nDQ#CVH1i,m.@hX?vz ˖@Ս,h b͊"Pt"č"_P p8)"E(ŢG=8N, R3>;R&oÄ*tp` RDR"R`"fL-`Ѷ[f" ADW#ϰ3BeP=nl^1qЪ;>93rΟ=ɲPz@Z 2.Kphꋬ_JP!B$A:CȤ#`DP$ErB *Қj#4D($Ѣ ";|P- 9,᠌hDB(ʯ @D<ܓ>K","[ό^}jHr'ӊVx!k\ĚƂ&y.>J!ֳ+%tJScl!%K0jk@8%o^OWpDNa"0li*YZH^% LSYRpN~H"j# "\+8Ȅ N6!xÃ` d0 Ld D(*& m TLhk綨W,v? >(m.(Ȋ.:o:`:g* :غsoA(t\?%˼.ܯ8"I3}9yFhW[?_Ԋxkjڥ_mf0<ҳb0U_ X@ "ŰT:j0! 0C2H PP  $(HH >A7DD`nC"T#BtP$84B C8Ll EHEaBd !@bwY)@wE&x~$$H+O|)!F B5Q# n!2A*ỤN}8BMjT KuAUzF`+\qvzp4 *(5" p׽%Yؗ NvP!/@V& j#P/# #-B,?V@KshR ǺPPX #?U'`AҲEUd BM kU5P!'pUD2!:?tTF@ `5@"aS[v2؊uaZwĈ-~"_sP6 eTE 0|Ѓ3&:drx.A2K HG2-&#gˈjַlLy1Xd$N:9#P,,A)و%Sd hc;b#o#-&2ȑsQG8eQ?M!3Czj@U8Y>pSX QvJ7賧Z.^\R©U_EٕPxa4EΕ= 3RluRM#j8 2x1K- d5c)WDt!j2"gMReeDzU-S nWErPDv BV ٭E?݊HA&~{لq!8Kb$HJEzpQH, t(=o7.9w I9UkY.h ,΋f)o %IDdnI@''3}F9!UYPh <'3ToL#[Yfc,_}YwNQ^<sÙ<@=jc4 '^_gdX1m 8$Bf@'-c} Y0NLD"躀B|!/8BԍtTpH9wȟh"+@jȑò"H`$-MN0.1Z &oXaR !,,4DJpϬ !!>%S+ReUZeMѸW\̥*B ~]d*@ *e.)]b2׬p 8 %^*#܊P- F -a2ܨ.,Qd@5JTf5bQ6b,&/8  K2##PL2ccT#6[vgj84clƶmO Ǽ3m6Mdņ 2'm3i pLi 1/X.l>4b9j6Jd)#<dz(3h܂ȏt 0c5"6L 0B*"&"!B"s D> $fk"~*# -"` s3` ! "Pi5p1C;*‡HxI" \€FD`!! AaQ&^A:cI bA*`AݡHtLt\a᪥@AB|ʮĭ8 `ELfVcX!r9th^^(]j"iҴT !Tb娪 N e zD"P"") Q)'#`X.G j#bN(Lڪ4 j ir%M XTI#aD83آd,kV؇g *3( =*R*@<,@`irA8T H`;R`[ C(R ,,$ $$@gL5K74D@#+-&1~o^cC3 Vj*Cv-ROJ'+"- lc9 > `@~(s܂2f;p"v b1AU~6oC?N"n)0JLde%<|a )l(]$<.b#m xŜ@"܂߂mb3&09 #xlel'^9\9"W:ˋ&_Q,'m,+}Vn!ZL`_&TP!! TWV!,,a$!$͸DH!FF4 `RTWFwy T֊m>mY%#"j" !Je fcA^afM{-7MBI]PZ7lp;}q)u^"Aa,SİSm^""Ĵ2b\(0|^f5~T--S`$/'.1j.E`: D@ *s/+*4j4a (@5!`,AT%di)D! $"PN$A#@)-:! v$/B!bUBh`!ޮ#MX:)8ʊffL$rBQ3"~" k$e#@bi219-zBs~cK #7juOh;:@HA7,2Y0<6b##c=NWU;:W,Pr2gxshG<+:&p#-2=\ H<<4fG3q|6ݧ/u~4kd "jj;ZOe& j!2zIԲݷ8 !A4,@H b861,Mn2F!I.R,N3")LNm3BRP9o0 Q89BEXtuAuYu!vIEBkWn7wvwBCrhxxסy"^rZE 'MY7!J{! H:J2b~C$5--7#օ{~ga Jm S`+#[f5*:bPKBCnRWfj3E$P/#L^3R;J >ch*$$Z@b;“+nv2̘KFC4;ţ7;{OW#NeNodJ@mmyVn-X1kt1Y'Z.11Nt(_\vd\v8"+h *έA !{Ad {5&*E)!]*'EAGOr6`+jN</:X+os]w׉('"<!00" A-1l"].ǁ R@@R4`|@`:%D"X*> $vJ838bR*.&t8̈́ӫje7u4)#8;e3_gy ;3^M~ Y\y*P@"`2܆Rjx:5j@@\; n! >(%RBAxUDf V41"IEG>Hh<@Z R@` bw@f.S!ޖ0F4,w*0P"P:U ]!2mw#PM"wa rHmN @-/$$_WK߱"@B2bx"#?-:3Os&M<#5-bJL%N%Z 8 ܿ _%hNc^SJȿ7:L#+^)[ɲ˗.? $ Ȥ2hhB _",ʩ?U4W @2WV@`*`_H8 BoS N @Vl˷-Ü !@+vPٲ.9c?/3$~ HF@"F-ڠ#g"O&RKFRć5|,tA !F[ 4<Az"W[=Y$gH7\@r&tf!LJ|șJrx*J&?|x?xc*˩֌ݹ[m8mU WMH^v%Jw.u@N/qT=@xh]O)m)'aD'LO2fJHKDK\![-$A?,lSb?ȩ+iX*K"T?'ܴ$ϴ?DJ" 13;5(`;$`@7(+/``-)(@&5lVdBP8@ 55.?T\p$+JK?,.R0ia-ĔpI&(4sJ,MІ,$@ 37"C/IЋ . KHN #C@р14G. .5%&?f!zr:?^R"l aas v"pۺ%#'9G]$E4f~)@xTc,h Dʣ8)ح)!Lh(X5@(.EUb:J.+a&EY'+#\bJ;h)5;(k4PGy!Toz]Fٮ%hI'*Pp@x51I221_RI?C*^,I)ү~}bEz4yNdIwP81&Ps$H#7|4Wp~:xγg9% Ps]]{}! Nƙ}'MJ[ x5 Y/Ch# KԨN\հgMZ+Iu jb%a0ȀkEbpAbe+yvl۞+E@M V~_Τ%ME=CI z Ha<+ yl*%*Չ%LY҃O N_Y>lJ[B.d>ῖ|@QěK x (1"B qanqCME""?7`oa/ } r57uEh@5F3aes}DAtKxK:M"v)й 7`>1 v;4e+QsUIo2'l(n0Cy+.pDCI@QS?Cr9 哅!*Ao$e.)=T=R=dAt;7٣9ERp?RlEBnCN25V p,] x@5}J ^dJ*QdK%G3Uc"1TetV5 Еf_<tq,PWu6QZ'D@mama !zq  q zC5P7(# *h҂~|0 @wn =jx  { j ҭұzw1`"'BDZ>.i/44?4qR4/Aj˨+"y2UE 6+G-AZ&V-V=nK-im?5d18"q=Z)40Às} `6'x"Q  t3B0{@T֡*x5D&?apU8pYD۹Q+a^q!&]"_S:]R00ͨn'֛E$e}A]JcͼѪr!8eQp0\x"_NhD>&TQ^ YT9E02Eҡ!$k.Ǵ#td&hKB(PPߓ<٫%rLj,qA7N+~`R) I&-!<Ŕ&$/{q2"# N +a @[zM.E+($CK-MehVP!5c}yY*C$}Gm  HNYɶ{9-19 6K  ݢ Jp"P1OD,8/@8M4:G7; #9>c.8T4"svfL- &"3j0Q)?K-ٌq>* i7+ʡu'j*<2Ze㝯 B*DDdDS P ? ITc_ &i2d@tGJaIh}%H݊oT9n$+lh$ڨ~a񠅪o$j߼щg(VNK*Kl_WQŶ3> j1$ Q?Du&Z:PQ kbF0Pzo0?iϰԩ((qI2uwU]+T?LDP_#j4~RȐ*>HB/lGVj4QͩA?gT#R*)@2+M$~ɵ)Xe͖%eԏX׆BҖUUF9H۪{=RP(VbXu@@Y?*4$MBq/?>j`,@N:(雸 ^mqjf?LDB, !@"Na&?{R?(?^4'ʜE\3y_$F ):p*B{F?>@1#pC (8J#NLD!`2)/$PI<ɭ1L1GR)*?z4MڔK:x ˑ,S-Q<N PEeQG4RIMWhcu.5'E5UUWeUW]]^/ibydp4}5Xa%XcdDcZk6[Ɗ#>de8'AM|?.(W)$?Hps׈!8FB:czUP&A8_a>Y $ [2~8.i5:)c^xo#;R" @y!#''' A&d>.~qAǯg4Fo$0` qe";@$VVG/0f!aT񟸲kk E ^TRyլw34>lWYxCk穭 }lET}adT(§ XE2:0*T yL{w@T_,2Ì-Z0BP*BVCn 쀋 =|34`8&)` 9RUXC%0 ~8n-<#"xBlRG P8+)PI*D=xBQ@.ÌIAHϘDF-8HHTwNN 1Ą}`E 9HBPq(\Fș'f`"#ȘzЕ|&2J:c0X an=)\Py,; u.DJb\k_I2=,vҐVg>=3"g@rѬ5y l!ۗvyflHl`8I4 p0G2ģn%( Yl+X>Wт^oϩ$YeR|Firtt34+!]Ѧ0iJ }&JwzR?|+L%I:,XZs@>@B8J{@JL>p|| ux ԈҼdtYXɜXe@|x]Gڥ?{#D$̖d$O'i_{&%+,BhE 3sawq?8ș)c$ =äi ?C!  25\9Pu1DHaİCD,HJObt\E[EiC|9D  HPЈ @QCЈ0X9h9Ca(/L)_aʉ{R@() K_aA hĀ {~Zx ➠ĞtE] H y ذh K`!J!l%jEMH[; !,ȗʐH,A,  TPU7! x; "!`"kh&r -y (*{,+еpLr"(̋**e=2zPQ$)@09$]rM*MHz8=P(k^&e M 3(;U x%p*MMMk%K]HJ%̝S1$` LpRg&O0/bBA"  8h'#< 0~_RB DEtT3: [ȷW :0UUaբ`U%# bRX%.ؐ= Q ($! (_x]b-ˬhKB  >>( Li9?;bl4@:=*HÖ Ed\;%m6@'ꊄ3RJ' PQJcH@V](\dR_v [AGP@e hm#dle٥=ӣ(T6n6qnvwgpIY/ CC8 8N@iPKgЈ&89K$3's̤M 6bh{b"EI;Bs'4 5=Ьd${{VNMSL %azqckw5=Ҁjz7"bb-&qɣ:#%YQ##XN1c%R"L5Ppjl䰳nx /B@Q7a\B$}BS( _z_rS QMRs`5V/j)AÉMrw[lH-gQ5ʑ#v f .AI""^a]Rp  0KP!ɬzbi+(8 d;hRyۦ,, 'S]p\ jTSO Ы(yω-H,LhF$ ˻ښ<߾J8` ՟ [>K*8">o׈(}(KZ$>VH9I'uIJ <<8P 1ȃ;LS7a֧w1XX 0y NL f2ha_" &=TT MDW񮂁wM.XY0&j䊲26Z*+LL~0 „ :{`D;H|bC :8B!MEkF<0HM%ȓ%ҧP݁zAO?"h :_?}*d' #/x]wMoA`uA8Ё jǝw x P l8%p!%[A0  jhiO 01#?rP)LP R%BXDo -GT } A \fMh1 Uaˎ`AjBSjhRCSdh"DQdRCNqK K:5-$|-ύZjBZ:PTB 瞉(ć/-`39M@Nni5tj dWӦ>A!dw :ɀR*AB ZBTIDQMAPAu`|/DAJXPJ(/R$RZ5<@ 4 Q0R3C;Y ^`9ld Q dBR%4B%!IÁ 7SE5^-ADmMeuK_b/p|+1idxQNbmrB[ 8{67PbB;Baݥ~:ꩫ:\pBD(@;r5) YC֮<+髿>pED-V9?tYf Q#0`?A=ԅ?-SR ,A$Op/L aS Y .'V%x$a?"0.:l"!(B-X&y%D ~ U>1|.ă4 E@=ZE0D0j0*"&ZFXD@F Pmq^Ep"fNx#ZA5 ?1.e(\C`f AH$e]C, \E! zܱc#fi1}ӈt`JQGzjHQ.4A"*B39rw!,eP2%"Lb(D!GRЅ0*"I%BI(}L* ! В !@- VXK0' %R@5LC V2 xjܠr.roTvF w@G X ZR`T UBTS4ZE?)%RQCHX@u [b_QԀIcy 0. ;({jFu)GT|3 qQȥQ1I !^BN< ,']J_Q%T^] o*ڻdX 8(@6P%ӈU?`@j 5^46FFg{k#b+l#%;qh6 8rLAd0lh5V!ʅo VpEӣ 6u1aFuPP":DA3\V `fj2\?6"VԞ!P-T0ש^?찍xH10/Pҙn˨F-Ƴ֫wRo:iQU]4"WI/?|!>b5oEX]@8 IZr#b-n 丗_;u|28@nCaKKGL(vLwqp\̩x> $P3)<8cZpg} R|:qn `@D(t R`jQu@_&x `%`{ǀP?¸v` B5H: x_H $@dt`B Î`B9䈏|@XvC9ʏx`5C#h4ٷU@dEDX 5Za$hT@NTDdtvEIN^aVXX8UlMQDD0IM]Ѕze``EMf(Bx@b*TY$@ U!'^DhDEEg$ĸ%zU470dCd5IAPrxEP߸8H}|Ty$H"_2@+xwҨߋ!c z)?@d|,$PAͼQBp j\^% fZ=@DCΌ!D9Ə@T᪽IVv$BA @BdͬB(eclwԹ }~L%HUD$iԨtΝܡEUgyt(] 5DUչ6it]BE'yz6)X! DtMCa,!Lbgt,N 6^XdwֈqJƇES"EpK _tNmΈ>)F$'*RUDh݄ Y*2lA@ D D?\($q Aqx , 5HV@9F9~l!ȊdA2A-J%CGPi5)Q* iR% I-.9AAmCB ܫ @! $$hC5R÷ k&4JV"2@8@Px,HhdVnʶ@yAIT0iR$D11ĎUՑ@ÑlӼDj44J eg64J 3;uCV) BC?,TCN"İMXt D9A{TGԜl\B6$l -Ij V8(JfSUx. ‘hEA4A]=4C  n:hS0hVֽ9nҩ0D.P]fЉmtrR=a@jFm؆AljnC8loC@< 2?<2PK݈@]W@XF8pYJuu]wA*G CnD`SGа +Ww ?$6T C@DXי-YAHYN؅ 2 EXpDEYy <̙EXD@u@[G4W mC(D @,YE@zptYY} c*[V2E$W@LE4 תbT@M%ZG[ ?lvaY1ȍG#}<OsrgmR,YBA 1?t?-(gQ3BT\'u΋\[[yM9C`؋ .T[]XNh0BDBAAzq>/J,c1sINDO[[3= R2CSB.fU=tB:ed-a.eWR`։JQ9$Oa]ϩ(JT]٥ -/ ČVN&?Ђ/DCe=L4(-ph)7j  ol*FT %S.Bf^jή^BBB3 enD7NKV#pVCWx*CICz,mzk?d@PA:^(ΰP Q#v s'sdNVr(`D`n : yH?묦H +%,&P P @ H?ފ* @ ,k,@w- @F^Bx[Fxxb$ &4B BdZ]@Ă? x`4,-50HP3ט=FUD)dK8y \qŌ4PWbhID|I,~-y<W~J}"ŅeOP\D0cUEwVpWG: p-FZW|"FpF02^ExFCX]ϸhFaN92~OME=&$āj> Ͼ!@L}Nk-Xw〄ǪM ̣AEª.!ݝB|O@}Q!A+葞 ,#ȸ8B$&D|o@\!#P&(mCl0F4Ԥ6F&H#H0I+4iPtevGB9X)nю &XU[%BSuQ`u`펒1|bZ5[D /Y)S+dRBhhسB_4?XPjft숦@p3 ݋6k90j'Hsa:^MMh4YQ$̚2DTgSDu#5[gI>DKtRLQh@ țI5u ZB(5/%r!㄁~ɫC8t@Tv@paxd DC%Apy*j6&3@8![bD |8!nj ZcI$Mj|c)>ɍ]̃=ɬ9hQG&UiSOF:id8/ſYvu4/pj[oƕ;n]GUdJPZV H:1ޕa a@H \I;RǗ7+ȎEg+7VSRG(CD@ ] `^^ zh]Mw ' ](,4z-f)*lQP 8Ma+KM .rfZDǎR^ax!ȥ &y&E0@^,#YyIV'ޥI^G~"k*H(V7d(X_uEȥsEc&T偊o#{FI H4dOGps}rx& dU!!2d $N  *@ЕP&CĦP !B8 7Hb |R#@RBAKg- "*.grI]>|0Gp &X31L08(`s&f$e"dI&ID2!T'(o$3 >>mDBiAO#t@FX=!y)Alɇ\ drU^n%X)L Ղf6A9 [ fR( b"ȡ"U=^fxxxPi}Fr&-Ng5Z4 Jfő+P4A3F tVn&@80cpo*%a/-+47Ȫ̱0(7kb UE@9fB ]LVVK<H}qjt>S * תkbPI@n$"D@]CtMK!4@sK]E :D:)el )!+?@$A(7 AHFh""!1k4Ϻ {W}Z(]&2)w>>$@۞, @sl uB#>ΎB GB$Dbk|A0ODg Wx>a@$H]D Ws*8-L0LԁD8&dG~ KɉXqdB NrEP}b>[n@baړ!w)0 pz8QbE/k"$(B"|>Kx}ҏ CSjAe` wAb@3! D+2aj rGӫ5"B\j؊J *h&XTeMr !WjR&ŗ<³fy,Ы!xaµ"bƶpF y^BPI)("4""B>*gj 4`p j~{("2!*):`^ @M g!ji}6 Q&J\"`阺Lw6)– `B`: E8^BxC)"8:D ,8D'6oI!;qI|!q$݉5vqT,i *G@qak4֨!B`b@ >ujc C(1U^$Q#M^"2n ~f~$6"#R$-'1&8r)(r i%q&i&3"Q :V0<'mr2 #!`Jngq )/)M&r+6 R$22^. v)ib-dj! ".- f++ϲ/326Xj$<}<H)ܶ=/2-21335s393=3A34Es4I4M4Q35Us5Y5]5a36es6i6m6q37us7y7}738s88839s9993:s:::3;s;;;3s>>>3?s???4@t@ @ @4AtAAA!4B%tB)B-B14C5tC9C=CA4DEtDIDMDQ4EUtEYE]Ea4FetFiFmFq4GutGyG}G4HtHHH4ItIII4JtJJJ4KtKKK4LtLɴLL4MtMٴMM4NtNNN4OtOOO5PuP P P5QuQQ!5RہQgJ,S/5S9#$SReTAuBD4ISQ5UyU%UVIVVm8q5WsWyu8}W3X7X{Xu7uYsY65Z.0Z5[1#5[5\ŕ6\\U] ]Tt\\5u^57U^__6`v` ` `vOaaa6>a%a/'vb#bbRc76#;r0@Ow*b5!vmw0rHBW)z,m7x ә/uB7$3Aywyy%`wyy7z+x-xOrWzrHŗ{o|rtvI|)C}}~T~~#7x 8|W2A)7뢂+5}ѩA&^! ,Co Ht!)C)`R Ki\GXFE7A R~H<| ͛8 J`ID%Rip@O@ӠL` σC[PlWm;W,5YԪPR$d `',a )ԧO0ԕRxI@M W DVCmw7iᐆ,aK+}蚇#$7PHS-$?-@P4z !GB!~{ -hɠF%J}On*P<j&eTP@pIMd@!t (TP_5Ė]E_v%Ԃbk ZAd٨Z#aE-%ѤR!^|JjSRUh@Qf+ ,hŕ#\;@qT$t "t BiDЦOd5+>Ԃ|ES:J)fAF*:D! q,Wd$'+ 2=2l,!z$T@ȰD"0>54? ѥs&5 TE?gM&'M__mݟSTcNVm YB]Pq^/M&w54/(O6I@`)āƅ5'kA8zU|PC(˞~gK|zR]X4TAڧMi ԃCԽA?!#A0tl=^-R)KX1z AX@ * с`s LXKJ@@uu>ǻTAg d!4Ԕ $aU4fg3|" hXCZr~(< .N5(q`vt H2~X6^p@(:|@ ǂyiC$BI$"2P 1!;47\e1) Ph4@M=T*ĵU!u вDH@h7hhr ¢!E@`.ˆ4`V+ RL 'QI% ORjd!OWa|@, Z Y '̧HE 0Z @@TPFv33rMAiB]0)y䩴c94HNcOP?7MtӘ$nEr_cFTJժZxq[C MJֲhU%<0Sd^XJ׺xͫ^׾)>QDx4?a(#oW'AH`=?^Ik =U{+]rB d@RzqrCcZktZр)Sm`a?u\A  E?K \j2V(X8T0/OC5MU_1_N!%\؄0`31/cs/P"sbGTcBb+2CX8a9aCPdPQ#s(cy7ϠP50yzE:6cc5d4 JCi`0\t ۡ!F>j P!zQa DF1` B&B{&9Ko U zH%*V fhP:j/   P٠dp @*pȱۥ z&BEf>).ȦprPCp?RBcD+ : ]A=K/4*ZnT0opk*^K)M ,i,2r1+YRa3|XK]p r(Yxo.pE&m /S2 2"By-.2rR,p4T'+A9D( xH C37H\7"K'133wX 7ƛ[ &!y/ vdZR-u!%*oHD91:3'YyPg7/<7BJR`0q :YZ1i9z]vP 0`4rEW k ~V C<$k e v#{ f )9ZL7\n`\Pu@e^x\F>0Q&c <==Cf~hjl&Tpr>t^٤YBPe08QkDQpD7a/[eA<q* AI1Tj26|>Qj* dN>1AA|AL]p]2j=o^ M[8@ pB*V@apa PUa@ꇺN`p@)1@پ !]dZh= y?D*ϟ|X.+Xc4bz c ]ƪ& zQ%=& +oj|0 €4X/0 ֢]o VV PvyVNh!c2ѫ 6%U# ǣ_EH7M"`wmK+q$hv a Ud| /€x -`!l [O`ż߷Bf,-䃹Be1 vE0F24!1?$nKnNNTw.o./l+#*xya  @$._" @ H36lXΒ+CH%;4LRRFe5tgA4StBƩ6%31YN JŒ^,̠Vy8[qp!D>@>|$RKg i?DUF\E,,4<8CDr(t!JCUH*! *С( #&<0HC"EL%-ctu`*֐;&7* 7PL46JȦQbآV%C12  d 4b!2%l% c.;`bLDˀ v ؝ \ 蹉\t׀ kkx˽<]4٪_, Quha 1FFGr.!ż !< S"$B@[ºHÀ’ `Hh7(: 4v1X!ɠ@:P0x<J HHA:x lʧH$DHʎH4."qLHb>li5 ? E)* >;@^?'CK +=$3J#%#˒@Ÿ, [҉`?|>lĵ5*!N1;r h_i,K"y'K`F^Z8 t2ȟHҧzzRp&v #`<1)"(+((=`5Z hm4}obM +*@q-,U(SUVU}-Z@PU-F9<j(* I",.銗경4! ?! IV"Љ@ q0X /cO_IҒ`-zvT[EpPݥűpKÓ1>}(s͓% ئ_q t; ӕ<: hR(^]9=sC^q];=7k^ 7 0}۳_h C ъሻ3z#Gr ]|Z/M U= k +e`\s?4Vcx>0ccV0e6SK8C3PaӔX%?bb&$`!fahI *\ Ĝ9L\.O}cN0$]U"y^Hng ȕ DIhN, ",ٚ! xDIc!~v  *J̚+o qN). z{>JZRPàXη;1\ϐhabGεE  ^!vGЉAQ꩹ yѩf )GQ]fi-ƌ ӗo5 Ď[?boӌwcaU/lll Vx r:I!HqCImȗa>KHsІċCK̄InbApbu^&J@ D#ԉ2W5<:R,uo, W6eoANC૑ >TL6O1h[d˜P.>SFeS~>ǵePVlpρk}tkRb`w^4H!Lkj'Ғvio_xka_׉* *vR-)hG0]pO(>)մ(w{'#ѹ;mR1׉Ǿ2*OE'Ob= 2f-Tʨ5`3NX(XKB֙Vpu%Tڭ-OuV02]) V(|lI$ĿLTJM`H|^9-'Ul L@X_hhhlgb奋3 1@geh0 1! `HK ~qЃgW,@03SӒT$&LqY7r)$An#윈53HPŃ^`x:w/ y{рk፰ h*d(yP:Jр`>QJ[UqE@&7 1{V /OXLkt*S'ߤzzXչ'S~0¿P<7s-#\hѧpdF=nʦvPA2 U@BDt \~]8 \A4P!4f@pyŤ-# ]@\@yD$OypH FGkc_t^AtZY11~"P0ka?]@U`PvS^BTP]d@ B@v7fdwYnU9'A,Z99r}?'|ZA&tYiBF֭xs֩gʙ&"TRpgY `h*q&Ы}tAZFjFP ¥@d"Z o`d*IEq}е&IE"?RO !$DIf )PESsIQn DQ Fo*?˨3ZV.e"tuqjd@vƷHd"Ĺ2JٸT@KCnY(R<'?x tX.A? \ \!H_]D:6kBh n LAb \x x`z b\%&aLPH8^0Ih*c{c)hs$یNQ-VeFd`ql"6gE]BkDg; ,/n0F MLU-1 cDfWb$s5ٴ7XqEH5F՗}2 #yViČŠS\d£QPd0msKg'c^הeD刀iěD@ũв.H6 5&>83BaD@⠲x#c )S:n L!HbgfGgdOz}lb-?6X6UF7Q /HH1!-lj0"P_Tސd3qATaeAyXfZ#P bD،C%ABhDXi .PxAAd?(?L\HU@Q DRd״@NMB@d VYL>>(` Jd?``0eʈtyJ>uIrQUWi ~ Hw@Pm(Hy@Ʌ,%ɓPH}yYFvAX |D@@{ I zt%aF+PǞHx@G@ a$YF Јzو%Y}G@l"@&~l i y|Q4H(K2QH5íLrxkXll wÊTIza`F#c6JhѭtLգ~IA B.NjBD ddaGBj E6KD: c@T?faoD] bƼѨB\9L]^N9=`UDWFD8C`AZHEc#,DQ"DiIhuMx֚hVg Hf\%i Q50?C`j\P\h51?hlGNX@ٝAAfENT!D2TC%$C%L-",;K]UB_A?DHF1D5t5hDB D ,@#P9 A&>O\@ABzhhPșP0ntd.|~M}$BhCL&hChv` k(hfgh+zzu(`F*mY'D!Ȑќ-z(a{$-$FX`lJ'A 5"H;IDIT@zԝ8d,D,8thX&ldG6ydR(jhKM*$-q RDN,J?̖TLU2Xe ĄXZeXYbp ʼ%Beef*kVagI_"h&&&Aq\xF9:ZRam\PddWVkssl~DKu\Qu@ h>z 8B-ȝ ؜Af,d0 el|H"dXIՊZ#dX@FNX@GXx@Ǟ,H+Jq ,ʂkpv kX⬐Y@NYe`ˑn#7z bV}Şخnd.rr`.ھ톙^PXnlr./QAL@NM\dԃHqL>Ȁ@8¦)eX2j@5BcvdALaEa̐A(~o*YF..#XC ưvư6o0#Sh\, &عHd@I vXP$.SiVn0w!Qe"DȄy F ͩ4B\0'4 al6U"B^(LC9A+BY@ԧ69TCAȠU+NNI9F*q?&́n0!A41nAgJ a"A4'~, 0?0 ݅%1hd "Uj*IlLy Iԗ -ڰgFL0ŷTc$P +e[Dcx?Lq?l|`\`8EZ%?0 _ПT*\M\L WI_-f4ĵ^\\Xf!A A C,M&72X ($䂶 3 L OKDާن ih7cƇ4Rt0]{]'2ɒǍ2Guv!0G $fyeG@}H|'^Fd\EDfo괈tx4lk(AȆ]ՉҼH@Y)nCNnH 5,@p{<ɟ447f_'IjLJ s Y6ҝ tՙjgHxJ*ENV Rw H.>)aF#n Yiji/ sIrx]|.iѼ4GA"G 6{ƟDluuwdsHAlz@+P#"45 D_kC!2]g`({D#bg%KlAO}4j%H{xrxO?UBRE?ith!Jc}g\!fx8@M? C 9$9d`\P8??CKTߗgjxB}?@LdQDKyL wɞ0@<_A&T!1,(!N AA A9dI'Cv '^Ta tA &<0ƒ 2t0 - 0 J0߆[@(`VB+ Z!7CiCF+2-T59Ap™71 Mi"l624AsP;pPLQZլaǖ=dA_N:$g}Aݺ7Demi|P\!nޥOOyxЉ`1xg?~}m0 '%AQ'0!>ҩHDŽ\.E8 w *_ rG݂>>L(D: 4P(c@(()r(iQD9ED`@N=\VUB @! zUFnk7Ȑqo.q,D e L6N0H=sl zP j +b" ؒh`z{EH%?hw\ne?]vܭ%VX+2ZG;\ʎa!0xCIK! Ꚃ0 G™(  qPE\?!uB.'C ٙ"sVAo?& kQ p."ps\Dg8aA!$R 8dN gi:x@A,Xd Xb=ELzF3",R@4DJbD/v-K-E!ASL h 'dHT3i!(0x|ڔ۩H( =^\y,e3E C8Ql@Fy?8Z%v2Ӽ) wT:S!? L HQt5!5t, >s+qxЭ)&0H (X8.\'J#;ApL9􆻂!_IT``=lK̡ H_ G?G>!ݣ ЇXq}J ~_5&?jJ|B1&,c"a[ RY(D=H{^7!pxɋW|<e`ă|WzY#[+XFlITKz(N$9f 5Q ‰Ygb_C>|L#I0Dƒ$^ 2LS$Q!`BG L5\a)(P,:pš@N?<`sAt75DUX 2C sBulPѲnx$I y x4! -sۦ|E< xpC mjPmϖd hs@$tJH~T)jGʞ6qJ_RL y 3p^n[dPj\VLd3FQ1$>OG"g?-ߐp s((a'!N(.:J]G)|;۔ I M̃XMhYmR(dg'p2Vi;$!Ir a?!d z$_l(Y!2O[F ?9!toȟ|8 >t&  hxUK& ,Sӭ;ufGu kM Bq0|nEHd_Z Egk@R Q-B}${xWčzD)-H7V -1{@hܕwǾ=` B n !  !kDJA ($QhCV $((6f:b%ք= 6$b: JV#+8#3"B-Ddd@*2~"6JI$Hl38$\%!: 4!"x njp X N n (/L:', "12ᨶ8ɦbt*<% '@>^6m7d`6.D nlLB j%b:`%!a n"hn ,jd* [V!Ԧ ;kOrB2 o H &!^aJWH@h H!KaADAyTA+>>b€T 'a~w "KKtZ0# "?GT u C)TXs!J"8⃘ +g,$&c "w0 (0HnA%2BN8CJ$H#c3,HCR8ު(t6b5|i%UUic*N$4%,123B`Z$Z`D.B0Vʊ6۸ X&Y[/$h;"N'hՑ 4 =jʭ#,""*(4i"p!hdZPT!$."\iP3$c&NL_h`&Ҫ" 7V0!V⣚$2$()ʫ0UꦰhD K"D,!|J QnB1 nR'DhM"g੫!(X"nj4qEpncq㯨>AkQJBn?m'@C?\on.%CO!$ݠS1M,!!Ddo˲{ "(|ev}$~y7-&!wA+XatP泚ȳAF*0KDdw$hGuf!POA@W#GnP_T`v5wMo~4&Uw!gc,2mKBb@x%nԔen&c U'&,OLŔ,8+-u7W'Y@@zL\2FΌ1!W Lr"#Q":maJNbApue84`4ȎdM8/ݘ׆#~,*=`v?6z`8XXC"fxWAW#UrӒAeE.lm MG iCH, c+", #^cU';:z:ty;p)`c-PũN&BEJ +K.ފN~Dnشc/<9::\ 卮t9ֈb^QTEܭv$ ^<79>Y>bAbQC[$DxAxhѐX Z` 5  aDP#FWmB + ȸZ8zΫ P:0R>jE!0j&&G7S>z<[c^8B>Ǐ?wDh@C,( wn x,6xy'v zA6zB|% " J "w8B~o/`(E [6.T0 u' K!ƻ}b׶c=R0t5$D@&*1R `["p+{t8*áP/Nd D"" qFB%B@/Ҋe\( "p60H #o͗ML$h:@#(lV0;":$W6:ѾatE"KLh8D9(p$|CtcO: 8~Bͫ9ޮ$\F<9Ʋd[9%gn@rA$A Gs bR A1v$$QK9==6cay rJ b.;e%щGIQ$NZZy}dCԍd9QrNY** Y0ڭڱ=ۭflR .CN\Z"@B T*> Z@!e8Ye3Ւ9g>)=eRmr&] >3A3nDB:ac$ !d N$:aC#Άm]83 A~T>f?F<%:M[p9WCP H!z @%7*S) S`beC0Avx+h(RG4 ^@k&$BO;d*N)"uul!Ԁ==ٞA*ހE "|PxP!d G_d@Jc66f` R @>TR1*U,S@;>+(su? Jp$tjA bOG$Pϱ @$ FD` b_ JL+ T*_+Z" D` <$vABZh14TI%5+BX(F9V_ԥBK0 LDh\!DHSJy/I P4<(+` G 2BP"LA"*)FZdʁ ,2@i:@n 0+ta$SZKb|mDC(PnO| 2p#˂u9_>LEj\g0mL=_Ea$gQW~]E! KMQ !Hqfი8߀Ɉ~S vFeAzYb-^S~eX14eHMie]fR|hL'Ue~?vYA- B-{&F2N .ZdE),(F)v$Zp(Vq2'¼6I.bQ8Ӧ*DΜtiABTg=Q?v[?m;يkE #9R4nv C&Z??n)*G,aK @ii!!gtJiRdܒA eKpR$?D?3 Oq%H p_$X$AvR(bNC?KS!8Uԃ9@s,`H HEGOuE&Jg,5bQ4v#KʌY~PABI͆ D LvAl{!,HQ12?4N Xgbpa>pR,FE2YDCGM&-O ܰIT11 _e0{A?Au`OE F2 $F*ٙД(#KXB(Rk (l0 ρH)*דJT$H@~' ˁ3=%;I$3I.gERLE8[M@JŽD5]b J>Y$fH2bƋ\F •u"-b@Jm A"A $oAEEVaL.SaYІIU\( "NZ,9+Q]g2"i  )A#8h %,2Y<.1lʈ.iJҘ0I]R,lTJժZW!G_YšVAD!KY꧜RƋh[ުu(::Z 8K'FTal8e?Ê9C9!2"2գ:vR^d@*f|N&E0/ցs@:yLаXQH6PEF%QdP)tdi?zָ2nPiT6Ђ #S# "9,*XeZ0J?}4˥bƤ@. 4I@Ha􏧂R\ف0f$rUZYֶE;-fA uVm7k%" Rb&{\P&-mK,/Hinn #͏w]ox(GE4Qy Pn+#k+x nfwqM"hZ#vGEQ}p8#O9x,nu!8(F(Lu_ yD.$+yea$ 81Q'<aeM$R6!z\rțVDB" ]L0av9XDd 8J.Ⱦ| ,bgءEҁT4+FR|H Ph>;ًji@[z0RjR{ke'.'~1X )HG]_(ZHlgBd)vEy}8`@ A5HU#8D/ O8]Ǯ1 -Bh8QdqW/X0X'2NkXTU!"ë@0%`$zu??p6B¤&[ b1\ FWlGGy

 c1=q@Q:U$ρW<`Urv7<Afs$T8UYQsXrZ VB70UP"8\' ?F|'E@~as8QZ6R$!ZxB]x[@}YU%# P@h&b!Q^ʸ$>|qπ&Wr+u"Q /83'!=Tt1t"2vt 7/o*X6 &  + RT qbOT`'w&+Vpbw!YwM (7,`^gdw'b*/| 1s" p/Lw2zEySs'&14F>&30|!e|zs1/2s(-`;)jӅh7`;6&zfP 3#6:c6UhM]CT6-2<4C>pY`9i6WЅH$3p3"eVvŰZWeW mI !He3"P;; -(*, u[qz./Q2&1U_&T9wpֱUkЕ#U9DVU?!'{#UZдł-2!.8Zj`U&gP0j)5` Dnl&o02P2 |  /f&lWC Ŷ;&yֻ[;RBPQ!D@9E{}}R^.Dždai\if09()Y'6f!Ze:0q WTvm{UJ h8iT'ljĶi0p ̀|cjs`'r`μ W7iiBfpI`j'Fbѩ|WQ͗iVm,hDhwp:: ppLqp]oցnQn=+#L. =R pVyV=Bg quXg_ u)qB  G(QTDG}4m)$`@ 4=taw}} 1n-u7 @ur4 5 r״awπh b qCz1=2muv֐+-,~7Ip)9_xa[ 7.)pyC?j0եMj |-sӧڬ]) "%0%3{*<ڕ:(!|/XH٧(1 &]7mJ֗Q+)Q BqĈ@`(rAa?G4HW!m}Q?8p8@!Zߡ!g#D6(ơ,1$n6PAqXZt-P:$A H9-.RDehӤLԷU"}ҁxJ!AXZXH*FOᵑU;%a[?JqZ%0bIwaV慾v,5umBfCd[qex%n%CtƸsJV9LT,^_xdg v{dP5=&2a))K*;i'xz'-W*n .ru@a-.O IOFRiŐrY`JaPw 2ˣH0V|eYyXb`LS\/]P)!I pJ  *pF/ო=|=02PiI3_:)n4`yəfqe\zse  vse f:+c639_ \ڐ)n3A:yQhѹ>ıdӞ7]ْˤ)\ iѝP$S BP9/f0A4n` :)64")1Hx㠏G&Kt"|E?j ^`z+炨"1)*'67ς4 XN`#1aѝ NqxQ4BaHχ |;= yHZ>?̺ `bdW'0`G4(9NˉQ+ .^u. h /p?ZU ]1 (.#ˈջ 󱞅02e2OL &% Kޕ' ) A04iuH 9e ,4 p  JCPh):s I ޔ `JxߨO 85@:ܼб(ia4rk: }H/qklW p{3)rk3Hf=g6*qQɒ\؞pbxPU8` 5ȁO`֚8H?>m&5uX^;d7 Yd#HQNqZYpm ⟟zAIؕ摩%м6,Q+@䈅w0<Ѭ%"^X <ԈDa^p nax&|paB4g#* ҈T(aK< P˸阀0> j9Ns肎Px8 Uhr1[CC(˃ eHƱfՂt0 iPᲟꢅ @3♮dlAncܿհ(jN?s ,bU8vHTM.qL+eEM=sD9 #ø |iQ0 hh 8:W ef稾~Eah&!>8?h@M,PpiXnba bUy=0xqRpL (f')rp] Xo̓令v%.p4V=9Qc8YcLQ=f&cWߵLG%I[Y@ [,Qa)5`nQjyJB`͟DZ-A#14fQ,_‘.pG4 {\'i%׊q ؀Yk}i  I[;/ mHL9es s@L^J4 aMКjѰѼpuΤ\+vaávmu1LpL>N4WxOtQڱAsv|PiIucx7P9!|xcLT!vEBF5HtwOy_yoy7F 9*~\_[@%k}!zQ\;^6>:с 3u"7zژ!ڊC(3;ì^:# z >ȣ{ z8BC=SzD'ȡ"SSERg/TGTEeTGH%IuՁ?PF[\Q},Qp.@`u_Uy=nvJ<f k;)xWX\h~%y&'KTDzuN1~(70))Z92e6qU sKE7DpGȿբa"aqaIdBW<'Pʃz!U`Nmc뢖Y oRtxpTH4=5뿭MrzTRY!+X[!h(lYLAa^z.z7^}"N hx_/7pCVR3T B CpRkS@:h!uS h`aһ)x ? x'Yx w[ o"a+ 8O:J_x%Pd!@[teUzLyt E!fHdAaxE!n-PnYfcVabXAvuY$fY5Weefvck)fe?^ma)Uj&Z%WXٚIve R zԢ ':n(ke%[5^mZ*NX sתCBNϪX]Xfu55*/ ̪ $N|2ܥ?FNAw]QoP ̪TstCSC#벾A4\-Co<TImD0T2iRXaT SUme`UMQь u\EgJ+#QG2XCA4^$ P.aWY'pESД! Lp?"$?|MGGM^hF{f}\u ?J`SW]B%lG}f4D6.y 7AߛH7ct"->(r!9g7C >OC(y !T浄|RfCLln5ehU^ `d/.6fLvf*g~|@s2y `@ u8 z=RsfT5|d>d G Y"ł~ӜeIlh 5Pmy 5"tz[0&\ PJ@ tcQ+h%AJ[TN"%hza{^rLsDYB/tP` c3}F҇-+0Kl||Axuc HYHF 5.0c0`^p##9JNVT FGplY%:n&$F^9b3l39j^b_C^R1Q.qO1fGW0!(  z#@F(=j' !(4{=$I V B@,݈9r2á9D("`:Һ!#X X6אhp6Vw?4CfxB\D`6sbҙg0dp ZrE t:ٿCt=:<;i4X 6 ,LG0}4d1ĬOj4xWQ,r8G04@D j Xm/_‚ @ h@4#*GæneBz@u!0HE >]q_Qyc'?^l>EPH2XEm|+X,+IBRFXS=KEO%U5TCܪI*L/fWC jRUe@@_kVQ[mD hQsfVMoUlHVeVkݛP (lv ? VXx\_|I[8_X9nz0|btX,+٦HƩZN9etT|u]mtnmH^w-&Fm-zZږ&홅4ëFL:Ԁ\frTXP?XjB/_*ٷjnznͣ>X >42C:þE(NٝsiWj<@d"Zgdn!"o*2/o*()4hCa4D)j&ćNH?܍ҴB^4q?4Q'eC{FilVsYlac+W-sƢdۣrQlsAs6DɞQrE(D Zs uz FhVɘz! m~)'#ثY0naC=Aٹ;BA bD)VxcF9vdH#I49q?u-.QCāX-b7[*ڇC5ziRK B&|!fZA#X:mZkٶun\sֵ{oG US ~IK%K" &$HӢfDP"NpEH`N$b("aPi@@`> /, ?RIGPiA )w׾rSB۸ůM'#7S_Rha* W  )he'5*HV"PSlتE"NȫQJ^25  MTԨTURU` :QP#4DM-T%B55ւ^(m X$h Y~Jt]q%@#ڂ$c^\m*UbXgU!5m`W%P;(4ja*xz@eA@*Z&fvS, ycҠ[ Cl$B R&^2E EE D0D-.a/kю:K¬,* *+t*\kBi/ܽ6!F)U& vpj A! -a*1Eԉ yb-a0 HʑFLc<AJXG4錵D#$, 0$B?@ A `RA( 9=8DsTԚ-h mA`3d=h͉@Zye4COD{4*O$MyROGD1 ,g3\WԴ%0PΣXU Yk`'JZW,UJ@,+6}ϬflIWB@v[Y5JDy!&Ubȷ!Լ:e'%JضvRN ԺϒfUZf7 +D9`1eX 2mbrDp[H =H'!4jlPp ] ع n|Cu4ߐbB11$H r t& CRX c8Jᨉ:YZ{bGmU(Aڸ76:} 90AS1JOj4%<1Vu2sS$G FiAդ'^-!!D'E?2B !b`:0 (8@-3G|V-\kI@u}p  jSFƷtlxGhۏHT9mk{u6:ȸ+9.AE`zh 7D$ %X N%6MB{ Ay1"+9q̕sh B oo1i^s9g^shH$IhC;m~[/)x u" (zAqCևC@ASI@o.OX yNt + ?$ HRO2 Yy QHu@` ! rEp&* !& 2@ + R *(Ckb炥( "^ tJNBw*G`q|zj'<,&42XkPk#h.c"slL6G 8Xl5Ւ!Xf "F'iJf5Rq"@)H@:f(n@c@l:g̪,#r(j8 d` b F[L*h`.C̪ X"!HIf!}B*e*#g: |!'Т<"NoD'*2~""*ze8FѰ,SѺGD{zL"HL`!KAlĄ^A"¶S(b F0~@#:^ w I!PM\ГD%@ Y bC/ !#($χDHG^T;OB +D:QkCOAƼO^(J!K!C?"$B4* a0.R$AD`4!ش4Nu찮  %&DA: [C$XϞ ,(!R bA J1J$g`L">+)TLMh(Gi0$fc2fm,T@@x+L?b ɛO.Nam4T`2ҮO5:Qw :9:g(AbEYn&Iw|cCe8Wye}uO tP0/X o_%[][_W\A\^c@ 5&& N8 'HI993:a:!S;a;Ǔ;S<`m=dܡZӎ3mT̰lD,=AHF'"NUB(CC"C$" S7# @(a*'D&@GGKQ e%RH 4VT0@JK$dNf6 .QfL`R:`/R7&8-6@:%*r,{k!pMro# [ ,C*&\ $Ȇ^slX6ǧ[B"($m* . 5e5!ebE:<@Jp6ѼEzZ1!L@i~$^ S@.RgTM[&7yB hdžrk̭4” H{ݨmt#$-:BJp9gڒƴp!D!p D Yi$H}YǙCZ##aSL6pDxٟY1k'lXfc#Z'+ڢ/Z)-3l" H45NHZMI#^설1a4D@6atOkj$ "Xt@5KχZ^2B<8r 'W b6 L#"*"@.U _ *V ("' q q  qCK!t`zAN5 x !&0 C/P$ Âx&UO|NON!0MhaTE4fO4cJcPa~EYA4CZpAHW\ G*Bsym`)[V D3bx%"p8Xm b B!"Xw;0鲥 )BǢ[ )Vf% W `PbScDb{D;J"g `"3Q,V~e#ܑ@2BnfL,*+*l*1#V##)%Iu.:D Ѽe]g "a2ڷVм"Ɇ>s\'E*%D+(V6e!s:ʺ5 =Hi [8+֮g4Q&i&vsbk@"# _z bz U,}YNJhFb\|mVG"OTEŽ=b1B'≖SCI356zy!L,!WΌ9Iɐj~LIcHN|AI iŜLQ腑UC2fƢW'9wM[>J(Hl>M%yS1^w;Lt-b@o~. c-YR~#BmI—8FYO| 7IP]DBu|PR|帕0fATȳIR2!O"Kc . DJ2@}5E Ea!&]ɉ hj"ElWEl*+T~X R@G0<:\C~>LhDTda (H(r!*ICH7 Xp$P OPH ƥ'IdI^4N]Cw!;tP_}ڃ $2V; |D Pn@l. "Eۆ zgCChz3-k߃~Xz ?2W*={ 籴~]WzjeQx} &C}t/vm Ɲka-"AL?Lt"]B(! ? A?>$ IRdZu!$U Ѱ Z h @I.D"BO/&RM1CBubLhu2XjbdB9hK Ty襘%E]s1Uyi`lve4PdU6Y!hYIND B*  AQ4 >^O 9:E&0EW A @vw2<ڧSnk@!xO.u`An>/H=*\2$xDODKtPq~Ol ݞ,=P :uP u@WQNǎz& P%p?28|TM~Co~ EVe_VM k \ƣCnV&ُ~'Ct[Úp Pi* =0=#+X(L WFppB2cԣaTBS #i. H"(L k%# Ĕk X̢.o})@2hL8mW &Q3%N ȣB  D.r!$#, R@CI@@>{'|`Pwi 2>M*(wQUO;=A%>"O P H1"Oc ?<,@q!H]LGnS(t(3fFB}.:! >#OLxBEtBABTaIa@ň p: #+-|bc#t9jUc&xCra-@B[vP ps.(@ydX'Uपjի`VAV@W[ P#JHA{R !IJ<2O ,б"?K%{XH4fx}k2S@2p-"qK26BJ Ӻ xHg?`XE2:0XޥD>Y\hǰܶg%ȾPV/6@ -B 0FćccЀ 9^SkJkQ@8!THKtd %y w6рh5K4>Oxcc;Ȭ@Fq]PEg! ii2W pp 7"AA(S/q3,mnranM!cX>Y?Dž? UApXmub: b)6q>a>/>8zSAW1hhix&2E#1@$.VrE|=jLB[a}2I } `Hd(rv?QL Q)XvA N !!L1i :!k,p&Ԃ_) E=Q*7#)@HWX]# Zdgm.Crarr  P/783.w $sEI/Hd wtt(ri@!dGS C?Bo7QwzDw{wtW2?3x vtRGFnxWiBv WBz {,E{w ڢ{{B|2qqhragx3= !l1=9;u3; n gJf2vBWpB:>cpi!po1<ΨÓx#x<i6q`l`&'12>( !Tm᱊ќ&@Q'7Ȝ @A "D*ɜ9$ DוsvPF<ڣ>f} @/ƀ2Ӏ+ v O XZo @䥿ʠB׀81) fYɥr:tZf!5%]f:0*pDaC0SyD:H4&MTMP M{NZ31 k~qɃE'K~"KߡO͵PԞNNPUxP¬>y%Y0]0JDZBEPOlѭ;Nʊ0u:0DQdI%%a|#`@,]pR11D6w33Pr,ɂ>.Tڂduy 2_peWKG B\Kg %`@1{WyEµY2.MDK>kjM[ovٖxꖘA5u/^sٖ5@s\b뵪<Çq -u+uv\];ZE^q^umK-{UI(s`CY,+o R`%G` $0` 7G'ef6j QEQ""O2)cR1@U?h/r3K&#j  >P0;xc,2!a6hc.־Wb.kK6;XAm3=g&MAt4p;;V4Vk""cATp:=wp)*1&7%$,GT2$&u0] pZ +9 %pd,gBm6\Fn3" b ;mq![ BDl) h) DŽM"mA @if2!!/"~owZ !cvh!6eja|# #37a~E!ӭ:_ y Ɍjfm8N-(!x3m k6(pO +j~CX:qs 0$&.)Chk2)c(W .=:Di2L#G/pW,q{Yόʵx3E>?nWˊ D@*N3B{'R-N\0ݬ rLϢ K C7qћMYɛ E8ـ!46뾺mKߙi"(-J $2m$zo ھœP JPB`7ѤK( ݀wݐp^ux Qz506,.ofѝ66g/::n!_MzSyde1͊D'ƴ#TOZd.I*Š zJMEM4moOqMsg߬ U Paf *^>- Ô Ezy%Q p"NR0a #aۨN{/pr-πG_@KU19vY BXAy46rOuߴ ׷嶁pF[T `lhA!P`… $X?Yg95B; ;J9Ŷ^2ۋ_#Es:kgKgеᓂ" LM:B|w$0O$H#( Z  pɾ(C6C ;ʤУ:H<2 @6 -@ ( 0@2o;'㰿BjpR23I1(ɜ6'!`(1\N,  =ӷ@tNQ{*Ns1 ldsK-JP==I(؀ # :၎ZT֮ZNk\<\ˎT> Ilj'i;k)K!nKaMn-W]ϭ,܏j:IZ4֣ymQ)xg ~5H^z%$z3ݒjRTݎBx]J_z +24!^[`x5@^j*#~C#TRX PaA$h>!ސ5& >ծn/)s#fЌ:q&M?,gtJtOm ZCPrޑ77Ur^=PH !;E>$%LNņ)*;t8.m PGz 8 M&uhHUZ \iTb`Dxoo[6"A N !LjU:Aq #e3&fd'32 Ûp)7: 8BL#(z r -i6nRv &"я4ib)I@sE*sH!$݁ro+YA%%ZÛC& HAɏĂ$ tбk3vX:VѬEK_ (fq\҅ Jv&[Ws/m&`83`b~qc {ApX"1T,'kt)/yt#,j'g5|4 #DGL8iBb԰dd*B0SWT ԰ř,h)D'OU\QD&R7 U>. $+VH0w*$ HvD*TgWI@A5Qq<,CUJ#4Ec%B٥9ܠ4EkhEi`Lsx\)?hEDш1-jg;\׸Enr^T&_N>dkҚ9]v׻r}xDQFdv;Hn;_׾o|%xք#~<`^.S-K.&K!?#I!Rza+ 0=R(YoSQT60n16VjIR<{GCu{tI"JGt,QǔoGMtDVr.{V `!%Crgz ?:w@^E+2Is&'yL'`bC1%\G;C팆YL0` 0' Y)ix֏O?2*Ѕ l?]v{Cm횸(LV66!<5SA԰mV m"6 Z5 `lV%Q,@Hv,dY@VЏY .;ɧRνM% 92P d  =-p/G,* *P?ﰍة.qTQ+/iJ gYAsPUH(pY ]'r*A  !,6]UJ%৐0@(P}BMh/,APM-BX+qaT88Xx(4B19k(p2?-yÏШaマ5y@4.4$!51p@[*8)\1 9[TŘYq XOs1Fd$ EӣЉGM>k$AnJpZH ȂdxG=Q*yi%h HHx%NFʤٞ/A i*p$pES܋gz"#\Q-ɀ"+ Z;Ƞ1 BIҭz-{$!0![S;` :ݨ"!J!5?ﰾگ PD 'ڛ=h ۰ ;=U"E IUTfS D0DR(JBlԪo$o |H[LXJ>˝dѤ|tީ ɤ#DT2%ܴM GZȮFДPH%AaڎUrqhDRA!I(:,>krLDB-1,C{QÍ"kOoO'|Q=l(-@~˜k(JlğM<)&H:Ԏ HQLs+1?  +Ѐ> % }PR ֨A!ɱC 4判 YRP')U= h 0T&B !=ȧ@0LԬIY-1zL;K?rg;5aJUID˞U1 Y]^U8Pp}\ZeefZRy1 Oqlm;U( ;rsEtV*ddI ɫxNNRD676/3H=6 ʠMS"$H0L` 1?P1AC҆& /+32s" !'¨ٔp^M+iHІ`8"0`= ڙL#0["+ %$OFi@Y3@2%c2P1#1kEP9BPLI*ltxhP(CayWx0ȅz P@HpH}ێ>8p ݍM;=Сd2~$jCAQ Y8 BF9 tVp\ _wt}%7(p~ޔŹйB^0иEޙۊ=,Ћm~^aa)DX5qPehaLᑾ( `db<( ٳ#ט %r!1M0VyJJ(Q\"y+h2.#83Bۋ` d 18<A ظ`É(RC!ZD9d[c@K3!>*1!KxؼHƉO. r@L><6̔J%<&+Gi>g08C W )H*z g^A|9F y)w6Qa@T?pPֽ 1P2s80i.2r&ѰiY؀֏ke\'у 9+4VmŨ6 L#Ž!Dx^BԙAGH߲ Q=I_pfp(z5I^_ C3$8 ({УO[jVHܽPFH `؅La`ͦ {{h &i5ae"S & Nlg<=K˃@1b}1n֓!CJsٛsÉB N_^hE"5`t?rN-EzMO `9#h≌)H~)28LARz suҠ_J W1U-I*6S:N}T.@APpl|pH[ܷD0/j͋duk:$1 Bح!@"5 0x*:kDJAG/(`j Snf&{rIEwIMC]D$HB"\0AB'4/oEȨ'8Sx*@Rg|3J #4SGu3200y&%a:ʃ$# 9uf y# <SӮY3SڥQ^E4nԸuE)NlJDEiG-ϖYgW5G!@&C[xׂ@w݂tB.x2D QAt۷^pX)֯'_, S1 0Uq@XLH_ ?Uhb*2j(oFp"8T٧`В>7mkDhZȆ_!I>T SAַN!zXTUI5*S U7* Q͕ zԽ'|EHF8u#J7j΃S=?-\9B:Yy'@ ",(IŎZ_Kn f <9i y)CH@W6`xU @/iq; )+IE1=G[ DςDhȀ>#xC%B#@$N"%Vb@TCxG3%!:C CLebY",Ƣ,΢ZDb Zu! vPQ"eԆC^i!-&2.#36#RX8 SB7ŔV*Gdp@PtùI iOrT^Qx@f8Qđ$<Ğ"- fHlI@ڣЏKBtDP-DİiTDCDQP@qO%}M8*@B@*CZ^@Ęx!  E04>Q` ]ϯ ikhPO+u|_EEN k.!j!h,O*KGN*3:YE\XRNF8S3 )Y]RqTE,1F5=.l( ϲCp e T0rF9alIO D4ID1V]`S„rDeE R2œ̮@8¡P WNA@f820_@(y@x@TIUXYKHF"d8DՈB5gstwx¤ègVTN+J|TU_4P7Ă>4 ^vV\!27=D$pHnG:?qA[ ?lq2|C{*VU wqXG$,(D[dq.Ԉ`DT/_j  tZdNl2`dEq 5Ns& D-g-@IL32S4Cs#vOFa!G=D- P@ vk¦"cN,| JL .y ;P^sS 7.@5 $C=,h^?\oRZ0pG Q<\^D{2@$䂔>03pN5H D84?:@AQPn42Ζ5taN=#, qIc f4UIJR4?@ ҋ%P`pI',P?#=rlD>4@XW@ЁIIZÍK!s`N&'v#̥阁0NW~mSA6 Ĥ,1GF& 4.&.EE%keZ&t@a֗F=@(o\ f#t%d[‘4"&,lA,lO~嫀g@x>?@\̢. X 8bŁR#*72៝/@ $@a})ϣ&8B^0-,88ʿ' O^[%J(`J*ٰ h9բPɀfv|63,c^8n1|$k:m޿ܽTV Y4Pe@x(`>!O_/LV4ꁹR[ I)e|A@,ɘqQ``Џ-/GŦn|bΗXi&_Th aH&Fހ@^I7}" )ez S͸RP@P0`啚& 3K'*`((+ɠx ; x|"2!! `H,;Hzˢ:rs sOj"<л K" *"Ԡ4#@Lsӈ*'N  ~MIJ*&$M4u*x*OUdTeG? 5/5@Nl TutNU4S >\KeM!3==LJ)'w4 ʀ@T+zU05vHزɨ$E&ڤU*i;e[Hyl >X餂 B#ҹOlC`3'@!Zj)ؖ2"-Y@የh8x)"`䎲ρ~w UnEԱplzj%ƹ^v+6'Z;Pp?i7)"C$2/[0<%b&D( wJB ]ªY0lK M0 ,EJC,#ci›zD8!)+QV8&8)UT#POnT@k# >s`5H9n n$5a/Š G| H!q0huD $yDA??M0 LCg Bƙ3$U#CFge#@&X /k2׸Ȇ3bTIX1 dGVVbIXIIP$@G# 0A$Z !6%x"|e ?L/+h@z@An$ !HHC H!1$Mh%L HB ptZDE\B, l|`9u a"B#c[$)?lZ JIӜ?jHNdm#H%tEd$  G.)d?  @=~ x*B҅Lt2[=K:R oh 2@29I%,bkX#] [lRh*eɴټ%"n.YVҧ9.92?u9EL: ^ĒV `mڈ]bIg ؽY ݬ^ ֨sZjT5bma{Uɷ4Y͉x"T0R(EG 谕X"i} bҘ@2syB& t$k*D,5U3Փ7Jc[D\O5?HY: Ej#E|Lb&۫5R,K- T"=D c`jRGԁ4]3UZKɱXQ&/T+ 'g6XMt&,K |Ixw/:ސ O$RrԀ,qY}[SK1:rt:i#$G؍!Q{$P 3Ѕ sdHC0ÙVD ޻&*|H!:~k>1$)#\QF,MlS9aG=r8T# IٿR"ҳl@]B':b7mSnx^p`O]j2U5)[xl3 V X!z 74.&I8`wu2z݁QA@6vg@<ٍpYx0!Up(Md=p>|Kp[J$ a3IY'T 76ƶ=L"@9_ԗD|T>& 㭗2](l7Nq=T3 ωKيҿe(h O*++*8ktbVo,+O.n@0EpIe" \CGbpa | OmKAvRm!zeP_ ^a p fp wp @g`0O u |m*,,P x</bPH@ T ,pOV!qqp$9L . Iqimq1uqy}1q1q1q1q1qɱ1qٱ1q1q2 r 2!r!!!!2"%r")"-"12#5r#9#=#A2$Er$I$M$Q2%Ur%Y%]%a2&er&i&m&q2'ur'y'}'2(r(((2)r)))2*r***2+r+++2,r,ɲ,,2-r-ٲ--2.r...2/r///30s0 0 031s111!32%s2)2-21335s393=3A34Es4I4M4Q35Us5Y5]5a36es6i6m6q37us7y_7e78s s993sCmG+:Q+b: s; ;s 3<r<<1=S:ۓ;8s,s)8;Ƈ?4@=Q@ ?@1A>SAAB,B14CAB9C=CA4DEtDIDMDQ %bEw!FetFi4R~f GuGw4Gw]E'FTFo PjHttH4JIpJIKKtoAnLLtMpL>bI4NQ?'-Oq:TPP]PNQO!uR)RIqHT1u615StSenaΔTGTQ$e;u6TNTUVQVWQ]FWWXKW[XX5YYUY/ %ujU1[kVMu0URU5]V5][5WN^WZa__-UXv`Vu! ,)p H*\xB ω"  /lj% @',':iPL6k?=uROTZ0B?=F%ta=*0Τ `3v ?hU:b2A_2#_RRT=Hbm DHR1 Ҙ,({& ē@gC ARHC JgLP2YAw,WNJ-ASP{Ę&/F1.bm) ? :PƸ5MoLgBT2Mu"XA BߤzMqTS0_^lG s 5 %!)]O`:K7%2wTH Rv@)\="e$ɡ $0@G:CC0!t\ΩtI Ӫ@Y3P ) SDl\;+E^.X$MWjXDJfgJtӼ3TJɱɣO$) +\a]{("8K)CٔA騁_ֺ-otÕnv m p)hT*U5:ЍtKZ .6)TkD6RpBh0A( G_#C,U ZAHU0 [$USqfA'7K~ ,A?0@܇I RXq@4FhX,taA-lEL NL /D25PbP"Gj'ȏBLEI1di :dS(KA,CVr ,Y@QFj A~8xA(@,HLRqME)^lM8pfMWP8_s t3~B') ?|=OtBp4QB$hM6mt #˫:,ib@?pn;vZK w`9XF P)T5B9 MW ފ,@>BT L *CDY&qV"P5!q .{{)NaI]UIe1NJ3`zoWd'^l9Dp^/K ` aXY*AY@t z%2>Y8K ^@K D"(x<q@dSrށXBPaQNUDe6v#W;6(HzRf4$mi$AY 0g?Lc, fXQe`•3a xFg0 @h7; 1!%[N OEwk6k6l7xkhQPu9fVn/R\2%0;;npn6 2H6 9(QoopMG 9qqr:_Z'1  C9@ (<^q "`ptA,R>!xhsArT?IJ q@BQ !Sb.fBB)@("%$4c9&(c!16c ^ 4BDWA#1 .^!-&*ZOwt\@SDC"&A4e1fsbeuE2vVWu /C+)Ţv8ō~C}e.zHy|g~ʇ卬GGwAՏ*O"0+d-ZuGYQdm8>/D2䈕:)_!J=vdKQ_"U!4R,5eFtP(Vo`Ǵ81Q r& T Fh50)q9edBlaYjVQKV!m&4ߦ:n%R.X@x6uvV5$PpӷI)w @)V=! &4)&,W&+)NRV?QV\S2T5ELH坹g0Q oV0r\4j, D2'Xp}A~aX  {N5oWCzHYW=4* 9T>+{塽;模MW{u*^:<ڣ>@ﱡBZFzHJLzu@* %!5&`^{`Ruv_=9`yELpYUA{q!a`sJ\I<[1@!!hevYr HZB$B3RBe@?":Gala~!V5tzDI@mN N`u"I' =a b$S~BzQۚp Px0veVH@G%f/ $q," lEiiWC  "q 1VQ T! Uڤp`p"0[*qQq%+&d gpll"p5"$ل6Pka 3ap\Ʋ1d` P `&v ' @ c0Ա dkd& \;6np Wa@[5n ~ fr $|tCsA("A'G$Bh.ّ`Yd*7WәN5stȒ-*7ZhSm(aLg!t=!zb rEc90BG+4_-C(sf[Ջм|ҰyqR%a'԰"@c&s<$vor^v{wQa\ 4z!ۺ}N92~E4ܰQ{C|75311]~6\*U̓H2"IU~'3q4}"}^q'L,00 51ƆM љK@j`m%C%b ~,\)U K8WZOc\1c;\QA8Uh!^}OJ}|8HAT*` [ B06G pjl *@ʴRv )gR3 *a&; 06&GlXI``)t%/s]0?U=z:j#alpDASr dʶa4@!tP vA#fARp@ 7qwϡs "qgRVo 9|#,'G "ϳNQ&C>bԆ`o>0C?bǚPd."2XJ<U{  e |˂˼hNBE~&RU۷)Ԅ!1!VYID/Y`4J3C ŸePUL'+aT;+u~Y~>9|CWAON{=2SPĮ2^\ &<~z%8\=0f$+c1*_*+WTz%kxbC'{%צñͧʾՅ>^~خ;*SAX^h_=ay]BED`Y!Ѫ]~2'Z6p Qf@01+czG9f^V=YU#Aq 8Qe<'a06*a] % =bXqTOe/**(@iM0wV01@A%v>!8=rf`?2=V/R ^p7K;H< Pog`#W `UPePG[{L;;m%1 (kokEfce&Gz[Te/ 0 y@o/O WoS`g?>OE*<RH%MDyRI# 2f1!@!Ν&[И%@9#LC8[IRPCR*eNIE*2Ր\cN>@\XHekpbUHW-b[)qҔ"5TM 'M+ "Cدp&L+~HV=S3ބ*O!Jhn$nKΎ($z$$Ia!8 X:*Y >HCZ{GB,o+)8KB#X0 p{?"|Dn89RH8$IdL 7?biE~:&rqljaYEC@C.y;j5pB) zKx6Q%p+Oji)0o,^ǒ)bgޖ)+R"ݧ*7IO'5NnO@+<@$~#nq{7I]$+ =GDbrVI($6 I0P>jH@LiZz$ߕ<" kL`;OHDXP8EXQwH:jtX# BZ3f\O qW$Z#yDt3$NxA XJZ%r$u DS^ç0*T*H*$R7d<IʑJ(NlI0,h'I$ۊHRq J׺̂]2` !` ]8 0e!~>T^[֖ ˈiD9@Od·򡥠h)!IpbpIiHZp9UM1, "L0,YH&%%Ʀ\@ zr<elz `'%)Ud/QݑSRկ,3 ~@ ѐ$ dG$:.{,P&{2}E,huYƁ[JJd?Qֵmle;[ֶmnu[ַnp;\׸Enr\6e=8DToX!Ab6:4 s8r$w[HM+`*܍Ӫ©Ȫ 5R)pP*a )O (3풏*O xتyP(! -3ː)(R5kН+Ј .,%tܐ4+,*Ȓ`ܬ37S0I2 BS,T,M>BJT8ԒH(@@GBBK%M PIOx]8j襔0ՒMXMQ:UZrw#Q?Q%ՑF^՛8I SȑXNU4ydլ` h i]jkVk<5 %W笘s3 /2  鹑WҐR0 #K20c*4 1ђ6 \(aXmJ_І3K@감Ȣ␯Ё M [X1- :h"\3XA k2 [(؉Wp25a:ڇ51Bl2E=٢9ի*<q[l4Eu?sFCNe[N8b7|WY g@U `Y jy i͒е( M`[i(pkpHQSiK \(2p 5Ak t"y#&[0q&07zQLY( Sd 6ܐ x\Pk UsΘ- { Sá;ӫܟ{۽ll ) r x; ( ;xTa(E򌌔 (;@8x4r@2; s-AӏiyS 黐 >(d19 FG@t# bAK;c<'(4jjKY zJ_&t0)  lr]\U nffhqfd $i!hkMg(x6X'H'Z}h6)(ܒЄJ6ɕi|)$%$ ͩ͵^;ra MVB-Duf$t ? n;HlAX_d%c KC$ET7YXpD' hpDjBІHiH$(CȀ!k''qe! iE8&@/A^Fü$:#nX1C 9F_5 %Ԃz%άV̽m J@,G.>ӂ+-?G#j\rfeNV咰H " Ԭ" X=mfmm:RGJ ?"LG*ȄHƐ jJsj/Jjk$y| ? f /qPo%2ڜÜF`F̑K!׏$ [p[)y҈g0fg$e@5 ҕ/*  q.s&F(wu2 g , HI [x|2(s846Ηe%rZ(  X1 Qu !#[(Zة% H *'Sѩi*Rѯ! Xp E nGeOIɬϪ!-=AYk?NtL2Һw /`w"S CG]ixָx(Aw([_T^ -RM ST n0 kVU}ߛ?QHcp!xjUV-  "놲O"`|k8V!7VauӠ ջVMד9IX?(VRoՏruuoWOۂ &PC4O0K/@4"bYeIٲY  98Ą- 3pX_hh7 x*7c_0tb>(_ڐX K`bYXsِ @11mP:' "UiI3Fg)t57` RE_DHR#&mG,i$ʔ$HL ʑb$,I9gL)ܩRֿ)%0៧>',B)%+%dB? y Pz HV $J7R`+T>$1A@`QHg ~`ð$L{5'DuA *DƪyP&I4HtӪ?5`#Aĸge+vVx?DpIAfi爰4D5D4 Z4LXa SӸQ4bI>AT#pP<`0L.ԣI(QŢE dQ,OGYJ!  Ih[AADADHY,@TxPNtF)KXAp$eiMrz3 *T*$5T`zM@IU@,Ř')SE (#hnx7d0TJKRKJTcWdL@@P 4K$ +0 _ eP:̤S*Z@#0H|"KK")LQ7YjiX3pk ;SI Oۘ>s)OrL>Y yقa͢#͒ =CEI&M}F!/LG--h74% M#IdrH}f0LF\DULDR+!xIq y~3iskDퟝF8;pq]TA@ LCчO$Dߓ$ )W+Ta+q+?Zs Zc!Ae!!4B/<xᭁC .F?(&,E ,Ɏ.L+֠$$# A*`|$2aTyHၧ("VG^+C?:D"Kh H@#FdEHR#)8b5`F\4"8BDb "DW}@1V7oT$1#HEq.L> + 1prIwERJdiPqf#(A8Y E,$0`P;j74Pko}SGbY@THjM?83g/-;lGP&Z %)SJD 夔`BX TcH imBXI, IF6V $R(3#,sׁ 0%2 ]$>JrdKd?Fo f\d? !Sڌqޑ$Om{2@A:ԣ~,4&1\hٴ*䩆jر: )o Iώӽv;n9%S҃C؛ /D+=SQV2/@B), F>P|7_O$~0: @ spP\@GA0Dp  H%Kd3娦1:"kJ j0%AD$ BWG{=uĈF7E(D%I\),9?E$A6KPQt"DDU(B%!ǽ%JHQ\NU&Lx@є 4ɈT?MIE $F:S8M?A4T`։"̋ӌAMDMHHA^ME$8mV]ACkԁ \(I0Dl(z9PThB2tJÎ"%!=HX!I\ĕDPC˖}_1!C&E"m7DM,ŖkVyH"Y%ŨF|U u&M̂‘dI&LnBI,Q ?0E0o1@grJGsBfTC@|wlD wJrńthX͍D}6JOJުےP,LƨK&Z K`>vTMęRebLtYa&_DCI YJ@\ x HPKĐyTW@Ee;FrQܲ5% @ TZ135W5_36+A͵ei?u^'ń DeD,$AޔVAJAVF (0IDab5$VY)&Q:lb4lV:dL\_I4K73H3|JLLD1hhD,BC ABȋD(h8TTDT\jxeQZcN -%II`DغLL0eyedĤLpa{^\I@M吲^B{K&LL<;`D+[Va'bphzI$M1_DI( Y]B1kfD(@BIzzc0匐/ Ђ)c2?LmP={'|dbp`@o2A|qbj<[yʙ!yZg OZF5HIbl|H|X`mI@P- {ƃKuT\6Ed(E.D5yF3ʃGDķ- ޴Ds}I8,A@,5-ӝS,>ЍCŒDE#E$=g?p^,<E(}D~EDДT l䓄"PlD!1!)E@Q"?C'"!F8bEdB 5X$psF %Ե L8Zk?WFN!`98 MdnL8HLii`YOY0(*!R4?ǂ`'2n&@J/rO[ؤ34Sdk Κa$%A2 'ύ)H4+4_Jsܢnݴ&wr{)~? /#Zs:!TH(! !R8=5.?'RޤmiО10D{A̠k"T'@HH:RP,RQRH)PMܰRdL~ru&@AD 'C@bL(P bHI 'APeU[:dKD@}CH*>$II 4&*C%3M2>8#JF懠iAs $,@I<Dz"{qN=@)u(L   ^JTBG*\b`0+c9NG(cqы佁p Z=bFQfd+4-҃L.B7}?B#y[p@!9O~y#XMW`=knR<1.6UM/-Çi $*V5[sH.Bkw59W7f= b=c"|x]T _ψ2W})`K4OP9&@M"cG B.k}3eE\a49D|K]7Hi|;YzH8: `p#b!#A8~!D0!N7:I0JHj D&Dp A:/@  @#n "+#:jJ0Ùe$"h"B` #`̯* !!bE' Er yb%-ְ΢znB&"z6HGx6(>c]Ұl/Hm+*D@,0 B5B&ptN2qT"Ԇ#79dɊi)b#%"=b#2*8(btd^1%m.8B(6@*/Zb3l "#H]i"#'f)d."; "b`&B @  C6A:P$3*"B2dCbK4P*B1`0KC C"cvN㕸#:j9ɧopd 98gK h BpL"+?f4E"Krd1"֤MDb!x/".Sn,aSAVΡ"BV%WaO"AVzU"-"*F|@d $:%"!X!`Or(Oc(˯et6 1)HBp:cj|a\84&.&F"2d\0""`nNs(kddN〢dֈX!(Zmft6:D`Z19T8S(/q9n8.F%L 2ʓXq8Er('p.5o,c.ݮa"c܍?'V- ) C]f$,a ԥdH8Ǩy.yӒZ$dt'd")twzgg"h7ܭvxz84bvx5LgCH !N d}!Ԁ4"zr:!`!k"2^"HMM/Z^"A!k&e\H0hh7'",d(F4S5uSCȏ0 `26"(@DHD:X#"t#0) bu38uWyW}W5X+96A% !@n/,"F)""$6!( ja$FuIp*\#Bx#o"@8 ZJ bL@p@@:@ !," F*a'\ʡ@ʡ&#"Y"LqḢb$ 2!2H!NV"LeKve JJ IC!N8g]?\c ,O f+Ե@@"'t ^XHQPzV"!B  "!VA:T . 涕*Y)BV`6Z|*]w( "x&f$(O:볆! r 4rQT,Yd"X!!f@DEP "n }PBtEVD@D"T`ĊĞz@aF ̪0nY;4M~GD\)qdC fMʁ$A?mN 斳`U#s 'P$9#4@4"8" "0\;gP5@c үCVc(4ڪLC3l#,#B$2-#-(6ݢ() @8D "@"$hbzb b`[#&Gu2r"L["zB5ppj("% b:`/D[*`KvB tt6FUC&BksCCstH[!2@vv04q2̂4T'bT}O"tu/zG&,z1`9z\*f2I58GScWu(j1(X((ys#x"("y*iL'Z,"`[G<8T;"X["PRJT"_a#6+& DwDb/-""Q]֭EC8E! ث"& ('%1'70 ~b+:"<"]7Y)BAbw2NR)bںEȭ'(*]D"_DHX"A"u"@!bnl:Y">'-/ :ZD~HJA0AګO.G|!"swZ%~?M]"c䰍#Z2%°&N">7N.!=+ %?%izz_Kz4>)vI{R3+"ɍgF]{GCQ=&4rb Q"H"臆z}G8zd.bpe.b.?qbހT@(_ NS'2ϑR7^Ew0L,kڼ3Ν<{ 4СD='F" BtJBVcz@U "t*3R2i1 8 >8iČ;~ ٯ y/̿C HXg|VIPhA*7_ʓ6D"@`4#`Kv5 V$ h=B˥A"f^IaԜqН#J`Eƀ Oǁ[pсbC IЗ@0>֌"C KuvH{pt L!4qNm")dd5a0G|@a3&C\FP1$B]Ơ[ ``w?U#@"xD*S?vv |$H0/4 /cyq S8 O @tI|ְ7?/`+ )m yЀtwg^f \":O?@T#/B)Tԉ MO/:.!pk ̐:Ћ Ş3?(-XZGHh*@% =Pce|Y@8)PR C].mpŐU,%aC#t}#X4zc$e^ځH!@x`hHAagh~XB"!$@' !=TPN A(*N(N]Y`dm _+2&tZl^RxЉ-tmLxV j+Vh (v`$Uxf^E6KR D 1nM:.;\{3 Ʉm[BlVa@Ֆ6z v]j-,®~B^š/kKfцj ZI2PM@ sm` B#4IAp <|?:6/Q8tg9婰|̒&DSD S PpzFDrE~6$N##".B<:1V++3 S}錓*R1Ђ.LP3!*CG^"vĊ` ҏH5B,INH '%rITDY=*iO?HiJ% Õ+c_Ӧ !6B,Q؈,zYPa[lu!DV5!t7X`(`Bt MH:RM]C! 6($J] F9PWvhFb",gΘ7нCڠ{yơ|ԐF-M>3 R:d2ęRQv[ To6!Yg21I .WbpdaKUYۥ8K;W;ﲵ{1w ~;I@^aXށQ@"Dg*Кd Y C/hJ bw;@>6R@Drsjo,ź݇> JiX \6FE)A1 QdPOPU1$"Q=1fDS !,%q$ 22$0ء1'߁t6 e $:+Q_ |E;X > fdA (_EDѡ!T$_x]!D ]!a_V%M=P.X1aP@PW7y7~ +~('b NR7$v"Q$fAGFp>uA=(5&+G-`@ )za 1h򒋻H2" D(g^&hV )*by`  by ?+IQy/@'Db."r5f d "!.2!12E9P{1Q7)9 !YP - p ;ՒQ1M:M E:M6i4SU}rm3C3`Eg% 8iOP>Γ^da4=C&92~T46:@?77C@Y"9x0MC2 }xV3\8CvQA?1'* $V? s#>9\13] PKaPy;08CӜWa;YP<t_w8oBZo]9]9ǝDBR?C#9`QS@7q7aA1a'!a3v$?1?4B nAWa<2;E A>Q6?|wv2Q5B)dz71J'eB>Xo tdGAZ&K Sl'3' 1h!/U:ml&btF pNՀ'V;+*Gkj4i`怐  yRDOm3N=dMvhHJdNnq1䣅1Y _ R s5*,Dc .>( p`sIEu*@b`OP*O(0sE4v&2W__Q&>POT b Ph"RCr]RC&H/E; X7V4ڀ6X5)036 , :%_a" 7 0YX5XXtP29K$e0@q"MC5M[ᚃJCaNL^15Fî^GZ]Ec@0v{_afy1f5[na]]9ZjY>j?ԸweY7eZ_^ ~"$'<)uW`35N1)`u[{{4t7\wmaR Ɂѱ]ab?S@@o f_f`qs%f:RK5(b'dP-&P9 ;=P7!d=-28%q:aL<& 96+7/C|@('F] ($4K7>7`uƼ77 5Yk<6Ynr>ʣHuuKCE /}ϔq[?2vE?tWwWCԢB}5C5[kWv@uuLՁP V_@XJ',[ y/@y`*P1Ɛy 0 P{ՃM؅} }[ aH (@tH4@,2 ڭ.}D㈚E~,2Bc2{E@~s~B6W ѽW wumLV! =!0bڀ$`HuQE$^x HJAލ`а`0QX)&5(%f@C&A1AQ+|2P0- qf5]848V|%,4QG.@RWÇ (|5Q0?pKT^P, Z)-8_|G  ` ` 2[d P)h(Ђ(}!%&0&>S,GA yCD) 0ݱ*r) p+}l}w.|]J$S q22`p(~b g3gg*`V0B;AZ4R 3rXV)]BtM V]=s:uh39,9?#3s6cdD8c6373d:C7X*O;}4g9c#j7eb=!9@00CCQ?q2Lw:Vά:$GPY)Z$P٣4-="c/u0QYyNsAР3TMn6/B_` j >q:Q!}CD]KU*>TuH(6w9z?q*NC5{G`|$bMR" Hejhm`$dhlEd*DHπ6rkoڍ&cKĿId|Ex8P"j p !E$ MTD!_ yn?x ?Bl1LV6&bzB =@;^Jb[*"*;Y-K$V%+R< B:p(&~+g_ȑ%d S$P/)& |9:,1ƿ !$oy8!F/ >CtӨSH"9-{y!F(ł ?"B$Co3 d d=*" *+Pg.b`'ivj[8 ȋ?:+C!`RDmQ)$ !"ɪ,)l Nʼh ?*.%JJB дbz l":1$1:ʺ z`Q iQ ёdf3v8a"1Dk.$A3@MQҙ$g9)a:wyNxs2AA{>Q4yOnMdhCeukU91J$ GZ?L(DDJ& hK]RT3iMmzdsIXOPD<8A//Qjp (ULvR "H$s I $H`WՁ8اHcU'$H%H,ӁxAōA. 0zZ-t a@A1!jH4:0MHCD v I@*a Pb?vt\+nX+C$8'Y"LDď&ItER5nzܦ?1:TC'gS$)D1xEIWvDT"B"UARΩ2A\0^`Mb? $?+^U@U&G" V\H@^9t13h_48F|{rO!97A`r`@QbBz  t0s! HD-R4,ȅ0A/=$X@q\B$ ƤnRLÄ5 Q&-[Dl `HB}N*3i@p#"@TFptI$ # @1@bg+pW(@@xĜL^Ym \x6c7I#lc1;xr훷K d-Gc ޷S1E@Ht[D~/@6FZU=@,I)PS \bHғGWz*q+4X璀# }e/uƼp,~#d>K攧t>yM/tӍBpvYr0>@B(.-JJ@[ѡH%_i$nHYƗӺI5cCN/xBz$ok|NV#< $3[/70fQ@k8h8 ыx)` pQ/X+Hbp)x i -@ p$K@ .Ȅ)]Iʺ"+:' qAI\ ڠL61HXp<\ @x֒@=Ȫ,(5=6  @YضM%/ݐ6`P< !`1Pe2Dr#Db < ( I m)<ɖ=q OE%.;b%/:pGrO #Rb`aٔܲ `7e4ltUIp/P>Z(x%*r9)ƻ8ZA+ٌ,! c#!ÑHÒ.pI7| X9:)8#@(C&YĐPĜIhMŰAC#(éغ`)ؘEP<у ! ; !̕C=XV؝) 낈̄ ԗ0k[21?l\hIp[f`ȿ?!`ݘ7NH<˟3 $2K+ RHAڮsx"" ; GYRـ0jG ;+/ȣO%r  H ü]ţ k(14PGSWU O K6j GAV-t(s^+|I$R* b%)QZ:_(LεXR#SQ:gY"/E5]"Ey1.2"}%IʼHr)k ҐP>݋)cHMc BMTL'>MLtU6)zrnKIUn NTZtɨdɀ:=TkTAK}GUbUe]VfmVg}Vhә t X@*90ԁԪډhH'hr "* 1OWggvw֋y#PUOjsx xu'sĬG#ui*iP̡ ni?<[ V [hCPn[v:ųr(#;"8P$*"S)Q ѐ@$S"0) m_g FGY pTs8V@Qz HHGtL5y %}ҁ"ny$yp5aMfnn45y|;ucd@Sa+ ~y4yYsS(Od5'ⰷw0z d:\{MJc(T S}\QTn0Gu0pΨ|F֒(,ޔ`^uE:Yē@:m9Ͽ}}}htH cBۡ*X ̐ʌ%U4C8ָ |jO&B1\C( ω" )H'2GIąD BP U&IA D@ t`BD00, TȿRuTQ) Oh4m\tڅ,߾~bsa @lMR@X} iҳD=tX]!QvGy8USe gq]TQ 0J1\r,wPj$HaTc~0VI"Q T )/a&8&Fh)Hr,9 #0NB`$paD!#$DK |)j B&n'D*"CF Pi@K"ta+iP$n*?XNh@7 Hd\q8WQ\D bfT2,"4*-PD"D%R)4YR̫xF"ώ6)̈4Ձ[azKdC[Y҅$-f@Q~F#n`@i[CWByx ]ʲXj{amVFׁs=/Ͳ0_dt&XPLƕҵEz*ȒF `A iCP!I rD,=.6-eDj'cG 2- +FYMb0F)}\$GI$Vb`HJ`%HaBl*x['e}bUv+  p1 i&W4XmlscM.s?4aGY3E{-KTU3N<X;ڣ`9O5Rj;Qge/UnȑA5wE}G@k1 H@Ѐg0-gr;c F A P6H)Qh- B3nTY j*& F 4(H  kXD +<'?`=!eEA NZT6߬//Vlb"{ioXI@$P 3Xr=熕3fLHڤD$fY)#Zxl: }3Fz$m+2P[>XeU?H,jG;OV<\//#ѸLCFPńdc(V6^67sp{ {&xHm{L+,V<R``%1K c<;<~lSg`%$8#3f JsC :h" R5bZ@0!0 Ec y=8D.cQK[>iHF@L#6Lz&\mRDm 4!ЀCFp :9up#*;Q\7[K(BK=.RlE؄@|bHLImŇȣ95Q ٔx ި!M; dXX| L P _$dިO R YR"P@/n.Q ӔF@s׌pa0:@ƪNUE:٧bŅ78Jv2oE(5ل:vJe P@>qTA \pWa)]|F ϴL X3Rh n*٘x_#KnWv[WB Y$_h 5U`3 t@@_/l!`-gGu CdHsTw֡-"D'fV?V:V N} ՎCGp&R\ELf\"uscZ-}]QXd3@@HQTPϢ!"c$ݐؐ , 5U 8ɋ% 9 _Q HR~?`(Vͼ;Rh(!@@'UVO"(D(a&r2 ]W:LZ[)5A?S\,)ƞO쏚a9侖h>7UWL?qHBͽH?i*ʩ&:JijHu* )ɰ ۪D?q_cTT|,BŬ MYңq8nt?J8͔胺DZ^G(:1ĊAXa>㛕>OZڥC^oѢp-|Bs-Dnf-ĭ $\Oͪ{aa)7?-;$X@ʔ!YtfL3iV 5 2KAUlq\ԋ՜R`!KyK7C[@OgCR-h%ܝC; uÿ_[D: ¿*}x ~*Cb(Bu"~Aÿ l b4BBbLYB&Uf5~yJ?.h !Ogҕo[{t'W>N=0G^}G^9V?3KV̭ O C$psɽ2pG ?+hx !^puܑ} ,7ya1([p>d)&r8q.0SI 8`h3>@B =D I'Ɇ1hѫb+HQD*dJ0 UXD qRXiH [(D| dgh %t?DhS B};_H7?ZJ܇h#j #iC9 _} z6<Ř\%ngzJ. %Y&< DWN^u璠 R3([i;?:`Zd%0R''g0)(E8jT&')ȁkXb-dC$0yFa'i Mvjpt&*&수8|M6:i']`*BPdHFtԂ**+A 5W{ǖk:{~H ^fאʂi%j3^n_ ߻;މA2^/(T! "tYEЊ RnXE a_yC00g@c`<@E-M>T:@0B l,8" N8" Hg^"٬Gb1 Fⴗ-Cm^.?4D-Pٓ^"`DžDZ@Z8,F0 N~[*:c<,,JӅ"E.ďRpj!|Hd@zFp%+|@YJa$` (v94a 3dA{C,LvOAzF=+|R8vG4Ԇ@)[J%hAlrɋb6*~BqcGԠ 0J7bcpa.82`Tox7bA-NKaADH*C!P.ؽ8]H ]ȡHHGS >NFcŵ!EY՜5{QSFH uyp(? 8,mm)c̄>qM#@Wz?2 4=; OQ{Е$/%@J^TW]O;idzJO?[B 3nnm_)$8PP&D.!A AhD|OSpL: 桬 >D,TKDDҦ>Ą  P uDr%%U`U8!T $ A "0SA0(ڜ>jZ!q8(ƫ p*A/$ vja%ľ%g!I<\X!,q!$@߰,^EfOp*fa b 5 b 6"-` e'JdBeP:x\]D[:z]‘,%&`@6A+T4@A~Ef^ t+,rҡl ;tR*@&B#9j 4 j"7֦dpn "mZ`R! A @(1nD *J ⬲kFd#J !lalfx'txg!̲ в i6²캰;XbN BB  <g g`  T|Bl@ j)bB3E)u)@lA)TV8Bʡ p ! pSq'M2InAZG h(n7 2h `\>#tM:lb<*8#"ě* @r0HkDZ)-Z(nx',C<%k"A+d •b4= )v i,b4"ЙBMcL fbIC_65&#(" R#c$2@tH1 ` $`֣Ec$/zG|gN+c;C=RO, 1+@,aI*hε0C"abRD-6I/YT R#>7&4Tu;P~aa*tV No T,-JPT; eBrY! `mZ 4Z&Tc ^@Th<@`D  @A 6"a"ju'Ǧ%@/[ +*df P'#Zy(<-94&6&SfeuD3e$ 49ֱ$A!a5ҡ6o 87A ^@vTd BV n fi % EnŜ j4S| e,7}`4 '! œjp"\~8\fslK܂e ".MqD0SMĢ w3 |;Ĩ)0b{voFDeּ2[( (` !(a:02oG&(QV!:ma+vK}0Sr_k$N)M>Um==ed/=F/%?kO,/FiOBB@ &` Ai ‰֊CȅH@-ܤAGD9CCUB MIi,@#=>JY 6(=,C/yd`lۧ%BpS` "X oo+oS7 7H&@r * PwDV^il:#$58~Ѫq?ORF#"U>h:&#4OVE,d$B0Lh :d.TY8TO?nA\2 (dHv/D PLB銷zԆof4;i0dNS{-[P6$b42+YCbFP{]{Lf ?Qj6ꤛg=[[HƢ[c\pAS!T + 2!6@H Bj潣~c qSFh!4Y<`YYfpF'@J PdPbr0B3"6֣b#6L^7I=* \bV ^x~,?b~Y x؄{ɛc^S:%Ҿ…#c/A%*%MNTԑxȕbC Cm=,BUR@&ʺLS$h4- 04$ h4Tɗb *V|:^h% f5$a;)`)j^92̩ B ~!hvkBb72lBj!<KTYRU>1;*&b:0*06 &V ", [L ( (Xx SJȿ7"+?>(Lt%ΰ _L&!Ȓn\s?7Z\B_  hˬ;ITd:@5@ Yj*0kRV $ځGe-ozwށ2ֽVZUK٭zPх7潚9 s?%*wSԿDܔ nV\'J1wj(^Ëϛ@`hJS)J > }HdKTC",D p!9x? J9%GD!nIhI$J FDr gtfVE=YfOy(g!i"HoEDQ#&@|&F`yOlIkMpWiz?LhUpV41l RA7"$@Wd"D@ xOI9N6 'aȢ T@t@U<Үl=b:!I%{NZnt -feqK;h^b9gkdhkj1LX+DQ\lZ\暗+ A@@c\ljqd!r2PG ?I\{ǥ}ArHpRbXNl ^: 4"Q;Bj8Rmp5%[mfVDy37Qh5NFrIxHwCZ&Qo>P"Uu"$$*nj`@o$PTX?sPBN2DQVvp4U鿰~HcI Q[O5TgEJ'@dB2:k0 CX`aP]q|%C2Xj|g6y!j'DdBL"ՙ3*g:i9R`nD;W8%?6l4BP$t,ϲƝX&hA aG-b P>0EbulzިX1 HQ( K%HI<~Z90`5~ # XCzI#\#Z~THWҖ0ް dۻ2k#,zwHMRdy@KȀ3mɓXͪVծzh.a0zs"D+b o%Px`DPt!*|͐^X VtD LPV9)i D%ʠpcycxCH~@R1?LS!`IS )"̍.+n+r5XL%o36@$@YU8l3a-5V^Sz@[\-%E[Pt q PpU"P j .XԈH҇ڴQ C%DdHpiH԰&jHԠE" 1?$ϐ)E P:ełۤڷP [j!@@ oZ?N ;m!I )b%K-2(D@"Y 2q Aʴ `*1P`Ii/H-h%5_FCgHc!֨1 Z2; $1;HKHJsBnZ!2f&"P@j⑅∓ ҂${H-{dK`3P(@0\y `TIΣ ,Rm8aPQ+Әx9Fa"%x|dz Qp2 ƫ*H.;P.a< ,{,IU (<9fCz[৩nq` P Y%@mPzfTyDPG 58_S#oI6HBZ-8Z Pzjy :LBs!RUb; h=ՂThɣb/WB4@2DŽ>)ǫQ ^Y)F7 ʉqgg$8Gfe;qC`d7Q7d$UGDk T%BjffFaf$%gjkjhQjtQej3ddC358jPiSFDlq 5Tgf{FNkqAD2]F5XU@ɊܸD%LAB(12j6DfK8xAIH :>!G*N(CL#Zg{BGA! 4ą84s=DW Dk(DaS `R$AF-kYkDf"CzgY:A' HF=HdPJ!}b/jA|}F)yG`%hvJ ZJy;Gz/Z,;'0|}`0!,otGJUD-2\Ǥ~J@ 4HG9c:U"4{:,9m"2ON"!$1rT}pA2Q:ZUQla 5SR 暯83USՃ%@?RW.ڰ9." w۱ c[9YCV 86W!p)S&&%YIqE'"8+12o!|"Y7( 7DZhZqC+y[b2e%_`:pXdʅjf[_A<ޕ pBu9KPⅸSbK"B+IŵՅg+]EvWk(a *ͳ.a*t^@He p J:n Gv 2qi$0.@e`WVͻ>TV8`3=a3jw}Ȑ{PKnvY}!jaW4ѼgXAh%p$=Q B0Q+vY%9$: VP  @!yW[@ۡhk"9R# ^y/uҋ(N&[2BY)Z‰u&g&^H8+&Xޛ ,@ ]b:hPfgݳM%Vba)%I>rA5 ! BDAعU;!ђ!qwC0zm01tY:VMLǃ2/97 d$*&3S-I,5yYxWh Uz)Qk(rG3 4N2ԂoǝTM7G gA;̨G7vP;ۺ0" 9=_)4Nn4aڭN,P]Rf C7y;mݛ1!! qB$99Da TzF=y>HJ}AZmJ/:fk͠4(e_uMm_ktB &Aˢ[?Ml}*-C=0pb=]%N$20L jϚ5e( M}4GjGדY燎QOUzzKRWQn|ǧR1KgHe?0Yxs0O-pQ~@E~W~C~% LZRӔ&|IJGO_*5:nE 5z\sz!y T.O,YsuLM'/45DblQCUPS̿OҟڿoT:ai%WNgJ~e=(T;xS7/HOG E _D0(?"  3RǦ:@$$( b2a!@]ּ9L*$=?) J.fPb=mCj%+W @Al!2䟊a"cvIl_ :FZ͟8/ *`j:.NB H|.g-0A]7\@WA0!z)叧ׅWE8+yZ [P8EAQ5xGV"CB2. 9̐ p %lA |A(.d L"??#ϼ#c1|DH0(i %6 "X!;h #0  rr.y4QEb(0ٜ,p .Ȇ{  @6:XubUR"[#۵!6D%W" <@S:4 *S *ڳ$LSq?ܫv5Z:`H0USL7ZQ~X{u Q>N%عa+ 4:*k`@L\袉vo;6 i;6ɮv{f K)p! 6;{ ֎>('"ެ>J H@G(l o?~`Uݳ Ȅ\"3\OGj57@薭/Y< +ui!?HvoQ}>zCR@@bQT[R Mj MF}U__?e:jTHK*!I((%y.?`& 3vi0 H@ gF@"P 7= "^tXP@:o)A BP0 9!v?|.] @41b.H` g!S QQ-BWH] $tQXb jHFZGl$Zh@A$d`zE&6 !=ICHĊlW%"WWɽ@d: Q( >?`s , JlDq3)it:` X;ȴnf*sTH+*״xn0cO$ΝBYkRD3*VԒRS*Bh+`YհPhUR)N:xrH;|4j?=•\ LF\SG3CzD07.i xV* He0c? !4@dCr?1lXD:2%+BFDF~KzUbUu?@"[`i|.m2? *hJp |Pjhhr|fkw43hE4:-֭kVw5A7@h@ΗUķCk?"~; _ i-) HT Hޔd څaQC@ (fDjv37%Pll׳e$Nq@Z>@c>4eFsc{6' Y *2E3{aJwe9몉*UEi5fN#u uF Y==XڢLü:LBgw]cqφv=mj?;}xEb3 I5BQݸ4Ӯfvjw-aĎf2:N`p7vjC geqX);5qwWx!R6f4Q"}umK%̼k4?.Q H"!E5D! r3.9GD懰,ٙ]Ё1Dh9h"0 0`qn5^#i&фS v'^R $B2Aeg 0ߐU#z^&/:2!nQ] Qra \ 6 i3҇r؇sѰ1c^ X. 2_Z a^X+ҝiHz ?e;\ r6vvf@D6 ے.Jy 4 7p4[؎ $@ LwtLHrthU9!RH%-Б퓒ݡ Sa "T a7#289yp-\ Xh4~8~ @hU8([ Fq!9ZVqxP#W;r1(F"yHp xQ90\&1)E'CjyyL Cw -):kiMy@s%z*i  ^B=DѕB ^i3Q9[G1 maF;Ix) Ps;C@ۖ@@D! G6*Spp%$9DCw:I IʱB)J:ʆHJ!"!A15P"<ʯd lH9%8E8%XQ᪶Z\xCkE BxҞr3T{+«^:~"")?) ;7[񚠡k0Aӟ,@ @MWt!pft_@-aJҠ\ QY[l0 y59aH(*En`5lCXx8shkhO*#`*Z4Xx4̃#'YOHHEIHp pC ~P5%?_q#82-;% ŘC <:CЪz HKj,K(#$]R$%R#u ʒ=H@HN+-xM_b#7Y%&x+H+bVkD1j ex]э=.)L Mn2:p\DIŌH+%PX:%ilj+l=@컖 [ ix dx-!dHia R X9`hh0t܎Xfx(hZH@1 XȰ ;M;(3@Vx 9ayA[DoAjE_Г0[3<kO6]eP+!U@ _{XU3n 1 L) m8}D5}gD"pWD]"Fɀ~aR9n%nV"8HX hxvc1lB pњA(T Hx `d,RKiC kƃ Fx}n i^ Y2g}E/(JtFH"dt> LH tMyHs1lw6XK9V ‘'>hPUs 8wPt_wË 3ja*T(X @:ˈ0 2O%k.ȁśJVaS|KӁ#Ĕ>><UԠn ?*IHiΦGU ;&YCjxj˖BSׄMW$ <`oPRcNwni{vtxjm>n|nېtM4a2KZ ۱LΆxdvKbOxg:1^ϘnP~08UAs0ݥ$:K8Ȅ4phH<´!5PM1:_N@ "ߢwW `mZԋ&%B (8!o&JqI 4ISq ,U)  " ?Gg@( tD ,#7EP(?!zP8PPn `jP$R$,⽨R,0Áj5.+R0hT̨ {gӮm6FA7"E]tx@sgظCڥO/]U^`v+["#~CAx]\=~itxAwo=aQUQtr1~"x {v{=P|xUToU'5Q)zǁAwE@iI\&uvF7[k{\yFs7)wթНxG xQKt Z?2:(FF$$Cٱ@ʙ4lBdD@dUZ,e4L/I#X,u O 4Ȓ!E^OmRY*֜sD&^1r^ bydjgiAѨ k/g-K.M+A)/R@G ѹkqYP1SQ쪕|\"@$T]M+tP]DHB`RCvj^drBˠ"y SO 0 <-PM-4g d+--)4 iBaAjB|#OrAb@P )۶nϔo1 A { VtE<<$Ka4hȭ,|cT2Qd` F!&v/3$,n ސLDe։T!'r.׀@5qUhL#@&>9^AuH>2!FfOO:yiь x=~<%*SU!*֮8B:^P#Gd{ t |&4)iRS.j ,"G˄Aq=-17? !(t/|(D#*щRe TţE eDRQ, -"C6Yz RZ'I@ɩ\sa_RiArD=Xy48#`T"2ԃ4d$xI2D VrAKv Č1GxBr-g m6%7Ep0 "0 eIL@&QBY86Aۖ'2 hOXK BCpL!"Z!!a ?6KFx!mDtjxH#>D]3T ʷvC7ٽ ސ` Fu  -T,pHS! )bB=Ϊf3]&B@l ުC-dqVYȭNH$!YIyI6$"،<9H? t  vD`/1ecPiG֨v9gm1 C)74< (1؁ 0"p8WM= dm*QK5z+(T@$ d <7!np+಩|x4I<+D9t#?f}h+xx  det3ݧk=tpO(zZ @ ,DE ׈ G{tFtm8Dli@Pa \}2J[݈qxA;^o,:* 9 pT8y !I*`>7=`CPNӱ<@qyxo^)GIAHע*ԛ9/CP?Bo#Y `$9e]+_t?d PmOSj?*E63ܸJ ;W5P M#/ $@n-4 @G};c1??q{3g cpP_ ^%IUd[IC]UŊa&|`=6TU!4( $@0AA(:"%e(`Q !C lalEQBJrD)0BERdFp`G8A ́F `g-df%"0@A`a6 Ădaھ8FAĂF`&Ah&>9ݖddÀp$x@{, 5`S+= XleodiA,y@ hʅ@\YAlEđHݞ5 IXҐtH|$z,x D4Ňofy]Ev& Q'FI4߁HWP8yTP ƓqmBdBWxxD@M&DADBB@X C:D?X05 E.Ď *xQ*PPiSA@X!yƘANA;!!c\ Nc!!/:"$i*%XEjxb|(bF-+BDۀC..&/#A#AN2*#5#6B#M#6*753_J]Qp@OdB|~TEiTLHL]u0 xP:}KV=Lbp ) `G DBmA-mَ-z? uXŅ dPC ^$}2DK @К,4Eq\t,@7l*-(8(nx- 箅"&#U1m$͇ .S1R"ep r{%.V^/+|bApȀ\;h#X@5 AXf/ƯZB4C.c9Z HϦSү/07p<Tk^Qp\IG]l0=VAdivUWga??ȁBz)HyVeDrnAleA-GQP)@cRI_?q?HAQS`TUB,#p)A((qAt-]]$a&dCBc b1c @`f *5<@ha!k:2b$j(8e`6$ +^iA%t@gMtpt9Wk3 ZOadFATld՜Êm܆iBF>(݆?j ?EX !n\(B4FL 4AB(ܐP;8ҙ>MYy;b:x-?(C:|B=@1C>:`-g/A-Ukb?DZ V_ L.X+CuN@tO%t+X+Z+t4Os++jb_Â-%"f+L<ĵ;$2.C=h6 8CB5\5IwAw8Cgʝ읝ihq!@(lP@]pF/[3ƑHq,OuTDXEqH@H|k] ~BCPhwgXHkwWZd@8l^dw[ o }?(@]E,ZPwEI^FHa@@s,]d}sP x<AhToȆ] ܁ڻ4@X?$dW˧ԅV(UGL݈JML|LMB2T54 NtUaE|V|YA7[c "l&`rc@DP]%A?ХL \IZm_?@HG'ĠC B  Hƿ C" B?E $11 "WH&/$i$&&JK,=$"%*SCkեS-J;lYgBZEl.vħc| @\h!G\ܱcQl>:1G=/@5U+O#<[=6nf7[Z$nփ+;"Fc+o-HbpQE-hq %5Ll0^Bo8CFNjj wc1Ln&`2*ގ30猁ah 2O{͠1IMndt3%fЈQt%'4Q`,P0h^T+6}K`JDO+Le.e=gFcX3)?oлQNĠca'$_f=x863܅zѱHD2PE$tQh[.HDIhz",c023,0N.2k/d.(4$ɒꉕz 4": C.2-&88 ̩j@P1HA54=C/&FH"ËB/4.>: 46x%SSdrTA_LJHLKDF3%25 TV .de)\XDUW1|4afovQ,F6[]AL[dƢA&@ L 9P3loFZ w,/kv1 f&qІ@'$hebB  6e,M LUQ Z)hqάuZ'"@oӆaӆn^m`eee Zy Fܡtd.^@q\pޑΑ\puJGҕ^0{6uX09','6Cp:ky$@,&vv>:3xh%<hid`~J`qs#J@u0h{Y8)`DH-+4 $Q? `"Pg*{ʁN ̀v4{5odУ!P$u@   ?W<Pj@D0C* jEP(AAJV*mA%G$X@ ?$;@.biKv@A w@DB 8AAAPJ)R[p"q|a>j n~  mHtoN'%4JAXVe,+Z0}bھe1@X%Qw԰v05 &ߐ؇S擂%8^  n;K8Z.ĦT'tɊ# -$i&X/%;Q*| 'TfY4_2%Pn*:ĵr[Šuݼn@4n~%^ sR(C<gzKcR Ǖo~LAF`'L [ΰ7|43\) "DDHm:xDW2F1J㚸Τ6؍iu^@+V12cg–.d-10P"@1JԂ!wIz4,hԸ<i|1?ԱGd!(2"洧5NԆpO @@i 8AvTTE(g@`A; P( 2OYg}*A7\{*)8HɟFy 5*@0 !Qm e8]pHE  5^[y.(#5,.$@Ox"@_I /] ~JA(9܉dE V0 z+ւ [G׵2LԩoBF Ќaȴ x @R[ARUF#^ILD湖s Ud INZ%Yb>Qv=K\jղ)_˲i$=hxkVKϒ, @T2x-iL,.W`~IjU$?(~D^;azR0`/G0f#wJzeoWsIt.FS`P5]rN5 Y5[{`e78[a6DfS^&`\R<,Xds7$}44+H>v-0H_* }kc08pQma`r񓆺D'S r?1EՖdS!$>qrp(bQS%'`s')!stC:tB H>5tt)Q Qguku$0onvH qc .Gw$pG_sf!A1&"L'p3Ag9BpjVi4`I| gH,#J6ylXpG*0BD{RSy=IB?y"s]S)${1zӒG)A)Py2-3N F 0)g")qFs0" t9YMa M5ď!mfnQP0E_Nt !TvZahg#Y"M1BY!2Ω6pH^8qc@6bx 3fxa0SZ:;Mw7q@4ӂQIƈa+3lC#,q59X"6M6FJФ ix<`V|s"mZ&vZ[_Y!D7cA+dH$dtvKc1+v`y/9Dzq^::ocJ̵Kւq^!Ht1Ҳ ^a Zz ړO$zګ*Asp%%7@dOMK0c@cڭAF*Cz ]L6vb.b0cji گ'$5-"ܩ؜=evSrAb3*&@N`"a0Ka0 a0b `1&7,p#a03$qg(C?A q: 3jCnp;{џ+bp.:mlV!6R!R=;;0ufjuffga׹h7H0u?j``mfhrSyqĐY:e#`+K;9bG \>S; @v ΋o LP+ jG*нл>|0 h `;u+2K44'́0,QL"#3s[6OG~BL+++Ej.7(+/NHdI.q*qP'}H7H|)/R g\1 JEBIS=")b^l+`ZZx"p[u#0bN0!"Ą,r4ZzG)3s$q8_-Ciz^JSk3wR1SFKJ|>J(\C>sʿ+4,a7Gh 5<ԦG&3jl\̿ 2`ͱ !@+sZWjYCøŒ;[Xq #*϶x ܺY;[ G#2"p(CU\ ϳ͛@ @X Qlk  q)Ql}(MKM;`M͓s`7) SY65#AH!2 MS;;|x†B2 K̓^[nrC;MN̾%[E}z""\I-OAcyۢ0Zd5-wY36c*VpR{-eAXb!PL#p:?̹*mZyBZVleîuI+I[iNtXY^>J`ɜZ\.O$` O^ "?vgw8qp)ѷL!(4PmP !K w\AeT.JN P a"0_/P?b?b!1` )vw Q * GS+Yj:Eij4 %gff $e/_?aohoe/)'г:{ NS fQ9j7fN⛀CXNӷJo Z,.Ȇ:-,r[,Kvp o q}7  W)MgпR`/{j8ATJ_j8?N'BI P@aI`hj*-WQ_>? &R5{р/wU4P+cR}[or0>Na)|2'Zh8!@@CpAv; p>aAm f g.'q/"NNO,~jAo[C\?_} _ ! $)*p Hx: *? t \RHAX6`DR7bK)s 7 q+7&9 2D(Kr &\3PEe+;34=s WA  JJPЬs>'eRK/IH4 - ݠ>u8` 'Vq+=#5-PYCTʼn5 ϒFeNUjM.g_h]muVRT \((IR@cZ街wKd:SYk3!Q7)\*`Juj–xRCpqŷ>zzwh/a] JrL t`W;V銌p"K j^o~ Stq)4c0 <_Õk>(cv谁1Hc buƛ+yꭒ_鱶^aS8r􀊁vKꁍ@#)dɳ xPPV!ԓbI¾?+r2$E#g# I> AR"B( g 89^H| Wķ#(qPR/ R bW?&,LN BQ 8E[H' @zf. 5W@EB`/|.Ğ $^p9$ _05UStx+4"k):.=thP@@lI1;i;)醍r+ݵ8M מi"дW|$MhAJDjMt#6>Y|CA8n6;H֌Qn6RC6>đ1<.e2) p!%IY oV0|h B8@?z gU?`<8&Y':ǽ:]i!Q!8'C L xB0wjbI zQkG3jPB,8P*v1;Xh8B6| \e68`IN`1P P?@8&Q,0Z:@qϕF&$0H`8ug'7%`vkʹ S6FN8p; AJD:P6Xd `/-FFdo3!4.n%HH ȑS{H)DAq'ЁΏm&FO>O'_sr{؏)@٠5J  X3 &&É p@  Sq! PXadTH `+ T @I\0I򉡔C^ӌ`"ĵd8)MГ"('} 6J1X6䘀 ʣȮ`"qҞ+IN!͠!1Y99˲FF84IJ62/T/̍x(%9%Uj28A 8` T:T U1\',H0PI#PB[% 9;:(ғ3/h}M`m$؆uX=!M ׇ-?KMm=KX5Ym}/h`'HQTG;d: Q uYk4qٟٞڟ=lX=ZHIZ )C 5w7=X"ppEn3s `S> \0@=qÉ %upthX6Y0 pPus+uYu~80XE0ljghp LHX_(ݔlGÅ9(H9A5쑘pqބ^`@:=^OX,``iTpV1;ى  kH6gػ#nnPEh`c3qr}E|xp˘ 1,E!X8< 0ͫa pa xxz{߃sxe`P{zP+Ve?UbjG5 8$ 5$?bՃc<.0$? #DDJ1͘ Ab Z&,d (ӱAq : LNd @~q 6 T$I 8ш@> tcRv=-qyxF 5yNfp ) ޸8 qeWBJ3 1JNiYI{f8ew=HU UaG%nW=ıYYfOYhP50i(#n4QHFP{^f_S`FnEĊ@p GZR sƉ&/r#u[k:\(k >ӌ F~SglFFDiQhމs,:%;{p{-.R@*} ȻM3`T\38MYJȂ`P(ހ+!E8+@\K ta&K:HƉ"ܡІ%2 Ҧr %Ph౜c(0!DHIjrh0F(LrLІlPL!X D L4'KI2eACJ)ieM(\ 8dDB9VG%XTJ"A2U%1(yW3hZ%P =@MR>$6Bq##+?qy61Cx/ (jQN^AtD}A*\n4b(‰%(P&nJh)r@I_YˡtL(qޒ9 ;H+ GŲۓŦNsj8&`kv μ ⯾\*2)jy룊@yQHSkp Ϡ*'jv;V{;sxbʶl v̶g`Ղ 6rՃ20Q Vjm.T%gc#xyhPy.#:tg\CYcya. cQ9S-hVHzB:D&X 4 l*Z--DKIjn#TͱvCn-VH|a"v(1HtHiAI2gҬi&Μ:w'РB-j6x0R.ucTY/nG]2YSA? <]?! Y RSK!MT74D Q`AA!2S nXFV >k`$ ajs+W$ 5Ӣf4gN"!K."oE#*uXkfP es[9iX!I2Q5p?I N-DmD?-Q4D"tV*$Z/%yD8!D 'Ջ|CI0&?TBM h @ 8!Mj?j* 1LA|RoS3H04 /#DA 9W?ĝc=T2 *?z8ꈊ?Q䌦3 Z#/`?dfA&lAD@Bb))f@SxPDkC L \OnJ2661bt)5,xC8 C"xZ@AH1-X'-A#=L,ET )@@Tz8@)dCPG-RN净4퐋U `!LA\ mrAM "sNipBpBZ#^dO7MdQQj1g!$Ad6DQ?JICKoSDOSū67-Pa_*tWY?&XvppQkhe?-dOdk(ESBXi%b:9I^&ı)*@ *?'2H'U ^HF1Ծ RPx4D (t&K B…hbj ɉCfI\Qp4 3C$0D 09.&ZFȜ^+?`[HPp9Ā""e iEb@ 耕 ;8BA$ Hf\5 B&(%Ău0*TS }F)j"5HD]"| u0 -:!NeC ЬM$tSCG2)8^0I,H7)XR*Д4RVBb^V6沇%>2rV@ ,ZKY5..;#zEKLx;ew !/K|D!,F٥ሲY@JWy"%iZ%Dek!rmm\"0r[3VԵ)nꪱr(QIq*R7'bp M Cj!.F6i+IC1 nE RD)`b&?ES*x-"{_?sGn,'Ag9me!?3  [C. \\"ȉL #F8IPrWAʂأ\q:BUV Dr D)!gO.`poR ZF ԉ`g4k`cK?J 0Qw\7@הlbMDBա9Ȯ5potAd @eSL0kq[~QH]- oz\_/nV͵CjL#9椅4Xlyyf8Ѓ.t$ ]H^3Vje"KD>t ]NmD[?;N8*w&R "XW*LC1 9UNšDKxC<3.+*UoYjT`byEo'm^k&nַ=/ ~F)O 6< x1IDr!0¢ zģ$j]45 h&PA@GjA,?EEDqfDY98/q5B:pQ4XElᕈp8A,ʘLPDb`A`E|DC4Be$$)i@m,BFA\f BHeyՌqBhAm~LD-DaԝŖ'w\EDg,d Ň]W&̜na1fUĖJZ-!RWǐJ|DH%KDrUȜ8ieUf@+@nL\P^)ۿDLjZCjIJFhLmY7U`}X`AV ŒM?ΫA!dufOLipׇD1qD]X.-:tC8B 85?RɒE8B=pQ:|qF)+ԤA@a(2OhVYI!D8=$5 \::* 2E׹/'2نer }N뙌EVhVIax@E '3A\\R 2:3;'cri 8nᄫaZą]H~}4[,`'PD?iE)DRAGL9J1?Se<"&0@#B*T t1>m|BBO .iL,~hgBj* +(|=0InX%q `=2pLB=h().++ Cn:,|@B7 CA,C7c-9|s<,ql1!7 8# l(>G㫂=,J,+QxЀ7s'JA-)tf bpNDO/ ~xwX%ʬ/3w[(?؁8sA &AA 2=-!A͜ġ't?BDTjSA9@I/ɗGpEXp埆IX WX5F80@Y% PzDgG _BL:D QT)(!H"_Lu"D#GGG`\('W({@@)A4%!"I u8pV?jeE [$aǖޢh jkZgG>}: 5\`yw[@;@?ۭߎTuu<< š?<пrï8&o/>+Bz X.JCkz , Eު0ߨK2I4dRbaf J$R-C҃'0+IM-#J LPJEQ X. D3ECRȦ$=aF(ua<ԣ A D+Ԣģzx SkWѤA < ƥ&,<: 8V, %CDqeT .Js{Vr魗K%* ^Q"C×#0D Q&&' QGbUArjGsZϱhe!hV%rpVeuGFIhghe埙9X>SvAi`T~(_tCnSTMPyw՚%\.#Co0!0|c>J7 덭<(Sx7I̵w=b((XU`4y 5vтۀy60aBӂ'u΄D(aJ0Aj!{{Ii输^b .2^ \)QJUhɣ\ )SH2IpgrCЂLCI` ]SxLJmn7s &) }ٛa< לg==6"^{55AP. ):CM|'Ű T.< PQxM(?rR5]"[:a? ,BgR hyF(p NrwTj5 h?.4(AE0aJH.fJq*p@VK"hX^N:t/ P*du 2(9>0 j+Ԥz"x^.´jP V!؄T` $8HJ/UQ"O`0yq]LUJE  -dVT@vX?0(`+`Ԡr^% >~ÿGGP`Ewl Or'Wn&Ic{K1+7l#PtjXQ%` 5(wuQO^E$@?ҷB]FWqm!hEG`;%ȚW< 8ٲ@ X؎kħ `B *g#+y\@$#Ew^h`}@@vP6,M, =P >a\wkJ0$L$DEZh=z9)H<4DHA`c9f?)':@M\FpQ>0$AC"m<w9BmtbB#=7t#V@ ;^Pڤ(" LDDp8H0ЂMJj+,N.⭺@ Q",B$dbAqO/B!B% %$v%|NlaN %/sB̈́I-/^/yR: -n(1 -`cSvs|NyFgy-,6Bu*>x(P̅y4$,8@Σd`?J -6eCCi;f&@gJ)Af/I=#֓6dyhĀfX;T`0n)ihԊJc?nS>(?\J޲rW~c8"`Ҳ56D ׈D jFȱHجÝ98" FnMh$7 -6iNRKB:)']XWWc.3ӮYKrh-YU,pU™?Y-.ܙ^X?qK*HhE讞naQQe{-NjM^L%HՂAp"Z"\澎U=ej`&KT/4wuO%9тFkvo 4rulFv&ϼDopI\af%У7/e)t8|sm Xx+-g_\r]+QIn-:-:wp ajJzwr\0rѢ N02 %l!^b<*D0U׌ko>@)D/Le  -KZ &S $j6b*(*A[- I8vWɖa d#.(-[<;NпBBaJO:~7&(@Qb9&h#K疅Hh¯%8Bx;Hwg\(@TB谩.闒o:cN>(λ-@ҡgN ;])RX7G?\HeD[JR|)=u^]_IQ(Z@T"A̩,ݡI!T5z]aw.8Iaj@TՁ%aJuPZ.w' Ic-N.WD?jL=&$F?տ%" ( aX aahB:@PhQ"tXcă#QH:0pbc9/EJD h",z1j_P ?RJ1h\F?D$8Q4_̔H?P'oʼtWdtI%M%I*DƮBWR ~1FE AY` G'?ik6Tm ]"@D*Zlxpn .-@,RCɇZʎhk D-!* !? L PXWX@ZY@DtS2 >@ӏVZ}%]Yfŕ?+@1Lud2! PdRdD-IB ĤåX?hF@@%P{TlV-ѕIFp$Fr QiiXxF%3@VAڎ iPAt"bPUȪ+0@@wElAӳ9JQKkBP]t=AE|,TA\wl @Ę{/cV|$pt+h*_WZT!nj|(4k>+0;|YR[-'"qW$$ TRЮ4{|z  ,-lѧm ] QF vEڢ,if-mpmFtxUœ㭭j?DxDrwYQrMFlSԁ ݻ.@ݎ]u~/?%VQ@$``x6f i '}{5ʽsG4PLc3I0KxOU#r@ XPXrq(@dwQ*$"W9DFq 4h"dxT%HcJt/p@xAi",d/4xMedg{-ש}Өv,n% 4$sD`7DP /!\緈!@E!b=H |PG >HLH%G\'AEz 5""dH1R 0,bDPWBDzQ"XBI+]&+Q ` :7H 蠙Ga 3#fv >2miGP_D"ÈЀ FIPh9eD,40luQ(85"[U9N,7.x9Ȋ]FD^}˥hNoT9 NӬwq`yH<"ROW wɪTs]zZ"K8NW3hT,HE%t*J Ĵw3+ZBPJ1*,hLjȑ=)jW2PCJLZ%*G0 afj @WD#"Mө,F)mra ;@,"b("N6",b+(IHFC*S bHia)m^D PHyQc-7&[xW BPjY'"磈|'@[ xO-ΰ%9R@Y:a  j^@Cas$R[R>j!ۗB$hM{!MΝe " x"E&U4-( R &( ,1t9KݞE׬ (SSdÈEf6Y !xӴՁ4!0H5mNs $A"bhN zKt8YduJV(R XnQyƇtޙw7rA?6<p&۞O6֗K.3_j[~n:iKH$Cu6XIc]Ij5\W_E־Ezp{nAy%RQG#,X*PK=zHGj#"{ o" 9('y]G!2JV|,oݭt"OeZF:e+lE&4H&J2T#UH.1c?N -RʂSPIk\E' V+!7ȉ } z?f%Q> lT<^0f"5}Opɑ &NTt+ xhQI""yG#!#4A.iT/-P 4 2^;BTbl3W3Vc3mheV>#;7WoUQBP F zmtPQ+.IEr01-LX:ho-%E,&V0H'lҚNB#8rp$$WMQ JҥXXne1[ICK" L(J[.0YyoRaڰ7zS WJZ!d\e abpl}lE(ʞ`qOE'Hy,!2[y!^`A=t7c`1<&y'-TQ>B<-op?)$ WO2ՠO1 $Rgs;I f?>cŇk>=k_P `PIw]s xa 7kRX8M%9!&iq&'DziYjSU8tǕQ!Q9Q;P$ckFtPag%!&gyqK2+þAAPqf!wZU<| a;OG'lüaa#V:+nZe9SSu+S-cEgVDӧz9mT m #lQgV)Wd A}p(Nʁupc`d 58_XyWo o ;)AjK簹 g9pLʠqar#&\qgd s{ʫ*T#n?Ō|0KdC֕Vu46s\e 4$,VEs1ѳҊ;,LۥkլۜLTW"1 @!TzN -QQTF@2 1GGFa7vux[u( qyX@QPu[6]q \e( Ɛ } jQ!6 %EЂAwt!qwM!m J $YP *`(W] $]]PX]')LP~}8a LC+4WV|#{tB  2v]5 0sS -4 ;}&fM%a$ `%('ɣт`) 5ら (!p!2$A!+ :/Ͻy`Q0/c { ˩-M CE ` %Zr@Qh 0A  Ԑ  z J4y /fS1w3ͬ#mUplܰ`֠vP CG2&-yƹ*Y1i<К`A;$unO DQ۰] .;g}9MJ&p 4( #0}+`Xq v @1@pG&C N@$O N w-45v6g}r3:%{!y'%ΰ>WIQD&!YE7O_ [Ivy-K,* څ uEh:IPZo&LL a<;ڱY>jqciwrCm@ i$tFc=,E" Ku(hC%NE bA< 䟆!Ud B×>!B" >@_N!ًPC+ĩ{*P|[SR A>+ ;z䈌!nYtcZek4>]NVp "^5ۡc41!IX/+dMo "u!:H`oLSɷ8sA1m "i$&ì  !!MhÃ&! R: $h#ZFHq ےT6V*:4Ör"r&KGFEɰHq3 R3Õ><7I,y3C/%:@q܂ɒW2V8CkX g\JeFeqnr RŞs ֠yB0Yp0!!2nSR [#@Y; j^8tJM%)@i^J~$>4Hpa_]YJ0`<̂<+H@(`Nn͂ y rT6b8ڡ0e˄!Ȅ2j:(B Ӣʠ8%5 y`#")k ʂlx$(Q,X ]3ۥ}l͢+zi1v1ŵRJ!m=$O OuM +b(wyI?q`z{2|x7 R"c9 GM7SGPޠ pĥb@*0"J.n,Yr!?0C@Xte F'AP|AjX#C0҃dCp R^W3qC,fQ[bE0QP1$ 8iĄ0DFB"sI|!BBN/g>sNXr=Bs0&c z%Bɐh "s:dlZv! t`?Pr lF$ I#HF' ` peQX EtP LFЃC0+R $y6<Bvh%'$:Tt"" x(H$JGQ@Xa "A`H p#t<?_D@K (q6H"*£APǪSuHVa"PA%P-`X ~lB-0 a'CaM -4 Q!Bi)ەN!:AGMr>B 8A&$H [ ^!^v6fvX,ĥB"p5 (B'Ҥ PHck@tRߑ@H~Ul $10M"?xBR`*]07`{ӀTRFHc.R@ 6ao T`ʇ+p!@萜U`$#O08T^X/*@d%$/pf_@'^or0#MRITG}Ɓ%PR&$S- HDٙ!".jNuv=kBZ# nkᎡ3YzC.4gU>"iZ7B eq8F(j ع(?h *7CI[_TWVH0[w 'c\T x("J`JJ=EП9$m>7웉£Hقo(|`|r$ "?o $L@P. hP?pOpD[AcNp4JZ?J 43 33Xa<076p(9` `0 X 4 %쉟@8 AJ5 7X l0Ȉ4ZI x @4A$L3&0CkC 9;P=ԉ !Ћ)CaˆAAғK 9b 0HLȄ#Z[@֘ ڠN<&ʓ:X d*DhĿ>?3jХD8+0@T4$H$ ˆ @;pb XG !zĕ@C4s($+ҡ~K!ٷgѓ H.)8zWX\{?C3Q9 Pe`2=DӇ:XzK{I8:WySk@X"$p-[(NPIb Z<,{spP03aПU(4z&@X :Ȑ?fMЁPȈhV?(sB5)5YHXPF PQAY{(|}(hVl6niRrHh!D1\:k .U #Z`PVH:5PPJJ(ٙhuXn@R.:,7*Å 5XPS5gp,ٮ pAtY]·x8)ۇX^ḨSI֡y5 Iz-hĊryȤ/p[r۵=5"q!!Q(ȱpא0"3tSSAAA4);s̨/܈ 0 Ѐyx{ E,CC!/N5` /;`x  z ^c1`_J9 4"(=EC(M4PC 383!ѝD0..ۀ0%M洛ȴ3K LòJ! 0fkTBҴTRׁSaX LH.% 1sݝdt(]y3F!O()7 㩠7"Y!!6q:D!+06⃠wiX2`^D1dəCZ{8qز V[9hd`eQsxaeS31S[p7pUp9q!PJHu'pJs ; H'a4<=Spanl\sIH?뼈YvŇ=w?OGNA=>(9O興vP=qudhhS یMB(xAA{4 3]'u#[x8(s&Op}dP' c +2 !lm >+Q hm3 FL֚ 6D8gp:>ފ(dHXEFV#h_^.\u9UeaI|c8oeI66e(5%Zc(fDFbfLjl pM<5MMyɴ \dJ3ydP/ozd9#ILH#a0c31ܚ"Ht(6 ssdYeFj#Or1<9c"-6L.驛^ PO$[IP&XAQuQϾMHԢ)G>}wURpg?w=J9%q- Nv{J ?:!wVGwϙX14.X䀟xxx'R+.AՐtl x. U5P ::?LI0&BCp֯j=Փ|EUx`e*0>W,YCсiìd e;B2g("Pz"hVXj]ioms:q@?tPI&X bL'`K~xCoVV{iz,ʒe8~0sЇF|JLC>H-&0wղjJDߞcBK$+);ʎZ5-m/)."=Ba۞p5ۿ;9$X ڏt}LZZOMO?0kx*"P)UEi0>r?yz@/Xę2ʝ"f qȿ+HYQ2eƑ*h@*r=u@5Zv-[ _%?V@A6ܕՙS~q D(@4sĬqʑ"tP ! !o<3ǶKWz 6!%?D7sD~T0-ܫsxug;P` $%?n;6ݨ/6SuTo*$J$@=V`kSoRtKKul!RbD=f3"G2؅P!4BNB_!eG@NN$O?_4S[N9Y?PQ J K& u"wdANhB ӏJL 9B9 ETJztEBD4iDFL*z:+ *ګJ|0ϩI-BσR"ҙ3 N:,ԀF䌨|* Zѕn)EnY-T|`a[UK=5KZ2GrD {mITdY=tq*̓t ?/ èlP4",rDPv $XoDXG("BI!9Ń|yA5Yy*'OEQ>qEzT t ?D0 )d:W @C+?gD 6@ǖ?/)  tX^j)WbdK Z20 0IyF /D\4h`9+! FQ !7Bs"GĠx(NJ8DBF_!W] >W!BCcJ~Z4*DOl^RX "St\iکb=P%S2TA` 1*@[EFDڴıDx$0cĈ/s !:D 8A?|bJ|Gs(80?}ˤ0AL"2J]*SԧTr/k %UED&,dkV ={@VD4k:Wlkd!a fAшn%UD.wK%4׽'WHȿn5iY,AXC#ⰜQ,hN9ʱfQI.Eq"5R!Jlx%gϦTQLPd\flRC4%^]V HPL/JɬL%]SbTV tuIbW*T;HUf!( D@eCh, >BIbPUu~WZ 0X&Qg ۞ Xz2cRS#<5nKD 9S' ʖ3dFЌGPJ]7Mw]hI9 /^\@/,d0+la3G p |.Pٺa{P.6S(klXF L X \K#}'C9ɍ2l+ccɩF\Fn\O nCV&AvjG9$$ 0栒i {@Ō!"AHY*kF%HC8ev~4Y4C 0H!1JwŝO% lk]zM0? ]l8y #m~#?J=RPŏV_tDo#:Ef޳DD`8!C5!ֽq*{{TD`EBa ( X| C?vUء_ WrRnC  Zq+$,hJ` $K] a/p@߂?l !1 "0VHD.S«)^d$$+,H]%BQ@$d ܟ#h @J谉Ջ:lkMEz*ruV B$ xW~1O*~1c< ;1O):1 b,'ڧKl&s(8g PxrLELrP@aJ@l H P@d hlplH@Dǭ~_ED4D@pqL .]FSEpjxDl[- ex_X'׈IG4GNLPdGcF0rftF< eQx)Tjx ɏtƚ 4ѐQ@y?s(YHd׋QP,֚0Wה!@cE\ !pVp SP<*CI@NJE ??ƒ䂪b˩$ɹ0D C+L܂\ PqJtB8v N$@ -$D0 T T)DBB4@؜@DIS]tST7VHa8.iYb還YR($VHOKKN#@̉YؖE& ÃXHĖ}J`M&EB`BKxND?P,Nt@ @\I@<tZS%l¸KBɽR(Nؘl,I8L@'JD2Ec3ϳ%@HFR GN%eM/aD  @@ PqM`qPD GŠFi]@!`hɚ(a ȖxRx@(w`$wAH`g4a9`W 'oЈL PR|gzژUHנY,\҃$J)@, CL+,Yepmm:ZlJD(j^Q`VDȥ&DrЩItLְ29Q1Ď4OffjV閆 Yf/FnFOC99D,L-V?CJİ="0vΤ!c!ˤԩ¤AD#56B̶8EveWHp,@dŭĩDED9]=eqx\JʤLxcDK*0@T¤A%,5D)TX\^'L yH *$CT F?TĤ)_A( "KphDLN\#KH] G'c8N'( ]L 3bTL52%> $C D $ `Ș#ȁ^XV$C<'  J"m|Jϛ_xaTGHKU@St gGp x@hP|Ѕ r,l`7 MwFxVWtߊ?4quCI'M_eqUQ+`ٖD i֪=hn4@ HRt p| EgtFwGEr}0Z 4bџ GbHZ%J TEw* >(fQNP"Tp-|-|b Vw bJ[PֽboDCP KIKNB0DD 䂱D#5:+\c62_-W&L:I{o@/!P;'P`g獉y ^xA-N YG4aVyq8b#6"OQj儢dp>a" \vDI'P4E\J ?_$C?Tو"#"REtJl9Bڠ̙/Kl9 ~kR/UvwىĝcFHdS?8+L}rT aܮĭU7(VTM[8d<^s2Qdk#7;-͍J8HoNX d q ].C֏l* i0l̆&>8HlWHIGPmbV,~'>ŎX7DL(dGFZ,>r?|}ҜnT4@A "LbD)V"ˆ<< Ǎ3jd0#I+YtfL-ML dN1 FD^)|ƄGGVzkV[vlXj21讨_(Ut*Usֵ{o^{i!#Ƌ:ͦ kurdɓ)W|sf͛9wa `ۂ~1K8ak+?m1 L0Q и $i)ۋELDM z;IZԡHA ?" 8PD)Eeď2!*‰?BR"X@=|0‚l z$pbɓ$`Df!=AJKh "?cDHF HF*IF ɉC 0'' m#FnoTp  F!T!4 fq7I nTyq_qZVq-L_[TKF% 7B|`*P'`1,:g1-l E0ז DPM]?Q ) K(r @p"x(vrx $RRz@X,~x`">iX ! FN* 1'>l0W:v}y@@s  `w:t@s|(@))9@*p 0 d㿅$- ң z?VscG4s%P6>ԋ%M `1+j%#bFϬdaǂU ZcN6PXP#IB ? "X RHAr,=; Lż,)+!7UtYLIF=3\(+ai 25^W AVG0CY0 tcpG IeaHhaV om 29 z!" kfE "ɊȾd@` +7}31aHn'DC锣P;ec8\V1;"0<_mS+ KR˘S$ 5f`0haHi- &$ 1$HN pH@! CJHԁ `*z0ČC}$O΀dckHb# xUQA$kC ю&?2H~=zTI l:pͲ+mhVfVYR$ TA2ZxAkK $Q`A+H-X,_$ DIJwE` L '"(o%?!$rrDkeD&,Qh 1/ 9?2"nL jEKr"R @= c: R}= o]0jhUS ^!IB,UpիЀuABH:)x:Sz]C}@AdOĥ%R^[z0A!/h% X[9$1BaaM]^.wiB24Ƣ؍BqPT(Ѐkh [2;):β0Qc*+<3th! 7pzZK x Jx!fDXyq3tI`ԛXAD FĥzJ R8Nqш WȏQX!HHLR ZW-P'Ą@P\m$H@"Bz^Loz%AZ!ae(C"׈591-v}+| CD` 9b9!ЋqE Y *Wd`di GoN8/ Y?C$ƶyiƝnD`) Qf:xW/!/_uB5C4i|Y#Mi' ?}&~/& %?p " P/a& pm @ BC98 `L$ @BM#@dt<`(<%  ` dbf$BH B "v?(*d" T,@!: "D"" αz}BG}hƳ@Hh> hZ $E=A6A\T >$1 !@;aФpF 7B&j@Q!=!)XZ &A*\0ax A5e!tAऀq*@l  @ VPȠB N!@ @a|; Aƥ .%Cet ~ [)f B̛ F MŸ .@R$#ө`"Z &F vLJ~d @|`: #KZ!( H]b[c\jj+Ec\K%"0j 6"GF b ""@c`  lIwh wdއN{3Hmm³k$ M< Ө%ȗYJ4Hg4Vo!P!l:"hn0$v_.Aa,̦KrdQíl FV BDVqc^vդfDZag+pG< $ qkJ .*V@=x&Xf," @TӴA T)qV}"(xFQ> Dt6u=-O $t "r-&\RjQCޡ hAe,T5"T[T\";V1`VeB [4e "X%N#Zɠ8J ]b]]\Œ^k LAHE `aaa/zX>'C6PHq"zLGMD BEi!pHL!rjt!`Z^{B%r,zX("h1 I#|BZ}"K"Q7O;@C N "6Nԃx6|n6Ì* {o Ȉ"(l!'ŠIk̡a&+ʏØIC:,́W%B$8g3< ^Z? ox#,0vVf皰  db(|2 9$۰3[Yt 6K۴O%!6b B!F /Wpx44};! : @b=Z&@D Bit!P D&&?6Jź a>;$"" t0t vB!A!PP|6 ߃cDԠvi;? BD1 K 17cd ˰iBb!1W0] Xc tumcCC>$@7jz@ C5 O GH7,aD@^Ʋ,WT&\\ k, JDN Μ^Zk# zC!&kA;c H''U?Y"#cn Y]5Y BR^<5ĞĜ!L H@A_M )'f[ bk*@$ht >]kl1rZ]\Y2֏ؓQ+q-pSSjs#Pn@e) E|8/@/'CpFC3v "7jgh:9_h B;G f!@<-^3As@FCB*?9&t9-uj!BZCBhi>3#@4!x"gbA5C?E㧳0 p(=6CK~ g1X䫳 "r~o" aXp'< ^75! C"O(yS*1o""zFpl32fݨ7b"\~ɪg! pt$U:v"H!L+Eǔ%vTDGx$ûsJH@((("7)L#$"-߆ LHMF b}_W20\In*^@noC,ʣA b, D#O9mqE RdKjdZ* \BE t6H͇  IB%D-^fϋ\ބ_ L?O_Q yϠhb(,) 1 qoRbkY3Bd4U vVS?>%WY9E!A$߹ҥ I6 DoL"7 H$_換G!d'AB鈠:HAV1C*yG CE0MG=Tz@`-AH|A$ EC=YbyV4]#"E=Ay.@'Q!B4! S,)!Z#%V=Ԣ @CXFrwH@G]lqWA] BV K5OIW #d1g=@ C QĚCiS"A&WJ)yx:+7i6IUD!Mw@kR=,M*Edn"R䐹 Q-BkԒKe$~j yeG* ź A"p$w:G7j:Ӆv9_ et7wx]&H=}Q PjEI 4 \Eyat4ip06(%/h#7MёA|_7ERt74]?T}_9MG17E~ZM "5C")$G&Wm0d tϱI$tKv2 E6}Qo=DdTbd5`IDE-F/&THPMpRJ$M#O/"C7ʀI0LgJӚT x.\<}oώFHMRԦ[Mup@%YSծz` Epe";i?€ A pDtLcyC 蕯`_u 6Tk_dcKM0GCi*EϋJr E(yZ C PNI`>\؇ &| .Aсz}sm "F1 xn@P@P*B)l] ok9?a-)CV%B *@< 5'$a0 VL!fXL3!CUM! Ϧxdm$^0V,Q5D!!<ɚ>C~ H v ~ z0nAzq}z)pYeAJ @"[as[f)uV  ,(+9ó.Er[IV)0H (^].Y$ C)䪦@@("r5PXh"  p =B@^U`'D!#I`-R=Mt܏3/ZhmfP`7-HLX 3c #*sL/ܔ*YCf(e~t>Mc@F+!sz/T ǰ|J %1cJq "!Co]!Zh-Z7a3mV+oW{gqBTd $PJSIH8&qP50)1^Hg&Ea4`lhZA%IH`@=><Z  CTհ䐀TDN5r7R*psI[!9a`@K#|B{Պ{A:fe"p-;!E"c&'"P!'tR7P!X2$S BsA%!-5$'I U 9=$w"XJQh6$H:"3(!N)pG`"6>dEvZ=ÂH"+ |r2E-C -/3.*7YNNQM;w.Q5|"MSTD0n3NtB>19 S"UyV-/W/Gt4GO?}OKG-i1Y¢J(2iB/3S.e/BaNI 6AȊt(5Q*HȡPB(M1w 85T㘳EIEX^4"tTwbh%iq^HIt:XቱW`:=ӑC ) (SHZȄ)lZAME}1#f@gTrA`CXDxIH%Kx@R!>RjU8k@ʶjI.ZJ~MB=DH%scCh(DyokIO&P._Kן ͂I"A |RJ zG+Q Q@J oKf9ޒEx'II`@TRM&@>)H-2Ksڠw$K{ҠFteD}@Z*AE5{մL_F-`5%pb%.)3s<:17ڄbUSXO*/r06эHa@ǬU.4b*JfQyz蚮P <,Fb +zSeR9 0CJT20(G09{>;\[&{(5Ee$PjsVD%YI9/峕6aY8gI a -Z̔ZZ4__|:0`ΥF`D P`hk%`>Bn[`q\,igu7@dЎX,4_ _5QY;^9嚊E[巡j˶q(a )9a }p CI<B5F]abg1,ԛ1H8H C|yV7!<-8*qa+ ?ɂ{$6svj`@ %hhfiiZd 3 F%QiQh8VX \ȼ fc-lE6(:h9l%cWh1h MËcFjٶ  <nf󙮖'6"6b]Uq!Gs$~W5}'0{+4PEXs prUC.U%71E?G!f+xf'GGRC\!-r Tu!$h50+ Rrbɜ.xȜ*+!S,yQɳ|#`1)Hz18yqVz=R|Jdj3JZwwqS|K|2y!0TA*1'L*lTEJW | g~Ҡ }mʡy^N.6 р-ƛp0ݖrAdB?Ar?4LH-d?tR }qD1+</T䅑͆i8Zn~"<=CD8=Y5^ 31)胲;g8q8#4rre l'7s!g08g/d#Z'G!% !T'-1-'t 07 I)-Xi1 q!%[^4zr88 .P11Qߧ#(#R(` )3 r *7b/Ui,G9*hitIF0-礕7.w5.TIt.sV)KPJj+7 E}KYV`UQ51Q/Bu1m+"Hb{Uqձ!5i q4 M4p${QkC՗q۸)'z 4 ` Q* B ( 5‰,`#CqY/ I q00|Ubƫ!C<%}AdC]"LN}1Jg+S\MH&8M%m בs|=}q / X.dM#gxzF 0>r4eA?6U2 SUBP"U̾Pr3ʊǼDM +4[?O9В^e-؟OS; t1-e%4O- YZ!>*NTYb A1 АBmR‡+t0xƿ頓M%H"Hs?"D4҉ IԨCRL!@4v8-OҬRp#?Eoxwa۷@LQ,@RhߜML >{OȮm}Y ;R7 6 pS\㝠s/H`,EZopE&h"N,j " Hf"&$B“N RPXEhA2nɍUDP=j \ rÕ ,RGXQ\%0IG~)G%Sh PBD0S Q*!;U0iυs/OL鐨łrpA_R獎ŎvʹLQ'nյ"x!* F 4(X:zZh{Y@6(*6.v#P=7@o ʠtv@6L*#vzcIX$(( aɻ8 %>遝- 5A޸m *C@ RZ:"hއF:Z<@%u+H *xwp @vՠ{ T(s;oջo}vCoA\ W@`T>ȡ,8-E2`U]mtL(hUMN]vsֆ6W6-P!`gLpaWU=:[G5&H<[!? qՋ_=7# (tq)B(B-Z@ @WC2JbP"LA0=D:T|CS ZdD [G D Ð? HB€n&hAn!"K[B )zw%rCD)! (Զ5A-zDT BA=BēATǎB!Nb ҂tDžT1U#СHcE$ _Pn! XGB\9CR40!XWGpy>@Ae , ,ṗDyYqVF$\ zPKj Cb H;Ar$yг @OtA=Ol"zHFW* tDft>/^SA4 )uAMFk$VN  `$<$B&;π ؤk'dVne BM+p!D\A2>#]PiZ򘼷*_5u S#j`A8 7` Ud@WHH(PK0INH2֯LP 5QhPaG` @rE~fh >?+bŶ1Zjs(EkvcB O1%~Yv>kQ iގ6 >*6 ԀT;3ɰY_gF)&jMißù92^w46 jb+.Q0qnz^no?ls5lt鮹+u\{>vg?M҇T# ns{w}udA|;w7|Ez4"AIJ.yw}E?٧ɮ*CX7ͳyX"Hd.T ?h P  ? 6Cd`Xu_ _!TBdDAE  e'5!r`)M`0090#ȆϸjBHIІxj(( hX S C  nrpA? 8 @CL2D (SFH0rFxȘh(g>(;x\xJJ*rͨ#{dZC 8U(3D`DJ ԄADEdĬHD+aDs J7#E E JĮD 8BXZ?'Ȇp0C!*;a`ƂxƘhko((cp(`o|0v0t8CKaL)2(U@22iP0iȈ2l+5@C9ʱѨ[(!*0[34kl4y(Xe8Ґ {x3hĸ؉R9xQ.PJ]Q+tyH9ys9!ʢؘDJJ1JJoJˤHF>Bu8iH Hc!pipq, P!a&ڡ*.z3;"18R78[фg5WsF\W`e(q`_Ss${XY)kE1N*DhH'i)/Xx/jH+"Hg"V-B}徃4I^W,X妇׀}+E Cw'y &Cȉ܂ȈYb ih#H / i x#|R0 Ԡ'MRp'M1=0)qax`Rk΂0hؐj ha=*+8*z .LІaF*p-bO,u[Q,@m)j_JA bqBny]sK쀺-\Zb,.ȥ&@NA"b^%Mi ",-eޥ8-+j 9,ˊǓpB<%I|M&0 >[3=`` 6`b"OJ؄-@3 }Jۘ5ݚM#@'IaR Z0}j+@ ڂ8X<*s qI*()|ţڧ;a9 +8 rۂhmhF0ڈ( }9ǻ5[qQCUA*Y,ӭc/ļu уF6dH"SKD SS@ TplӇ+?d|f$tATEZx0TITKTMehNTW2)łPKaK#SV9CնU8U\5_6a(b=demgՇVFD֗P=&“w)r8R8NH~NqqOј`>NhhѨOK 3>R͸fSɀ} 0 K 0ht 0xYҥ{Ӄ7%q 31 |PN؀}O)=]Q_.f:k툩* l=--bxe,`;ʶ:K۠l ܼ+]ڭCVfm/;$c;sk@mǻ>]S }n0<@2u( 6<;-Fm#ß(-on6FVoY`KND (<$+&(bZO>)X%CCpC zި !(:,H Ѡ8B,8L`q(d( -uE+ՊE6 @ ,I0`LA L$- `[8S} +]E!Gr*r I,bT(w,._pqOxTp@ Fpǂ >PQJW> E(aHTg$OM7 4"q2cp>z0G(P+<qHUQ{yRf少?=h`^}| @ EQk@![HވHc7dWZu)K `_A.ULJB^i?] KJolҢN.^LJ27pɝ˳˽Ԏa"rYNؖLP P*oˉKP}4XoH8|a×i>xAaT,ϵԸTКTLԎ h`~ӵ!̄ɨ4a8- ! 2l!Ĉ%n@!" 0<и$ SLr%_&|`A."$SB"X@H\74uSRmH'פYzW`өBGn(-& eX1T nO:h˜[ θP'ކƨ݅n7Y9a`Q`j hW;T9?K57A 'I)R`l̚@ºUH@Kʵ̒,uzr/"= {Qw}iH*Wイq)`B'/0/ :/0`Ƹa= NcE4Ԡܴ2T$.30И9G|?HeGm Gd\&/,?)X6B{u%V*FutJY U9FKj?vI+HPx#?3+ J7R%J?8 bP$iy uBdD.dj  1BROZ I EN&DL[S[BhR#BKѢ?􀆰pv3G uI&?>)n -IoTRKL &ɨp@%䫇4$oh"M KP@yG"AaG(Q_wĴ$͑nt P@7JP &P AB1L `x4 )#A.w̞ `2\1LfB0t`2v%AŘ) @ =ub#ukT`FYyg.aeEg i奃&Qy+;9v;F`PKPXI> u=y٫OPAòi<鄐Q#?[V%hfuaFi1#HLc>4NZg$(UWqF%@UpC!UXz k-n[!ȸD] B-4k_ ̅015"XUQi @dH@`# HGCDgjaϸCbҵ1D zH!Dr25$k65oHK$a򣛿٦ #H *! NSB /§ Yb7ҡ17N:?#pG> 4S'!4S!I3֧ߑD:B %'` Q?tZAHč(,rѩQRzD$_hI!vLH![ZBԥ.3 sh2ES!h&7ܢxS"+iO I?E(C!Ra)IDSW瑦l~j !2BC5!x1Y' 8M y@ x'9"j T@xd`{X& xQ6PX\-*e,\ `$,U,sKKZb\$rnPrVF#ryCMTgy ΂+ zeys h^K+_Z'MtUCY'BB/fZ2wGhX#)]0l o8vb`\-qīܖLgFřp#AøxӝW˺g% !F>2d#C%Qs&(q!&Xc6 Q<,1f>3LVGd)-L(e7BuU.3-A[̯yUetZN43MsӃveBbv  AA3raP"hEH8A!6[R8\xBvX+. GG$D̢6) p`7(:"[AhŅFigb!Mh8ƼhC7!$-' &@v0" hR`xC,X!!D HCK"Pp&BZv(ZmLuԼMB/7Bt|4,?'(B7N0 01 x,:o`h 0XB;'60G]j.E `I$N^+^<q"AD[fIP0BIF;]t*  Z8V/U Z0y HAy*H>(BU QdqAD ZP tß /DPB0T$?0?L .ϳ+w  'N @@B@^P#ǀ2bR!@+b$bF>nF0䏉}DNHpH^\]I@8!FEijAVFQNQ:/\5VOe^tIHRPdYDf= D[Ds(4yJQ$vMueIU&E `Da*D`QmExZEzP$P&FO*D0XIxY3HVIBȓ} ՞.l1r4BPLU-0 զB4<)IB0 7@t_l<qS<ȅӇHYBԅCxL;@E^&~ވDBYʛ8gBe O=TBL4DaTQ4lzC~CzBЂ:-A`VKI1TtRjIEv: 9(xD J{8rJ '?,{{ z `@_ 3zH ɍ(ލABjC0v;AB;A{6^J-DjD`D3$iD<EA|V1NŌ`$hMNwuH|ZU;H,itxiDYb`D4Dț$hQbJ4v,JPG,! l4w$B|@A*s%2׮l!/\LJedD$+cdwO<>JbD!Jޏ鳠.J~" H~F}=d29@$Dd@i"z:dmx~BFѻiEwY_>ADCdsB fqRD9F}ID Dbe]+]+d=ZZ]:76E\f]>;_@s\1>+Kf% 4@8@A!><*IdoB xHp@%Q,y2eK/a 􅑏+s7Y(fqR0WCK13-ܬ1@RRKA@ˬ,mTt=H?#\\OSgmիYoqaٙf ']Ky^kL'hv^8@=UtҿSuVAsa8lW:BHF .hP0Ԝj6JRA_ ߫ʼUR)$Hzqwt#zIXyE @.蘁nDD`Q |PB9"L !:ȧHIE2 @,a=jR 4:@K:H5`%Pˤ>KT(A &ǐ! XVHX X:m@'Qm94%1LET%Ӂj8%aTQ$`@<@*`bDbޣaiw=OtE\v "4DP)q 4FA#x\G ML"P[3T|[ls0YAΖ͙[Z:HYДHMr(࣏ES9M0yuH `j**ͦ;x"4Zl@׆ uln[ n r9xSPk Y$m~Ez f@5ծ'5 ~OdI$߳"Hm-Z3@" h͛fiF|$]ꬵ!(ɮ8LD 23(HcSvO-*?xh.Cb^&ez䧁 D)uF:)Q)ѭaj 2+M\WTGz"H@K !eq# (Kh d/#+j"E݈bI\b !'q u?#PsӜ ":R ` *-Zt8s)KIe*_<,bJ"2-iBzf^ SRK&@#e:3)!(r Dh1]#E=@⃑I@]3T8B N\0%C@\"ɥD"bB"|k60DA# p = 0Ғ@lUH:l@m@-Q`̣@ԓ*AՐT(%1z5ȅnu9d5M0QiYEMcaҤ.3\F:f/k!U3d#&!DEg-udEWԊX6  PG(JZHv$&x\J>bZ:eX#E<c !pQdqd%/Y۵2,¶7909Hղf5mvEG栓03"4b? I9 gBZ'HPȟ]A $Q',T5;C0u$tЧ\ ܁:/Hrh3( шN6:C.帑a+\D>yG'b/qB SYҌ_R?@t$.a~˯$)|'JG~FW1?§"@B4 V!2" vp<\S`,8)@`2QrFЃ!|7D0t| 22|0R afm!A*b|B *&P 0t  T0j~Ƣ*B\F/* bpI2c|a{2C1|E 2u*i)# *., B$ 4 l,"D@.^' @ql mϦp'kr Q&vS:`->  h>":nSJւ>E$cAOac ? bn6@LL^@ @h0 S?/2j2|B28 ZD3XAZb" |g}8&Qa56c. IH;E CjD^XN $ $N>O4N.R F"|_HM JRF|`cx&AZM MIA 0Axf<  &PG7HS NFIR@NUڼ$UFQ"mH[p(aA"%e~(shljh q ZU$E]hb |j!$@TV!D\`>`N"(~` nFnRERpDJr[NGtG o "?$Ltn֦"Zggম 1 醺VBz'#&M &UNǹi_Fh@rnFo4&$Bbf=khgh "gnF$6f&zPi@#3?-=C8Ij)?L ’֩C=)6 %\FVh"F/3spa0p1ߥ@"Vwjʑ0d 1"0JND-ۄ*s~9ƃu6n6BD" 0B!.Lg(ZTRYs+YR)qR긠r"bdp6(Kw*jv- 9jhTx>xc(rd$f!w#j "!,K,-+Y WJ'|vj26sM{+%:+!F:&8%Z CKoQ Kd!zfۂf+&*h0 KoG=Z !F"ЂŶkari (J󤚪%zٺ,haa"6a³$z >cҘ`c:TxzGz[6&q2qq&d;E{IMQ;U{Y]a;e{imq;u{y};{;{;{;{;{ɻ;{ٻ;{黾;{<| <|!<%|)-1<5|9=A ^ ^ L޽~鉾K"|^_>~ɾ>Ǟɾ彾q^g^-;Q^~^na_ %bY|CLSK'a FJQ=V> Ahg&m DI$N?_wlj?F?}hF5\Ae{G__??LY<?!%??" 0… :tĉ+Z1ƍ;zРȁGYҤA!Ud 3˗1kڼ3Ν@aն| *As/չ6'fdBV8ɕzR_YffE%m95\ :n^uRoӲQ,{{~]Li[;HΗ+8>϶ ܤtĿwwz^pI*Xѷ9؜u^ҀІ'!LYv%Z7bx)ay( C0 Xaxۑъ"XdF*鐒KֈMNRvtCj gkCgv޹NxY|П6$(j硈)gh7E褔*(jZ(> jQQy**CzΪӫfz리ꪽKGtlL&,BNK-|+Bv,ՎK.$~ [næ_n.+oRo ?J0Ok;0W̨@ڰ OY: jc&jIpRa'R"8&X OHBh:W%vxW=#/{H @ >.?lA5}$ SjWH%gC{$ì` ʵ'#1)żG _Kɠ,`2VS2BNlj *F`POH#m LeM~B> @oYj\̈́>'}bHMmME֊k &w IE/Srp-at!zVv`x(N2`Tbbj5.+Gg"a "HNca$L*[(N.{`L2ƀr ,"ve f11~Aρ`A' x>9Ax)/QT8:gA4I!|ȂPǪM%j(GT~J1TCQ7K|a$`]*AĨ%7^i~Ү  8A"?NrA?`(C-v Qp$äuSMu0/$I=E ]Q J7勆UV wi$"HDhK !`S]I] >gJg7O-PD^$0m]ݐ7H-z  $Ҡp!XPgoa H:^|Q Tc#{3/FnC 0\^sUh^7+p U2'aUc@\@ /0 WO>P,WbJ)xkI-|\5hԜ&keyr'-n6{%]8ng/'~QBP\Aݗ 1*R\F|K 0(.#1" 13)&|CR+KC@+D*P-@+T\Cb8M04#N0AKe^-@aC+a_b/ӔcEF8a4D &-@ƅEh7i8R]!JC9r89# O 08T`-eZKRa7΅7dX(&`L|fe$ A   fB)hafrrSKEٓED'7E?cxO737` BZ f,A "3 :Y : w'x%[xf  B ` ~gyy8{{;v ]q{g~"4d|#6s.69M1VKN0 T2LL\q4ub"6O=RÐqg:%r=gHyPKY$ˉe&!=RRfR=¢!+ U`rɆ; F,wɣaoivE55XUEUlK!P|a<!#_Z\zaip9 5øx2*%zeZ0 Xg>(]" x:GTC!oGNwU#w(";ƀ)Ɛ *[ A'ՑaWyJګ1>4%C:݄d*6f=\\XХ}*f7g_*"Z+@)SQ[A"` 0زv"p"`h Fi\'*j,c#:_k_![{Q dڲUuQ MDS#gU}#-jS=ʉ 2~,0{ds.{XZ`XPչb;d;febuel۶npr=x$n4$@ryGy~P  B DUhVEQ~? o aaiMi/9J"aT1AD`Rpn?RN79񺎻 jvT qp3YD"[nV@0V0 f(1fpPp$a 7ra0 Lc 1 { **? DP۶-AWsݷ8R~'g~4<0T "~yCp!B:<'&CPcK;"  dP An y1!a,ƣ\_A1w`@ O:%CSvlQX%k;"2Z $1ybQ V\!| yo _<  !ʱST"ʤ Tɟ @| 0   !*%L|vD4Rq+T3C.j#CH,0*4&gATқ/='#ބ8'#= *,M3RQr!5й쒁6S SD( 3,K/?QLk!2KB3UZF/e!H ROfFGAl1S_.>7 &'D"膐W yu u frq p_iBCtoGn45\2*t0w6Dȡ&e<[>E,OTy6DaAeFtQ]bݴ.מ/y^nx`+1UX26M#6$0-<1QG0.a V=255bINNPNS+dzc:Id.8GgS@T&'O)*mQx^I$_ D @Q+躱RAr= CJW`ȑ&9OD¢9[F(M¬cUCV j` <(ܸtaA-QA<da5 C UPKp?Xp1wl%|0, "Α. .>0 !W&_d%_\_/f/Ps>ޔ]Տ'|ُ v8?~k3AP\X5R8c4U18?!CA2 !¿7xoC *<E DRJ-]'A,0K=}TЗm$zΠ1t 3S2\Rk®,2-ԵWݾW\uśW^}X`… FXbƍ?Ydʕ-_C LPD*J()XSjLL=,C-=g4C)gRЛG:ب ㏆D Mg?A(*`9Sϥ ؇!|ϯwi2ȉ=M?"pm)д!Sb Tc8P' A(a3[OŇP m# zn5L@9pQ%R),9 (ILj!71"!/Ha6$`“z!TΓBP T: GY>` Si@ PF#E& &yIyV^Y P|UXa ]Sx5V gre5;" ZIyaII*l sDLi*T0**:k $VYۂmAXe `UU,HIcUceg:~"h"!g@zOh+@K]U6)-8:- 0<iJ #  [3 W|<K!# *`x)rw  ^jqK,(+3z_rR @wg&hjγʂ,AArd IUr@\dkuC`NL pD# 0Nz9K8+\OFțh 'A=|i+ zt"l,5 d-@I$%':}p.PCG6 /.2Wl!C4PnXhP@>b4,g\+Xa3ңc${!BElV -DHJ.)Bт4ɓ؂ 9qb0d J3M"cɆ W5r["Ni & HJNS:%q7clPM 'gxIXN1)X$(}|"A;"*+W H "D& ?$F!Іʓ{* MAq( 4*RUC) xJTiI~ 9”VDt;gb(h̔B֓ FRT;sdx`l4 MiC$&T ܶim}&Rj@`;ILH{',MHi\(d`uXAĤgYh(Z )Ar[EoJ3 UB:*Uv8W1SJ (InH3L @ RP PeGDjL$ )+G纝pJL$u@8CVk:M~2uz3NJ7˓f5\mK&ApZ! 0JծH;`, ICx2$ɠ&w1?Ȕ2! Hdq\ <W!E|F`h8%I[|NmC؉A1D V<| bJ*Ud74ʲXduAh^^;ISgLv +cN:@8d/x fo r]/d哼,Cɣ x|Ez&;蓝&^C"M}u4: NO^qJs)oC B,d{$:8D0А?ƒBM\H>O1ENJD\`aE:A с죢2>oz #C 6HC0А08 9(?$a?8)y yaYy IAOXH‚%d',( 'ec*ԊY- 2  0%P!JShYc)g3G"<,ÓȵÂ<"RY_hp=70C2iIPoR3[ 0ܗO:1Բ2+0OJ!/AjWaɘh ;," Ăċ]7|Z9Az) (=ѮD'g;GHG0/8|9xQ8Q˩ٌ 8~)*d 1).Y  X /) H"!G>ɛL>) B 9H- xKpKK/x8 x HbFw#8Ky ( 8 🨋, .͗p!„,N@⽞p4XRM‚WH;D]3iCr@IN(\N > ɻ/Ikg`7XC;Xb%E_4M@DS<5; M !R!3Zx/|!X%>zAaJ#8;T[=A^A` TCf\{ڝH3˂xc0*Î+!(ahѹjLsZ)q)hH#Hl @ ZQ"3ů?x!d@D)x) DR+K2 Lr@YK( Qϲ /R}tŦqJ⊮  P `jSJGO- VЮȀX 2-p XUʼnb]hR>.pZ 5M=4phQ˦0*0$QPka0| FekVȫ#K40}AC u*InB@ وyxA2ERn]#;05 9AE͓$ݤNXA"%^c̫`j ̲D= 9y,9<ұY!Y]-V UF c( 5bʊÒ!6o6% `/ؓ(9e'1zm0%}]Z5f!=0Y6vqOr_ -vSLcw/]X+ { x'JO#? Q_  P(rxYrhr(W)/9AL|!_OHk&QPwg $~-j(ҤJ2AB!"!|A؄e7^ځJFeWԩx疅Ddpÿ^fBs2U~ةb"h{1,Ptu ⽅x 64n:ڷ' 'i?tBH{L}BQfs,U^a=Ёpѷ : EK  ]k 9bz$d?U8Ph @ʅڨԓFm$h ;5|w#I*$2Fլuӡ@ 씀@P 9~9)?\ѕx-PS-.h6y'Q: ~8z(>z( F h\ FG"|G_*+ktiT@GhWF15DOI0&$%IyB@EXQ2 ?z$#CD ,pN(ҁP?f"?H%hp,FENc|D?d?Dï?e&\)""ttmO ? dm;&X+4?॓dm?%L !aa "݉ !Q\!> L9IX}eOo֧[]<Kxrw ES&,?1ϘhrH+)``.(0(F.Mœs+ɐ* u)P7#2lxOFL <@3 #8e$?e$\?$Ҁxa5?pa j(@Z„@ H%B'? N+A8` D0G'5$r FB0g`PKpӴt@/TIFv#^޸ <;AP]26N e VbHp TvW08bKQSY4e " h-1<+AUTm!5H =EzY! HTo &0k(~ i X`dLT VhR &%@4 (*N1KS0gT`*) \d@(C%jG\3<*EY%Y;1L6#xCS@ drbԱ`YNu2n)6wD(\bBpئ,Y}cCusکFPiu ]C:hvH@PíȆBM00Dq_x("tFBq fWGAC:"b HےLNRW@ dΘH6<-UviKD!F$rl} i6V8@k^*{/+x!!{e$K2:y`7R8~ @kIa#& PH @:W1Gp$qt2S5# 8@1b$dCňH&m|]: a-mh@C~1)X@#Q"U 3ct!h81ђTĀt3z|ҁR#(Z5"g;rG,RD.C1W@EЅXPR邎x"HI)"iu[Um?9Toǝ)*N%@X1wƓa˥+y6"*&+T%뼐&N jB*H Zf#s;[Qjy ؖ̈ $ATNI ; ρHcWpygdîA m#sHA;:cY?j)H'=މv,b8Nf`e10/ш.6SiR:Y#U (tM)P{h$`ҕf94a:BGm?`&) 0RB[#n-1L@*bCI5s͈&e p{@b@ځ`I7$޹vF*A8o;)Q$@c$+@K\"qEk=(7}wjW@'OԳQS /V1BMTgOQq@X H]lqCX@4?qŞFQHTFmX*FFXP@TMՕ[Dn\ :]bPuåB԰8QH9ETӰG{8!GzR9a~a% Nj D=\C:Ra: 4 !>lD!bDyX ~Ɍ!X /$L(Ry%"*eFm*1?c&D'hiXO!l fDh XRQXF cǢRrERF*EE.B)]dD.$uSYTF\ 6U"FtB-En '͂/9B}Fg ,4C("(5o9,=|8V^CކQ^ @(ɖ0hR - --h?^\FXD-Ԑ|@dƔ8mXHAֈ@@ЀYi(xضPÆ?L&h @!@H4`P6F#PæDCPX-`B9m ܖMLk%0}ĦmfğPGiDD\B@o"XLFP-M;%!}Ep]\0]1QZb%*2iǁ|n@[Fx<@ۧ]*Eų QqRXQ{z~fTّzȀpUI jd b!4NA!cvlfjUR&4lcC &3S7 ŕ ]Æ6{RX ͵eѭ@X@Ȱ=.'ŴJEqm C& Č!ץ*jD~2vXFG-L6h?-ڪC=4CkmCo贅mon?.IQ:+x@$n\qmD)@0GnF )OLWk-|,d}B:Ч.bd@8^C0A@u$rutMF|TPU_Ćp dUlppOQ_@| }XBf5YJc2Qk;r$ʲyz9zA@A:pGXo L&IF $pO@!H@ A8RDg"0 ," Ϙ0TŌ̙e3,F])XM .腁8s!A@@$D+'DkdZh ՠ@hN?UFAЈ ̽f DPN?P@Uϡq&Tm@lF(t' Os(@Pp#,úi E/\cx>2l:z0+9EV8FyУ2ldܰX,R R[T| Q2q +əSR:*eX)FD%*,D!>nPSHEx)YKpbv$[ӽ,@d7Pă ߃ $F/>(d/CP,0bA(>Bȁ: tB !JPTRaEtÒNo!-S٠9SBcu##Rq 8#!   `y#=9P⋉d+2@+.L<sc ݁]Xa'Q&LXP$X߃q2~iau. ZUȐ"?:DUs 1hg}l)q&tga?('@l/ FVS8!S6| #|9a5W6JN-{N H)@7Cɱrm/r: :1:nP^C\-$;/&cau,u|؇cS*:|ﶊ KƩcxn}w&AӜ5O0a(:pYrM!$2oB.s!"&WXW0 GMe J5cBP 'aY^^D|H oRCg К CZ$aJdnÈM`OFu+PNP?Np1:P{?Vt[ (A]^ C/B2 E`RT (&p$$)Io#\ԑ$rC RG^i PRH.1AGeg`hB*I+C& _ `M|ZT0nh8*@M, o"D`$T;H!qZ %!&qVsN ʺs& !ĉ tLX, !,e+PEH8m [!VN15d@W. I'tO{ &b =1pm@@@&.CPC%! #,0 aXPAg(Z,?P?bD`@ eC8:$`Ɨ+"-0YL"+ԥ?|PCX0[n@* 542 Wg6.8e?}8 PlPP@D } @Z17Dž}\qCrGl|hT-AG,Ge&Ѝ7 ^1@]XbBXC5Dc %GFWZD*H *BJ f"KtG@*P@'yy'bĸld$C]e$@P}T $*HKF7&!E)pLPbg5:Jٍk{*,PbbHgR -|/PDh"p/|nRL vCvT<6u (@L8JV:C'Jc>+D(.ɍ6C#`-L8hh;/>- nbJ yp'qOLKP<|mԶR? RLU|& BdRCD‘D&jJ&!a ().A!AGQ!!$-PIDHd !IvEFALKrAGNcKPD檉4 bAI!& n&'&d@аn4|\!K|q \a SzQ7ePK 0n~FG!gOvW~e ЀÄhh}e AZf BM[Z-e6f,r BCHF0X.xbn i]( xR!"`[f RU/|ؠ-pF1` W+/ଶ&&e / F'Ć#&&:TT8 T wF/nkbrv),( vb12Gh3"+DO' & |pnt=bbt'{T+{NSs j%r"yz*7$qHv`l|C9@17$/'j'( gT chHpBjKX, b! (Q!&"#1&&4r H!KAcF.l [%'I`R'2?Hv‚zenzp&&`&†hnBlh4GYFA zI.!2*p&hPJ&b1j naJt 4tמ-!Gq JNq )_" BBSio DѨ&K! vN1>E1Ʊ;NT+!,8ф0JW6.O.j yNQ"8VC Ue"@6 F B@'U!> .Z%F-(*E!S4&jT&J)6@[ ,n0&(c!vjAfWt,RJ -&J"E+6sTP"V1%v~C6 GIc*G13 j1b1/eʺ S0n''HjTLbybs . h"A!h"iT!ĕ-*nbؑ{k6lkfnfN8&A!&Д&Q.0jbFb|b+eĶp p[qԨmv1I7j#,< jb3Y; qItMtQ7uUT@m >.& L!`M uj;ڂxofsˆJeN-@!dLbPCjEnx9A@KH!&~!"}CBL+˺ h" \jhCԠh"XrK7Wb,]8-Hb<&2&$j( jbJ"t[";H8R&"عa֠v @avC@7겶;o?.in M~8m"J'dJAޔV$)?aGC noB-8ڢĂɏZMIɑR b@i-}n1LpA.ʍ Nd ZhfcٸXMEY6,,Mڄ`+=%E@@!Ƞ.HZay E NqbH*yۢ) ,z&dţj0o>f hp#`!/,"N"/+,= b"&{9D;”Cܯpt<GOnb/& /p`%Xo$rȯP"XbS)"&<@HZ3`Z092mrc4C*\1BIgO':2CA/`M#/0V"<`I"26Vx9. .:WSխ&:3 GZC: O.1bT* 4G8ҏ: #q 6q?0MT~mnDOva"pJSbBQEQH\;MÖfMtK!yGcEW?_@ZMN Lg3\U!9Y\dw*@lMIWB!f b{eՊW|E$`dmw {4~k@ 3\L>tiBZ$h VHƁ>d˱9 7$?A&Al_2a/ x"%L%!FoTc|2.) !x ֒{3-2/~<ȥpIP xW!h rMeX< sf:)uSxjNj&2fG-zUz/"crN<'$*~3cs7^szeBM Ǜ :6GS-Ʃ@St"m9:&3gkoScf)s&|ifU=bb(]&"@1ؓ&&|r"R^aG!=N.ʡ2"@lo0| "n\ BGstk~dڳh/Ctr! :$@ "a_-;}T!>vXo"BT5_HkXYɊ$UidD!qb9ɓ{kDJPb$@[U^!=~ѳiLӘ|VKƕ̞d"ă&XLNZWvITְ;ʢ`bU6@X+j>J[EZǢuX78@H`z2*,jcdcS,o?4bB AH0,ȐB~qA, 2ȑ$K\H)O2̙4kd%C Od$L0:!w 6_7S'Cn>{6^ Մf =ԫ[҃:D rIh4iB2 {>?`VsT" `& Ol$y !hLAO"}@EPYHtCPH(BcEE&"!J}\bfH$Y:&SNigS:-ە.mvIea]#sh?>D E:БBI0)YgAT@gq&Z"AH-!VĄ'.I]zVZyI?~d/S QbAD@lC lE#_HHR H2NAUA` %'1*BlDAdRvRG.MyB3M R7꾦Δ8([Smʐ ra$@{Ԑ*JAF%ʪEE] JedT'Q3?@ w" EZ"A('/(K,ˁ$˻L,hv ڲuhAFA7D,%(zJ$TOP@!koCIAOKERdAAN0$ETPPA!P/!@wWbu@dSAaNS q31_PB AT@JC?+8N^A"`A@BA :@$O9(7 Bp"UQ_@"0!PRx K@`esH',~4b)d1$bI $! łh14)s'{@#‚lT@@3.v..QG1?43ȳ R kuj*G q1 *B+(0媈*X$ n!_+"\(kp+AUC]XA΁ xA-c78I$93\bq+ b[P XZԠk= 2gURKk"𨚃ʡ9YhH*PP0DRqEV$?$dhL@4`/$ <*)|'qA@1"tK?l!8q/D hGduF)01 "A¶FMhc@5bzt!@pxbS xT:ʎ0AUI fv-QD8@Q4$K}lZ1TP-1m9[9THRp#PuIJ]h #xvB9dJKJ>3Rƅ% osH0Ż#x@ݭODI/X"^Ҟp RL^V0*aGk.|4x4 {<,a bq҂$$' Nu4 (82k) HTj@ "/=V9ojD"*E0Qmp%@8e0U&1C/TF@-.H +1hԸsqr0$FaOBR?4A"Ir2y?䄔te'B,k+ -]+[h`0qcVF,Hs sLQG/jd 8C"Ί$50jιS?smS=}X4@]dH`T%B9ӟ&c-@JFl$7FFDHA6gX`@+@dm!|2 $Hv+iU|\sRQQ" ;GZ&',$;@:>I<h-6!qb7],D%Ior"V$f;=ӄ5ԻF*#$:\1gk~Yv3^ ,mpc &@f {o# I߄@ @>C R|+rbYMl߽k~H1aЇ8Z PiUI4H!#!(Y%PR!~0%dHb1Xp[.ai!/r " ,# pE) DBR#7YYR;D8 AKW!=0 {$%",Dž 1?|61qq<#xC ( m!LkRR I45Pr/쑈g +hZ3KtKK1 PI PK {a WDUGcV.$ PxTf(  '| A3NSB0v /Qx[Սx 16Q.7tcpXWbB!"<6"xg%,Q/R}[W1=J7H^$@B=FA=Dt;=q|r|"? A3D C"B"0g/?iubǛ?!^"E"E[ah|^ﵗBh_ڑ'; #;*a,t=UvB6n|JC(]1D^[a]~\8b-) D7 fZ!2 U!Buj$*Ud!)< l0j>qUUIpP{[P@%WJ&!+ZW0Zà.c50 ڴ%\fd[i*c[Qff[Cw:,qj1111Y(fz}tc!Qjsm'+@EѠfTKꙡ¡$B+zyd)*/fJ2a02⣑ Q 5NzGZ6XTG ѥrV6,DJ!*"%AQ@oB"]tM0Y"Y"aF /&?IQ+1%Q%G$/W aq~_l2%`ޱ?Gn\ pdIIo9y) 0F0&G ?E|S‰wwdn<"dgav&q:BFK(EU@wӳ_aαt}]p\˼6%ޔxf`;YP*0%ν$Rz's  '13՛5Btʰ !2iASʑ0 x,=E"{*jplJ}_vH&i}wu-3Mӹ7v[9yhQIeP݂i"!V4"#P]J $B hQ6] 1LJ}S2eq *omi֩-JWT&A֕ Jg- NrjŠ"҈MfW 'a Ŋ!!QфC@Pp0A0Q t  7UoMtXzM:*u'74b#,a ]x+ƀ9 ~hӒ,3zJ#8z$" Z0A|TqE40Idʋ50-իx :פh^A3 0 p׀1c *B/`1ǘVi@4b,v !0-$JJJ6%j41 / @zukPWɠkNbk*~F2%!Q  Vj6ބU"o/Ӑf8W4*1 7UƯS.:=:QP32{<pSQv>S7Y=,rIDBs\%1f qpClӧ7D?||W뻡l5 0 rIQQO! JK* Cɞ@_y+  ( !-j ơp  V_(4z-WSv B k͍ [4Jf~1 "Jo![~Z x@V)lڽ/,1F2 \)RxYQzUUjfZznjz] >]gR38tbrߢ| 8 _ WPA$!"[LDI HAdaB47Ã"SH n,b.ACO# SNaŎ%[lٯb@Ob/Yyn|F>XWം>@ߍFpwՠ.Pd6ڍ?;>L5Y ?_WR4kVBSJI`_YXOUعdAZ%"@`!,j#ɽ=FÝ2ksg'(c7:GZ. [-:A7xBN '#͇BJ#tf:!q']wRS`g/ BX*6h zGy)< rީɀ2/2/ = B{){6L d̄Kbb_1a:j>K®# 櫂U>wv (0 ^'y"JwZ̿b{/Lwl~'!>z J4ߌD3-}o>Z_ 8>~d`D^}d<ҏ9 #B!/PؠxCPLYeo!U3p# x/X&:q`b%$^1"0F=n XAsj Xx Qs "zVTt鐾A2 $ Rd'=IPR$e)X< 8bE]D 9i7NC CE FtRw!k!Kr]&3,G򏇽 GHqFL`$ P0APZ"\nĎK,S'(t@a8 *YD{pM@ ~q6%'ȃ/O,"K h N8ؠ DD#>i#א?4@, d ?Z;Ž{(, Ո"Z%O"A[fyo&Y ׈|N"-p+H2 |G {c%/0iUVp+ag"UDdg l6@cXn" 5R #O<1w{iJ>, v1Fh@*5TV;3]~[ m=pv w /XR3 g*(* T@lê 7yo "bM{ Ӱ >Ν]&9,@ ŋA4dӵcL3ۭ,e׼%yjuIqey[ j׳ZϢ<޾ L(y3Ț^ sl}!I^q,h׻5NC2feHZueUUFc<ڈȰqeC)|m*Pm_PV6`/jtDmçCR0&aVQ(Bjat{n'Ļ]XƺZUz޿>G嶦<˰{WgB6RFHB=QC2# E - `_*D~! ""H;@=<Oen "Pc4@YF}H!a!EDžxj8 a @AOR _HA靨@'4XF><@ DޞҾxR?$1H?**Pnk59Ȱ( z Ҡ <v F |С t6iȣIx 80Iq$Lz x99BA' YUq?+7ɌA`;tA3ԣa'(+(.8 <܉ )D} C#hJ1^#e2ED 0GD[н#ōtS>c5l Z 4X4;F?6ԋ(F@p@H?FQbz|h7Ӌ&hXs.ۗ:PQRLbNEA$P=QMQ]Qmэhgxѯht0hА%ὰ" ĈȧM3 JDE0!gJ {"#CxC[ :`ϛ:'';+0Hbx[^J(C"Pr(&(3B$9(4d 34 *끱x*Q(aӂH@((P耟uH x XsX]eLO,(  17\2ZIȿJR*} WP-Z!') /a Pt`d%Rkxr/erxPnU`2h 2*:ZޣZ Q *غ.Ou.`P`M=` '!-$ .=!+ ̨;Z2,ӉMD `@ S<&501 ,kvs3(0%8N,HU!1Ȭx<{H^(S1%󌯀 3ݍk]x1 3{1##l -?K;ʌ1)]cᨽ܋4 &Oa<ؚELy ,\<4ZCMhP]1;ByPmϗ4-|ѳvu d >|!Nt7lA~]J|821kl8Aօ "K: {P0pJf*HՈ9,PH>HЬH*'pLQDH"" At@9m.4`CM#T~)h&TpJDB?)I!8ApB!i2`X6D)Tf-eC:Dfd*醇TguƻI@V8%]f:Ki@1\t &@]v4`8paಆŏ(`%GTc=1yѮ)#Ti]jjR*+.^*l",SCVZO qw| 0)PF P #1.kCRd6% WRpf~} tCZ j<'riMWR?!]@ A &ĭOI.(JHiUQ2> 渁%@BRȡ4B +dBkaŸ\AL E>ԊL L^A&Lx"TMBLuMHC"Rѳ@.٠eǜNB@? \qRϥ 8L$%U%QZOۜ|xi-Jx,NA$V*T2t.uXB܀9MJ@9ZN[~M^DeX-ooԆnm`=@=hΊ>@v+ EE'P^WmP`$m%x&ͥcvPh2Ip^XWTk%TdgWD䉅 MJHѳ\BHR$JEKAhg$O͑!JpRJh*qA^xH@DyMDzN҄2hhx(u`aQl(^NfNL hD:vMhpYZ4X"&'֓AܢBI8aE"+Sꅊ:L DA JbvuhK d>2#JwLDZ>qIc8b-A8͓#I(,54B%bTJԥX(C݋[@.b5cg <H#>UAio}XUwta]]V%HFzZJYVA^Q} a(If㧲l8VXDQD`Oj1?S9}X i:P-ŦvNryh Nx씸0I+lZrbJ[x+Hm ,ɀ.LI XBmJ&cAdƲ {: Rׂm؊ؒmٚ0IAC|u"YLdF dDBF,Al?x_i6+v5AʉiDc61C6{ceyqTt=7xvEv;ד,r$6tה?FvGvGw?,‹u]GH@Aj XIȯ 5,i X`zPkr L; tMX$8IhֻUUmDWl鈀@?+p8ki]lu8IpQW n,)9W3Gq,7BOL_X+n&n!ǬǓ,Yql"{ tCJC=qӺq=–:ۧ? 4xaB 6tCa/BxI?I%/B^$O$RxA8 09$m)iFq I O:P@:0 zO@C tpB%¢X)~Dm:d, qA^3?"ȳ&V.h jў\_܁AE=_Р󏀎?iU@MR( RWRgȍ1 ݋UvG:g#wO@ȋ'0VOG@wD0L`qGg >(بT)G@l,:I$>x4@'&"@G#r7kZ "[H0/\+ R"3ǂ6K 1M* ,Ǔd`H` Yt!,- "Ht),GM 8%A9% + BěFᧆU<@ "[Z, *3T h N');'hܘ-YLXZ's)8s\-!gL&>01R 1`=5 d`Gx6ī ?jc (d/:NlBOi 6@`'uH gr'8DE6:zS,D @tJ WLSD2Mq R뽧-D& dSA8EQJ6  lpVBaxSQHl@H8!H,L."2 mh$"&̒p HC l-"(RHJ$C)(2'HIDF? s8C8( qIL"0%B3M#))y9zY\Q؀. A!fO}@QBs\fqx"nn&+wpB p'͈Ah"*s `R`9ÔFf:-DyzX;Nb}1c=1fDl-Yk,`3%c@zN)% kiB+9E&QM:B:|2U+ɤRJT:&1jYՋE*DD$d eaK*g' OӢ"?f KL' gȂZF,@j$E PmODQ6#{JqS/F{4@jaȴ/MOm2E+}[;xQ!q77"#C2Y ֡" ; (!&" _1c:xƆ"bfA)#=OkGE D"EBIAb3 6j&=3T3i w b`G6X/>rIA&4G'-{澜O +xFiz s+b- %l@"9muT "^_ +kƥ|1t \mkdžǶ@ÈjD߭) oskEI.Aum)vZB<`^^:!uu5 DyC)77Hd33hKlX*9@vN&1i^sV^d=Z>C:4.4a|/ҙt?=dS%Өb`kY5^vV+q^ܻw13@W< "AT8uB+B*X``5:nAau-tD (HHAI5$=A0ٱ< ̈́KA9hAO]]ħ5CJ 2p"NAPd`  O: /`R   #@.A ,2.>ag&`n!>brA0@ 8CZa `]Y`"`00#`a@D[ldԠ*CL$Ɏg-XJ`H/!(jHzn, HIMKd kkĶqop"FjS>AdFۘD ($jR*7laGDSPlFH bTJE @  A k " ^&AA)X!T&HA)f"AĔl ̤Z)b-)<+[^"K̆/+!+ 1" X0*i kqPF[e@"&#ݥ1#%n#&]B~/"F܊ RKf &-ւ/` e7'33)Ee6&iF$f&'Kh! "hN\>*$] '\ R f+P b>:E"61@j < s2{ ,1M3, +vpؔ vf"zb̫ b6"6ۣ$;Nj܃ f4 I1*.c|8r"4G ,b" m %#&!(B3q }A)D`}Aiaj`>s(`c]'E  D ⊶أ~lEv)!@ D6jNCy'Mbґg "1Ft/dcP0ɞS.MK B H46@@@|a>@8c^'X!&G!#X,LCsA XaAA(NL"` 0"@@# @#|ZRP 5@@ A8##,1*5,~H,so-t G B^C=7b dT02jҠ B(F J"q4j @h:{ht$+J @# 6o@R|@%PM  0bV &pK<ǘB@^NK*%` +>.jbJ08.]bp3bn& K U}S8SQc*8xX`A zZY dRpVRMphq6J&䞤p>.jJ*ƪ_ZRU,bgM/1 NG" 3nt0bA{N (!2"*~gSzqx: A=)7ECK{C z,쒬G!H [ B {IkJtKKw TaLtTytMqHPVcּJ B $խN0ŀ 14)k{X[nq: e)mUA (  d&xHfv8;Nrd.*.pQ65+lBkb"V,|`aĦJ@ҜNkiުM&DhF☌(qw0y!d-YcEYUn ?pؓCٕ_.*t tABfe~Y 8 y PD,$vUٛccfyWYיٝ? I6*CBPZ $z1ڠIO t LIt2dyOKB6RV p  7!K "T"A6t /'l4#ѱVa[c@|RJEIpy4JJ7$ "P$@ vDua-"FJK=LTUC [.2L;>"4 +A `A;O 1#N%#AmC:F6#_+q1ɶ 3@ ?{ { 1a[Q`yЬLȡL4DS"{$Z*#9#"'"R6fg*e- 5#`%"S?. f Ri^ *iJa&kCn.n؃-d00:距ok1dR32bLR3| "R-12"]F&eM]1b#fA1/- F^flOdD}z0R$[j%2.{fL-iNQ"7m, 34YfM:*"y8]= 7!"{ "bcSsZ :xrug47 -"7'9g9O+:;7μb,Lgx")"z0na>`I 4AHr Pa~ "E/K :&7Z j`3{@#!u8 b bq+Q tmAA ` >duA o'. A BùoGtv"e͈4+bP3"=QttLJ2ag>qT/ A 5o!ԜS!\}0b`Q6qFb@Fv [wdZ*y!m9,`N0!Z hZdHjiXH^Z[U6 _o?oH GPBZ$ t8AZq?4 60dR?JN7EH߄@$hE1D0d % HX Q OX":ޜKݻW #F2$wq$K 9| @^}[#hMoe_^oGp΍X@FMHGA@k *_(L W0 Im`A,- Dp"HLaDG5t*B0!A!2H2v! 1 bH:x̣8Bd!D0؄:(?h -&M,YB );Ivlۘ$2,Rhn%|k.@Dxtd.PB/^HLC&Pt`D? SN Mb"0eBĦ!!""nЊ h"80)LbAM0}H造%W:3  r>yLAI<)^@U0`䋉8灎` ?d&ȉܑ5e""H "AbrL%[F9N$CAO(Nfc $^@@n,L) yH LN vzT  РQ7\kl|5 sMG rmZ*$m7B4zqI3eNHUYqk[8hn6r:bznMP_\"DbioAU$DY)x YsjHlkCXI,#)e*32)„;7B:WDzʒUd!W^zj$DZ1+|ùMpQ¸ rxQK6f leU_Ջ< vXalۯ d% *a it٩벌JOQ.B*GGrͺ`B&H?$‡H}҃H$p0NnCʎ'3LRGlKP}4\P b'8i&O9Q61 `gz  Ob/&>74!h#2r"Rpd@CR[ 0! P!a ;!+\1eAD 3#ۡlq77Y#FQ}wGEC" ]4jm=$|8:y "A ӱcn"p?sx%1:elbD{ GHFx#4,Iҋ||RO6FCF@{$M0%HU+!2"9:!ҍXH ȃƁcRd|~"W}h(״!G!:<NA)3 \! ф B*}bd:;q%$2$F1+70r 0I#y0`em6k9'Fp{5;s6w/w/YYxYeWaT?2GC ` rR2q7~GxY25Ww!Q2mB93Byߧy@# l3 GzWzzs15!qK7v3F%J 7Ӧ@Ӷ!:L;+<`ј_ę @PP򘞱ndMVD)ԃ+A=InV"S)nF3:daQaaA]xclX* '1GqdzG.t@{TD4l^F!Ux֑Fß ?DԦI9ZZD} TJ[)WFwy&z(C bPrC2-F4GC*<ڣJ $~)>LڤN@dS s*ScTtIJBJ2hTc8+v  bC 2!2"b `*rKH%M5NL!NPE*jRI5{NP Q"0PO pLLDEHOLԨը"QzOä́L5Dl#RJ2 F$ŭGPaW6Fc :C Vv_C]Z. QVVREbe&@a6t Vj!TV1. tavx ATT xɕ]EY RZuZcZyu+`U)yEK_IF^uC۳; vuYYQ^`JU ,XN5XU]skL_U/ŷhKl [[Z>;G+rv0 %>ClgS  @ yDЁ a:Tj̱iAfvf<Fd"#F_û9[J ;kZcV"668 zeP@afi!ilc"ijtQ&91( "rV+eB0z1a*6vA"%U9hefTa6Z6J8 lqb))hkDBSWU|CRWm@msG|ŋCm7wn16p!!ǮA aG6pA& 6"=lns mWapm7G0G-)FΠ Q45E`7:#Z:U7sZ7t̄s 00W q5(KE,Nw02B!vu I!Kk޼l{*uPa|+wΎη0R[:;a v2*,zgxVyx.'WYٜ9\7y0_ ``{qP З`475+Jti6>0)@38An|&1l2K g0y0"Q`7@L)|Bp4q8A.!1H@!D @$0> M"HCqM18/8arh ɠN Hci-ʤaE b"V=I$ ֓0HFQ}]0!X&)q G *M -ܱn\`(j-|wbkT ZWn2鑆9dVɄm4AKa9#vj#}c;B ^?q}1mGr`hF`:M"7ҁėg3'} ѢRcA4"# ?}'!'pH3x!]u=qL' $bڹ(b.nκ8Is&oni AG6!ă737\"ܻR iD4B.:D! ik%P0771 g2ep~+`B B̐qP.a1s0}Ww=9эWv9ii1 !37!!q~ 20B /"=^9~kxAg\]൓ FZf3L``(Ҥz+F$zoq< >qdҧcu1~B5mm)) s#ţn6oSܢ`%F@=m&7 W l3D/zӚ#<#5QBk(lA=e?igC iu0@79$7"@Ḏ?a]*FO5Ssk02b#1`5@@*@@4E*ʿbL1yp_ ؟@)+d_!O b >>qc aBPk! # lJA!K$'N- b$0 Ht0$cB"", ?':0pb9)/LϟJBhB.m:TҒNp +M(k͛9 @3YP`PT\"+MQ?:&E?qPxOɆ&5TaǝQbgxR3 & ~H>>vаgōLAiw[poH@ R.~Z̗dR`I蠋Od"N=y QU@@jIiR8 j^PPPȸ%/7ZI WR\QWc×>|!7|2)CI H_c !0БGK1X%.2Vɋ Qc*EbA0NI0@ J^P !J-=QHDtJ^0h80Rj0Iri^1(xI~ T!RUyTa9i-l5lۂlsK3nc05xpԍ7x#_HR" 08{@vF@ .R>\ jj @h烊+am,f0  .Z@2. AG! ; f7ݩ88.*-:K "z``cc{(kM;J cl P ~0jO? ڻQ9xcjڽuxoChscu,- DŽ7xG! Z(h ͧ ^ :1 |/R^׵![ы?d8#^{EЃa0@n0wv:t}AN|\?30r 8H@5ctYI^P >? 0@M{@UBhV–9R(@+A+U#-\?QV J,%44 |g s1 rcP8䠁l g΂p'_X D y$0HKbI"AOBz%RB1Co$` сqE>?DA9"Df@9iV*SEcB LS@zbсQ)K}` HMl 74 *YC Agn@5ڂR0% y?bJb8D + C hD,ĢAT~1( T&8e.ѺVy oQx=@\ Aåo t4ꁸ*OK&&%k>Fm W֍׿g[Nuo v)aXؐ Ab뺩 ѦK`jp`4cb3D!b}M FN.Fa @ЏM 4)Fܮj S0u9Ph5]LˊQD2&d-H Ѓ1bAj₰)49W9v6MB @Ud RټtA<;n8ʋ "m,JOjWs:q׽Gj+Y j($E5!aP/5^I* ю*) ! Hiz$ mMjB_,rF)VP4)5TQ^ǑG]ҪG01b3G3͂Ls֠mP/~yB L gcG8xM]3|>5PgyK D`8B޼ap~l @#pv_%6`/iAQ%@N|m^s-z宓7 ([˨1Y]rw<` ]JP!aM$F=bOwnsɮ7LzV:vֽiw@7W:*|oov/ߚo>K0=u=`?víQ\p%؅{+dt@061|(&`7 s2þTdt@B/ປ {/.z Y>h( x?P%P(8O"kMK(8!06C)1C06h,6+<<jPPJ؄ 1ļ2*=H34mPц*qX*IDHPLQ,q,NĴRHT\Lh@ D( ș0'X'0`p Ȩk쌿&2P xFfPƚB 88LX0\ؖo>0(b( ~br +V h"J"xCj0)JM1IK4[DID)kIi Sxȫ-" [ `# XwD\q J21tX*y;$Hwl˷ppKx`UQ;€IR&3j;i?Z9ٓ !GV8L1H FqL$02I*Y#ZL!VxߘxeP DQkdieyx0f)B'0!n`X{ B8c*i!`a隠B.V{!+/ πЍV` B 4ֽQۘ4f/R=.*0씏"nĀF6` ޛQ-ā>Oٖ [:~JT3=Mx:Zi)T"#hU+uxBhM<$ȃOf3F73,峧83DD^ _H!"K뚟".;nR*/˙ XEK4-D! I&:+`h`X<,XL+j0 TP.(a^A| _Έt x6~z%X0dʤ ЁY8XBJY˖?YKXzYklk@Z=m7?zL0Bf)R{aۃ(ۃP!}#@B]Z^2y-neimM9>)/j- n# 3| UƑh3(< CX#Sء &yw] VBŗ9 6B=P p%؀X*;$ k{BޭAdVI^!Afّbֱ%O:($w%O7G. ?zed\7@A;(XN|x VBIJnk$@dK7SGu7Ah,dbWNZ[\/lְ0F*@j9u4?`Ӄ3l89C=k,8r_(w@XP+(i w< bTPX61'9lX(,J_wLl Z|[dihD` 4DPT(FH,/A`ț!@FH+6?$ƆXBC4yhH8 *F0ƼBERt,GKlb fG0`H"}=M&Pm`.}* f,]Z-x#M0pMͧ_8@l ^9,gK,*p4c0B2*ZPHt@ Zxe忥8@Drk]P|4!D*G[*}AowPZhS$EU)i៛=50O_" zBLsWC`5_0!fI.EU#"g=Wa&ҤJ2mԩI! A T ះBB l@:CB"ݭ*`ԩ1Ra&VZyjR E E!5~ 6jaS @5!T lGn E-P7X4[{B {##] ,E3o#]h9KE UpAs{EU3-\RǚQ\^R GaEBhWtR<@QT=#A:壐I}S9Qg|T~JQSJ*W)Y3h[Y ^yc>`A{UO AReX Hgp ?!d *]E.?~E?2(W' 9jf5,$3mPvgSZ:2̴KuFԪT. M"LW` D"DeA TR ̔(RXteZ̒%RUEU ƙ)-lδ0%> ))TtHBh M QS1RR?4.+@:)N LbW2 .3 RI"T)7 aF G3W"HVqRoK՝B! Wq uiq?vhFI\J0PO5s0S7<9BLE@+IQgɋQW E&HLaeR=zmD/pwTKA6(|Tdg>Lq[ (5ЁM!f⃶@ H@; Mh#Bp n2H$(]fBa %{dwܥ1겗P24jU`8IrX|ĮJ)TJ*/W%Bx`2^蒘4@{,FFc.__Ä.]uPRK[^i]̮:a3 -aS@"'cpgdb4S+A "*,HRT)(pa&A1{Y I">0Q40 N)WB"*I@')hAюxIљG 2 @"p&I,җ@f"]ӪֵnyCL҆^,j"\:})O!/"[܍X.rb.>Ա%EdP Q,e31f,;~4MdҘ6EjL Ld\3{5)}?v%n_C 0M)M 4Q nuIIf'颜xU2 ?NG.種sumtU O*K @Ytb]<ET> V^@ܒ+t%l`II1keYik+fbp`PeiA]8(AR[MGV K]NeS+bzJJ*n.[)vIq(;+J̤!iEӔTLL/Ll}{ښ"%"NPݖ.bn(>K4e֭)fS&1fb T9P$:LuI9HR2JiهW!?9S򕳼..(GXJ\)֘/̉]l7F?:ғ3[#pz&4 JRn 0:ޔSf?;Ӯr0M:g⁍ǁ~{QFa#%:pB~.GZCĬ3ѬA& ,$#}[0*?ԁ?^gI5Rj H??H B"" B BCDF\"'p材B ,"m@^-8epR> dĂ0 |VR$a(d^R$-_m^ь  PQy$F4G d m=M_A_X8A+Q.?"L,A[%,D!..H MoLC$,Ԥ 0DDU̶tQ?$-HC`'`?P'zC"VF$N"RKS4"3"L@W'TWD(ATL *DCtlB- DE\DD؁+AA6THPC0CHW D͐172plŎYu_fHPHĈ[Ge}i@70[bgQD\L EhP[EW@t9@T|[@UHEWpIMVR *)Rı\)b+qي(]lx\'_-EDe<\RHYʨɥHL]\RUz( 50EsL聶xщ,ШټhʬۄYʔh M8E\l9KmO8CLÏamLXp)hB6'>%KTK`RDWR\PL&_-"߄E-Z?P-x@^ Ab/ |;W:ܗW7C LrՃcь#SܤR 0L/ģ0D0`a2-8ʭrAhRװXR |"pi /Qo.:T% a|@8(&e*5YdxJpǒ RdjyLi])RA-hIRivRLVe!ƙeQ`$!<&8MPL΄44 :E!S.LYD&TƩ)E #,m)E > D ?INO Q͔_#T@+,DR\RpҚfVRrj5 œL8ME0+hLH7BTe0|DDs,,BF!*j?(F kDJcEt/dݟG >H &58Ԡ6@E!14a֓ვBփ `pD /L3!VS_Egj,easkhVP~:F[RS_-I%_fkyLSZIWqkFϞ,TtEztvƚ}`wx@T7 :$A:A:X  @ % (#hH/*i.  ?eQGF y? ?C'I">P!J 8 Pa"d߲GBo'UHY˶3\ cOF|iaj|#E'yƯ7њf:!JjzqӞ\$*%'0(&KBF&]!d KuS0HҒΓD$tų]u#+8%~IG~AK7\I;V1_zc_IsqW$Y` `8\cl#a$ !/ DHXLjbpY H$L@R4Ib*z> 87+;;74Ѐ$6Rn$C@X<9:$H$py gBFLɆ:IqjDOiU4[\ÈZNCv"l I@*Ĩj hcz`Γ?%c${C$#$oP $XA2sK $ A$84N `Zm27pJQRR#l<)o& g%E HF2c fr@;20.$!\MnrDN~ 8Ed%[LQ6쁢 Ǹg>3ȀBl F&Fƈ!u*.r@  Yb2,lvCTLVȢÇdQ;dSB$βqVIl&#Dцv$@p˚Mj ` 5)3B}$2;KdLǑ'rLB&N ,qІ lӞIPWjNvy Xv)&NK㐷%2/HA*#/{Nl7}T2*Hdҁ$_dPJd`Ah8Dr3Gʴ\eg\M$F Q^(@"IdLu-\}R:iKRד@Nӝ2J=~Kd 5.&5 Ԥ0PHi4W(UTu z:;c\ZkWWr:"bHhB UQX?dq^%4{; ƮtmQx=ŗB/Fz!勋PE, (E2Y LX:K@r*Bi h>0-`l [}26(gBѩNRbd)A~2`&Ɋ s@g y2 |6Pd2`,C >Mke6pQ&Q.rb/=`(@N89+I(<6E@V -`VdC 3tc6HG)i2s krl}ԡ,OC@6dԣfUkrIRN*n#C.$aWl`:ՀhHGZyK5BiG ;+wv8ʍ1ˀkM=p+W=B`$+vHgqd~13"+pDC2"7Qrsٟ l<+/;r&9EsE7ёtɄ!aN2(c1C Ժq~s}s|w'( pv ,h“ l}/dB8췪K&#u\$:q 10@>\b\gaI9ep =2"b g@#@qEژ&L7 A0)ُ0S'O U+ݞ۟~P@8dD AETpb M< f ezp02 MJQb@R4KԪ5TR MG!F j V `zN;>v$2vf  $p;@ S9lA}B @GEZJ"Y"tKb IjBf^AT$2)Frg ,8ct"4 ^E A`@ҁJte@ ZF-Ԡ^:@EQbƬ fƼdf)\&/  F%K,n1[jPP[ 2GZ\T @Fhq )pT\ * #1̈0-P"C%B%̨ @T@{B%p"l?fոB}B)F $dr4(Z@8&',!n..`.bb*6!5#<[/B##)+&!p&!1@D  .h&('4n+)~b͉C1C1"6H<#HMHL%$$ʦ-(-@!5n|,2)@.^BT ^@!u CˊK< c|3+-@r6`AN??3At2L:4y9'wCH"9OBB#64#R.bx-`F!pGFdKH _ʫ`Z["Nd2:!6bG}㮐2$Pe fQbl!*dbAaXqV0 d!FT24.G8GTf |X^|dЍ`JP [셹]QK"!-a ` D)e `q`ePrQbYhu \`UZEZn! f z$vg8gi`jՃuA24R@4AD gz g29!4|Uo:99ĉ&'@qhwЂ5B/hww PAf pbbp^(B7 t6fg Jľê j*Jj{}/'2&}@`;6ho"C DjA)kLVpNȃ`h,(\4pA8v,F62<C)Ji3,j{nm5;V6 )⅄1#ڊZ.$e(9"蓊4IhΈ<"ӏ†$s{ (&Kbm BZ)?c$i2,p.{P@dH%U"VӓuȒX*ww7BR:@ 4[5h#dj lH'v[-ehC~BÛ {6lߖ=-%Cr RIawS=G$G*v;]Uc whB 5u26R&@eI-$x$r*8l\}hJӠφ6i;j,"kSj"2I2Ėe8J28j 8l =>j RdRFb+H#JkNBe[Nc OFʺN m;H7:FBYaJ1 XVOX!LaLtVM¹,N5/YЭWtEGPOKOE2](]Ұ]Q9^l!LC )B$ݑ PTe92e8Q.r$J! 2ˠC%$ 58n,p#L"ŪB1BHC,f;W,G(*= =Čo 9vNvCi)B)ۈ yf͚Mo8-r'j Moe`m&::#E.zW<՗E@WՎ--1;_!Od`;27,QEN4]a~KK -cjxt n c;&W,ZA`|"-8;{;{ɻ;{ٻ;{黾;{<| <|!<%|)-1<5|9=AS^ =$ݺ6$447AH$Mc-92t2v[\卜c2^eJa0!腞>##keh~ioǼĒHbNt0b '81^a/ma~XA ء"8b E@(8:bi /#F٠ $BLR$RNi`YTne^'}If%3&k9I'nifzvJ} d! ,BW H v8a`j{OP ah("٤@?)"$,)•0c&ˁx`ХAs)(3&%Z?Ml8P{ !u`J7DjHf0xƟEv(rbJF$K1@p z)ŚtNdʖEG៏I,HY T& +l}j0BȴyBu/ L_ZOXdt lU!;J>(_YNX"0dY)T \˴+W."{@jUeZTZ*e,- dRAA`PB`RPp@$I95,JrH@ ɓD)ДP 3@ Q@j)`?!l@cDƘBBn"1 K=2㎅5!%Ud Tp@P@A@&O'!I5*h W6TSAP>ɤA,J28AgP]dRsAAF`?*C"X@l  .J|mP-UPO0 @Ath!|["0 /pK@P@PЩ0L&Z@T-O??lTVO]8+ºLQ0}uAdv -B^&@Aeo[ &)-o=xlIK3 T9sJ1h'$L?^^J A1?X/T|BpaMX? rH@=c`*B\F9^bBCN/ AaS !!)&ȀE $&2D+ p-T>@yY1Q(x{?0a+VAE 3m a?MlQ>ЕHpb@ѡIp hVTCj =PQUzh$ *H ven1 B Pb*8E =$#C-$C W%aDR`L Rp "i ND:s5?0 hByen(Z3 5W&#HŢffƧke,(IϺ5cfMɂ6<^pYzX-Vhr)8X2 P/LՂt F[$X3Dt%P&Ũ`:(Mi|&+6h?2:i Z d,؄!,lsB2BĩO4`eAJM@)7M@x%@ {TT 瀝@ȗDGsb' . 4˩6BjuY dA2Qfb>I Xr)ɵPQ? V!F(v|`bPfp Z ;I@dFy0A9y}+`n xO (8ek VX`ɗ e_t H+o1[$%jfnNU$p{:D=`fhWDpjPb2)0VL1T[~ x d&;UG 0x%+.{^.fBݨhN6pW,$8Nr "X~(d }&NhEjqJE DcD[T(m)i趚 | hpS7)j24SZ06`)VȺ4`=. mlt# ZE!7 \D#`!@ 0!V(B N'!3-Aδ: eAt( *P (-jЀ2P,dь=s3SUf *'PX@jKU+ YP$=\.Keꙮ&BoHt`eUEJB)w`8Ptj+luO!\&;[vF@a`^4 |&"a+*cEͨ```Ac[Avp B @  Tz |Wo *GzGz{P GzW W:'{QfS N4v&@a47`eV!!Y$!؋-6M$*'/&\sAFۣSpa&agV$tP ?`DjHb0iatVUJpJ V>0 |q}0`UG mGQ"9IQ Gp>r<Q E)h`KRa!t"MRMXh+bDM d"iO"h!hN(+).,#O *T)0/1cQ0PU4&+tKWs53f&*ORTbhhX/ pVV7I$ׇR^` iSl"fBg`SQ qkVbJAWYJ"up^AGi9GY9aJcJ[*e  *S"p71fSyW33(!4_ iE8 P$؅*ȱ]] ]&椲7CƒPcJXT:6 c9,v5Xc"'N K3(bb1ecMF۳'K;۵^`{4dPEajr Nbvɶv{xz|۷ wq/a$]liɵsd<s!` E}iiBDcAtQDpp pg+"/1mK+7qCC9cD5+BqpQ aq *sP f`|fp a}p d<+ 1;Zr/5v0[5e5N 0l׍%Ak;6\+i5 uYXH[`B #{  * 9  P  \ȍȆ A@  rP.R*"8HW""X R_O,]BP>\TL 2* ((#"U20)҄+$"؂ b2(,cD&2,:3Pnxb"1$#e!O!3ʣ2RSc5ȭsa1a~TdX,YR25 q9E4aEV -5G]>js JMl"spL[V]Q0R<@>X;A/A%TbYW/5Wp W`*8 `Vu1䙠IqI#2qZR~W ćR=OYOjvN:2 rTB"/%"gM)DX;M4 B4Rb)!NFK1dK$Pb硂0/*7џYg%2ah~Lm)^Uc qӪ n"@=F`FQPA&=arQ< a ;8 n3K 4T4 \HF/vR=YSM gW]l  ^s8 sɡ|ܻm} 5_} M; ݴM:c `])?;&b62Z8"cEae*RI[{}F⪀oU7NDOf>'] P7"2б*a%1- +hMU}Mc 1M(sTꆅs5&&Hk~QL/*_3 ?}iaO !B?o KaQ/LO?/<2P9&PX^O@ DPB >QD-^ĘQFᤠ8a#A0829#0E<0X˂0@ Ρ'#¡0xƟUX@dNu`䟇a4G*A0 p.Aa8k:  "`ݱ?pA6)fqсhR] Q-D ;PADB@ӱwlX޽H`;C pp` 6a^p@lRaHܗeV" ![(TC g\6nqҫ[, 3B|"e4/nM[HZJң-ދ 6`{x, 5zDAcYuGFdJwQR9 YbE|#xF ;H\@0Bzpt % An6iH&"2`$ $ƬB1@AV*%o_d9j  D Q P@" qFl9]ʛ?:'&,͋_lD|bp?@ #$IpA`]G$ !B H?IHriGP< czT, -f|J'8v` T)q lf7)s'7TtvD,\eO 6fF |B=o;Y B<@tצw>pF"Cl "Xݐ+]8K rCT:P`"Ta(m2+309o#bAms 0 (8Ih# `B bU2A'q*C(Neg qI + [BN7 Ezg/ i3TΔġUB늄gҝ8'ˍ4u5~PCS DxPC`Kxc&45  7O ApUW`CTX ݈l7>/IlԀ#M):|1Q5 < >%nXPPtJ"hp3}U@?@2?p=P\K,Q*#UP r{خeYuX@@1PA뱙4਎qɧ(;B:bA Û ^Ɏ| {Y! !10rQC6 !2 v!C h_ lA'I|DWw8 (ÊP ) cHC11L74b443j` X6')I;"'kF`FƂ#pFYG<[88gY](GGK:8ɤ1  н >aP 1Pr8p;Ss8糏>!gpRcg?!pipYٟQ޻?ЄT?j ZJJ@#B9Y ^=J>Xxq8Y)(\"cpR% YR885b:H q# _"He:CArڍH$pH&tH褂L 8'Hg jx:M#Xz%B (8&8;<P-ZMuFJHaL8h;L(LhKMAXMz/k{ C^Te$FX*yx PbByc,2*m)x =ɀ:)QC)(4L ~(,)E)n 7b*R^a`0[RPz,H`L@$Έnia`ѬSH+o+8 ͫM(&ar9H+H4U:Sk 8-j@u.n3 -yX@^0ށ!^z7yB(%IеyN;Į-%gc#|"% 9H (`~7;Y1 0Vt< p3;@M LkPMpxซ o5t 3#&hG2 t `#@ũ; 1(`9;c@2hpJPJ8V  ]fT.zށp MĀt8zV=vZ4TI&E5#u*A^m' ,ݜEQ(I3  B![DC8 ~Ң2P $b䈀xZLŻ!Ó _D &B(sBaQD-DA?;5웁ȀќЀ7 P(ΪH'^P[d_d4 P0͸h0šM.CÈYɛ];G"ByTYFsr\N͉\FΡǍ6FN -Ox΅p,n5 _l J; "pւ I0fh8xae@pppYa嶛< ^iEBYH]M_?9$J(<{ТQih]ՁX񋂝ćʈ88<ֵ_ X_)8c%;0ˣ+,8ٰ-7U̜kŝlм̴a<><vt_@NXx:rLMR\ݪPScHM+Esҧ/tdMګhǓ/Ow^n\M@s; .nB#CE~)O \O UFofVVO:vex")k FlR#P [@r=DdG08U?Ubu]dT9&ey&iWRF\AvV!Btgs$X~ z`-IVFmd@4i`~s?#Hr &,$ D}?6$(mCP#3[9fL4#h9>KP\=l.$h1z?v@FTp Xru?$ :/,8ș!  q L-[ H̱P&pWg-D?ն'fK#9j1fH+EwB5S-s$$D-!*(`/"4"?o<$ Yc*9K.ě:.ͣ?<歓qw5n|eP\Jƭ @P%)w%FKW *H05B+Z$$L~nܜB'`f]qRfA` oADjQc@d* DЫ͕A>o0BD:P#|+ Al @"+| h <I)D|%ʹb?o' T2|n|;Ts' *óHK2-<ޒr  Ab0ЀÇ& q,Jg"xE5 *l` b24! &eF&t̑"\!$hx@dSHC"!(1F'7r!x +f5r.Y 6!|P/ xzՀGܐ4 0]P\7x$V"#pjTq !xśs 2 =Zr& Tvx,D?W<7Š`nWy/o+D<ɖPi!4!< 8 0N^i %.|oׄ? =`p#hOuriBF)1u4]E!w d$@n$|a[ UG/QfFp gb+Do8 rk[EėIB&`-A`K?b-Z?z2rmXKi}e9weX[.F,h@rہLWAuMsyw y{YSAFz | $gn AzlgDH]I?6=&Oq\4? mipN^NZJa0&HEJR|/P1xJw A&(_@ 3^XǓ"1$ AF 2&A<3`73IF81%Dyt|#s;B$梌 B4@vjQc^K%"KhU½]$D%H5.w Qsi| q{;WX㛃Ir=W_b*Q3w8D>*C2ds?=I>mYFfSo& KMC5P?a>l^HhV+L=X w`-WW`IsA. 3\q(Bp:~\yÆFH_1C{_d#y.XW؏-Em@ H<l`].ENHIJƠ g `X`FASt8 UfF ,JbmE|Ynš8?8jZ@\DE Tm,lBK8^1H!І} M$,9? A!&: 1CLo"A9F#A,4 AV .,ڰ A4?\QL  AB?F,!Tbx! 4Ds eLtը?U(D'gEa /a*A@x~xDD: VB҅'AԌ.:C'(?T-B*,D,B< 4? ?;4 _:b?8?(?d8\?DA>M8C?OP,: `2,%A:3_ׄMtM]B¥FN܀BP %dZBBTAhZLpBx?jA 'fͅtMXMA)$mL .h_Ye$]DX숈mD`0WH m^G],Ud_@ \ ˰@8PN!P\ hXtONIHr~ƫ`x@`tARFD!'QZ!D OlqVr&@@g(V(@`Dù@` D  hJH .^9],Il D}đ$RVx:D (T0iM f&ńkR)hdWFt)$UI4h^ڌMU(GxxVOTE#/v.A- .xAF(@PB%&dYeΫ,;4ʦK hB\K66n?COCQ?d?,Qb h,= =Ͽ98LH /M\M Aāoԛn\\2ߦ\܂°HAp?o8k7qHD`+D쁣I|bZg. gjPilf>)_X(y@@ȜDPmgA|݀!tD @fD!$6D"̀3FH!raZ˟nm`~Fxb#?\BĂNtF?dG8A D"AlDVc@--`C 4Q2?N0wu$"\BLdž[h`U.{}Z]QU<^LcPLA橅`đ`] ;2R\E}1cLŖ_ܨGUC,E]C.%a)1XEFD@ a-V!@,Eٲ6?ȕkz3,̐Mƀ .3,Al:Pp3jCw8*A-rkx%K AjE o nn^EgK?-A8-Dn }Ax},pit@WPeh@NF0꾺ZF;ĦFȦ:TANj?9Ī*CI>F>-=‡Do_ f$ υ6G#\I,6S*cP!@hZ*OdK3䃁98"Vlq@c/2k+"L H΀R-S(AT  R!`0*iIժЮȤGʙ(0;hjԾ~3!@5;, L6yJ(Ֆ IH:m2>ZSSKSI/uP&, F".6Dx&s-VbM@Ϫ4 o H x:x .⎣ػkc+6bh锲J@ CD)AdIAMbpU!cET!;;D.z6  bjYeOJZab)lRJY^M:J$OIa⢩,{#&yIWbT9F)g2:.ȉnޥJxdHɑJ*蠄o$"(CNI^:)0eVskbn 䙸[!X.KdW 5-ڲZ Mퟅi@´Z.A-h *`ЄL8LԺg,3cL PTHo ;@ā\ 8 0 %=Iu 9!%@ZºX #t6{cS2u'6US椐' Qsyb1eEJ R|Ť3Fl1Yj>G$/w\Ǻ Jc21Mb3ka?`t+ P3'H QH=DBDSXVi Ie%sty @ M ؊h UO][.  H Fĺr,pyH:A tӔO4SiWub"˥B1PCg)h@$ ch6XNDqФB?yXU*XUGvvIRn SVPg$Ȧ4`€HbaDx0K|Ww)qZ쀋  {{Z㕌s;ڣ%Mw95^BH# OUUp?9j?0P/iH b<F@ÓQςUgI 4f1t]jTPD'vOǤjK@@0T4> @O)ˆ/8᜔6MD6"ye%&]Vf",oٖ/4/=|TX4.å% ZD  #C(C0a i;晰0¢1!v % F9Z&JZL/F" A'~0f10.Π1<6a d@@`wP$f6AFi8Pc h @z.3.Fo$A24&E.X(KcK2ODR:%KVҲnOD K16m5N0Ď"qQM.,-T"LKX>:K(^e. hAXhY(ML21Hϋy#S@/Q|f&Yb؀E<8MC1hB.L5S0L!Jt #**0"C)paC!1j *W2<@D.사a`" 1` \#]O.uf^yBV dVæZDGM!JNʱObuVc_mE)(.%͘jʬs+ɠ,nAm ֏&f?1 !'V& j@v" l '1ԡ #"!H. , e"lq@1, _ /+, b h2w dlwD Qnb@I*oFsK3> @rӏ:-*h@>E.nK V&NK$@.l-ʇ.-2 Fz8mV4{O0ކbܢLՒbPABxMJN-5 f"Ո 1*)dizc|\h~37ڔ .xPw HܲʷҤ8XgC{=|W| TќSK"a",,._S `<)ڈ..t JHf<$Å> #aS2n.ph,tJqT40P(H2cCy\DU*S(3n x4B+S!ά @24&S v ouKNi"B)bZ5~pR*"%BxnB!,AԡzB""a I02t:LO^ f$拒pOD"whY,yc3ibghK!OuSY!&R|r z(zFE ƇyzDCh1F2Foh)$z@ ifdqd 0g=C!ģB<93YbX):.<B1 E;A YPEVFB700fbGcv^՚BM! A:H I@@bhGF2{ $QL#q/CAN[QKx&8+QhT21N LT3Md'D)H0 1KnĴE19 >H.($HDͤ2ܓ{1˧d%kaYb:(n#c C%[ s瓥:YW`?c&\QP$Q;SC2ܻ.HX13p;Qit*&nH2<({k fb2Ywh"L+O96擑Y93q{(pܪ/9ĮeBso*=ý!  bIP(#ik(@B@(h gLŽйI{ h^@hK4,:4 @E 3:O=:)B 2@:ɧXFӓiBt](`EW@zS!]!Sњ)1Y*DQ*"% T$D mD=ޏ3IKTQ0 ¹.AJ2{d^GǪ9Yl4 cHz--x!hGJKJ4>sT!aKKeiL1~iMЩJވB3ۍ2He3M>~陾s[7B4 -3HV >Y54 T6iJ"cg&e r YJG"It#$.j~ViK!p+t cOK)2tj "JDBB5)p mJ{`QAP`i ~%x|GрHi eBz VLs h2b+o)fuBr3i)g\xvL'4d6,B0z S$琠 .S^B OƑAn$2 !8!A!;Y?}G/ - ܴ@H5<C!"`P0RZ,QV%Ht vHpRV+w A G`!:$ Y߬) uO [!An* vٵ @a:t@ $!(Cce x8 h=_A?V_m!PYPS@y)ݧuIƜb"aBBU[ $fqQXus=b@ @A+HdWq8*i]6BY#iXqWu=z\V䥕IHOBPUۜ[ mBgvh- FDФ~^:ldu!$dJYe2t**y Y)Aꮵo=@lw@}FK-I03}4P J_bAl,Fw]yw4 BBn܋P! RC+C-M/IEsh(}6* -GteI)q?"T))DZ=/F5߼3%/pE'D_VmIX+]BZJR lj+ p? ,s@ŝODnzpHzH(pYdlNP t @3?x @( ?\iF`%ADpz9 (Zvm?#uD Ee# t0NR+$M@)g=J#חF0Ph= 6u/Y ֎< L,XAtT@ <0aplЇ;q1-(.QJ/Y I,d{]*җ)p0D\ (#rգ8SAQcAČBTt\pD6-lH!6 l{BhP]^PB6w(E$q j$Ijx!I2I͎?hP#?Z)!rOa7CqB+W# / r- AeL!DKuL?i!^Pad_RH%la 961LQ$=>ԏ飚+95 $ = bДck2  #H,*4H4ܙ(F"N2$7hm$\fID!@m:$@ *?j]0ʦz&H-1!,942&_sJPU 0AD|5S+@ ~ZQeYVX˃%8`9ŘRȦ6 a fr!晎vg5VEw,e&^F65SeG{HU֪Oa=vP niҊ e q*wc@)C#<^k# !2d:jy|Wm{ w t1rbĠQ=jzp$[ &pQU󭰅/ b;re{4,!yJL]2 u6HubEW< yD.K[7>\BGHG;3  T\BH(*tDL@+e@( pFJ, RFQ>D[".d $(3<e#>#~Z:-'oN^7?g"&B" ̜1t@_6RxO!B2: 8ͮ =m H%Z(*q$16c$R#/OˊnZ0^V HAy?v$:MDPIKjQ6Ý*_i@X>UB$nFpaayC.ҭa:ᅖKgt\ d8@܃!˘Ǹ"P&6FR@>xs%(9n ax304q o ǀ1R2 s18Ut'O 0Q^\EvBDICa. ^Vww/w'J !5Qt Џse3R@4S CiH:#~299SG9xvWjRHA  *Y"P;A<pѐ@ p: A tj0 ƃx0 / S&ksaK#!s(y8m%t EP p3#!1iLb#EB$@HUe_T&1ETdS(QaS:DKb*TTb|DB=&Go@2#GD%9hBEFDq|l"V*=ևABɏx@hG9m!8Œ8đ#(19 ӑG5)Z<JP;3[s@ UB6jC##j7Yyn8Z|sAt(Rl+!I)Yzڡ`Fv)K: Z 1%np*R1/]Hn` |7r*NȦHj+!1RAF0 Qsn%`P h7cPӑq 9( Q+UЍHw a@ Ce|/@!N;"%<@#1 +afbcΑ$ B&-XTyzT&/&ib;_D'9RBe&_J :s(`(}> >/C?.X,~齃@ Z/m,6s*m3:#DTmŝ*]~MyG{%h "LG( ɩ$ކ#/(101+G"q ~xUn]GAP*@j(*> k/:~'ΦB Y)tھVjXM)TB%Nh0DX6Txd}`P ”ě!A`ZfGx܈T`і&QE ! s?V f,x`~ qڍ&|&4fzrԠ -b ,ZT#X̙5o@AD_$jY)7μuk鲦MJnQG؎k7~|8qL12o 1gݻjܿ"3/կg)$^*&?Xj&O">*XؓpB +PjR.7v :h"XĈ`'cyaCvy Fk B?TrI&(Z(I'/ݨҠL6 R6&*ӯ,: (7JO@tP>(EP+H,ȉ$5(0 cd.T|jd"*"̸d ɪH= !ADa m$vML!3Ma O p"P$=+ YNvf#(-luCzxOC*h): Z$%(MԘ8fUִLx:`"6iڹ 9UJTHC%fgE\5Ȏ%"R^Yx~XGI z~7r#hYqZ ߳nt hgTXq&%l@^y<| >&[HhU:PWҳc1<`[㔂@AIYXJ*,:I#"" $"!H'|^ڨ [HU D sWIV?@:P*V\ne\P @$jDh@@6Rs-DI aM# vab'sЄ< C>E @ӥ$=$FDg遀 hMX) +$R.^ܱ'nzaǽ'LSʲ}%>"i,MLwΕPRH0= !39(Ivfj T;#KQJ2җ \d*{\F~ OYldhT$*L}@7np|EB"<~0Q^x 4$"q{?>u:5@wvz4 AbD$ ؘW8"ҁ ";A>IxߊgFDz*H Y^!,K!d#2nd g HOŃB, H&!BDIČd='|j}n$*1܂DxHu | @ -H~9CpӒAiYK)baÀc@7 @n\ $*6I*,$kف0'8$щ1x%dYN9W@>ÌB|W1ӱU,|A_fֿ5AAS0˾/#vzJt53""? JJ>AI"{&@HFrш"9AZ=7/:^'-}i $%SN7)| 4P -t4 R  5Qʊh{}VpP`],$"98\(hGxةqX(2BBښIU yЙtpgXِx0= D҅d-^H \zb&>c% ƈZЀ]>m(NPJJNUb&iPkp\LfQ6frvsTHipkx m.g@]P802,|dgE 8َ%yڧ[69q\hhAɕ$AU&L/"(0@h'܂[t[$?4?6АXLNޢNU kJ!5.atyGwyVB ø xv0D8.`D@H󗉸U5l0&%؞1⭗JU,xvEop9hiB'iEYth򀝂H"5_$5g@@x~7x?8u{mk>A [ϩLΖP*= tuR%.("y?*Iܭ*nkk:6$NAZT54ȃz XO@g˂dݘI霪[nQ's9ޛH8,C L"C١w [Ц%r'_ 00,͞hLx T젭Xܼ ʎ505-) X4,Ԡl!# 2ˌ|!(;7W 30CJKcUF[\;ؾ' =N;Ú8#Y (u1unhO012NP͈ m z8UbPdovc 'Ef 6BړT(E(r"  5z ߉hS:Ec&^'5_G)(~B)?"X#67*\`ɾЂd 鈇*'eJ3:(TtpN Mؑ*>+A4mI}Hi͐ȃCқ8Ѫ92*+ʥmW]z]xu]wLRL(DH@΢,ѥj5fR׀ex ޸h( ,^ٍp@JC6u]/ź+h n |-ȯߗ0c KF4!O5ڈ=? HĂ(/. 1ʷ8l,H,sy6,[.xBK0a uP 4ą-"D?&V,I$dA˙fĉN 58|xra}HN(ɨ .IBv¡Lд:?߭T™+qP P]AF@V EJOkS`.I嶥.E>T'G fq[Bs JXPȋ+m`HP @+G~%Iזˆ!DmG}PGV`]4^ PPt|=LBXvzYUE%TЃ][-RU 735\]u]\m ހv߃>CŅZr+!S^B+ns޹r4yT Qzz^HY}A8;3P3߼CmB켘jV/gKB0?zHGs| YN,dn X?1a5PH@agA$%A~8B$dtW@`5YpA(o"H C=`"ck_ă&kDD@ɦ @o$m[kIzW!lYvGV#مXHR,j ""B@̵]+ ,Å~De :<"`ljհAy p W儐}82G%#t+CITVb%0I,If@I*TOHC2砀bB4d)!ߚfEBX1-AriH3)}dBPvϥ tB aB >bJYC4f~)JYe))Qv2G8Wy% F&!  r j5}OB0Q4z8C[|@i G~slAr6%ħlB 4SZl@uNJ$8H!ro$(OHS4Ji}}!q! By˕+CDGC D`?1 pm ja(j\ #7 px'"8@r @=X@{]Hd+ΆrIۃAnvE[wIGX,4IJCm[ڿ$lk3VX0>P.&#B: B=@"[2:1ɺ>;Y|duܵ벛.L#{C[awzjx , &Dž xPfnLG4zxY 6~!鄰(A <u QԂ) l§%TF!H׵$̆z`{2< ?)ķ0B8٣lIe'%(IVR-BۘHB ț=Ĝ?=``BZ -I]T-_BPZq $EGCJ!B r}A`28Qí%M[ӡ4C`SUITMSL\'UF`_]DyL 4fZ}ELKLJ@?X ̈ P0zSHX@[ FcX'pC8 D"Lc@ 6݊ BL"FK0@dрl ^F .F(XN*^T9$EHM':NUEلM E_^`DLdMd#!RB8C!.hMeQQj`BZP"TReUZUbeVjV֎($:E# .H&,Bg O(OBOB?$-ܐ?dUA$at #DPB=j$f!T@Q(] ExP2E`AF =CBnl!Pr>D'di fRBPѕ$}J}EKqbXSPi&C 0}/:S#v,Ղ$"tT+H,/(55M !\UUjx-EHpAbyE^U_jITiM(MR,iUFHKBDCEMEEnu+HB|I"D9.D Hf0DU$ 9DHJ%ģH%fB|YYYRYB iH&_8ITkBQZA2 hIU)dE>RfJi"H$8KBs ˟("/j;hޮY%&(i3*Cڃۆ>"BBxe $$8-x|Dx$@ؽwP ISL@*=|WiX捪ErtbR( ^$gIG>#Xi эd(LP.VH&((څ e_ C{\$@?8@H6w%E>+E@DHƴ֍E GTL[ED\BjGVAE@XpGK)A:,+C8+TUILFB&D Fx@(w)8hC,HzoF  /B`B vmnoQjt G­JJD ^B$׳BAMLWN8@'0qûTV LNW2hErhcLy $o HCrd,#g^D K;@5ZS#x?D;Ȁ&[ahC #XO.DC=L8尜18i6ϤA19RjBYkhCGsB 3?\gBtgiB6T$ȓ4$PCIlK7%$ÙTDxFM g^m>e.JmMs_@X7k#m+mjpK A,'Iq`oz}zsfy9&'FhBn 90 0x8`JB9$/$ĝe}f# 01Dg43A4 9@:t38M#@7CT;u=;3C8?0{|?8/dC;&dG3\i?$-PC3#EGw L_&~Q\6Kv>8ϣfBe:ii=TpT(b|扫M *0n+Ha 4SQ --ZL;|rMMrj (2,<=,:|F z_iD@*yx]mqW 0Yi]_ErLx)` ǒM63\ r=HN^]pvWhb\2R9>LoAH@F@!Þ3JH-2}LFAQ|?\^weƌg"3f+Zj0Y}Y}_B|q-[jkƋ/>J,)9EG4r;9=jMg=%iVRTdDdvRIjUP8Aҝkpu%}SJqu{~ٝ `(|p> #  )cKbHADFcYKjR '^?hf` oTrz$8 ls2n0%rᯰ2밤;T6j`/b!6MS %hQPR-@ +EьVb  T7JKjғ\3 }ԀJ-KSDt&U#[$VХ!P^hDH<%I N6 D 8JOn%Mg|[Lϋ+)He[+iS!\fa  1є> @PeT`W؀*AȐ0L4 = 1TP6dpQO4tD)Zј@&nË0`m"jUJYR4|;擒؆5)I#xZcK.jIURԤBH Wy3% x: Dkp; FZViK SVyKCM3IBCJX8aZC وa~ pA>Uk1@)s%?H+P)B-fe)UB\XS aՉ#WP \U@TwCtvS^7}-BkiChHAzpC)@P,?[gJ`=W%MDnIk1̺s?'%:j7"LvD?h zLe1CbaaX So TZA"+H3g v E .(=d @LrD԰ԐSQJ@*B'<J#Xe1)xbi.e1L-5"B&ІP4O'bt&!AF!䌪S0(6ĉ|ntwU$&zSkS*vNi=Ρ d$,-H5B P(0BU4 ҈)#@xR 0 xd@C `/UX%+`C㕜|(R '~{>W<@ w. !"@$6 `._ j6O [W ϶\I,ɅxU%׭[Ye~JVFdr>MB]QzJ81W\ڻ][m ,«Ce@f/UtBD2rTfȱj<](oG_ t\{J'A1&A *7b4>yRb+%A ] ƒ0ccWZw5jSr͑b+U;*`$@*ib pET")#ҩ& n%HgA@9r'$pG H "RV$d_PȶZM ZRBTB l+Tb@>df D*M<.>j =W$%dR$p0%fdp%0%L͔RbEX EЃ taҮD#C4LD1"E$* `ܠ GTqGzG@Jd'A!5T$Ψ$9$%ryY(% 2aR|"x nb`"!wxɌ b@_* ̸*C 0a\\b'[G{`" e٠hVY`"_ YX`Wⵀ*++1':$(EV%\ED^mF*[U&؀Wen,`!a:`aq1> . R"#5J/D`R0!!yr8*+."B4rcn&#έW*2 -xi.n6-;MN0lf .f/&+0H1jC(,G%,gb 6Sn(p~:, fVBpfNfk/zp !a!ICAQ'S%S%ԇq(' qyx "+|7oY^Ah **XUY%+A:S%(yũ&{p`r%n 6} `bEqf(B.0Q@q@NHħ/'dCi&"Z'ƘXBUO@ uClo>J)2*"fe"І?3IR@ˡ6 3jC<(3j,RjhGj1+5'VN|2o 6f(i>6,5u6c;c)T`C%ax `y6dcgfkfj 9XVB#`Cޔz<.<lVii.h1$ ikVkv܃W ! 2Ubr V7%ܳVWy["sWknuu5R< fRcBt '$ZZPԋA(!  , !Va*Ƞ@` z€X%8UR % > B|HpYdBf !1%eKU&>X-&TӾAfb$o֘d՚  "Lx77r G5#Ck܆ma.} Ͼpx8WqB BM'֠BFjmF V!l VG|dDɚɂ$zd"&!d5TA_dav0#f*?^-A"ppbjD ~9B#N#0"?n&J'2 B V9!];/Tip6Yh&s! +\ !h 'l.%hljn25BSӥ?ub˶+& Opi4&f"icU/p+L/# -J"V/d:?qU&,ĢħbX&T,Ay4HI-X}ګ/o%ȺX,} P[S.],% X5h]@ ⰭLOW>K+ Cg(~kwm,R=0f! uN-WbPj%ر%b۵ܒ⹺;A*lZ$ oxP{%bd ܾF*>w!5߄j.4&R<MxA-FCaj_(7S"NY'#e1sPn SB IASxrɎ@R$s \R%x{pbG%:|}.9#$YB"eC\r% oy 耊QKWW`6dUdzt3 |Qh%ٳ^BZ<єKƧ\Oc܄'uB>jAwz!+gR~ 8s(S7!SP(<~vF+-`nsB C 8'o xXZ뒉3›5fئuai+gC`\.V/2`}%jڢoSbeBb}3skBj"m>$:b-/_q9>"3j}&:AY}1""S8/!|sPC^IDSb*&6Ds[-%ܘ 4jD>]~M%[MC{ޱ [ggYu+{F{%['?L{%-͐p*¬JSWbHTHZE[[.QmLA$% !i'ɦ/]őˑT"-$+f?kT1~_vx_|M5@"jN;Ro(?ӌ_':6T>VbYߪU#) eM/"/1"% %/0 (*\Ȱ@B C2*Ԙ#FCv(ɓ(f!ɖ0\B,cd8q‰eB(MDMieNI,իXjʵ:@yB! IUXS UC.ػx˷_ ޜ]j`!dH*#KLz&!$"@^ӨS^2.jHu-M DpT9sƑ ~r=؈ '.HXJ["R=!pNPGp:RDlB!aH8@P!9va ሆqXDoҙuD24ca񏃻B AMZ %A 4d nHgn(?0@aV&-BU#sPe?t]ҞU|/d :YL 9 O k *mB!`x>Q<7-Hj B?,%0 PZCRPOXE ` ?*X* q*BjҙPF%JC-K nnր Z 0+pر  ?`N RN BdĺVbn%-h?j"漳4c`VֻO/lDBdu\QX!;P<L<P@G|9R;r)yA~~ RL*QQ@H =h 7ppIҕ{@4zgqOY60 DF8|=3Bz%>!³xdcQ"PBћ adJȜ%QseԿ:qqEii:(Qepac~d/YAg7xtFӀ2ruBw284ݐxG"lcݰ 0 (+g0XL؄N0QC\a"@P\؅^-qZ`h_"0:qG0VE&+$mqnAk^o6b ! 1ypL,$@,PLF$@'" BZgHa&e5tfU (a&h&jb" ڠ PҐ"!$r~&Z[%A"h$%N%ЍZ(Q&5TV&F'5p'sx  c€}'Kx  (tS))4A n(@+ {(pA^`` q+P +7@Q@;2P R)' +4N(51 O"/Ð#,|0 Uc1_5R!vrI v$qI,5Ok( 谑0/`";n.B)/.3)ry. 34`E4A4"Itq 4b//A! v!Tg67 p @iG --D%}f!?$;AA:V ߑ$ab92pZB&Bec>0sxɓV-84z#!,IС>bE1D"+ݓt89"=ij>sP=1A8cA38 !3#7sFˤbU A<VI(RոK}E i,7R qibҫbi6bdQPńq6]&Y j;G@Yw6h^ %;k0dkaq2BK> ʔYY]k]5ʵXtmE;]׵ݵ][JRdUM'`U_TO@))P0" W0  [p`x*a+מN!/by9fTF)&* Fi+=VFҁUȡ_9#B56dWyc;-mƟ&VC7!*d+t?e=. vXfp6"N87"Ag(TȆ,`X*v;(vL}y#i0)1´l9YjRkj[U6̦X̝q^mw& a[Ҡ[™EfjBƿ\ֿޜiXF2T_ Aɖ7Q"L,ۚvl']S?jѮAiɀbݯ Hw3 JV "Hur2,uQ\v)J0bG!'ĉaty ` =cs rK. 1QN[StKO+XM q X7]BKf h(-ҶA%pr[7P` PAz)p$SɅ¦;R=䔩Qd8wUDz{҇з~|eFSbցQ{ܗہ(W3bѪ'qz)7ŗ ت}Djz}\Y${D~ Cg~JQ5%@aP7"axWgVPs&*b\&:w+wb1a!R|7)|#(*~ȱT*1PJE}` r:1-+@d+`?XB* ݐ:E -V~XUΆM OdY^f~h%3Tp:x(KPS+@ x @ ,* pP j.%~њ3`uJE#&;K蚰 #?"XȼxXra1 ;10+,0&  ps3k"x3 B%8h%$a@,|nŽJBv6`er3먐#(P6e( }p owB:!wsouk?@%Jyh"DM(Mp@ `"0@ #p"`@+8T Nc4+qOP`1 $o1 v0Kp9sI6d'  6fS PvxP6_3.ӠP @ ,-|5h0/()s6.3o6!Z`r%nx4w RB] m/S0M@?B*l(0ohp2UI a}U|;q;BC"Y8ϣI*C8+? D@AB"BLB @ *D:q%.Ɲ[m݋!\8A~džH::'(!lAkMͺ *Rk= m 5& R ( jLzP@{O,. o:Q+6C Wť+SzQE^(hq*xnqJJ듌NR`qIJLi^4F,*K! 39= IC%AR'~I_YWR݃H>|a|#"'y&A@^5"YyeI%IaciAZy7ұp$UY% Ku#'ɸ%)4ALTW,J e_$sBE(7)nhu\qe%V&)ik!gS.)t8!s3' Z /2d` :& (#! Cޤ45"W6H9 d(("|*""b `@3FnrZKڨ(H&  H*I |0 ;9<8b$L9P=!a$qI X PhB`aE:b k*@>HM$!D::MPN[?Dį(: 5{1DB|d +TMH \H@QT`]_aebC*cFIBl ,/K!P, p#: EDg5JOb H!P 鷘$F<CHe2>,EW|"-cW1Ȁ9T'+Ĕ8 ʼnԯELPvICq·i@>d~җe@^ȲGsHLxB\BlQjK8p 7|3P dgёq"k܁d( I BECg2N5 $sQ I$  PBN$}eWS !  *H@P AnгdЀ|aÂ? 4?b܈ y2=Āt=@~aI(Gh!*BeP1AD^pIOz^~%Qfbx&[Ԁ7qSD !O~ #(BՉ.:+(J OGDrN9 r.Jj-\W*֮zԞKRF.hMZ1e< bg^$b/KG_!pbrK,B%Ĭ ;0b]>6,gd'Ӕ R7&@?,YWbh_d'}LP#1DSA*P!*).AD%S!Z,AN[\OKl?pA,`9@JpF=H~G #aNaR6D'2x1Ou`?J3սnvw` w9 x.Khg膟RD#Fxp7y)=yvx5qw_Y!,8Z"<-'IGLNzϯ 5%DM&0Ŷ s(H zJBfXĒp ×#T?pA3 Ű @X* @PboD "8$Cu@HqdCQ HN Y2HJ@ *P@HI@JH@T@ 0h0 5h5h 2 p2 Rqc("zL7!||XBuR'ȕqo \2X@?{ ?=9Aӂ |ЂE\ [@%.0<PP 5X%Y 77qKiykq. M)+pM+!.Tkp,i Вѕ( B" 8x 숀 *P:#q6p% r Xt:s:8 9WJ( h H ؎  Z/  HF(>C@G1( @ɖ |rbK ư A@ %hdԞ1PƢ%3В u#6<6)KdIK+C $KFtD 春/H$CI ۰ 8+% pL5T{k-r%2g6irE 2P69cJm[]G3+@ȰK6&|F4{T3,󲶕f9ge~Ј@KSIc[gcӵ~v%%!m2 ڣRsf6hEt%Xz鑆7l+6vhwXgK,EV&q3diјiz[izhEvVj7]7=\^8̠Vj >d-RXr Zfv>!!p9?,>9)ʂZ lɹ9r]l񃮺20@i`? =mރց#:%Im#&"  E`vE`0ə1n9\9ȄTFC  jn^7(ި*@=àjHV:op 'ٶy(8'@ENEh!n>4&@ cPPxP Ui&JБ[X=UH.r)!KS+SMJH DAĒADPpVCR 0PsahYIk`A(-\ֲ;#^ |()@BpU=XX>A;KܽHą5Y!XjY^x`L*Wb>|Q) 8s_B')xtaЕ:\/E5w+Xx8| Ȍ p h( rl[9ڡZbf,r6 S(G\Ip ~ zϲ 8Hʸ TG!P/@ 0 elȚ, ; HJ`@ Ȇ=4g5@G(%0B؛NSl !` [8߅xI @@|`^( ɿh%눽!=䀑p8A,( Y Ą[*62jdXeX|Hd _+ط/VHq{׭*;w"Bb+5;;!{җG> A@%cS6_j$cmR ^"b k{n!LXrFc/ ф]#vl!FD^'A@CЀBAB~W` 7@! C ! $"H aH  a?H \I}8I "B %H XexPPuGAM[tPb-C=1aA=Y=4?h$inGЀUxY!$#>([c# Ty?F6OE$B[CȺeJBuyAkA+ɚb?a -VqW.$kD@Yf ˞5mAxuPd`Vtpsi[yUO( $pNh AB.v`2/ҋS(= \P L ϰ$:Ki5!l"8f!U8l!2LѲ@3+C EG=A( =e#HPӬrxNJF4Ȃ(yd҂ZliB@ ̲>c#3d!JdGRDD2++bU^@ªuatB6MsbFlY 맫̘ _<@A\X P{:2t||tKU` }>wҭB+ ȳoxlM! ɩ -U.\25haiމ։/JU}0*4iP VXi KHq &aCQ)H0BVh|.uZkeSAR͇ \Uh`S?*kWdYpNW?FidPˇf{ `7v'Y}bp4Y%0Ґfk7(ra0݀~7{ki)@=_? ?Oe-L [\?W̽G-Hb߿@@ _ @8sEYHm  @^i $}[Q=@ ITDAD2ATBUHS 7YZE(`;"elJ @d!A6aA@(ׄ<Շ_L,d@BaLLO!oPUʐSV fRbEK6 ~<3aZL`LB+BX314T1hjAiR@_`:8@]OumP'dA00^`' =EPFHyHTIEױĸ#hh['0D@;d4|CpBDY>4BA=; k<*0@|YWt1$1B`_D#A$vdUJ6Q_$/Jѝ$$p@A68I-\L0Di($Q*n"mQvQ$C%0p?\B!\A,<r(8$怀$?P@%%lR[rYL{GyBHz KyG7It]}H}hBGQM= lAA AHSt}=X&A(ȖPHÚa `u6DF?dD<5 W!&stZ&!((\!h8A H?H}pdN bt  DBMw{}]'g Ԃ-9L$C@Mtg p\ɌYF@?dHG BppLWjT̟¸L^JTn\\KAS"%^i l xt0_,3̤͊[41 z P=J)LHĹ@@|pL>KML^ʜL` Sׄ t)TФ ̨L@`^ KҊ[XiTLIUI<p`O}YʭHgN>?v@DAv)?BҖCcDDIm~0ALQI$K‰J$_\ATQ|COj:ה%ejmLDl,D,L!3 bRJuǰGK X> &nWAFL8a, 6S2uD3 0 _JSB@$!S :_-LD()"‡`ʈ> 2$G$O2%"oo\UpmyBrDp?zƒÑm!+TUA>t MCYE ͗BVY&A}\'a6o/h404.T\2:*@:/?`TPAAhC &dåpC+hBSWuH{ͅC|rA4awyt^@WH?s!YP^J]>XF ?X,Xp)‹ISA&Ep==pE5A&HQN)NBE08 -%BS-Z C.PA`AlAA:YM:?(FF.Ĭ-D0E7mجqO1Bp^uU[ucCBbZ=C0T,4̃N^;>[j5(L7Y abPm ` 0VsCpsu8k`C0ifh8A.u5@(ezdA)`_ve$@\T8H@Ah]u+@1B\A#ht\\C AT]_ |A Yv%ާ-Yhxtߐ(9Jcӟ2TZ"^uKެȇ-˾|Z`kOL8ﶞF)RLK@݅Xǿ^^ G GmEl^ ~U `4(ĀĮto@uzk:˄ߖ 8$ #L` {lp!%`BJ0)ksCl;#7-Dd F ^lR[($:[С IDBF2 @t( 8Ï;%a "K|PCS3H8a >>F"$& /bUQ 0&B9EB)j)nQxv\OFM-T1*#VL?046vA`AEht+,)p7Ejk ?ƕ?؃փgL90+p |Sbm?&wP0- 6:ȣ;(Q=h?soL-E62@p46m5α·J .8/ſ$H"HĂ+ -I,MBL!2"߂.@LS`VOm%ؑ?Iڿt;La@Ko0tDPLzr !J|?'HLYt =| +$(!K pŋcaUЇNĂ$U)EJDlF^`sZ|0c{'> !@@e/@ ~k-#\{:@A4l.op P|+1„#ثBH2,,Q|pJ\QI @ +1C2H=lOGH171L۫D)Lm!TP"hdN-[LF>Cѷޔ(V1з%%l$&(PY2%*%҂#()@Q'6C uT]3U2*#JMh''iVZRʥJZA*Kb BkÂZ+/=Һ.R00 :,V,?,| "ZMnUC$2h^V: 8@<.#>`u#1:h&oҙ+fIry@^i `d^HpڙT9 Vj ^z&aGI%24i .o^g !#0NIL W^_]Hl'yIQhf!Ɲ ky0`ؗn]=j[`k:(}v' qnW^[_H MT0SSZ#@Y> ;7}- QDj 7.dq+ 2>hoy` r\bBX@ B " _3@I" KTـ CI\ C-JD@r3Ͱ6Tb6@ p|Á&33y8`@z rB`!@z$D:yxA,= ,|`> &2eF4reF`l(F!#X< B,p#ydّ k)$"I"2gq4ȳE=݃FE QyH"rRS} u8zZ9CO.&*!3 ~oF79ZȢ UE%0}[kjzә=ЈtB`ܓ(8i5ˆA p-T7:j_.=$85PaZoLŗ)H)H4\%- {{  @ Tp[Q%4났mhzu{*&@R?~@ XJ@)@C2P !},]FcRT r@* tVI#9!e7B *B̃9 (E.z()@P@BYBS+QdH5T!QZVe{)Ue2"} (p:@uא&#|h @ ^@]>?vM\)#"y?gRdDJ׽n3`,F6n-0ODp"Njf'0EZ 6 g"'$I$LJ!(G.1Q؃g-\$yAL3D H36@n#M"'#p! * 6PO`뮀#OHĔdQJchw-kT!mUbsPv،@젵aC} E>bA)8=nއ2" A' AshPۢڞ&lThPhʍq ZwM0JA$ 9}H'f!1TC . (C'} X{| EGqx! vMn`!(Q2VA 4]2!d*LR& .&N2.C' A#bF&@$: l# &#РPB = `@ A) =Ҡ~7e B6Jbf4fX<`d. (bk@ S"ZCS /#!Rܥ/\L+CZb,c `/j6$@!(ZcR`r( B}*cV5QDJ##ndA8 $j6"b7 a̤"dBU !DBU@$@4B:$x QTd~;$O"ĞĆTdJC?3D.D >%Ed8:DL0H@bHŞhddO: 0o=87<Q:<0ԟƤC" 4v>oC^InʰF?[I>cLCץ545Jԣ ERN%+@e%RD%%K"&glRt.C$ʏ 810&&&#H 2# F&U`1֑@ 29K+ s]r1qF1?B「2 23cF 6NAca&36fN UV 7k6g6(c!,b8Š8e^)8=ܣH&!ќAAhM`ig>=(qp&6βf^ާ=HA.1kPnt^Gj n Vg Јrݪh S-\ |RQ~sf~/un Lj.#p #!M=bMr;ɎH!L?aj?ÆJ)#H D6J$#_u#@r(bBVW6`El6$2fPj5 6=$mH3)*)>U3v>M:QS I=~FtB$``|&  s(`O ap'E̶=i0 ϝ0I,Q$jI_oxTAgJ `OcFi0CWG˄W }7*} BX汦A~A{fsԦ֣KwZczހ1xW2B/pjXH`1|3] }_kt 5JvXMs8x8x8x8xɸ8xٸ8x鸎8x9y 9y!9%y)-195y9=A9EyIMQ9UyY]a9eyimq9uyy}9y9y9y9y9yɹ9yٹ9y鹞9y:z :z!:%z)-1:5z9=A:EzIMQ:UzY]a:ezimq:uzy}:z:i:8:4:ɺszZ:]x;u%oWڭa0aگz =:i֙ZAܣ$$xb}MҚ?BwhфWvX]DY dWp7 A3"wQhA S`5 _XɲC +IYǷUə5{A;4곩j7 7YoaĿŘ5lDʻ9cNEzz64q9@>>AiB?UNm֗Z½~A%AD_AFkcY08H4 yl̥.#<VQ.D ˂!`(8aP < oI̴/-X@H (L@ T"Y=B{D@BZ|`7e| [ !-h 2`@*C ӡ?Na-zWrʻ"%f) X@ƌbs[ x̦PQԭSv|YaZs!+QVF#ݑ+\= "4Y[zZ- Q׸]zkh)1N S?%ʗ4xM1 z Bj+MbgD` W(WyC\4Kc|(^`!d@[Nncy9Hdj$m!vB!y[KJ;'N[li3( -lH[  }sG`O "&$; 5)DJJt$-ƅ uhW AAؠ΂p]c SED-*6AٵM&J3@#1Y4 ?210" hd#"YD̀+^? $ hq  A/*(A@ho[ 0kdRTՠuIO2KB/C6o07}uJT./L2iPiE _d2zD&&P P #uuaja F 0j` "%n2,a`B|A|P -o o |0 A 7 JAHD2 01  2X g ^t28^Y t61#w/^GFS4TS!\r8285m<7401Gy2"<f%A2oV!9Q3hTs8%$8<8|5QX!fM;33*P8RAr- 9 9`.#sg-%`82PmQHa/#!xgCθeRA%aj8326A<67q<@l=#!#:>hS6 1 b QGGG؏~hFC7Tbo͇0b.$hgyDfa!u@ /ppqaP BuGE*"9E68L8  9p q  鄎Y 5 ) #5 Å5 4 ) 7J"" N0/Xt#S0`tr['Vt]o1ȕDg܄\D:mPB&i@`pExg\,QGswf*5>1p RqGpy p0#I I q)-ڡ#yqsI 5z.:*X@q-mbX^c<_$,*ꭙ/*y抭Wz `bzC*"4A)G9I]2&ćzqa|DkibXkn)nyO 1zuP&c]!Qa/@$ FɃYB8 s AkJhM qg3P{4۝"S9(&Cf9Cԅ +q@i%6%B[ʳ5rawJ#izk?a\V!%ji9%3SaCiz0=g2VKA[a_ unxA@aG:@FD0 P3sg/v{ٱKRAB @`~~z1u :@I"Q/L3Gשfz Dt#o|u5c$jl\zgB q#y,yK>(b"(߰FPJ@r%/5U*! ^IsV"2"\b/8tBG#92~\r]KQA_$x b+"`PR,0+7 pHB  D`;S$0pCA^Q  9. ,g4i Ƣ a SЬP%  K.[-BKq.BW(PnX?X-DSFӹפ0ӈFhyqp/UxIh8xC r.$-/(Q.5ؘTŘɘ9S|uC80E"_.z49u3tw8n,8Ei-#/7B^P*Tײ.9miMgA4"B.W6jI7µg;lq|0ګТJl<?򑖶\79)Z[!C}2Qs“F49$,i&O#,9jh++,(5b$r!6k0T{?N@#Q bb1NRN=Y~"dC:1P::VB2g^Zf|ٺhʣ}0+`Gg><iaD:m׮#? naCxo rhD @Ȏ9b|AAJ cb~ęSg΁!ݙTRM>`Jf92+ɝZvs$׫e"UJuX>5(۞ Vs ڸEX`… FXq}x[0Y^ 6~\7W*/YhҥMF߂7B|/B k^*"&Zpō G 8MiȽ^x JP/ŬGZBZ$dQ<$J` D;`#h3RjB&p )8T pBC8g˜cFE"d1Cw4 " >BD ^ "  X)76,⋌pbK/{CFXoH"?Q$ * *U \Fd'EaԠ'@C  Ȕ -l $&4If&XZBS'“Bɋ JCOBz@0.8 ɺȸEI7Za_HhE&$y`90+NH@y8pbsB[CBP%: r-#I|Ły1+ApZ-W,b*.削 _ *-VHAd0= $c#jP,ȏ)W|TS>#dXbaP1B#jHn-3$ nU a j&B ȲH+4H!xY O2IL"%JV8c2I- B1d B\~<_&a t+!u$EId{fDjjh&/  |ANh HM [K|+dS*T`E]F< ʉ$ Z| T 4(%6A: I)NzQT!M -:q0N ]"RT]bLKzJax@}Lʀ6>5DZ:1ӉTPm1I#;jKB9 $,ʑ[\R9)+%!b (, dq0DߙwazMNo[w^ wZyQk]YNnHE>\C+ ď`Ԝ.5T+zuS($01hYL q OZUVeYe"T NQ= l%66  s71D g:'^ >u(;%T4.BMXUS UU! BYCa`@ ΤP Qd Q0|x6ut8{$ C8$H pfeK]^xX9@"Y PЁMkc4tXX39tsLS5 m)7^IJZ # ¼ N,LdE::3 a\H&aު #p$!"~B [x j+b!0z+_%(G{36W: % m @pʈa4~y5b+6,pH& UrW^KNGȼdWI9 bQSY2" =͐f^f_.fܳ᫋‰u >!EȒX@?h3#$۾?hxg"gCCpd# FȒ(<N#,k#kp+AILov tF0aP?oȑlʆWi辠Bxp) *H$)Dj'NLbpܞHB/.+NH4VU 2DϋI۰%f4;=$e:bPV~h;kk NkC >0]\,1ĕeTY'@UV4P& P$ bŜhlZ 0T_/ؒ42-sk̃+Π kt1(H05` /@:/40M:p?ha#A 9$B0p%?Bd@L&āM>j >AJ"@&DIDZ$!?]$LSI*ĜSg&j#L\LhA A` 5ZihTQg2Ygp`BkRe%qrS<`Yb+.vB~؍T"t* d6INlbꧥF+&lZ.>"E9.{/)tB!EPo :0A59 B($` @ z,Ƹ3(B N }`~<35ۼ.&TM|am>ww<$+x^%=Ot #BtqB 7r]O2o2B^i==7u BxAdB6)͒Bi"9MJ=Kݿz#{HA~:>;'ԃThCs-G{G#$Dx>f;,N}("DUB#! !IAf4JӇKL$l+I nq#1F4rxA?z% πV2o!SxA J AH,w >8ĈGp_ !< JB)h!Dq= n1 d%*d "A)xi8?>adQ!A2^`8d&`Dnp 0`aD  (Mb)pjL h" 8 %K P?p!ZU0p f%LRJ!TH%ʄ_-b #01$H^@AH=SB]siɇRDP1 bHzBƄ0I2A`| @W"}" A OEߗ 'z;JPh;A9();I)TeǬ&9x +FDЂ$4nZ$ e`G^ĄY ћ*y+gQ%{֠8E2ۮkC"$ Ar7QtLh, Ebam ,-Aq ^pi5Bav .䧅@pMhvya`$*1TlCQȳmW:Fʦɤj& j51C1Ұ;4~wBG="gBjQ^p#i8 w1ưx 4gM ;TB>Ȁ>ܝb}_-ǔ#>b,# (, 9=|B$_=->$;"-:b_Nހ@?J բaa1#5V#5f)tO4Ka A?HlOB#A,,#Bƹ#A B)™ŽXBIdB@ aB,D$ ! -!JEDFH4D3=/%Ҡ$! -T m@!?e̚RFQ@?5IH$0@@$AL?4IV! TD菠iOT!T@Bx%6,D'?ebS\D!DKH@BE$,'d*dH-,DeX h|4@- ȏXE؁|?H7$2< XQP b- Y?`@(@8tzd>= 6/.R)d*BA:@@A:\_EhiŊ$dޢ?Шe$f>@LtBCЀ, 0E/A $(V~LOB Z6,gFnm4A@Ffd IAhCZ4D#PC~JD,ڬ"ķDV\RatOQ"/gtOC [AM 450L@BiB-oN! BMO D&ìI/)UumJnUh q 6ˍ[bFHJԷD AhUDAܴ8tiK2~Q˥AK%#sYa@q)DY!KJNX\Md2#RE(L<2!4,DPMD#@8BE#1MA,x,(c'/FDApZ#hQDKtJ]Y)J ,D.'t0XpXe@*qOhRF 3?X^ O[hAE|B`,s^6D' , B͢_&D]?l;9mDKnF”boSA<f48gy5"֬JN" 2/r@^Ō. F/9繞QS&@WS:?xI/ DuO0iBУ,ϹzH%sAQO0C\"(TMO/{qĢ忠c0G18BB%"@PD"DA?%AZT&\iBBO@gqϸHSHHA$P OAÐ"@͑T~+˂BAع, f"A/T*э/Al&<HQFA`A'fTdn5yfXnzk(59`tR{25??4*e+Yl8C´ҭj ĊVզ/@ނ65Tu$f1 |B^}h-y`~.KH'. @X<pbZ&-R m J3VVhs u(ö<@[ƦH'?,&'M/бc@S tJ!oj4!uJ;{^Sƿ7L8WHcL(jF#GkanHsMB"G*G"@g&!( wi\'>>\ɍ[rcN a-@2J ]&JɏLYB0kIʩT Z\r$h/ܰUBOʲK( c4X`C=?K#/P89 3"Xs \D@I 8P8A?!5PI$L 8KZOQ`b1Ѥ<ƃHi:hU  4X vBE83y#udg'8!#-VTJ;chkih늄ʧ}Vhjou$Zq<#B"uT즤bᏬbClj,WVe5!Cm0!bģ+Yh-Zh>V݄цyr}D^?1K- ȹEos2fihUV=%Q˨HG $${KrS'1r(MφEG()vГTS '"ύX PB UHFԤlaEBQ!rȌd¸h /HCM:IE~ԥ$ fgI paNR Σ? KA0)R@WrFR (HiZФ/Q$Bf/zq ҁnZ ΎMJ_yi͙L"#2f* ʋP`"%A\|Ff@| - T:癭FUDoh`x W40=)X*t7O%AxƤXÌ ɐ#Hļ0_BI+Ve5kX]Q$P(! 0X6Ů{H76.dl5jpΚX.-Pf@;\9fGSPZR>xT9XhXծuka[ْuWCa6 *1J< KN%*(GKoH7 )^"|y həޢ4})H &CB "%'Ai v̝UD$P ݘҩ]"1WqT`&p ` @g7؁@Ou z;+\Ņz0Q PDG<} D!yOȎjP"( i"DSͩT M9E 2rm(1@K'K_yЁJeX藓bz+LaW@. \cxJg)h?)!fAZf&D2@1Chzp^[T2ҰOiiį%&6R+ц_d ?5QjB@V8*C>>#$dd@AJDCd T>d 6>D0h UDH8&]C8]&vCc؜c؊m=pdN)MB/!ʍ+!Jf)(#2L.cM6ʯ7`*fR,!0F* 4@j-N%U gU6'!f^ @`&&&"'K\$~AT\ F+%[eW'X  |@@$!"'f|`g&4g`f: NBVo:~&=(I b`,)f* n 6b de*f̆4{/!$$Jg/lgl2Jx N*+w"vsBpBtʦ,FrLV&w q$Sz& 6j!"oB#*'(IGx J4+)i␎r%"!0Ǘ^ l?B bi^b2 v&^<DbZψb/y#Q8 /%HIF("lE 2f#EQ*C z`!?3悚#i. ҈R7ƢO)#!c ? (P?.paȌ.Č` !D?bաBaŒc:odmPZmߐ#N*p.P0~j 3ߐ>H!zP#i,& X` 67"&¡6""8ɺjJ@lMt@b Bn+#-h BֶZlct d<h# a< hgl )`g#jvd=e| a.h&?/m %CpB Dk-V]V9x!C0B Ѫpl"M|<0‹ijږSԴ3>r8VCt'Ҕ@!9$z | nzzB V!"lMt7wlpvoh-xO!+@@ du<^#p]4gr")btL'%N* cr `M: 0ZuGC;į%Ɠbn( )' z#" ?n$" "+"@vxx讂䄲F&~Lc "@S"kр2*!.n*7PΈlN7bo)P0\ïPz/Ri 0U!/8+TBtG6ж&Ƃ6ykX'#&TkOg Z*( J|kv *dr8$By);JGJ;V 29r!>,'ިBaC3A!!;Av0#a bCsY8ԙKEt# xb=k TiĠ oOFF+ NKK\=J1 uCIOj,0ӆ<3j a6]%foEyb|0scQ]@kd4T4as lV/q`\(F̒Cb )kG<3(b@6&{*ʬr 2B(7r `#ΠUR%(x * 4 kfQfFds6Fjl#Vi.|iǓ!O(f-6"wV+XU.Om4Gor0Tr6u&w "x)F%y(O"""Io ]*ƦX)X}}B}#BK5ȤB?.B`%/1'=Ō0ǖ @s F=121/:c(2iBV֏' qɩ*UuqRjјن#@陟Z T9h 9"9pdbBٹ;I0׹Cp| y\ L>&? :j :! ]?d.BZȇvu?8G *0/!MQb.qX4]JʈCHO4/sfJ)"r]ؑ]O1[L B/b '2کΆusT*݀f,32]YɾS y0+)!u%='%4]BѴOaܳuᕦ0:tTubN!2 ! }*1p%Ba4G0 z,!ZӅ*lIf\,k- ^Sl  `.0CSaD>~ ̔"D+0G@,aM0{ &eqKbN!g++Ă!Ll\Е*T!6O xggL>dB?#tPTJӜ.A$%.)RB K# )BăH67PR "HD`>^`x#?@0H_ k`AN]XrTU4ZU:D!`Xձ& MdBwA~笣]v,^[@&4|p Y0pJ&$/jZUw1P:Jc߁{B$MLPK ]ϲ -`5II מpqU?&)%D&Qd+DٱGPK+,rV(UJ!Ef˛.8A1&T }"ؖ7'_ۉ\L\ve^)IPt?9 Tzlry MC(%ji(AK\u \q\] =k\ #Bd&TZ4 Q:508_v61s~"O~ .(hFu? i_X:pg2` 8@)\aj6l QvG ΀8].7 Nl^ab XB엀;ѼJN$(Oͱ7D+LwBu!zEEMdTsH5DT}*QP"{(yqQTd&LEr9EY (tJt1]"Xݧ;r$6]?:ȕLLv-ktm!ЀÁM@WV`o$K$EADtDDlqT*$6f*H$Ƃ І2HL+3!DELBEz@E@*,XrUL*'rC{s*vCcEL?9LUf́@ ƹ-G9YBz6Oa`1Ħ]$ M+Qb#S[8!6q-*1 [AWl(VJэ'vm8JA 8\UK,6\dWن$C, ]3ZE@s*MHĖ"3?iH+i GHAz@m 4 ိ!-(uD(AV:P!uǶrEy RtQ[x "NcjABjIZK I}YAJnd`C"Y %ȸUU4`b(W`%?5VK$؇]\w;n온xjzA\-V (?CrBkYܲ 0s%}xҕ%Z#>+"¦oⲈ f GH?k1 |O087$ D򤦌+)aBSD;!XjI17Y0nba3ݱyt 6E>>A>(p>>DRbd!Ca=bKS[w#C`|=ݣ>u[<>752&G,7k7 Q A0AYI  D!)p;gY&04a=hRpgPUI@ b "b A8Gnq!ddIWdm ab*(9[֡^&Jdn^!ΒF0"*1bBI*g GGJX"≍dLf ;$Zkt, bAca)I2KaIe>DYaf r-AуBauB] BH  pٚA21 Hrco3MŠ4p]@ G \A !wZBD:wLIayJ󆢬zpyg| u96FsIcU6hi!W XqJe0Yz#iyP/!S 2*UAiU$7WGV,j%4Q[lCЧ6˰ 8A Vu,7U?C aeDfDFcI 3gY9jЖ&Y28o qz{yZ~YqEGbaR[ZabUbhax2, [UU^b2+CKYe_u_^a03.BtKh2[2`q]Db%C#KЎ28c]$\Ҟ4ndA#Ø)&-Y9k'-H%ҳ+d:Hx'D]/ղe;/4"J;i}x Bffa*>@kB#of|dr=s}# 狾ciai8ݠ|ϋ:jgo@j\jq;5"d p$+e#Gϖry(=l[8n #]GJ:/ 1,3L5|@ @ bD@!xIħJN0/p;p00=g6}Ӻ1 !1dU1Op|ӛ# ` Ofpp pGjYn vH5+  K(K l<.c91 ,DjP dp ܂in( Z [#-QRv"VR9&h"&p2/H p!'0o)X}*.RS!R"A*[QBa T! Pe~mG{ۧ"!"a$#qX2KQ!%7"["\l-'ذ*'6K\O KqaR}cg<](m\2.66`Z;^v]ZA+ۑ>c $!wn;ҳ3ӏՒʐ 50Ur9IyNz]q {?#G{zZHL6NN [zԔWX}׀)jXSVK1ĕ5]tPHݖkB} ~PP#W [> 툠4-"e;D!ĹAm pXv|UvL ,ADg`(?J*U=70&O aIYNBǻK)8-,0\蝸+c)Xd='jD *&c! `$4ʱa)`RoGGZ)H,\HQGAs4K#kl$+ "n ^rƲxei{le>0aL>Zrp*@ ;KZhͱ$Q%eb<ǭsJS%JuC= Q\u㟑 \!vp5q 9Ua1 4SBe2QIL?V B…E,Lp" r5-o4}5\B`069{!XřBVc8IXdiNO!pE Y`#evaZqΥ[Y֪B7>x &|wСC@`!D^GLvpD@sB尰& |vw-w3Paέ!oP,z|8hBDf YP-Qp>1`+@TpҊT(m4\K<1+d AK*A-:CJ .Jň:2IGTrI&:˚ J,+(^@z'ri!(Al~q x!s*ײ3I-O@]kf@\KQE> S;h|"MlxjkMDSUuUVۂBUZG0ʅO!Ѭ8Δ^= vǃZvZjZlv[iy'X!0OYZw= \ʤ޸xT`R$ 7 Dr^H@qD8E@`yIwFȉ a:f#f`Z"3hCR.C""h`/ C%:C$Jf 2󚨶I&Sʉ@V~+rt' Jk.F*@ WDh@ r7"Q~sQL)>.WR<pUw؉'WC ?_(Gy1dBh9Wc*ȍ=^ 謾p=[rCz NW[ HHZ0Tf<*Ip >`aP0,!VvY 97B%vF `)y]`q"MyD ^Xg*#@!(,UMR3.$_YlՐ D>e@p<W@GzH H&4FVCZt#GL! X/OY!ȕFƒgG*qKD3-ėr`[~M(GH#&/G\H]FgecB#tb])s;'jP!@ pÜ-0$i3.'♅?PZ$8L[% *քx=mFq~E$P@DF LZ."~R0YT[*3QTO5U*}8LBᆈ!"?JE`ƘG0 |%)h@ސ b!v0?|MX@* µٸ,&d:Qǀn&(2"`8: #bVv J׉"vP>&i8<[K f-$D0r!x/ (%)i֔Á8)5fy-FN$W HsIeP@ڰ-Q$+W L*>HnĠ1 np&,D) Up9t' c[ l @Pp_ y $H` V, &X Dܷ90S|0m"70ć}yH &oRcrfX[| T(D(/A$+/$f7%\$UNXj4FW^@/&b8+VaAR'W5FԨ5""eFܳ01K1%X/B2)nz]BkÙ\&~%d^ZN<%Iq{ h*(4*_7AęԢX(t ܢc8BӋ G5!6M>[1n DȞ 2ymK)\ΤOt49Zj"_T۫Bt!8SL ()ro.nX&Yk*Vo"ShȐ׍f4u+x1^o~9@X2I&gD+ 6KI+ R ITD ?T`E~:z e6T3\w#@WT u&#E_Z,xrR7jDgkF&4/I54q膶X[??%?l@(C@l |@ @@@gCJӗ0K`355LP@lJA9&( Y@ _ݻXXM 8+*/AiK{q Եp*vXL@sq[?` I_XH'< HPYa 'Ȱ/t).41 3} g+ a"1a̜RqaЊkR*U ȢS!ءNt LHp_( <Fa V`ʦw[TJ5(H5PP R sKuR R$ᛕФM"Qȏ(+{(rAJ * %&ޠ@E{Xn )!xh \%`( X N˜Ad"M"p%($<)3zI++$tkBS$Q6G"쐊\_*)$٤.D x4J[cʌ[` -6~1c{iЈ(v<-Hٕ8ۖ DUHϵ'< ( UOd@Hgh FUQźЩIJa!ڃ93)KJ_HtȟĀ7p+hHp9S 8Q>-ȋȬӈ0_ʢ:Ih៵hι+tqʃQh >QhXbqXT0@h/v HLZ/BZUYCX {uۏ#a`s~=H,ԄZp.J 1`H.hv% SE`ưV+ٷA4贘4D ]3H#ȃQH? OD 7Jbj&E Aɂu;#=PLjI@ә [;8Q/Zuy6P/Ž7b (Vz4ͅNVJ϶lUu׿Q͵ y zc 8;@@ѱ88_CpW + Q0 ӝJ JagUOQzH_YQR}! X5'5(R8 79ZZbA2eh6C] ӋyEUC[GeT}~$vL `? :0?]OcHP1J%X`N0Љi <)Yd&.I=l.g#}XxB1{> 1] pwaXbgiZ ^"hNh^hn%Q(XE) LPxH(׋vA= W)K;<`?[@O `O"Щ`R 1BQ RU 1r+hǎl)]   e9[Fkl)-ͩ{$E!JhG[(1Hb䎈I))Ӻz2Q - Drtau0 ̑S"тB4NdkHI+H t|XOnVoLV@d{tʤ]`+u\0a:lf 0La:eDKԋHN@ 58M"]KWL.CX@##'!a G#:#LSZhRBK. xL6<$ ά[  XcbbZ"Xܨsܥ)?Z 5/(}G꽵ĄŤγؤA'($,%$?JRrDOR'kQ9.=P Pm 4Dc/ (s: mg!Ҁv7>bGk E<}O㣻rG<{H߸ _) * ήR᪚ȡx-ŸKJE0*bBim,Pш;1&8dǪS^>U rmt![໅X L+WzzX-PfKX 늈 + XcAs-V+B :򳘀=j{8O ȂdP%azU)W"0܂}&iֵ 4ک81)WC$ć}X=mˈ`P?˽p[g)sVV 2V; >ЊMXF%'p! Ⴢ* D>DA0#<J0vSi&p3B*uϟ; !N']۲KRISڎT\D`W1%9k(@E6~,T!7d" 0'` QQSBZ!b|(hoGU8XnPpC>p¿4 9aoZw=JP ߊ?"ࠏ?G%1T&hg2!=O 9S?T0-@+( GE P)?ԢAt?ROv[gXBAdv)!TECu|L`sAi  a H0K8MZwTƌA P-,Eu@XDXDI+/hf$p5I.*`/x묵z@2 ,B-Ց @&մlE1SAO&.՘BT Qg}`df Yhu }`kՅtPUB+0 YO?&qX*dːב"dUm#/WeA`S`>dT A !۞ZoaԼU4YpWo qQPMu,Eeu6F^/KDQ1a5TSEs~S;PUS+"SٔcsJc磓奣#RAaκ/y޻SHO4]Rc#stp?"aa?tAL@$?E?IG݇=!tCN (8DW':^O p A ^h@'D— *gÝ3e0+F - ix?e : )8&!'( !ZAL&3 fXp acK q_}A$o (" o(FH j"!Ey!/ ?V<2PP 8J )2I@*PaahЋO pIdФJ/D')a00\ZEr8JL[ -si' LA@L2%Km鶃PN %r@ e y1~1( Q/ +Z nG7?*T9.B:`,%0 )@* *@Ȥ/uXJ`pKh,Z]ƕ(@Tz( I| `Lg8e{抖5%5ZHb2UkTq`!6u  .9I[yb+51 i#[¢ l=JB'@A@; WwIeʹ' h 9o 3I^CHRߘ,8@()4ID`6:@E_Rp L2W"di}ȴlIDI@]?&KN*\<$1jNrD;9iw:)!d?QC)9DH3{тE3K[Ju0z&Kn0HBe/de:2 ` %c?t$8\@Yt0,a0 HU=Ua8#*pV8`8F`-ce( , %&p/ p ()4?`mxӌql9!nĀWi~  %-ڐ<Ig 뤐u lpohH VdOxR{P!)@%fN؂m$!5*P+%ؙЀ^t{B^l=+eo,t; I>%dTdaK`4>Hq6d AR$#敶Skoip5ޥ,]/ X^a؅%R2/ɅH!BERl 8ՎWlڈi nh%b>7hAbݬPq,U8G#8}' E7H_dwRp-\j,&S] BA5sDAbP@ 'dl֦uLA@P )sL]P <0?8$Ȟ $&^87B6&C I ; :ѥt@Ԁ:C0@@3T 4 bRD p}CK!A:@3D[b7Ԁ'A6DX4Т:@hTld|Bt z  '5a:0آt/A8AY AP35[xBC^iDęV iH2]HbA'?LO *Y%CJ)ف(C=0;184C 2CP];2؃bC=p4C85I]?D8\[X-DU֙D5L\uY TtSxJD@xHAܝl hHChVt@KuD?MwшHVuh]…j1Cī M@RHBjBA|k+%C$MY8;Vy=GA\+i]n=ĦI*Ck Lo䶢[D j!Vge x@lKeZ,DXTĹ8Ny bAWGċ#iș:Dp -!+W|Qu>XJ&}n5EoEJ˖mWh>K0>o,N և?Ȁ;<)46?(72XIQA#ȍH 0pD0T;@C6| B dEܠDaSE@}DDOYVX!BL!U@, (4 *)Bp&D^E Lx!B ʠbP 4\LC=* n5^\=0K8̃jCN@ (.\DÎ [.\ IL@`ŃyP-@_PClV6,<ZN@$ @EwԂDtT]_: >Q-'@?,ЖT&zqGi'e\?@ #@-1Z@d Xȍt@ue-@ih-#k4H[!PH D6$PCKD $H?HB9ЅvaG`u\u-/^ŽhBYEc8J1NGH@33EPP0P`psSC=V4R7Mꍞ6du2fA؂eG8 Y'.UN@mYu!T^A9AH|>i@p36&4X?~.$ CJ NQ9*̧C,9o*n8I7`D,6`%׮ZWTDhwLJT-WPHJPUGH1t=@;@H!C _ @p_Ȏ")ɐ"XTdBX&A&31'L `a%И4}3@ӗM)@'YTeL :\dؘ,vbHse˷/3ɍWxP¬WZд1PA"aDr|ZujS? q5L.Uީ҂M !"4M'O]_ S2uE.f_~_R K_Fκ ̧WC/8˨܃)C g3Ʉ8P8 ] 6B4O;j`3@iVvA>ye NdOǐR!BBHFȸE X%慒 j^@i~Ji*BR˸K i6HIe8:9%N5Jiѓ'-%f/-; N"=)GH dR?;hZm}), }IAR E"C@OÕ0c(7ng`d.Yqf6UGVȩU xB2Dq=Zr4$`җ6ARXL:$b\aM ` ,@DHM :2 P~ MG""yu Fc:IdM/%~8:TP&T;ZCNB3 M{@ Bz$):I69 0n|. C;Zj @#a$ 1) A8"@+aFbmbf#U{oez mVD8jZ:  ф~ -h[},ꤚIl{@{Pl#K~, rb&Pl CNӼ:Yd2r@xA%xq\~Z`⯐rQ`ifaX"V,$*@3>`҆M0Y$DC ~C C͐K#jNM%(U#!!_h8 Gi/@4}.&*:`:Cu's]qIv!y]HqXK' }^$ьjtT H?x%I% Ҽ9u%.qec*KԔS$jdt6HDQ< ^'DJ.Mq;zZ YPT A,u"˟x P+zq$o]:u"UEf#yˠ`HVp$S "Y)RX !*@HZА 5@؍_` ,q5aU ؄jоzU$=|A,^[CqeT. 40-8lH*Ԡ"IJ`TJf-5Q+2Zgy`}#z_֡i紵ny[(' uf;7.eT /YN>ѕt[݂&ba ֠5hUI5k<|a7K;VoUrtVhr>)BȥSE0!I\bV f#9A2DHhIO!R,@T_8`a@@F?1$.3䬨9"0H(r w{#`r ErQH-$.#(g hX˳T9"V@p  F g@?N RMcʔe}IS%Kf49C8=qC$ sM2E h>\B)YB q(kPyV! !fi)/ V\ xп &XgP }Ub6jP %B(|`AxpBa  IKG 0$x[2 د1|_5O%oMKBpE~ ,ksK@{y@Gy2:,0g)$0/$*࣬ b{#?jN$`/JX||" 2LDx|2** &NK&T*)l?2"$*:L(::cO0| )B֊}-܋+h <ƃΠV"3.HTT"j=%jP#,$Cl@@e:$;&$=D c CA,"BcB!C.N?r" ޠG;-,HVۮ:(ĞJH LLX*?$B>?N%dT" P:(f3I-p$0%I%iRT$NUhEfhLl9e*4!kz}.pw$$Ղ!qT C$ $Y$ ZRcra6aZ>$ʫQ%!"O&QP_̠>ru ()$4` H3`QdnPa 8XSɍ&&`fN!0V`C ,-Nph Bl &$|$/h@ 2Ɉk$H HFa.&@n"pR-,5Ϡ5MԐB6/P`^ O2%nj r*G: s #. tD8t&Rb&zX ډBs ; R"|瀊G9/΂8$Nd-#Fآԉ)o.lBS.=Q@2A@%)2C0Dg:֐]n8zT/0n'OB)Ⓨ @ԂLS(Tq@  s\ "-}Qh KĦ+2b1g(32pB2abL$rOH(7U7U8f%,*S:3 3$PA_sfUTT1$4*``/$@$t?J#⨦9Sv(bQ 7PVJMm|б%1gIU%\8\62 VW6<^Cp2$& *%V%&MjT&u2$AbC\(6$fc%$!-ǩHAcxJ6f Ğ,$ޑ6/ P#2QY_ʮ^gMUbB@6V /Ȫ<"$f(2>9ఎL/+bcU$61'-;BDqCy**BZz?D1N給kFT~Jw.qST;$p6wZs'oDmWtGtK+[4`6B @'Mvowm v6& V 7wyW.L4}Ĝ{W{yNbNcNBz!*BbRBxa,tw˞$x *4  a k%pN/Ӑh)jM%VVt ΞK~,%z""BB hc8%^8iL%Z&MH^9tmĀ>680֘8$$Dmbn̓׈Cb̄ h/'xH@NA> @NBTH^:4a6AYtc>k$%Ԡ1$6Q3$:!d5$jƁ+">pJ$d*V"$50’CB n40 K-V(n:#hY/@ <tI.^Pj˞b0zO!80z0zy-"xLb!LhZbx~'07z>T\B Ob'"* @*@=B91LBG( N#;$ tj} <@4,$zz/+s:)=LB '(hP* qQj: 4z a'hUó5F3%$B*OB8T#}# 7PI獋D~o(bDEENb5 N`S" D`B`hX0Ń>PȱǏIȍdT ^,@&d4YTHfU xB9}FBgcCDj-t8J7^^ 8P<$pU%H YuY9lp)t/ ɖA5逐.[d&袌6aCͤ)#AB` 驨ꪬ ԉ $xA뮼)%aOf0?dXI4(Lt}ޗ-RIlCF0D$6AxqdI$0 ѱ  !t 6" OEDRhtD s?"?a(29PŌ3q#&Լp@!Ҥp3y<+$,up3}v1!9q?%o}0PP7sA9$lP̛kCDAR&/$3FCpa <+ٱa Q C h]ZlTBP 0&W,! }AgG2 G@AyT1Ѐ* ꞐRr**WvEPn"vpD  Z`x ( {P ;C&!ñ~ e۝ Hs8' xXD"0 UZЀ?@ABod(\  фYQh4T^hKԬBlTgɋ"ulN8.J*:1"m$Yu"S4{@E A&MP5ĩ!*빲!UUHVWI(*V?%zV/~@D*$9R -bCE+`2Xį PZ4kA2/mHfÚI l^ tLPћ"9dSB&BPvP"d A`}+lٗ*H)q Bzy'R8ptDU>"%T#O F !jL)^3A*d/sM``)L  0q^DA"8Z #s0\Fp)2}+c`&}#)J!h >'Scg@?@8-XjLQG8eܣR@fT$$W#P[QHd9Y!}NCiU[! *H>:0!Ri`ك=e4B:DZ!7?MφaB|!e E(f'| $Hޫh"" r= r*$dHrByK[ ; A nf6@`f-.Mo5Aj H8&rhԀk`e#njj|!<@R hy[qeR&Q軌hLxFGA 68l}GRIl-{6̭ՆHK'< RU>uLNPc*E]"PMڇgI6juPmTJOݙsuӒz7oD1DrZ-sLipkl>VNڗqWod4Z'C`I5[jZ uC|秈"E? vJ^ε}#)"Uooj}F3}szqrr s&GF6V [daхec %?х|΃]lǃAYP *x|(gU2~] _ycQd1e  vd A B!6b%vC}bUODSc682=^E]?do$N&e4eXs 0Hef`dfff$7a#Az3 @V P}Dq$P{'Qgu{ 8YhuQbu4Y8HYQqL먂 QY81ݑtePZY"("#BUAV&x)&r%&ayNbv4bB‘u*f7vĒ= Px<ٓ>@ >Wa ЅPR9B(B(\PB  oTYfyh'5$p(2*r+V2iyxzAp a,Q8Bn5-&$r7 hFe/mTG/=~|K&hRuh"L $ r6T272΂3Pa88as`8"0pIpSrJS+ R327 w}875F6u3IyX#ҟtSls[ph"CNP8:ÝQ&М %0 a 2=H5 9W\cQD=iX =ɀU#QW*00R]H1T Vj K# - &Y)@jP CeFQ;&PoP 20 63 P2:Bv0 Q' *AÐ! G&d@7D3+dFp bU]AE~;PxT FE09cBd|!B@Xy`D `De ]Q :YSְARSTKPYBP>emfGa q!WʲW mv38g|G,LLc*۶1N0Vep &oyЇ *@An/q0*6`ql*W ] D ?\rK#5wwaӷ\ByV%PwaǏRvj7![2_0)T^%Du] 'KhPBVqzV~ |՗`=i+I8,˘MU}Lޖ)\Ԝ ,̭pFg& IlޖH }oޘ; * qHImѓҡ 1PYuIRH[hgq,O ~gԯ a #by_RL zZ`“‚ -p Pp D }(E<=R4S"L# !XYD(ӂ"T4]oU!  xR@zE%>hԊ*:(nsF,l[@V(`V"Hԁ- 09=.UOٚM6 xab2xST,J%y^+uMb}`Ӟ=Mɩh)* 9 e-U6GZڠ *r @FWRal(V ڦZ*徚 yoGT_! TK5h3R5h8`E?$ ȄǜܩOjZi.Ղ\E<|Bwcs&bْ/$H rw:N^SZ/Ȱi컏lL"R j??{ -2 Ǩ  l -q3z Iډu7wPgwśųߦ6PQX6vp %`U>`@gKT eQg"ٮm^1g,J -"DQvR[au^үy 4UcB]n^n׺o 6r!k9K2p[?dVg%`J-^?9J"e|:|G?:FĽ!niMN ŰJK`_ۥudv?W%}C>_Џ45 ^?TSP; x]& @@kTEJ>](5. \t==r9*0!$BH,&t'()?âK " J6ȢO0&K*)s836(C* 1\8@è0D;DDP9Ą#āЁH숏X`0rX0PJlLqhr8LȌŖha>0 >@o9R 0I+)Z@ãrÄ4(;t\ *FzH,DI /,X'rb00 8J7 _ء5Y= `^ XO( TFiY:H H  JRaJk1؍PJ 12X r=( !"zkxth*0iHwj(KHj@ h7PH  #oʺ:I3@=4I M-ɄF @8B"#D"j)lFDEP XɍP0iRXE%8y ?ٔMPk*@ 1C)):b$$HBlZ*\:YK# xj-'ؤlHmT I肅*'iƴA4|ڤ9`@u ;=&=kZ۽54&k&GЦ=:hd -2L]z*ܯc14X/6rޒݢ@"-⛜fuⓜ<N`m= +FP"/ |a8P-/`MSaт b2aG6%N>˔ /0"".a MSrQHߣ;Km\Q\M:d:\a[F Uc?3˭|T̨1Yэ߆x]jЀ@x?(Ŕl+\ ~Z%c0`:3a\cj@_c2@ aɵ;܄h_yCs\q#FR4jG#HCdH8)^L̙%#+hi [˵}#mWu@5$uʨVp-h>hyW502Wn}ePZ PizXX UX(7`ґ=81BV\HNxV.$S`=3jJ,QZx DjʿBD):-v9<b8y+/-&ZI<4{SQěs$9< վi МҶ ! ;P:X{ 3m_9날r< dsٱl9 `,Q0EfԻ^";)?;{ԾOPcKB<1"s<ۮ)v~?B>b8[>*@XvHˢ w ql࿄pG_Ac4PY7A|^WcTA=2 Q^a!%>.@%d)*+=a= X X(FM0P(@x ZuF;/v0,/Q 7-h$mq A(+|@OB=@+"|1< pCMthaУ; &LOgЏDdiǡ" B@bh)#x&H*'AC/G$ bF-l0QMf<υ!:p0"݌e}Z pS2hQ#G" 9eС7~8N3/0f5٪Ҧ`z6f/G9<ůJ2Ytyeߣ>?,^b #auH??d"QSeh ')DP,h"-B-pF-$ !AtnBnPe/+?v RB? b)q?o䂦$!BּxX.|‹HslRȈƇ0YҐ9dP`UX56 aAbT݈;Aj?HRN `2N 9dOC)&|u$F B:T?8L%'`b$sɋt0?IN[H?dM! g`H0VC%R=Bbt b P tO h ~EղHM0s ̌P$C bHMBGl!tCP@_O,$pE&U#\G@F)$6KK@ko  2ɆC=Q?G3hpaa[䐺eQ˻)۔7 =?Jt\BO&t."WPlQuQJ1VK)Gfxv%5}rEX.%d=AraOI5/4u&e[7i~BYo(yǑ(%Q,2ݤ0I>$ p#t[ē,!.1̸Zí?DǼ6)Q$ @6$u\=:LyAb<`Z&8!Ev2C8XGA)Y2e]Y Gleudžj$^!85 x$EY!EC 3R@@t!^i( 㾓,̒3,8M@cYG8l+tfJ${% @$y4 11 r. IB" QpjD|arQQHI"-'9 JIL5P F8)"?&@ xT' [cʦT)jTX Ҭi;Y君TG(c,!rYq֏V8\ Z"(x n~Qx<Ę-)jl!@ @ SJX`F9H[98#^MS*о 8LD6'" ڟ U%|m 1xNJs L4*Bh,WոDIxy pHM(e6;m #vl&9!.+н|G| 3â:%l' j[z;B؉bQTw&>1S.NW8!Ub# ~1,!F>-‹FC;LaV2-#~8o8rf>3Ӭ5 G.)f"C!7!a JZᶢ )OJh?d,A!DBCH_p":'l D0aMrUb HBA]!;?M j@bC(Y]mQb":C`tA\B?u=e*B ~ 9w)fIHw=viR/*;"C׶ey[2E1  ^O0HBRRvhB2+@rƊrtjalIg?9%* ?:Qv=:L+JI4qC+H` nejR@<6P-!Zlj B4M 鉞cԣ`;jzvHG`!gH,P"7aKn VQV`D @q ,ȬR{-7 _z`W{TF4TFp@ Bw~ 4@5t@" C=H=KɊIU=CP + HuϡywLEp{4^]Y I\ p,uOMmSkB`G4RPaJgF<~ČXDGfhS@C<@f؄F?8lՀyDd<@@ETAT@fGE *[KD@^mECDM E ]Ex=g(E`Krm"aNZDTPaK BaUa B8G܅( /XQb|}|yf{B2FE`wEo 1xk8~mDqyQc@XeDe! ;Jo(5},QQ9D!zRrxUa dT(dydTxlAaQdcqX|Ǚ)G a@+l?` 9YC|CA6D=@rt]TH&ɈCtad_P= ?TM_1c~ZDV~N#nLt P[Y InyCV pnq#|DnkI ;h!FeaVeD Td+]c s,dŘS$*SPW8@]X sQdTvgD_ndH_׊C,cq~}Pjה~W?Fb2r+5?!+!g}1sum"O4 %@LH')2**?ИfA8;,.2/21B`lr6X'/'2/33C\Ir3O35W5_yYX^K*ᙲ ́!LBx^aiEGO?+*qȼU9D!U,tI?4 B4.HB &4, 0@)DICH BC|t^ZQH]ʁtt:+ڃô! 4ѢA2@85Be̕*!+lu+\AK)|tA-@ td@PsA#&EɎǩ Bp!U̟\T4MB0ܵ@‡Co^= o5@b"Ć8DA.%XvuIHb<n;؁ TCdTçRlH``?^DM&̜L73C8d_Cc)AD0`+ˇpFŜxIf 4IA⽉ӹ&B 0/ `:/ @da5K EqtP?(Ȩ' 8O5)A쨻a$ =a8R8l3 ~B8C<|eLLY8D8?_|, wxH8LƘpJ@ Ţ]2cOy rPUADꈅ\0zF E_IkLK^ `x:wyYBhD_L$QzJDU<x WzAST%_'@qOd{̄.;*mDXP\ ^OLaʄE|Һ {-NJ} t#4Aag2;GpF=җ] F^%|NGWА(kq?$G dP 4.42{]Dt!{/t7jKIJ.C?e=|4޳`4JD?t]C H %Ftd&@]TLsSHDKrB5?}>T=`? (+r =`vU=/8D|+|S (WfS}scBB:|1C 38'@`B6@'GMa/R~ږxO~抗fp@lF e8barKả @HvV>'ڸ hH(B4hҤl똠C%'@( &,9?#8@3 4DSQ H"tP&/?'48Ă$%ATaL⪐4iXHHߕK"t X<_Lu k߁s3E|  _IГJ:P*Ł ߄8-'xAgl'MPDJPMN@@PiqL33$ @@${2t݄Wз?  : a>RP ? H9, O*Jh{Ç9CD ):Ⱦ C?L$>܏@n X0ʂ`%;)o?'E!ArI$"#3HäQC2SDˍ$M7 ɠ>S*CASJHR(}H+: &SX=4@[ICLcXJ@$N;+ Pkҧ!AVi'AA ^$ye71F^P&}a9~IG F Z" 4U O.[/`la~ 4;!Vn\q#>V@h<`!H! .&bAp EAj6"m~{B$& ta{ @dR2 Y 6Pm@p d JQ@8DQD0!T,F=@.A& sPHVC+|61?Qeڳ! ?SQ O&J2RFHr?=b2"SO#_HB9 䒙6( i=Le*tK J>\*%Lg"THV!($t `^Ois.!\󱥓)M3ՌC@*HO|&" i DM}~ dHG NN^ ctC!QNXhB>yV%gA!Qi̤5Mq%AĄ<|ꔨEO2-f(4SUNUUU G3b8}DEѡQ#&ѴFd?m/!CB+Q`jV#i@􏿰p pX[x -MFt}N"c/5tWU!+3Q:d@CZP(4 ni0$*PZ& |r[ EBX@*@*jx,?!>kAхt2F^1 av[7A ԰ "*Ұ @%v ŧGhB"@%MzF[V83Upk/Fَ٦ dz#[BdJ  e7C| Q79ñ=YZ%@\8!WHATHMkZ9MH$؉&PFdjs^S-,%f^?e) ϸŭ.3Q^ XЎT< l:`٧){j1D!@ ؇!2dC =fmp `>FDB`H&.g܁(a^ΰhxM>N=P :G v*HL N/ f^A ;Ν>M G 4`D†R94GMZB{I >D܄4dߎL @vwq༪ m,!-j+ pT*$<*$!V'*`$ .b&c%b PHX@aH#7"C B"jE*#/5JnfC1D V !:[.a)b#ER *+l"!<0*Σ !+n`& "kDK(B!EjN86V"d$fc ,"a$+-q JtDZÜ>^B0LX0pbnBbf@00Gd&`Rb+Od@c.>):G:/HdEL$A~d-iML,E4cdfeGQGx#AބNI7'H:HLC(E^oN$2^d1Ä:֨O`)2QF3;BiI.4.QA!JC$d<1IjHT3SF 6a 1@³ qHfHe./!. BhDa$Ѕ&3 !TٺbSB.IT8%%Y4(-B&ef|^!zUV>̔d`4@0If,*+,S6"B$m%t ~=&UdB>á>PhxG '!PPtXL'S0ǂMa}axBFB0!FP>fCӆdİ R`snTFjTcaB u#v"p܆E1!񎰦O/A1}^6J$T`YcP. BVUIPKVRH8bH^(f((L"* BB"4Z(HC\h@,O zWP /@!4hDVVEjbh7=)`Î|H^| DB8{5M)*>zV^R 530O n)C>qTisn#J5זj֩)$+!l Pʓ]sI#P +IY W" imAu].iڃ(DY`ZuW>AvMwM Wwx7zIl%:BlNVz*I5D5p7}w}ٷ}}7~w~~~7w8x 8x!8%x)-185x9=A8ExIMQ8UxY]a8eximq8uxy}8x8x8x8x8xɸ8xٸ8x鸎8x9y 9y!9%y)-195y9=A9EyIMQ9UyY]a9eyimq9uyy}9y9y9y9y9yɹ9yٹ{yn}}yi9ן?a0 ":!ڡuzRځRP>6幒p!HWLEZfxeso%إYm&yi鶀`ڢ]!zumکu{m_W : :Z Z Z߷s}zcպWZ׮zʭZ[9б 9;0}[#߹59;I5!Y{Qq lZOy)a z~Stۂ{q[jBDA{ۺ;+8ex۴#I {ϛK_hR䛦I=&|C;\Oض70<5|a>83AåWRR-}G9\e<-W>zwMlj|ȃk\i|ɛƝƥJʧÕʟ\Ʒşʱ|<Ƶ̧\׼˼=ޜ4x[✅| |c鼦< ЅX OMܕ-15=Wӡ3݅EIMbGGԽX4 { L!pf tO@Cu(l1x'F ޴K FUT5 ^(zT f%H i@%x@8!FԊ@AO2*%WX ȒPiDYNGbhOAL2Љ" Z+pFoNnUlaU@aʽA!TZdDT,k^(K@R(7H@5 JsZZPOp ԕ@I!A^xBwV%?)$Fm? ')TEPRxHw1e76Hy$O'<E@$h?8PYEI xMNPNHh$s?@*%?"Л z 8q. ?-8`L6*(`+1LD+"``"}qPd**dG) H|RoSQEBRePF!ДpPOuPKhUP >@V%8A4"{Ju$k \=PT(F AnAlA"+U0;ū0D #Kt":V Oweุz`t(Ct<nnD4a[N|p@(wSmM45ָ]X-}(TO&]@&.!s9#yAgP}UT@{!YP#2-NPBSAa_otb4Y{ `C}X \ɪv4haJIG vGuNX43lHW'G2 !-0,R @3$ K/K#DF <10‘_;fDG}@ȠD12ɨ*劶(2Jvcr@>J"C.^IFEr6$"#&I,lsX1~5^R,]`E^j9S]{Z:.XYvTH5(JCpWp@LhR6* @xmFUq$K`{H.X*;vUCaT+(In ? "A\uXK-&.bfҀ{$ІMp՛8]Nifɜc H8}H9( [@>U(i:zC_  =x!@ &20ͰgjN`f,AV[l 0$a\y:L 4ʼn 1JG^$ PG t`BvO_.ʠ d @eJUYp fLIa)7CAr:7H|1:P%kQW\t5l6۰Hg$5eD̖(,F;7XbBͽ*pg ow)| "X5xGmtI7X@#V@؄]򡲈C"螭9 '-^gʯ~Q%'nR֨&ˍ^dґ6l'P x\ N*5 N[ϸdVK]5N(OW򖻜ךO0#@*RX2f-Q+S;"" .3Z341$!Qgwf~gHAI91?yCYprav 2  p KBƐ@AaC)63HI^~A!2x'!UB24`p d|їxVb G)9mP(z@%q !u$w+@  @&A@ B  p ᚰ)ɚ`)D@ )TA&əg|#sgR1^ P18IqMQ^ey</A4$mQ[˔\.BbP , w:ȕTm =ex!S1 h)744ԩ-G`P H ZWp &*pA `. `pZq%ꢔ&qPc$aɦVi"A@Mh~!Y"TTUR 3n9nxaW_j0:rX2wmUBE0xc2mCWfUaET #2u<85FՍ83jXrnF1S jq  Z&<!߉00a|fjp%L%/zGyMiCoY$epa1bjh!73Q{ê.jJZF[;3 p/J{`AQڮEJWPJ:@ ARفVd[B9٫aB$6C@{ nɗ2YIhJ " p 83ӲA33;6f.♠yVxPmaQY8pћ)m Pi[ ):F1b1sX&<9!űghhf:Le;$dV#92 Sף0tHfMUykv5f5;Jg?YASw>=jK8#2L2l 5&0S8;pFK|[Aj xQXѼSV 7~FڽSWT%ܫW9p~껾}6*0˾h# [< )\|&gzV q0Oo}  TXtMA繠$VGu\W_9JqxDsq G ,1YxhvsA gnB17 +[A|~x$5yE|Ip;Lp8,Q +C+c0yH&p0F&qI7D %^ƒ [k~5[^pR"U .,/H5q$/iW@ݷe*f +\ p-\p!47LV&29y$-c$j?9cD Er(#p!0LL a p090\Gن9֏Y"U"3!2"_-Aa0.kqV8y283!Y5q0B.%D.ׂk1w.ؒ7{4Zk#.I!A/#7x<I=w]7X-Բ07=!r--[5S 7## ;!'s%79>s>."Ȇ{j&Yr "1–k!#@Y&[dehtG M|pFҙm+@qу"`{&c&|H?b;GBI1AQ E!t!09p m ܆slIeh${107rrk0D2KgIh/عz>GQ z_ R{.^0R=M 7]Q 㤫i4Ix4iM9"G㊫1"NzQUꢫOL[0$N:"gG]q .H.b7.(j=V:8>>q*@ض{+!r&9hX5Eԍ9YpY"fiU©ߊ"B-A>KEmVNe}5!5Ui85%5sS$sz%j40Mq+P]kw@|[Ee|_/.51R S2b:LX="*D]1/z+aZ~*3 oޓ 0(UQʂ9?KB?`ྲྀ"]V 6" eoA&п?pn,]75Ph ӿ>.@>Dݐ$F>q Yd0+pP=o`%i};;C"4Kx=/Y>$<AV3)O@ *  6x_0 丱H" DI+Dÿ3i6,RN=}TgЄ$6SRM>(OP%ѭLZ/դH5Ժ5-ͮLdžZeܩa |^}X``@[2E$U89 ƊG%maz9Zhҥ.x #:6HtArW}\p|! k6 !((wqݽ^<@4L6"lJAAT %xYÂ0v:c$`"Ajhx&‘4lh:8J`$!!a"' CD/9ȌQb<Hb BRɁHGA!G9Y"Y;[nhV.[FAr[2Q]G#E @> 4Bt3 d(nD蠷"pݨ(UH(@D` ,[!M-fxe)Dnh{% ǙOW>Y"đ$"kHvR=)#zHLX!JD6dn'I- X4"&&$q&M.^+\+NCв  ls @P4j z-Z5hb_a X$V8C 8ZStLZ(Hِǝl1zK|Q">Pj xV\DIӨ(\ +Ueo֐*i<09K,@aI| b &LK 9Y7P.o@ŰQ"@ P xVP DthH@Af ~`.L_F<JC A8Lr#a1WDBptG=y>!UIE[bt, @QUS}AA7GhO[GG.b0Ѕ3$ mY@$?L5A@%$BMLɪ@PdH@#2 vj>t$ (q"X@]W} &'@Btw,$P#\8 h 6u)KH[( @ݢtc,tA&DtՎPh: 7BQd$H'9R8=HJC@`ֱ^2N 2 ̩6eB&IFGejH\G|+kI. %ItÖd$QD*ޅ$dk ;iȆ r*F'z)PSR y&򖹜VI0V igFB\o{<(3,|CIlHc멽shD#9{H,[bEP -eQ $}8?d? Uɀ);:ɖ!|( 6Jt6E,NJ5;(p; Cp =HLA+1'w_@I(HLr#-pjka28;)KRv)yqA(Nt3X!JIJi\ @x'1Q:>C DO IP@Pya)hfôi !U2h򗓹~QQVrU))Fh q4pYeppEji(pk|Xw0wiH,Kށ40ډЉ8 2bZi 9B9 {D ҈ҝ h!$# YX$,Ձ! b̹pI!zlC X!   YIȶ8$ Ќ"Zm. 0<<Ȑў Ȝs: #)-K˿8mq xc7wx3""  wC ڣ"K Llljd{G (\#R|3 ϳ O{9 3eXXO٥赽'a3"6M9!(6.jcI!VI%WDD!a[Ѐ0P=;> 2( 7  piXj,&8&~0};sÆc$iЇhkPuwLpKY3͆8m+Ê#`# j*yA21Ӫ(ς-Ċ1T֢,\ T!+EH#29,0؂HS9%L100 9i) q@q@VT MȆ8BІzjH(i9 `L/X`0芼b.麉0:: 1 u b.  rלd [Q*蝆(,@r r}ϘV^ $#+!:>>ې/# 2#24--LȄ(*H$`9G B5,ي!@ȡ`1JLmQ O TMUm xիZ@ (f!&Xtx_ *(!\ӝp#%?6 (8M!.HOP./#Ce؃0GXVG?@jA!Z\0@Ȅ@p$ 'aeg(r )^Hv؆Nh~f> 3FH] lp@k"PaC( 8ܑ"F" 3" #XAxL0߈=*UXq=xl l #pixv Ђ*M288qV$i%Ȅ؟ Ɯ(eʞ% ̶(X˸{ ÑL a^Nȿ3) =2_TV>JQ( `ɤ7@;R [wq^l'vPS Xb0`p[Ei7p, a^PݒN[xDꁰT!q8ָD{(tܼЇTcWW@l\0P [PwߝQ]ܨPjL_6Ɓ-_Hs>pE 4$urP>fG3(ؾCM=#ȲHC+HҦ&e.iOR(#!aVhhVjЀ)dab: DXA#(*>EC l:EbGT3;vOY X9pTeRd B g*^:קOǩl/z* .类zёaIG: :!I+I׻ ".D ʹbEIBW fИؓPԢqrtzAu/U<1bP*?}mr m؛@@xx0PԮuި؆@Y@BK.9x|N$ $k 6ǀ@s^ hp)TaB :8/UE9OK9Dq 4@"U+H8ҋoiPT>X= qஃG8_BV :v7)C@Iamqc Sl2̚73|@>d"A"l` 0goJ45 gc5r萼 _!D4M_ʥ!X]*W,Ȱ0?BS=0ހPAU D[XЄ]XÛo&oy!!8"Aה=߇]8Xբ{`Ў)Ԍh,:hH$J>E8@(%]z%e5tQeQAWaVX>  8 0 (ɐA{ 0p;b!|X `zp?0`x^QLbP`I0b v H7p 4X*I{]"R]YjYt/+x@@f0])BM`T8Dh!5M *x~ղA2 ,(*0a CR0y@B$M*0/K똏eN\3 KKúb @%+SX%` {"}`[V88T4p xr6OPj!dW=ߠ& @4@AWU&#=H{0`F1o3yQƣH *i|LSs.,>=@B'm2)K3#xO QI$ Hb њAбF}f_Y&" ӜQc(莛sJ(h 1?2<gSCGCP?EdG&KIvQ S|N?@(@@g0IUl?c!A%?Adˤ@m>"}" DpJ &Cd??LyYK {@wbhhޮ 78ےdə<x81-#و"jH[0/,jH7ʌ#Z%H$,S`LH1p ^r7)r%o`*z*u[SoPKӐKII#nTLC/KE.? 0CYIEDqU>BĚ$CF'0ddFʮ{vAa$.lbfb2F Hu?DPONKdA fDG{Mx7DDt[C㙤, AЁۀE[BH N!R XO¸,[2D'xC A/PEVem OZGq%Z7Bn-B( @l(i`V0@BzRM5E۬j%-+,Pe>C0>9$T\Ģhsfͮ'QpBtA(: [OZg4N'HҀ5H&ޗ}nXX8Da? +`!lj:x00B 9jwAC&xY*+.ĉ?+X@@ Dp! *A ! 5î+:A RAB)`E3smm9.pB2Aa8ڄPgD@@/=螥QV]?om銵V B? - BT4ꎶ:=oC飑vk:G']1B5G-qVP콽xN ϻN@(Ia<ԋVOjy:B&GSSDȋ[>8ЎR^WB$ařT9&ąR\ƅjXian`rs.Zȅ̫Gr ˊ r(\2Jg˅(%e!(E7)`!W`cGtbߪh*O tRӊ<q4qB7](ǎ~t=z$T]Q+1'-74|]X4FF8U 6 @-BLȤ+.q D:T:XXwa_Í+7skg= Z7y H:,;w-7/Nv/M P4sa؟蕉"rB#`H$U$0tA|%*4l!`+a!GB`{tne4bK=<`N&̓hM@ 7b -;Y 7 ЩJ-$TQ*R9)Jl#UGHvDuogr?T,} ,9CVLMFE$$"(M YBvѢhF**EdX^>$8. 6{:1BQB)n` %OcH72CÝFy|lbNDЂ{@5rRЏ bg 1? R,6g82(A"(4gqҜL>gwL;x"8!;ȥ-]p0g̅K}9k< JOP4,.w:?wJF}דjj԰f^YVJ@^iF#uJdfN"E]‹=.di[!YN}ްib2/Xh/Ջp,/ G7F skiY-d'8%]%OV!b5/f[nw^ BA@4'mhK,('l@Qw!V*"k<; *a? O"4& y x ;drd7'Q%~*5$ Z94 4r"CJX2* lmba 4ҁE14gpNΐ_4cvZEl4 !G &{?\8(!if7[FM4aDehE8CU /~C33 =+7Hmz}UƖ!"b_|$`]+c!8G/#?0}lV{-mN lSfHZЀ7 5j=Vx?WtqxAm{ߊV pþGlb*RX.X 3jeq*Qpwp)@Wr H? Dr ;cfUcGdftf8]>uWe<Izz]ެ i![zlxd$ګlNUurVN~ Pߣ9r8vGݯۃU);b@Bc~?~9Op"#ͦC46R(*/*XJDC %n̨"FDZ*fME+)6aM!E`!M[j" KV0U!'T rMdU|!"lFOb%D`BަpPeGUdLFHmjd"4eBBTp%6#W4!+z/zo4|X)/%/&,rjNk0G m8]v AdNC5!f8,"СGaMƁi!Zq]c"cL!J2~dBgnBm `:&яWb"!t#i|͎(r.e&n r"kp"!7ʧ{,'6 H8!!CP\cu/|$#l6u\#Vy*Y.D5Vg"wBG%"2f#x\#uT'ާ!3$r'3}$#xg8ʏzCtT~nB:8@ //p5e5j h,ňtNlBa0..Ad:С"j0!U,dVezv sQ*1/(401* HbX .,5''H/8C/3J%40'zS4(Ba~ : DDnB mXAgIMixdq*!"X0D"x!DMUJ!'" I $hDj!P7̐5!Hr`Qz3ò";r?oU)4*`' ( "e>C7, =C"r FJ"cAF50O6`"@"8bju<@ 8B!uL"Z JY4/T/\7ƋBxhì@Kz4+}Po@O+wRtR) &g,lMB Q+uUYU]5-DD ,VVa K">N}P js␉Y: 85S62Z}8FFf!"!fd4(`!. =b,#6i:̠&oR7э5`ex>~N@iC1 @ya@YZE-K. G-5p7:GE"tzn#zk|Ns#$%4Fs<67+sq-6vrr#y4j2RgZr.)' 5 sO\R)DI46SCv&` CjdAmүsS/36/(fGƁWaB"S[G44…jLBo ‹䶿|n+X_Y5 2B3W/T7I5Ll\<6D 8–:@s!Eɠ"aG!KB0zdz"0:;}sIDbTd's_"fw՟x:Ļ ʩi@uJrIhD)e1/j90;Fj]y֯؞BfBG:qҤDS4:@\4( pJB8ЉjxA6@-&$ ; RQ{6+éW#~L-6"z6Q WN]a> A6 SKc1UJ.dz32d>~#VH#;""|u!xbdqX}>^uDjLRx )U"2[g2~ ~P+Vq7,fenuDVgo*`s+ L2?B1Z *a!BF5z$!dw4BV-@F*vơ"zmLlc`T(a:a\ |! 6v44"l4b<abD#B@&<"H2ȑ!ߤQ)L0_|}p򟂄R| I@ ()4` j*TZM5XӋz9H?7t9C)U{J)Bx0,K(Ġ:%䑇#<\r*k*4|)qUN`SʴE--8*Xy_t&B&i-LNՋ!z|AsǏ͇l b &d RMའȉ aHEI +d"Y@$2TB4Rcvp?H<=F0xJ1"BT 4/!@{A>uA"ăyÇH? RE 0#+\:ϋ z@t(1*uJBU#QDq(@BЩ XS@@I V G;Χu Q#$,R!lW!U% ZyF%Au '#%~} *,$!s\rIvl&]^9 ̄()>ǖ9]!p7i~ރ&C.CM@['Է!^ )gB7TK^&> HO0 )ȯR )ش1N@

wvdWG p<Ջ0M'bYUMwZ׼c`A6xmz9rF#gt+ r5m )?apXW*ɉr`\'6wbr*9[]q $uJ-HtǻΨ5m!s_{B#^C0^C}>C-Z# C3  2 d+"(AEoCoTLi 2H΃6tuK {HJQE7 H#T#!'n0pgtWU:T JGPEz͂Աq,Z$"Ld%wű|u Jr0TeiKɃl3ln X#q࣎ eF qh ՇXon HG!99( -HΞbգΡVS!tcY_ XzQswFapܱfn?nZzrn hҮc- eF Tl "tj\CACrBN3D1g]k/x2TDNGJA64C9h$NGCz6{Fn34zADl Pb$m-ӊg 9i,J kG \~8? EA~K9E?Y*p&¾o(b!N؂O܎cCHi␘&`G T | Ҋ1E0k@0K'J<{ŖrIUJт0(h+&i9#g ҍn)+!|ݣH6E86`_yaԁzuɅA&QXHC6"0'PNO`* A0m@ Х:p b: a0`$ty0KQ[˦4RqVczkz {E);'mv'Q(9x'r#L,cP1#X")B(@tEXwr!Q>"d!l$c$Q'B_&γ([%pH>p5I@8&M*=(1pHuR x%2##v""  Ӵ)">kEd8Ac &!*\?`TdT(Mh'LA%`XDQQQzxmay#-9 ΕH>B(˶/vH#+( aq1qRf8 ePV؅9(Y!a'ahpere5` Ph#Qr xV' “n050 34AvR 't|y N}˰7`qip{ QWU0XiBq`{Q xqnr~ ~`Xɑ`E:(aHI 802cEt $<Ձ573v q ! fp@qs 3DK3o0",@O1,p@T&uM  063M[M `iITvg::ԣA?-bU?"+u RVɔaRʗz((8ZI+D>Nk/"fʎ7v-B6vȴ$dj!!r!' /"q[%Ij (2\H2+tFeNZ+Znjԋ(i4]%sctj!rJ3vP$Q L`WB#cOaFԘ9<u S@)O"TPB ?c%yQfFQr)@J?qu j)Ч5tFRypty9E+l-uG?IT7t;k^3Nǰon0Vda3L)D2)-*#>!P!X-b'#CGq(5R/&;x )XVR! !S)hQa)1q."(#dTLtѨ:$)|YlUcb1GD'"* 62,KdB!M*"eQlFT.e Dc1~/Q^#.\ScpLQHg[Ēٝf`kE$a^eX&Pj0j*q6 #Uomy3ո ;3xgto VAw[ T` $  Ѳe}nW 0  p Q8`035Np P͹x1|Ϟp@ND0_[;l+2Kc VV-!ڥatפ *=fn>! I {1@fVC!`@(fP^障dI` IvQqX! ^2 B[`l0AL=~""FGR1Gb˨b%rލD$ d`߭TP)} Iǔtc[mGdZAba\.DܒKk)irBYl䧴Szal М -sOs 1 `|< pԻOs jC*0` M5Ob9ZOF$Jgyppqp$af[nWV UC!x _ H\BD7% oEп TūM0sb443(`@${`] s|sR^l![ EDFS hcCzn1?Q䜙5o3*G$f;FS*ᆩWcC :l#쌘l3pK`_"l.b_bKVTA!,hA Zz%`(nHL"g>69 % SqE[" Y'htqG=5ƛ,0h>LbR6)1(ʈzk6+L:trM6|2I7]$K0&3xe=kÐL( :tjHqP8" 5 tSN;Sͪ4"QaKu5݂i҈\\-V3%]jUuYh9`Jh.[]ܶےԼp[^\ ,6$߃҃r!r7[z^|w_~_j aR`bgf#2c g(+ll/mx#̸WLKؚ"Z eE(D#x90HnHH+-8x""#B%$tH[^AMB"%N3DhH<0~(f&x P<"IXqB0$0`9F%u0&! UXvH Q' R@Eb"I|a6)#>#^(}Y~|C0hU!ИDRbGU>XPFLb I.h?7B D*5 W*7%!!畕 *с'5*#k\NxbT Y2#)>+CC0F+PQ H Oнt%o`AP`@P?HupF*`lD@E< Li(JH*RN>|'/T@&y@JȤj0Z6䕬*0T!(08_Ƭ"4$D1a| ɀ`pHr9'C)@#(2 xӜ)Dc.)3:[n 9YbͰ٘DGhJ"PPyAbJŤD:+Y"|yT'eɩ "):\ncQ3ܖ, KL#{1FBQc"#ёz$,hv.q]=@DJIuFFtp4,ȖDj/Y ! +UtsѳB⚆Q"|8`CdžO/#KJP|43+2"8rG ` vH P,!P4?(NR(220]|9 Yb)0E {4,HE"ȇ\$7cIZX $ph4, `"DPE5^ +\bx@ަ'liPD,ʓ$ s-cܐ+H@,a%™Z^Є4*pM$;Cь0$ J! O$l92=0b^`J"3\##0[6fP :d|9&c|/4Ds ubdUΙ{ Ǟѫ3j^i癐4%>ر֩DbjP(<5P?I_K)V;mD q+e ?2P&$m}.3qML$!IVjRX`U ;kwhPK{5 4EW ߄ C&?$,6Db̖g6pHc:|d/yCF^-d'~@xjba: !km@+"q^aRzҀ i&ixѴ~%1Ĭu|u}%C<(DVʋYcňD5Qb1+>(SP "y& Jba7D5mnw>r%sp{[n+3"%JY]W0吤 ?v| ST_F;3E$ZNAh<a%=Z3j:фHpgՈ$ )@o˿i8h@xi3%*АaA}y pR37)i'HJ4b)*9Dk`B*q6 ' br7Qk5A6, Bkй6C?C@ D0Di+ynAAlDG|DHøUsx$*DMDNDODP EQi٨ቮU98YWC(:*0`(00T 3@Ŝ YK` &j2H8ٛnyPGLwlqAv3x>x)6ɡH)CӁY `'0Y03>pH„E@jGz9ϛɞgԬ7xË@ Ɔ!I@ 9(z-H!=f= 88<jrQ S$^J-"S/v<"ҹODڨΓ$rƆ0!P%P%" "2TJ5`#W7؅`H׌< @{L @$ΛD+qN%Dsx:h %A`ZPҋ9 &iν(P"hUB)AGCz`BV*&f'Y"L :kmyqy(帶6HtB=yJЀK*SWKҥ X%N⼏XS۩!4ʈ؏(( Íb{s$Q236`*78+{!"H(f@S?݈Szی|-m )T`0 ڢ`8%FЃ!ʆx a0`+McDզ@lԍ/L>d""̜#jEL+4%m:݄LTS8FPԌ/(`]W `^hڀ@R9`G34x$hL؈k'0P(w#躆V`6`S2eJHKk9T*@@(0`"ALx  M@?@3@!S34Á6 Na46:@Ct邡$l93ᔒOz6)97 ^) ɀB598677-1%7v,Aa/KZb'(ːu\HB F"BX54 P %ɐ:-0'T{a uى9i(EuF[40a;EbA:OJyPYy- f,}T5nԉ H'$$M[,-ۘۊKhmsMKԒ֜ x Pj&VTga j.7="Iը, рV=h Y! _8F3`ibbg8jV !9V`V C q tUW3-8=@W{ ^h1Kz@`9)ٍؿx@=P&WȀI(U28(<  ' _ӍI_07 P3A5`>V_ʋ'SAna TX+(g.rN¢j7w6}-хRhbG!4"k1lB:1lggaX6=YKuȉ4 Kw͠:ɠĠhh o$ -G-[ @!8@0`)-2!`Ja9'>H!^pj#P ZI; hʆʆGD fm"Ca32iR # 1ؘǼg O,2/%Ln(rxq@!; bM=ڠWH̲sj?by )t(ѢB'|9QBd"(Ta C@D]AK_ :xUhEeܵmv it/_"DpxŠ<>xxh@P"\S2d|烘h@)&E'r3E& @pwٴ^ď6J(D O1MMM!BLIg^ᆐ@]?襈p1FE @Eh|(#"EhY[Qp-F%1"w2j$FA$qu S`4rו \QZ^qҵ(^KΩ <2 4J?#D5|4!BL /?R3 T o0D# /H&x+7R-!I ܁Q$oc˙;uEOdSojv+$?NL? G)%Gɳa~UEZ̢EdÞչ[R\y I9Rd+b/2+ 1p1&0l&D B & ?@ђR3mLl icwvc?FA-p<%߿Y=bDaw=bp_El?_ao )}#sԎ6}sdW\gEe7TF'Ĉƞb}+o]˃~EEϸwv\/ha=B(;5㟿? * y, /D@<ͣ\BvQHm.{ ;PN x.D5])$BH&,l/:!{'AA{s"1J\"DTB.ѠQ E C/# Z 8 "JX4B %CD6("Bes4+Lr-I(_>ɢ?0\tP^@?6!k'AٻF/ A~wp$I`y$B$`Rd o$O g- I#ǔO%/"":1NPDHA$!%baƎ +# G4?QqF%\J!/]|J 4@PQp.A@&=ԠB+j\GFmA&D3*TƢQ#P-4 Tl/qȥJtZH^ЯlK}MP_X8SV1Mvu"'O\&PM;" 0F=jioU`UTa!S' ,(ڜD`GERS\Xuƽ~FB`UP( h?" ^۵dJ l&mHܝ$J b`D` ~$5C`HX.5,N9KP@t逄!^g{̆k<<B()LtraH`15 zW|"5$$O*psREVXv;yw HC N:4b/h,9NCgB庳G~EC)VPZ@;p`(K3ښ#cD'Ic"%EAp ^Mm0r2"k"ҥj`-<"`*j@YKč"T0=_WZX!*>YMPbW=[ ohz ~P"RBN!a8V C簸' lqBY*%@G C Gj Х',"$[# R!xGQu"LPD<"q&b&O$|i EBp"=Ʉ7iD"Y4BD%4<kr3 28DA$PwhpIV-Yq&B %y(0 MPP!c#b ꥾S`g1É3:q[ịb"z1u&$=qHMb mHYA`]iXhOzH`GpFEP<^e@b^f8F~DvP9 &E }ATI]LMĔxP=!!ŋOgxEGQDl^s@D ɶQDB^GHMDhyMu.ZDy5P^- @UhoL@&HC] *$J*LfHQ,a¤ :LD ұ9+J$L!0?B1ɰHwЕ۸,E)$ L a!EEX |0RD]D1KQR}cՂ9j1#E\-T6R̽ |MD!uAӄ 8 J,(NPeF^ J oXMp Ql]DLZڐ^iZ3֏ޗt | ٨aKEKL@RjtB x@D$XA٬}P KFddڏ bd&fc"/tYfrfgzYV E`#X6.C7Te|fmmz  a uTÕ19%g _r2gs*He!Bd@_8vrgwzwz‰<9]A9,&hta?`y Q}RPß$"hB4XSA24THLR9.0!!$ %SDNNQm4Ҁa^Y=P eBuJAc!1]NhNd4鞑Sc^F%uG:S"Q04.h:LD9TpC94?B1`K4nК D;4ВKMKYLє/T 81da0/ 0@*2$\$# .cji?pWG,5H)MɥlNJFXLL:zBz@H+k^YCt!жD!c@/|iaE`I@M^fP͸?( T)0̔2X?MC)5J>5胧25`@8 ^I>tTBWv]xuۼ¤ֆ,l~IG. `Aa},Bp|,d??hJ8?D&b I?hF? @!d]P!#]<ċ@ڭpP@H $5hBGHT ƝtYI?raHt>q(ՌLR:^YFu#DHX`Rx3#D%Ş3AXOPooL?T] |H|%.P@`B&>?M?*?hLzbK"/h=a`g(dad@^6`t kṫ1 I HoT=eNަxd˶ݚZ`~wT`|Uv4BƠUbrmo8==πL<6J!HCj84!y!IrȔ`,/+_s`/ZS@$?8_G35;_;[mOig{t!QDB_C(N\tF IkSڊzgrK:HB:oIW5L<0L;[0LD @j ާ O1 2&DQ0fpa:hݒQI2ZqkD9~UkYVnjaDܤ%hs q"S0> ##!>,0Rl$8%P GJ<@gN?l {Z T@ $W͜-AMN`j{G oMyLXeDOeSz9#(iuOz HMG؎b(mxKDPʑ@gc 4zK S nP aOXN8uqWUt5Nd_q_ i+ g ;A֗֒p?sP s-{kmֳqw8 C?\"w o{˻e:VZgeQEټ|ω\\l{95b;?ICP`(\<'t -A;cZ)<Rlҙ톎?IsD%Gpdq%גR7EoK?,D(=h@F(#G_DpD,-`9ڿ6Rp Eӕ]=(:BdA8+CB,B] @@AQךDCs&B<3 [QBqMDO}@LUCv 꺴~J#j/dC4$ *TŽBT("gL@%PMȅ9xHkM3aE2^D(#CKY ŕK$+? J÷I ƒ?E *D LHE }Jf8ʼn:ha,DpDHjkԇn&΋v|}hgIR`!`*U_ 4 !T'<ɫDi"#, vtHA!ٸ5v [0Ϻ!'!@ *`M2$ˀ#>̃k Cȱ|ȿ8q= H`dBſ H"£@EM0 D"% ..2 κ7ċ;9 \4 <$lO oD;<8 `,2A TRKmAncGW-E7\qv^)rG`_X@t ~Z$MRM"0N!%IO<M,jYR/ աYTJuLR\}MV$Я҂bH9, ܎5N!ZP KU`DIrA^bT9+!UǭGjk@W F5)DH+ C:Zl@H`[ a:⭥n銁x1`? KZ@rh3P:!! 95$4f XKh'2s'wI2LJcsz! \8(("Jܶ.}Catb7k7-R&D9d+2Wb BR5`>?bm)Ҽg& 5BہCAHi`d8aZD3 c"$t@qŹLXH!E\Nv$ZzN\x.b/EH ݰsv"u9a fh )qA!ah" 25 /6J7~Ic6!eCa<@4p7&A8@CH{2Egy@ =P"Lx&&\9&a/C&<<@.\@ݝb !}BM/u8"$O"bdp:jiիbAPatz7dgE\2 o .8ɱ4N@]+[\20u'HK}|*&I@N4IZ!^.'LҲȄEŠW\h.ZXXhӢyh H#. D0%XJ&XqWF4R )n$;0(;΁sd |ig%"\ Ҕ=UM1C0^ > }=(a< 88X60ƹGLp&م08%gTc +ܬ-Ό EЎҏk^׾ߡQ \]=^)\a _. +k~EL W&W1b1@]!&΋8*P-(iIVdJ 8GrE@3Cve/a6(> p€ "R tR 8\:,s"!zFz(4R ^p'B\cuȢHF4-ew9 -@3JgA $2YBk̚#ui?#qЁ9)d̯5%4@A=x^}<]>(H~@jiyMBZùߜE Edib8XҀF6X IXa $dXQaDQq2T-+PdaH'B`(pcnK@X EV݅9@HX2"D n.τ4QypfDIyNwG2):x$xkCz:N|%Y_PDڙ|t b4LvJHX@PD(@y\ hSQL")hY *UpX(=,r|EbqFrׅ뷏/Om< ,. ] P5@W|Sf>N p  ?+ȉ8֫x"*@$A0A|k;>HBb#7$"/#\B:8c1cc"c2X"8k.ҋ|cl@$bT֨5(!Ѓ2;.*>"aTw;Pc c/.8D .D:C0ZESJm㊠,"G"; bB@~P#%e~NO*M"0LOc,!8E"D"8cl, VfP !]!'2@ˌAB 4ьb[B_d©+=jNօHda/!(k!J\|jNbkNAT&DLt*eQ!Q ^ ?^J$ ƖdJ[($R$RA  lAA[1T!@aE@ r!gjJ%aJp Hi b !  )|!BpqVɌ=b "q\bG&!M<:X.*K:$` Luy%H $ |MvZ/u%%*D&>3bqL.  G"` D`Fsi@kR@ Ђ,DbHd#x(pH2=WOR˽=9r(< 9(:@3 AC=!4@̥?iLc,@;6$@8l5F+!m)( # $,$L"~y+*ls}Xx "w` D";PfO`>`4AT4*`M؍^*%~QN-L-~ͅem~L ؁8;",; H m9"!$J+~AYС!~%XZ,H !r0`_\-!VaPZ N @%f!ь _^"ydXzK4􊋣LWl'` %9r)|%e ֠ [ G5ШɕdqRz}pp6q6rm*iُk6ms*p&Hmmb*KiK2HaEA^I8q2L٣})1( !@!.O!s0%hRk2ޣơcHpV]CHd[ ;&p?Q/f+Aw0KW!\>Dhȋ"tcvG5L#Cn@0@a/S^Lu!0OH&T 41A%?/ G}$tP\t9w!*$~} "O.h9dO_ Ͻ.ݕL3N'.I+]k9!~lv#$B2/s62q# F de(ꂕ(aW9`` ixNRK#JHN)TPR `N hP&AcA7{^R /]@FgBk /éɔ5}Yڔ)\NTۂ7"IfZ$&]:KFm)tixg؈ GS L 䞐 餔Vj饘f:dRN ? Lc\GpV>eAj뭸ylUf-v6F+e`Na3" ",`E:IlA=qlA'ilxVhqIF$ $"4I IqB)P?0 ]G:Q .@D $)Ns@Iya" (&sؘGr ALOmI 2hM 39Oy ǻ6dHm!+n A~'dn]MWA98DkA&;af](;yb` 2Nfs:q}\2jfAbAp@_>GuR`8Ac| I %?$@ɔZȵ|pRx"?H  @_?[*wMPCgFJ`԰ِPL8@vpG찈" "T@>zFZB#z0B4 7aD u@8Б$4N5 vT-6rC+FS RF)u[X  i waArA B[aAX#0?k\SPIIA N楑DlmRA"p/`%B`6.հ>d"0?2!Kh7% jB*@Y ebSRMlP8YQfA@@Rh@8׬Mt JEfĈ#04%-]h"09jC)}"pͅ1J#B*pd6;yPs|J;)4 i Cc34((H;^2OYV, vҖMH@?2I53AB`c(E))VJ!9aG Nۚ \EA;r$AIJLFm1\~Dgɘyń 6䎹9l,>.Bk!|ɠ^A;Ozg\j` 28v=;8*|a>0 .$7# .!l3 LjT @QE5)eD[!@@-X +`9 # [VdIؐ9-˗<:j@`bgeNׂHA@1NYA l/~0#9K@ a#I+6kOڊsA< -PDdl )H?y|@n0(z@@57A2n} 7;v^Bx,N]q#خ, *v҅JyH6IymܺGۊ6{E0&F=̳TOi%@aQRj\d`tzHJ NҼN+7u;Fbs(y 7bo;(]Yj2ȲB5G/h!RJү%5^)q{盵+d+4Ah;"Aș M !CHTꃜ, U|zfKlz;6A-/|IcN=l0zp@@qk "Ttqp gGqkLJfWz"u`#VPr5-9m=Eg#=/1я)Q^P[Ӄ0W AhPh\LWH%!U#q  ZA!2P P2PR% X"',SuKb&wGbu't%8''ctY!Wwg{luQ\rzQ$bya&RzRWҗ~ I }&ui)%9Ysb\0_ (o rVry N w2,ҚYywy'*8tp=X 0e4 bk %1@m@>.?g1bq4% dS@#o`6;cE9MS:+:0f:P;p ʠfIp V::95`48qrt6a.8E9Pb!3rC7?zo/@819v1$ q5:grf-b'*;HRTj 1($R!=3Q= ( #3@"0@ TV3~A%*>1gLV=fr 6w5N@$dA o6*(Y5:TeI;i)G 1o =`k±V. G5kvm JE 2.0O,2#wjv˺ R ~ i)`ɠ>Pl֖rfFW5: uP X5@?@6rñ+hNN~rqG wJ"A9%=pwpݲq~ggٌ]G ;`@`s Ґr{+b{q x,{iupuuvő}D&)'zL!v0ִbմpy'jRv0$Axs&-2{KwsLhwMU啅JWx;,7) ¾qycw0E7LGN]؃{R'~2rϙ@ Aj _-[jP}tG81<\GLtp (<:|.'Aga+Rջ! (EQ[R [[<嵓nK"R#Ux">a~TbS!bWQ=8ZOg)+tE+Nɕŗ S)zW+*^v"P &})ܝNjM0K♼0*1)>yH9rڧPO-L/W^`,wQ y3t 1$~á`rÑkp?16Nkykk/=p /\u2b@6D07uMf5@/:ʂ߀g ;Z  ـkS#@* pS\:] zmsChq^^~gOv.8BC_:@"`,@ N P366x/ gQR$B-^ĘQƋ"AE =I Ez8*Y IB!|2a HI2 PJ@+ZVND_V SbS%™`V$C"8o ֫` /e4"u{8 ^eR,/5|'#y}@KW"+Ua ( xDD I SbY$1-ӟO0#,?5pJ:8tj`r &H7a\R`W< X9q=^5B@Bz 3z`əZӊ\n)kS,2nR/p+\(R0 ` ag`' 4Gxgy<"{XjM3l!]yd(IRlsݪm@zDKxo1)C`{wȌ̖ewybG}~րb03Tk"y/Q' !s#fzχ~+AwAstָ}{:E @) տ8_?)t0_J)h7RIK9<@s?dt@Q1&h2E$^Ё? ȂPX%JhP')##'9~ªȅ7qA7Ph0'dH0K, ,`z2*4{ hwD\k5`H3P|H H=,ɼ 7!U`y7؃bp I!P7 (g`7 lӉ!!㠈7`0,G(#8UV@HX )9r 4}|2K,ˍq8T*LPI  >Ia7`m# 6^k0XM;aD p8)(09x`00' jE TX% ȓa "PX;TBi` h""e @")##0ۜ]7@ ;Ma8 OX ra~8gM:AōQ=5Y#mQQ El2ʊpK U;L(yg`@P΀QB( W{ y# م#P2Z7WLbj4-̀KX?YX8X32+TP Xr|L_j>aIT@cP'H.K%y Ak? 3@Ч:ڤ"QE~-$ZErm'Z. -HqB/xdz0[Xl&IX r+0`lBJ$x@: EɡڊH+%):)x)JMEB$'ų,H\҃Z@$1&8m(f(x7:PKk-(= 6к ,E<¬Ye, ̂,ikӱꚓIpN"uʪdOKǪ*.;?<Ǣ,u_:_)ʻ'eK9!Ah#-K&}6|jma{B'z]%۬Z+b{[51 MҊ(HX`gcV&i-j X0p =M k45E:ZdK~.uBT|$!۫{hz ՍMYIPR]{^UUV}?A3XU`Ut|f]5SbUR?CVe9hY Rlou)W zPpvw"cWxM\fV-FeEH$h M]bYX_9bX*ÝeVgiEk[I yYp :-^Zq}SW<:(ԋ8'f \:xC$xI$Hu;҉ ؀c0(J%:aЀ#҈z8˚dNöyڀ;Ӹ-sc t;8yCmbF=?&:-!9SjnF`jt0tnv-WP\&㍊xwpGΈJNQS$iK=N3'ˉ lOߣDK$R;X= 4 GTh.4B"0b`[XOTpC 'qqqOGP^'5 E8Jܱ0%*X/øax9 T`FDfDQ99!CR 9NO䑬-V+Xօ.)%b=1Z?Xt)@55>]Ё# Nssgl@Ð0=mD)K3ƠU㨐 et3'eiXFߋKbHfHDU`IH[7hY 8Hܙ|W, ;w{ٓtv 踏O_Xg ZPoF e`oexV֑̑D{w; ypgJ`:̼wOa1Oh/pUy9B3P#H tIWH5g>0Y:)M` Ve` {MMx|ǂλR=UCtQNJ!a᳄P7">(Q\+7+y `|ڴMm8<qyt,OrI^|`<5-i=?aUX7zP|L 96jX{ а_4D7r#Ȑ"Gr1$.Pp9RM$wr Q0F >7>0sbQ1rd(ǔMQ"v|`1f)nղ}M$k> ,'*B)q5@X%IF21Y͞v;G+ہE?hgP DƇYčBSA긦?N<$ֈF$rMHn`ć0Jcv,"FY]w4GA7Z,9pDr? ?XnHF^n$\UgJ7H3@[E/mEߎ"$ .v̔I*ZHN$@` (sC 6ND%p\ip4A!$IUPсUdND?mj j7A>B! -ނ{PnKB>@D㐺 !I"U D0@<0[,)p.ZĈT!; D ߰El29 b8OnDk4 'tvFI4Wtƫ-I\C `5 D`344ؙr 챨kjIzB^N٤! K-QdDA`q5M=>ed]*=AX`J@$;E AaeRdBD7. ĺC@݃N]+U貓nzUTwf~T^]uqE+/U%B|Qǽ݅u(p: #׆@Xi"JgUND YGg~/*X#wu6XH 8H`'"` th0$Cn2J6$hhHΊ665$|4]"D7c&Xd%.V5/t |K%0e,2!"}rc` 50p(Nn0AvCO6icweHՙq` QD)y2LH}6 ._k0ahh`D@4N=CC)+ RXkFKIN⍚"Ozd,D&FD!0g;ώxμ S{>H0C,xDoA&@Ȇ jxf@bMb":~D/#hȫ!daEH :4ВʼnF0Vc1pj*X&1q(q?^hc# Be*Mh1:&BHC4>hE #Dp?t@9M4`Q櫅iW H?CZD8r`ojv[xMč-B-T 8T"t!"Sx4"wfKwn*uD&a*@3ptZ; St`޴ -q,Jm!SToDi ~U` QxrC!4 ǎ@n 1"}6QAEBrQ/p4ԃhpG1 ay"C  o!0H_p (7:2dP@JOIyX@ JD 'A L0D􉝅l@~У*r 20MHZx ě-'t( l¬Jq LČ QGo0׈eUNzxDl(RPJdRfE)&OX W@y9 _gIխfLI4NDH@٬΂NΆMW*XGhPNa#f(SO(AX )m^>]rDuJ a "mFxMcB"GG4YMDtQȔv"B\f% hP'G<&44rɰ:F"+tj' pg2txv1K^̖{&hPAg嬏WgI@ QA n G %O]B9vF4֑bJ] 7z#K*vF#:n5ɚltNu1 p `$p<$(ɺ-1T;FF$GLmmNڬֹFJK2Lʤ G $VʒAf⸂CaIOAXveQGM> Z[R!/u/OD V MAGXv,(S _dR(,JxY@ (_pUȀUt@V[AFA 2bX"V bn~WW$nGHpWg?A+ffA.E\EX8#hNz}Dc-@)H(RrPq_Lor3w3O0W+ w0j( 7p0 0 ǰ 0 _C&GPA40 .f C5B,Yl(E?8C]rl yWGUF GzD Cq[|nG @! $$aB445"hD ٚaĤʁl[c="1 kJ̩U (RtTBAH@$ A:M [G ad@!B.!@Gd, ?X?d*DiM _WHGXRAG5lyvF Dl)ּL^g|`%NA@A$*DfCUF}0]=4C ̃x\5LB0+BX[ 8_<D.Gw,0bBYQnS"vG8%'6;h'QZ@P>GG:O'G,HJC0{!P|?tR@D5ͳ.l]KMpA 洬Fxy{į(? MI;pk|^}TC?lA&KwepR fqJ6@mJC\vA& A%qsh\A& lrO }Kcj @(PGr,;3GN}0D觀C'3!P[!Ez&I=PKm XLx_9CcB8_ENY^(ODD&DZ,F hHt@7tA:$OadHB jŒ!P!C $!Dr,f0!ɝ;Ȅ ͚ 3j y͉@(OIn41hV0+G78ƉNz1$ ֬ilkAUq㉨l"ApXF+_<U 8_+*?FbHcqmѤ&(D$ztI[yNB-DIҬ^$"ALyd0ա¿+#g14Ї! I!Ț M"(dz ZH46<Â$p 탉&@ᐸp1X4#J C=̫ DO=" X _ʉ!nDP ud@k< A8%'H ӣčbI B XPF(0G.q& TeCr`d 'Eyų7 gg RzC ݰ$P MZ $VƑ(4aU PCodlziX&"`΍X<ۉJy]kJWe%(ʂ,JTY ILI'nE( !" BP |ʂ6Clsڀ"! S"b^Chg9%R8>.,0c1z9 ڣ)!Rd-d^L9-!x#ߚ4)#y"6Жy+hjI=+fQOoi?WҨta+t6'Ŕ'uoJt~8> k^wlFxno_kMYAm   @ n`Jz#|J<`X*Bΐ5 qC=<03F@@@x~1ɘa`."9 b# jx6? H g?Fw!Lj( g 1HB"8@xEDXn1D/D(i?0 @-d>C&>EX  Q#с_СЁ&C60%#bC mm؄6T[gHjá`H!):n":9\2+$x@0($9Dh2I%d0ЈPREr?Db ,`HJx,тCdĉԫaiVq*<݈aCM8V0Jw`xTIb2jI 6S-:U60LbjUJIB/>>4^{pI2 p*U,D p ½yuBMII6J$֤a4$^IQk $)A/*`M.o"ʽ&2S$Q adn3whUۯ |H:VV u#^ҚK{^- VҞF@.%:T\׭<$@k`d$Y3*Q2 QU(0QoBlhy`S>2mǥb]2 Pۈts0Jܚ\$V?ҫ"b/*KpnL':mdA\a#-A5p@JR S a9\ΊJaU.<3 F<ムz Q8sMZ#%@.;k#HeS9j=1@mg`!\ŭ6A8><PO/ AzˊQ|_AP*,`~3-f^yڸ6r$u!ܻ@XE@Mz uHytA&Тۈ l$ /.T  ( iZXHسX%lǏGx/f9pG61 fn` : d!bB-^#zaz FP$U"!t#`CB,`€OEdmm( /&0>О !>`2@<`vDcL wMMv$Z)AB?$6Xc#05tCmp 1AT$@#f&6n7;YCg1`6dm"r"cB\ uG0t&&0&- 唈 z߄ aHx8oc^ eH*j N&JJVrXJ%*B-C- xDR%Tjra fWVb^P n#.^]. Nj+ŢBC#6sZ4n+0ir"܀&\AV+N5O7yS((4kb9 %:BX$^rmS pH Nr"^0f:)^iF djlh ,)&(`_`ClMC~bdvBO$J[P Ȇ:KE %DƃH4?-ʨBwP|(Gq4yCu|F7 y|mݢzR!uF2>G/6&>upH# 7l$;(|ds(6+D+4 zԧFtN5PkHXD8r*rHOJSPN;t"H &RI$Q9SRфBT9"*4uSm[("UwUTUmVq5WuuWyW}W5XuXXX5YuYYY5ZuZZZ5[u[[[5\u\ɵ\\5]u]ٵ]]5^u^^^5_u___6`v` ` `6avaaa!6b%vb)b-b16c5vc9c=cA6dEvdIdMdQ6eUveYe]ea6fevfifmfq6guvgyg}g6hvhhh6iviii6jvjjj6kvkkk6lvlɶll6mvmٶmm6nvnnn6ovooo7pwp p p7qwqqq!7r%wr)r-r17s5ws9s=sA7tEwtItMtQ7uUwuYu]ua7vewvivmvq7wuwwyw}w7xwxxk6N7y-yV7X7z&zyz*a{{`z{NXR|y }U}}}~U~OS77\wS~˵W x\877 WwghmՂ?w@?XA1NF8W=U5#V؄geDWV A|؇؇-{a"(4H%x B|ہE ɸW特B{׷[8GV8yܸEn_z؁~IȐ$HkDՑ6f,yf.8''F8#++A Xٕ[e(VcaٕiQ9ؓObD93֔yA|٘ɴ{rar`uyɹOC[Z9ٵ76cvf:g`YW-yZX@ B!#zŀZ!;Z?' ! ,*b HSASll`41?EH?+@4p* vxA LH$Ny3`0BMRZp&A/^A?4d,ۂ4ddid*%[GԆdаBRdԦ"@OLMā0R,9/ "a8'0};ݶ(R ?՜-Fs`.N!%W*u58LWR!PD5pv+WO[MaM,"%'/%)(Ń9֓TMP@ Xa'm D5m4bfX%(TUHGPSGhQ!$(q^* ?R?: K:B OƐ15/4@0/G)`0YJ ~ x }v<")P)C??I( P D AC P 9MSЙ !yAw}B?eH`.<ܭ,[HP/(H?y)(rI$L8S9$% `@,SƛF:RZO`ܞ7 FHdOz#6` E(Q͐5 CF )\?@*C ' &H#4ICr#)cNB's6vn<ҕm (AR Iml ,@(ІZ&wqA*1$+zMс $cD`` \&&I@P$hnY#( C|0`C.e* 8.Kcz@C"YHxl v`@)!UoK!>793*`+H8ux˞agB2)q D@sϰ:I#"CSj51O"$ hTJ՞LiӞ,u _$,l‰w@ ʃBP(5Ɂgl%'E.gccR/=P؁VUIIbLlHFr/8GVCR?p ,*9@\YDnw;u@b?OGuQA 㐁j +JBh TwC@ !8M0Azp\&¨LA٥7] Py҇,G? d z3ݰ],iJz 0@@]%6 eGC??SC)Q,"P%G/ (:@5jXOz |ֺ xYFsl T&0(6BcS^ @8jUJ\we?Šy!d_kÊ#*`?^!7P0 1Vh*QM!{%5P$@.&a2!1[,j1R7F-8V.4SV/9,OTikeR.+R;XSʲ5t6Q<1-_51?31uF4#ax%S E,qp1PB#,RbRAU72U4,k؅r72!5n-ІP0`*,MTnƂf286O+3&Wcn48MX6hQ!a{S6hia?Q Ep2ZӀT)B%?wRw0%!]`yq :zzwaz/@-5V%O $܂'d)|n `PB$DT  p 3 wu q&GC!ѐ y ~ &#!')F>]q -J_ pf,.Cq`yO2GG)MT"4StU1jaXC4 ?g ]fGAcaM7=ӤK!IsuWp <A>3 D\aK )јp3 s[J\bFFS54)Okv/.Fv#Q3F:Vh8+!rU1SQg*O֜6QPhQKS,Uh ,R\c SީAla:`IIi9l~t,/e Vp#"j.Eu1j6W2VGYU0EY+k%3yY<ט8z9uI Z8ON& P)Xӝ)@\l٨* `Raz*`zvEaTLB%!xhg _yА9p&* gRDђb> Slӝx@c'(bf(7%nrhP".ŊƩVl.h]geF99:ba&pg*ĮڛAX8:a᪬8ߊmƺl6k hnWG+7RS>`cORl[Ur";$;} :pY-k+K 3+lcQ %@m {j&q*C[Ѵ Xmt3A{XS!vC љI_k`.[{ hjֶu$X04e7d!KFroN4oUy@poF0=rYV!Dq@a4q1OsWr qj8(Sa6t sQqMAsa1_WvDWU ?5۩!1祿3uW"QRU@ur e pA%yy 8Fy$xWx&fe?Vz-Ry5yaQzŇznq v0u")%'*_/)A)Zx@vx+q00d (S;;H>Йi.l=lG=ÇaX/P&P ]!@ǍQe&\ܩzC>(, yNDn` bp+$4CP*@I]B,D3L|:c*sqZaZB&W5Dկʒׇr pm By]L~1!EXe071ЕRq[Xb$)LRK[\PK"`LJEMʒ[PP  ջHrrYF*_y7s,iq1 U)[7@BiP C $7-YG7<+ζNqZI풴M ēI;Yr^=բ!F9cE&Kѡhx11dAJÐ{Y庅p]y,NC߅:Ӗ| _o` 1 p`}'gRbD^m8ri; _|&NwUW ^aM]UMӺP?60hOKI U4,ѩh9Zf#q9hM-nJ8-&&Ց)^+ S3#\"\b^^\aR% h%6>Ӯ[;gl /> +xHD7Sb7UK\W[ukʼng*֐J{7֋^pO7Ekj  x*h8OKn?c"/~"iaN?/lE7E$p7l¸G<&g@cq)>~ ?\MD@n!ޜstK'u< +ŀC\f\蚐" `q+pIg!taq[+*s߇;PP" pat! 𯑻 aE AHlJ-]0EAM E0hb&i$ &k@i6^0\)^d%DPUԅC"uPւJiLJC mJؖ)?2NQA_+,J`R.YpdG.?Wv`?^'äQ)r:Wm$/ gfO ʨS1U-~!9[W J]9o& m5 r6PZ!0G'ęG{Fg5!Z P+&Ol L J*`z`E *Ƞ PKK h[(0Zt,)DIҲR!Th"%/P*G"xF6A@ @ \l  t%΂Bܓ ςؚS S $K3JS 2!z5L;$']K @FPSOE ϒmId(@$RTJIuX%0ceYW,7AO\Q3啀r-pi؅LPȢy=X!lv= ߄Q/&7,(),lcWvc V3P1W!ڨUZjrҗ%zr=#!0dąȸ%ۅ@>g`FdO!B#m▗Ta[ǝAGԣ'80CIꂮjk\AmJy Fd ->0nZf Ց-Ǟ jCiMxaآÃX(I:6aOHIpȃ&fPo<HV `b<8CP@#KX`c#lH : ?KhbQ4 4&DDd$ S&V5}I@ o1$? b` u !f?&0. >(H|`}% y^3(?AL#4ژ҈b.R\J 4,* ZBI&ˎ'!d hA.ɑRzV"3th㐤$ Lk:T$/r!B+mX]eLJEJɘ)*'Y"Ela'3i)ʋY.:LcP w)D T8MvDD Q,`R_RR6?>+ԁIzB!1@gAh(ZɇΠ']? <1 Dx.FDVXFh _tkj":s\\Lfņ8 URժL'9p-J`sBq-0[h17 t[+ߩn gb G$r Z'qQj\_ QPn!C2ǐԕ1aH~bR{XUNjsm!H"T]HwGA"ז/xW5Q9OSK R]ޒZԅ[ JbF'܀ %`U [u%+i @` <VP:\7r&%$mҁ(ilFвnP 'ۊ ^&[[\lL%^aK] ZM|񺤘R/`jieNe&Y9%"MR?r|H^lVX% h9d*ĴNF#AՅc/p1-_%&[ռf6TH5$W9G/Yva6B!in! p=jk#<-LgJW/y%YW XS!1K_J2"r Ab )pH6mjZַ5c똰ᤆDIf"QDS,lxU $K:.XpX)  P3!}D@C4Lrhl\`p 8 w_⧋Xԝ!N2;"DĞ8t 99AN^3 ^J"4 Y³>ߡD$fF ЇbDI_z1NR8 $H_IfkD^vaX[̮<ԙ]0p S d ;Ú#c78|gFAn "ne9l``=_zջ  > r | 4 x4gC;$v?p 4:XQtUd )*"BKG> )oaI#N o Zc ks(hR>k XhV@=c $z0zy["&$9$Wil*);h5Y8h1 L?;y#f>)&(<)-oɤ `xBBhaS‹GiBs!sX BJl $;$>J) /9Vr?ي O!CI Cc+D/ (p4̑) =ԑ=-P/im*^c1!c;&)#;Ci 41HF:Ɨ.axm®xiF0p3< Êu h;tF^ܘi k1<:2\7X< h+ڙ)+g0 U8RA }+p8=! 44lag tQW0 ȳ;ш>4#MH`aɅ謂p᫲ 2%ɤ3lb?z{Y%hA3&>: V%&1L%s${) )$ a ! OQǩȈlV kȀ.ΔP5)6ش0O'LۜLLg(`]lѼ%+Lukx"@T( S\픈 C+XZ7@ De)0҈b .X V RhP(ĭz o17- .`Tւ<șɊȒsXx-w5sX -rɑ(Ȃ0r )I4nuЗlB[drЫPr,|TKL˷לЏxh5*ƈK/ALkUJ..1aѮ.Y  43c m0h/0讒(Gj%62C梕ȋzQBxZ- 2;Hǒ F/5H#]3;ÂG _DҺP1)0vڵb*ѷ]3}155(C302|qp42$S@(e ߝ3pk\H2[(s_٥5<3@cIWH`WMp_'Ip^<4=U_>^4G`7-_\#O ORB<<XH5N 1&ӭ`K?1_?5a :1e7Pe &}FCZJ#NRk/ЁX; 'P9C`,j*w[l跂x`Шaccc^ 0 <} ,a >3@UXϗ C,,8`97dX$z{:ˀՌ oi2>7d*#("MFZ;;"l^E [efcZ6PJ0 5pÀx(3?ㅿHkH՘߸7ۑ=,{yxPY,py@ 3=ٳȅJY=HH Ԭ^ P|F= *Ԣ茽#-q8!KK2ڦ6k*PDQUb.7CpBхCH(DH B`"l9 p61 h:Bǎlp5)kQ n؊B( @"QzX!^y"Q n J:-^5A8|4E_3?B5E4 6)A6!BUU\:☀}f\1?4QKǂX6q|;SRp/m` 9u9p օ؋aG N*Gjh>Iʙs : j 煰q C ɂH{pˈ-WL dhJ8n\+gr<=!hK,=ׂ82I^ȧJ^|"fQu ɋHq/+7O폄PLLT@"ƌ7r#ȊT8B de So8Y?e4$pC=)4/%˪3C !2 ȡEF'.%k؆%B`".+!V؉C!ӰpD60y4I@[|*w}2 yң<0B!Wn5j ͝&ssL1.2s/@.}K IM^HnOמѷ6\6ӯ1AW*O$Cs=Q ͓u#? B "}%j4/~@ f5x#9J`ъU"C D F$Gk14:L>d x)O Q9A Ie241|$D?Guy'AD @xuA1F)d= =Aʈp&%B I9C&^} ? p>*z++%NdFgѫ9tD;ڙCRHڐxC" C$"d+ hP`X?&? H" `!HG`ɐ?DP:49ېT/N-H-24%q sNSA?b$E.?2ADIҌ2C0$ɀ oȌИ/ Ry9?D!:"C>Dq 5?El4  ACƋ^, 0P (2t?輰14ߔ;I.$ Ԁ*1HsC3:20$C3N?5`>k:S3 h?/dtj>C`?7Cr%6ƀ_xyZۚ6=h`{rA }|( a@@(0> DdD sى۔0X!ߑqJ <ur*I7 m(@TH HD9 h:B @ k,aC* ,$FXTc '~?,H"̮7u y@di$' 6$z@nɸ *(@&d+h0NnGc%,J(,o 2RWldDPTr7qcC֓$E"ƔLb%ZpkؗY=/OJ/P19:KbXH!/G@OD"bE8@T0I*r3LF:6``c3ʥc,TNk9ԍDr"=礇0!XM~G?*t{Ri|'>*H E j:$ CCʐ#MQpW ^ шB M#Dg!@V09C#:UM0a 7L` BpN CR0L$:;vi@I? ]HBPn(@AE `:VkH,Rh.Cѐq`q)?ѐbtrژ . 8$┈8e հ-8WxLMPÒRL*?28^Wp%@@c`y>Q1""O*6&QihXx;@1_1;sB&JBą;&ᓲ)u%Rϔ`㈇C PܡƜj l%e8HbwVH(1PePJԈ!@R),!ITr, *lv5\x|U:kmG>ݙCt8:dڐİm? bH /H%2ac{Ek݅؅:K[hbh,щJ!)2I$jY-G|&#C'ڄ3P{ܳw^eI :<\#,a#½1B',:?a"G%" ސz 8qs_CP mČ$ 6hݘtG7DClGDFtBL-F l DІ\3(JAJEJ Kd|lI-(K  N%^aCTk"C$ FL JDHuKhD'|1JD*_?FC4$)#CO`@fGbB5by/@E.3*]OC9ԩQjmGCHC*,@ CT Ct*UC$qIda%4*\&pj'&ҵ>tCD+%[RC&ffLCT?4Ic1BR2*"HDAzEDraCފ*CD C54sA\Tt"0?,@x4B,X.͚?BL& óAD1?̏C@BٗMWH&@(B|,$Yu@ CtyۺCMD $NC#M6Dx|ˆu?ˤV(|Xn_]OH]AQ=yoHcBYsJ }"xYw瑩ƠqgJC^lRll&0,p$vbH׶I\Hr̤u(r P0CC?$!, mo6Ұ sF -g C-C ?ج2D wRީD C+.HHzd IJDڊEDCGKp ďяEU_^ xnrd7_K B` )PiRn3X[3EhRͦṍ:Vޑ?L()=aCB,ʍCD]Ep o|F @i ęš. >s&!/QD_zbG jdLJu(J'55`85LkTXFCH DhC2;44R+5[ BCȐA75tcE^ThxCa?`t\I7? Ή5eWtD^/{nqPTq/)Z6lǶll5fCfavD`*_8FCP?4C C8"Lw" 0"2 |-%V5D9D*l0RCWx0$^-8-"}9b/B̑ۀV,X?T;&dA-<@8+?#CPhC)C; %(siE",O3|$*T\W}dI8,Lx^f(h=:TBab+A`HH(V&yK5 9D86N¹! RGCyCToOV?5CX&$1L.#`Abdg?tyJtyD| >Byo\ 4B2$EC68uxfO@Z=O[&+CBA%4\ҊrKe/-jpF iwú٦nĦhjCRXQ2R>bE]%5T@!L@/\H'C8R1<2'ſ$ r)@. D adUf`rME30Y濙\ 3?D:I̔2 @΍A^a#C & B6  *u5cn+g `3a )`;( 2ɮa&vmB@@7% n9 ;? @{^! p&**0 >wYS^7uRMt믷c7 gqӁO=^E݆G*Ğ鯇 +ToGx:&8ndf` —K[@ObR "e óMg(Pt@zkT$@!H^bmP: qC=88$"nBNqbB kH Qe(D g`ZC Ap :C1 S qADs0sGSHp͝UaDd "Q D?1"8(K9CrH *ʾ0DzbDtNpJ5 Q>F124U0 p*$?*сUQ®xP, F ɫDI:4-5!n؃( hP"\ 9c-!@:gcTJAHLoMF5]YP!|Q&>,"pð0Ѣ,n@p4 \TjUD58VK2TL`h(64d剉ĥM` 1kBW@ESWgvz 0@0BV)]0)`T )@p*PNe6<A J8bh o;{2r?y+Q٩ JB&`2)@*Т 4w#tI<A%$5^k;[1d,f 3I C4v ,q# Ff9hz.F@Ϣ]8-!jw{E2ԀΤ>!@\ Ǘ^Zܞ˵qMyc<`ډ LLtt[ l9cH}1}N>sdYH4cP{K`)T*OV$:c2P9*O#:b .aEG5m [H*#\11d .L^׶{ CPa@:-ʚdp*ZT[o&_up~>=@00Ǻp!?*&T ;!Bn@#5$-f`.8DyYd2aaBSnHEeRVR;!'O TUt:i+l>烄p$a|)}5[F<`"1TDPLhz:x1Ag2 =G$ӎ*#&SyCD[LZ2#'VLކM.C2|iQTp.T)Fxܜ4 dVARN1@.@ 4"ѓe½uUW<6 ܃a-jݱB ttW&G@DI (i(mV#@bF7pPOnJ0vD1 D"t+_"X@-b8&JQ 7%-i,H`ϐX^YY#T!aѸ[HJ 4(X: i[Gz"Y%|p n`d" `Ff,P's#ƠM@7B@͂6c9JoC,G j!"`Fj`flLfB8 FQ }d B$hkB҈xfoDcǤi!|fj$r-x6| kM7`^xcv#^l9*$ FE$1$$1Q,rņjoW9&N8 7/@1qcހb*΁(ԡ؃ In$!Z@:(k5r#9wvOv DQ:@ƣ9XhX"#a2&er&i&m2c 8#Bt/B$"j BA'Р oc t $`9 &h&NB \d !$&EaN9\\Ҟ#%6* j A"H @C2)11g.3gb2 l IQ10fK0243 \r>3!z!lca("`8A& &k : A !V"㟎6O+ʟp)$>Ad6#l-^M!@*&\b\Ta62 ,4&&7Hn#zX&BN: (!"AbJ1$BFi.*Ll)<dC !(d <"A 䈂Ir ֆe i>]:Q}1k$BtPPD@#|0XbA"@@NͳDm'jʘRlfhk}$TK9"ID f)H'ʭ ЌQb Z6ZkPl3cNY; #`Hor'm^h'S,4SXU0#=B DV+&$Cԕ59Rb D2X˂FLn؞io6bխZ VEȼ1Lcɜf6p R6R*W6ώxuVb)h%0F(`p t7;)`Mڠ4)!:,$)L`IBbabaR p-ՌE_4TV3\Q]7!M64"kc^*&4t !B@xa3gt6ڜF'9d4z:& k #fq oI.6l w!~łP6 Rӕ6j&+ Π*d 9"mBm RR`xN`#F,β!.&% څ7a[a&!M6t|k x)i A'@b RBHo ~@C~/&nM>eBb@1dBtG7ۃ6 D 4> d!@Dx eYg1$ݚ:D`w @z/P{bBV/8Zr=,HQsP&&ǃ]e9;JOqc` l϶BJDxRwubHQ|#@* э?$qh .7& 6] 8c[/GcJDPN[:B87w 79oe{W+JxCpo#abc7yP"GxQ627M!LPF@|='7 V!a,up4dҰ!FSdYV![0"fp ԡv|96V8Eoc֓""_x%i-sOq9!Z qU77r]EQbe /.u/ WKbo7&Gudэn`㛰7k&$Z@_C!667>&$F=FoRb!rn'H59:}(zz=])G#Iɬ-/W)цl5_X7"/K:emqho2gv>(np 6.6%$w2RIM7r ҙ=A]1"CqO;&?2&v\: bcG:2#%<,kR m0x;{ɻ'0%'3& 7b``c@a6hF>608%i[~ s}5:B.3!F66/Lʡ!2M$q!a $A:b/6C2C1`! xƕ!t)W$ jja*j( NHr ٖi' wݗ=l3B L>?T%&!\!jJAmBG!L=or !L4Ca"64L!ΰg`E0^*A]Z`^ bW :@v>ѭLV"X LH` kI[+S<~&:mlvLvzl L97Bc<Q?pcIٮj#8@*j;cb"Z-tb!2T8j`8%2777y!ޓ| +4!k7c?^p):-׶m"gBދ '`%*cAb^kW dZ؞+fՑfɲZtIڷ}0ܒ"?i r1saUAIBv&AX}U^\,?Ha*R \+^)U*tjvZ$M|I5U0T+_ Xr B;H ᎘eWP7˙Бq˂0Ԭ5ljs|d?!Atn8_A\C+>,:*=H"O|dT:I d56Є*t DBE`J8AT3i rM52=+M]1))\y Әt4M[iӜtuq$Rqh'A '#Cf8:h?T0 0#x AYId>0ָ獿0?СKDF/P!R `%Hs" S%d@p& d r!ARDb` 3{p Xʙ%2{#UE$JqT010]ΨY0wl\\h@b` QhnzBA܄|C.3 F l"Q/e iDc$-  CL8 |A HAn$ *.Jb!? @ XH¸@ yL%:%I> ݔa M)ȗ?qIaaє"fabAlJ b; |XD `=Tf5h5HcȀRR?XJ;y6:@M,OP0cml xA(R{>Nɀ) "l x H?mP@4M?rՁ6(fIRl ?J/  $w$`N,OIE j4)ue~}REF yu*@Q.Slx5[F 76HD?(GGAz]XJQʋgҜ( " qA" Rw\@+1N㲁?@QW ~ɛA+/ xBPc 6\θKI8Q~D GBr\x (9ho۟du}we#q I?@4F?!ЋS:K-Xm`NHF kvh8vpIh_Ёv_}&.u" wnAbp<Aa#]F>TQ$GUQVp {HVBQwWXuQ" Yw Op%XW Q PGMႝa^ dCP=7/XK%)o$VkUJD~;OC<`S> SNU *\"E,O,dq$5xq(R)$.W'k(REz/f)qs)!&bqJN*TB/UBR T)O3*CJI0-cGJpi<~I %4,Qȸ+*5CEq u' xtvq^#vvhg0Ñ*W/'x;01EqWhW +X6xI)11d%\(5"w$QHJrh:/|xe>0Ks6qwy{A t*0Bg@xa8B`q6PylW:]r]gapzzp{w{qsGi!|'yȧ|f|6-ߓFYE5s~~~Ie`Fi3ev-B1JatB@qAMsc$t&scIl$bv:dB5P!?naBi(9()!DCF!qsw›0EnnnsiS駟pٙVSuGQ Z,9i4B/0$Fj}@pcQ*RS*> :rqa3> 0 PbJMd|ixWyl sq8V{z-P| ?RCQv&0Q ,&QQ$Qq,(e2l꩟ *J񀒀 #[$A,NFU=,]"AIpjkSfVzVQ吃0&d! qa 0a |!@:}/hhU `P4PXU!l6#CS"etjavk/UE :&f+ s Qqqh\`9{^nP2[q0G po@ 1 ?`rA#K&PУPDT 0 cd8* -6 `tgRp+ %`0_ TD3m@ _o;B[e` 繮k _hdz_+b+ oCMRm13uF.U&-Ij>>d_oЅ-*&MR,)&SVF"G-+-[fĨen*ogog gu0'ejRjdr j-i%*qt-WBeZK[&v˼Y%3k+m4'pj r.wDD3l1sY#? !0k eW@lHld.t!J|L,bv d׀U,Cz%#-~'9xq6|Q|K & z'v13(G)9P"1[[`S; `g@ ` Sx~{ cKR)~.@ *H $y`ٔL!+|gv`[;V<&y ij:D EH\eA%tkΒq .XauwuPpjª$%X]u1|! PҐbP!HxP p_@GP cp(aܡ`%J4>Jр~@Ay}Vk04<6(,_.+" d)0IP!s*Z NqosB./sbuh r2Ҩ2Q* ֎&(B ڡ8癌!mdJ:.*ٞݙ<ڰya!fx/q6zBɽQ)]3/vW &=|˝SR Zܙ">3HuQrqΥJ3_3Q-0J#- #:Ec*mq0Ibu b y`<,A.'+# "S;|əPNh㵹bky[|5>[5|CX&8,^wga@3KB }- ?ֳ &y݌`fPLSj`i'9@lgdIQCmvBK(aqBR?q,AւB8dMgyC4&y_\b¬,VWJA[%0ET<7JCSqu-B:#Z5־QC!,׸Jd+t-W3,~5KuUVSL G]t]~-ߧN uqYa ;ۈ Κ$Iso-/1S~ Xu@ N\[LY܇*f?e]aXHa]I{SPnRCPQ%-a\0MU{$1ӭ>Yq_=Q""~q$$B"h?g2(D`X]i};aT:p  9:ȴC9"au1FЮh#GLl>LA,W^eO4T_2 uW%> V^ @` @u_d@} ԋoP`gP|hXDC8$u:p $,(MH93͜8!|?4eiS "LSҙZ`IVQ7*፰&K /h Z Xj_ 0Y!C*$F4!>+|Y ՜iico_ZVQH?@!١@.zD[>Ac+L{@CRίW4BѢ3CpnAԝo&=SRX?x)k 9n$|z<0kpb 3ʰ ÿ, 1Id2 w{xqG jD1'r0!܎$rkʂ򂲒&  I3'&ƌο4I *3(,` $EJQ<#-H sI3'rOhɩB#>W2 rHԠJ(Ԃ ] '`D%WLDUJta#\0b+ 7V)\g 5V9Ic "%lD\k1G| DZ+(3i/T#ãH)RH-6|0 n u1$^;!g^ )ʇ8Z0Z*'!|pY>IW`ja1*(Wi:r/` Ao)=(f Nxf&Xh! _ h\ AZ"⒂f{3Y| 8W ;~PNcN;MzYH= cr>EB8T%th!`2) 8tIѐX0'"f'QR2\_~G%d' `i&B Ln%; 4mB#r jJká}$>cyƂ8MfQ* v,WF &7 СOL@3(@ڨIPhy:r^ T%$^򽓈z BJ GMq5/ bf% 1]A #%0Hs"!l._$, =KD̠.V$xyJq̇_*Ȼ5zS-tf |篜hErezx4 00 aЈd&lRceERGK 4_N`M) U&-MBP J7ـ ~'8If TJ;4obT4p†Vc!hPM`G.=&tia? )hW@O΢VN"t|  g#%B(%RSX?Xw{@JK#LM50~N4/4 ,'PE{]fW.ЇW`=Gh2zDADp(M o}{_<- &w}! x1ߓ"p&X fGXp-[cLJF ''N$Hq'#<9aIeV!h@G`Ih`b@8 b&N8GDgʐ/Hs Ǒi2:$ (RA r 3ir}oIמ< #58Yx:)& @3! B@% l"bQ`0^u ٺ 1Ys F.L'"JP'WA&ғ r,q-E+W" h:  Rcm @*'$fVk& `DJT*>1( xIL$r'Ⱥ8@`MCYƓ"5/x"M:R9Fc4!֐a!" bqZ3s] UX>_gط^_ٌKS7DaZ@>@jAcM8Wǃ} l~wӁ6Zu <@3v[T`A)HL  *ON@ :I^2!;(}f2އ&}e{ ǔ ̿ghMqqwMbby?FzD;=x/'d#BxGy8+Q!b0=(59;, "H9% C:R)ˮ +peZ8ra@ (Rr k`RpcQT)PpJ{CZJPlP7 ;dmBaX {Њ [ 1DB:*1ČR F(bVAUNŔ18֐@ʉK+p#09)EE耞Y 4ښE10 ؂HFr 14; @h0F9EUXl v|GhZ`6{dO餜4 HC+J݉@ŠAǐ:\:hJq@"΁+@HYp" ` 9 ! .a!^,c!r &R#y"-#>  C3 1!@֊ 顣ʆÍ+Y2:%q/ê,/;1Pґha̹T>GZ,J1%)GKL a0I8$| M~˪"f:EQ%B"@!ɐ|IBux8 WP<?-5D˳訏LdMCQRʐЫ8>̂* * ␇J N64)4A)  7̶#@ی.E2 6 9)"U,`;:'Q*S'RQ^,H+T3ʮ2K.ݲR8l!ؒ*KZ&9AI ͰкKY-V۾B>U% "XUUb@#B-Ve /Ì>?VU.(\VA%]`.P'Uo=W/J`P.|J_EW [ʞ _5W|E[(X@؀]XmXmtr` u]0{6ؓhyAK) R.c0r1948h ":2`BkMH2d#(9`ڹᕔ-T{AȐͱ_ÕM8m۷m b,&ɴ1ؓJãJ) O4IÕ M~*3XT(WX`+a -%[\LZqsbc3*&  T_hK&,7_7tu# 5p |K ,(?idH񥄫8J)u^zok[g+E쭺79=/a H49 L #`DSF : )(d9%eZ)l9 >$P$pVcp!`!v %kh rk('.(.gا(؆=<(I`ȃ: 9(Ƞk ؏`I8@ K1m>Sc{ l[XuS!x=;S{#I Ӹ3{5 X}K-11N՟ X&:0Y[W j?ݐ1(ǸlP݊ +U )-9)AR؅]?XR^8bMܣ>- 0?RZ(C i>ߺu^z }An{u{ AjK„xar+SI8(^uvm Qxm({_Q*0h&!LR7QB=ށX곾XD;] } (H(ezIxpmDD٧j\ZhlPl.Kmm0VZGx#P!ȑH˹ :ƽy<5QAjF83FN h?H&1@LÙF)DͱXN`DH- q?Sv!< .\HXk@Sx:ɓWpql腉!z> č ՠpdJΠ9"h"YO! L*P3u_>U `Gq!>"‰ 9y[&(zds;qo.jxݲx(%ĩ#r*f F -1h{erFk͐ pnIe&1o?ঢMOjp+?piK%D plCph=wmd;6ϓ Ix`ipi&ΘҰ N<MlkO+;qs a`^=AF'8 Py._)&lnÆфP 'Nlt"j R &GU.y¤$4af.ŒrЙrK+ f Y,*wvt*/ AQ CPoDu!< x6?L}R$(HT, V>˃"b{r 4AQ*&*U+E(X9aUk jaE}ۿ}tmԔ}bj2!An}_~ SWbZl 9~~Py ѷvf~X1 0`o Eh D  `5@o3EDP_ Sa!%t?"00Pi#^Oǁ~>ȱ ? ֤Ys)ӓSR,Xt8J )׮I0s 4W?s7G1@|pnбqLEw``xb '@LhbBWcqH!9F`Okt.ܫ5,?^'7d4j o.ڹ݀" z1pDW$GͿđOz5DF%xQ Dh@@dmwT`pЋ k}o!'@O'^K.U4zE_P#^aN DBvAN `O;bY$|? Wpbĥ)8f!d}5BVd?e/^T1P=PM\O@d X0"vEEQ?!l@V@4PDx 4<%X}GgAlI@{@VGJPS"@R'@beE Y@:V~dִ+jR*dQdkVy0 %m%5Ik'zQVPAxб[6DS+ϰ COo2|*WYY`p*ZTSYPo4I6pX5m*5SQZnxwWZ mpzZն^/5v 7_sQ85ExA|@o?sw@@T wхgPw? {YG/X%h~E'!$P b] @)ET^_S5AL#L"jH@)#tvv'M/ua1M]cPTtƣ>"(51sCN#LtDR2c*SD$M>PDP`# 8D`DC|`Q4 A((H +@ L1|SVZJb}tX7:& A vA f " 7EքKfD1H/iژr (XNS]Ks6WO LANڒ| 4h H!n3B&8EbX fp0ti Pu YԌ5l1XHNo,H&cvt@ &1,@EA+ʲO5T' g%:nG W@ Ӏ_,d=k : LJQg;mLI/W " "HVa IHir#@͉!ETFe[ @ DV m## 7tƤl)w%XMYU2\Z!l%߆U=be]sb)$@jH5c$r4>pDxqs JAu|NIW&[ &c^ފCDW"9ug}GP℣]ʋP &B">i, a)LE NX‹pR9A p Po~d1;@h4o0qW"zTmuF}j)>"T Sm(XF,2H3|H|ҳMj)6@&?t( 9+{[Td\?zΌjť N5s-j3o7$n90F{l(9KlU km[:׺޵p`Gc1l^פc#>EeĎ4o;5Q %ٰIp4n V z8 nE7ԥz5GLf c[IѨ,W 3p1 ۸DED' Z棠$~ gQ\B@L!:{c| 79jU'_/ۋqyb''B+@4MM$ɒ4B IȚXtIDT", B1IE*$ z$yLB.   /D@dNmN  ?>E4?EAE ʖXDK8IMu@ӽET@C[u@`NJ@d? : X$@I *΋_A h@TH %0\[f}DqydK AEHPbd\Zh@TG 5Ɣ,"\ ŸxLđ@tՌ@=T@CxbNԔ'đ}!ڧlm}DFdh]?EN4GHxȌXX@چU-=:EyIfOԄA2 @ۅ|aA'@(@@PMD$ALM34*?( qCFH?PZnE??>OTt A]1%]UICmA \lA+@$H<*R P D0£, sI@SA,f(atwyE A G(C%MR$-%qjR&(_}ڒUM*-]LL0lhbՒX0faU0D\A$BBt@L߄0SE6?^}۶pc LSC<@D8E9ӤKDP#PшK".@U 8WHOM JnV:A e>ͬn C-L\|Z"˸mPDB Z(ZЈU &h Q&DUUYDɷQ, PJшVGYDB ’> %U TWdb)֓>MevETEiM,\@iCiCGl ʕFt 晡/ueXJ=Xá%s%rW\\~lCe:ք6S Dbdpiiv\h( }`}%Ef .Ϸv&o,q6! drfENtJ3ubvr' DzޡzR{Gx~cpM,a!]vה RĔIfMЊ-8#pbdWXDn lbP@ѭ#Iٙ Ǭp/Ʃ1l$DV?cżEn ^Дm\:QNpvŷ +|UZRK9,Id DFppmR@L>Ȁ0CZ7pp q -B{U2&Y.YFDE !x sq@h#S~SP{q{)ET)l F쥆 =Z,E8%XMm9T?Gܬrce/ձ^~J%Jj; ںbĚZdw}<1klE0]\?Rd{ VLQG  ) ?waB 6t"Qu&`A``' N,@B7307!! 6H05jT'@C4BF\*׭l `P փijM"R6gȧS{pQf. TCA /)]t "R>8QR9qq%;kgL(o*v}s\l8Kkt5MOdnثRjRZڬLGڿsU궟N?V4!0*ij<TH;XȂUECUhN?Գ@4L:uU!_ࣄ4 =8(2@a 3HJApųW5Pߥ5׾F v:*_/ 2x@CRYv=Ȕ]~efHyYu8ИzP`w^HiqkNhݮޚ뮽yp;:2jLBhMh}^p|(AM^Yf< T>_#8Xp2 \$\/jx:|B0" Q 0 0&, !P!@#`"4Rc5ؐ55`jƍ H|D"JT-"<< !, Ltp#m$ jXC?,`, l!4`@M!@<(fBCV@“tJ?"P!t*)<(,%!JA0ڰM|X D !!ABDn ML*K6k E!$m/tLOERM$p(R.-XN""*Ռ!ZRKUob#sLT"lB6@?Ueܣ"V:r"Rz2!vRb Q  U2Ƞd L /ĦbB΅PXL]y` E#& -+,,l-pz>*!.}ϊ&__R*CtPbqD12#s*^j`AF(hjAfaS2cS6gt vpB& fn&joFa69sk2Fc"D DP%%;e34&倌&"Op@>b ΠeRT& AU6';@~f8c 2H+D߀h="V% NEB\RR=YMC@4A(FhByz""~''K4k @T.K -t! L()eC#o 6(<# ApẮvA"7L+ <%|*D6_DS"5q5yb\B^@ #>T@ >A= b% .阒i"ɐ;4*( \A0U<@3Q}fZ \7Za༨hC) `3+ɛVŢԩ ޣtʢ$ bu*+ @]b ~uFXs*vf 7iajPz-̪L_n`"4GΏpɲ%k 2/S-8V@s+.avSZIofvX變"K<$ *ăpvpq o 𢳰*K)p!F kgKJj"&–uu*8/K>~a8C*r FV(#<\ųVry*V< ؇6h%E#Y O+B"]n4$*e!6@[X./E<<-^K*_&l@7@"1E EZyD"u=62u#mQ(f_(KvcB0d%4@&BN^LOVBD$#U<lW".d|&pa4~2g1P=;,"IUy!.[e } P0!Yb1B1(#"ɛ"{&hyWnT})gyDZ;pBhuBP!:cM=C! E"|$ᨓ54Oy#aP3ڄ"S*H b1HbXbQ0"Hya~!V`f 51qL@<)b$qRVQbBǡx9782!3 "&xc rٳQ RrxJ (@&a, qIR؄%Kp pQ. e>ݲYLW,}N 8Sbܤ*||j"E#8@F-S7,pks0S}aq!sRDe/L$Fd3^72-3*ކh܁Bj@/(cfƜc^g/1ajA6:6%reS#+Etv[>2b>!O."b}q_?qeA X-(B4|A;b~V'7%.vUt@J}M @ 4 !H3*`0a t@)㉚b\H 75 2.MWF"Ht3f$(:z_z( Da)ϝR 04dT `&("tk&&ߠmSBv ()"j# kj!A`C-DP~oB R5Ń0*D2řD8 WH[ESPVBjv|ygQ ^`x#7a腂IzmsM!\*Mz%?GH`]_C>¢TA;@\?W"0T&N g*覒$qP@k'l6U`vbAʖP$o{+ ի`; i8`YzMN6h"?lAT@LH@P`IBpbB q)EDp % C*xGhPA Mԁt@K0)PMFB"Hbr @Ch l tЖJ@PF\P ϱa|AAP;U]^Z⤽ TN4j%ʘPk)fT "Oh%B2AP;5TAP`Rۣ@ "t? B@ޢm$ 0`IpA!ϸ6=pm0nCL\BBO|X,PP A {qGbAl mC.cr ? +2 0 \"04FD^t @{?$D2%ԉ %T6<}$Đ%MJ4KQ:`T8!&%jٱ Y%W|%X[Y`| a 9o#:hQ-Bz[݉+Ҝ+qfQxB!`*^?Q ١Vu;p]^?E45-0M=Q8pM} L IwpE?XSX1xpB&4B23&$ LA\CbB%Fd~pE&4YP]Bb@[H| $H a#A I0&pA< J+!Ex5 P"bBWB ( j@pEx`Y1-\H @ 6 AҦRF78%FJy-#OGP1d&fů$aGTʞ&cJWnDAj?"5!yelWm[`D) f4un N!|NCZPj-yN$O@ NL2ЊjD"Z^2D ВJ"/mIfQ;sִyYON萢ChAuĬ0i%IK) XO^+gFB4ta%)V Ep ZeOv? bDD#"Qo"e$ zcW8 B f|566 wdVU>`!=pXCf Q@TD(!Xk2ۃ6#p2`T)eRμ2̯DS &.Dlb+~85" ZR) F&ٰ47{=|W A/dJ=miSA6App~F "g >wyj0a0{PE s ;Xa\`t !xOad #y. O|Cҷz}?c^o.XB@n $`pf ISNBKL@ (h_un(u@HC,‡!d`16@3-XůDTEH Uhd 5AU$, z +Bh5aSa Hg !;!vfgF$0`,*"3FL[    k;MJv;:l#ު=otSAIoW>MOܝDYo@80'DIq(OWO:>T-!0@r@ЇNtC\ x.p|]ChN[Xwy/$XRʪͺNh.nJZFIwAÿZ1cA=8ɸHI406k2 C;B.WAwE!XG'0$,L,b+fE0L.qaprL  hD1  a)`@Qߵ˝#- 8h"tXn O/ WTzC,p[D&Np!Y4HO@ *~ aa@FߧH!r"f0sQs]$=itA0Sw6'<#C 8$ N G5@ E \p:" Py7D F&`&AH )d 0B  $`9 U=1.M.څP/p0Jqyh~ n2vuaCXя hs{ !7i* 3 ɸg'%jJn唷Q1UpA2lRrRKpU!P[a-.)B"k,!Oy/uPdY'Si,zBa( )2/q&^b*"UZ 8, "i"?҅O"RR(&8& z#&""&2{ RS9"61i"ׂ=p¬Y"`h r#61Q3h4l$l+`v32TatO," C0 2}cб /.FezQ 0 8_TJ ߡ hL Pz_aN^_~o廾sj@Cba1>! pM߿w^!H1>( ѱ>4+) z`)l#S$ *( kQdҎGET QUBy Dոj\ dVy̭ܬv@ &E.Q\*Mz+Kƍ?,P9Bm%ŀMDGChFw,ɰA E(!H3YbNf'cA G8= Ġgt>Eh (  j :Pm"Mm\*$D Gsl,)&b->3NClM3H m J;NGۦ3Ȏ$"ſAb<C!|#>4>3;D*H/,#N;!CΠtJ':c1DzBS]<K TPDTEO ԎD5E4D3JP!@D8R@Z=/ XT n2EqP:&Q5uRhQɟ>eIcqTZY}+k/UVw[iOF@a8`S9tlE2hL[Zx6rcq@xްS8یy9BFK#$9EA *|@) o9@@ 'A4Lj2$ #A&#eCΛ\;SzaaeC)sD/Gva%9&`*E 9JZ: O Ra; LoZ) Q`mcY ?Dl#*~b=q*@Z D2 fāIbE M,9$Y|#!%JD;QIV[qT,Jg$& #JTE|ю"/gԁ Q tڲT%eq  L+ـq5&ֹysR;6x2PҧxH[/gC9Q!3}2k’(EE:RԤ';_tC0;4\{ ;B6ԧ?jP:TU`L=`g"GSaS44jV՟ʢ\kX / utnTf6Jok\:Wծwk^G* FCe YN0:"8x7%eiv`U- B@{\iGZHmi8ul!ܚy v1cE @q>L(H8H01f&aF1.t $M`\5´a?>k41yn2YA$e~eäM@ (q0;CA| ¤d$8 `Û(PmkF`%Ne\s0H_ai՚QJY 0HRp=D ф@d TPˡc@W(0t#ȓJlsuYEtPsy4d,7#aW2("$ݠdb+v^0Yy.?()NCBpTB`u80$r! yCK3?L hZ$T0&  0b4sV8X\9"uOd#A>1j)jJmH , @p̢jrAڏ݊9$/KKI?0(9@.M/W@LZWtQ`Fm.CI p)ʛ批b@`ݬVxm+%`&Va.sCOS઎`rEAi'TOñzn( Q;@uc&qG'.F[hU݄ O\ꘊ@[ZɥƼgLIs<=z}6ݙDwVY;hc)w9-~Do!C_pF׈M& ib<%`"Ґ@@hnRA%QB"Ủ"H'{Œ!r C€IIB!7A <39M&2Єž''&D +*b aX 0i@(2 ;DizI`kX@6!A؃6¬@ (!! ̓tk{77$R,R AA#ԝyB´ W ;0RP @a^0&„:*Ȓ,rsL0 ǎ:83`9G {G|{t{DXm|0рP8JLH +2(yhBiX H C߀*ǔa, c1F2*DAlXASL2C ]B‚qB'TS&5,\StH B 5?tA"j1Zp(AC#N؀|ڵY 3&ǀ?Ƴt="I $s¿,q<#5#BNh@҈@ 4@i҃ @+W]@>pՃx q c_@P4V!D85 I>TpԎCU2 IY{qbtcq_oHpipaTcJf(w%r,w < ap 8L C*N4Otv+TT\ v#Qẁ^?fҁ]?QMc{'ٵ"Dzi'$j=u\ِa"˨fcqoLo?u4`O\V7Y YҀO)xD `*A  V҈7hPqaNȁuEwu7Bxo:Xcaa'Mi@‡Rh"ƌ 0Q`4>ct<0Q DTA`vx'P/LP0yAG 7 ND QA % h$)d;@+Jn93M)HK{qu$$^ܘ|}[*\>>Ko!{'4w)w: ҮQ?Zn0d@j@|r @&j{'+v`]*4\`K/0t (  Znd?@A3UB AĐ EF sA@^8SU0׃%T P1y!IX#E- B &Д 8_C DJZJseC8 Wl LT`E6GU6ّHvI9@eqvGP@h 9>nM8XB&}ji`TrTHd(uD)B Tw׭~bĪ ?{A`J;-Z{-j-2/%*U.骻.˧#TSB j$/ DD +0 ;p,Q}  aadb?z:@$@Eht}xA?5-El@ ejl?QQ@"IdOP#P!% 6US1.a? "a Asw&yS="l $z$CzH"PG2*:B4l M  4?gD N/I9 PE:$m@no7@]c"O+GB n Y@co =`/T> pp(jXQ(B|-G'G)?%@h@`2 +:;T,2N  0) P"H&`=0haDjexh `q,`Enp `"8D' t Q0@PXf'X, dTpZBBibԋ>M9QM5h=}T z)4 IZa)Xp0;OՊ6Q˕>doKe- AĄZaQSf*)纉6P?ȲO?$@"[,$bĝ4v @eyeՠ|"n"/RC_` `AåqE. I@W%C*RKr:np {Z0CB?FYsB: 4rP3DRwP/]W`` 8a,+j0aD(/9I \+;(IY2K @<ۡESgOlY V -%OC}X1U\ƮPo\/*F.`2$B9B .8C"\5e?P@"wH@O=$8-TĹIKAɲ\Z jO5_N\@@ x 0DxݠERiF{{r4WtuuǀTW }}ed~1CU3@t+0%5oEQ،qTDDb<s`7ܔUY$%HZ= m[X)ӄ eDIQ 3Io1-ȁ4\I 0I Šp P,BoMDLEUL1Du lcLvO0Z8 t?}G% O@ny;戁TJ8҄Yl֞LH  r]GrsͅXȍ,VBeUDnEȎ@|v5$/Z w`AK%n `qɫEJMn]H4vB&WAD#IR -S%"TN%UVUG~HPDz%D5(@eBUZZ.T^Z N@@%[_eE%`a&b&fZ `  &,0?T Mۀ_ C%PP@^XO(EŰ@M!`ڴ\A4D&? xfp&&PƨG8?d &(T@MA @pAƘN8Y`G-dL8GA$@N $ҋnȨN\?gѡEyZMgRM|,sz(G\ߘC"¨H ԏWBL>D\>$z+k1D DTM%?DX&ap$)avdETѨ%|x2 ]k@o 42vw@WYRC|ׇ4$Qj4}+Ep<eG~ Z&`Pl(ij@C{1%?/l+J QռhBDS@<qE`*,r|/`Dy4i P(@EY@ k/.N`Z# ]T UĄAjKeRLBF73d*J31<aDq/"G!hPM x ֐w [@̚ nIq$EHP TD 'E4BAP@xp-HCP@PC@ô=N-'ԁzM D@!xDĿO DG B`G( jǑD@H@$$m2?ԜK%3C#3DH,Ez \dYs,@.%D\DCE#FS ދG!Gy8RJJS92X{;xz ĪNu&D\H0D_JiU8҅EJTE]/~@%?B*@oR.KA0W z";>(:#H?7@4p$R@#P@F`E*8J'NhEUP`Uş'PU,ŨRJRF`0RYE24BW Y$ ST ((xЫ-dJf.2_߿>߃ $"Z?)& @Rv@3P,/@H(^>S~Jj-*0)P `"L) @D 0/ )BH4)ѯRB ,CM4qU"c}^(peeZinS:`Lc]R7^@0|EXX ~qRH@mR8bR`X-I·by/0/QZ&(ztթD71P%=HCD2 9t G0p94{JI"*+X% 갖2"DZ6""I0E(ʁZB  H:H|i )LHJM~F!)Qw ܠ ͣ@|`88 GZ _XHE@za2+8r]@LAG䖒"V UA:0P",K=|58`ԕ\UD<)S¬_r?-іW"b+LVB  ,{d)0,,R-K$z1eR>H-壁(i'sg3sOFJKYdlI倱(g7]^ L .!u-/Su~*Z[V"@X* , ”t" l Z"U!E¼H%DZ5D1R(ŏHK_5u(P#p(F!|Ǚ1 p!EAEIdR=HedU*\T_JeڂC >аE U.f$=Ds<_Rlt/ $HО1Iiq C(2h}OEZ?Qp9lJ(6 r{[Vo50_KN*S[&w5/#'RoDZu߭]xfv=ZeǥZ@@ B$l ~i HnmGE9>@eQm֢+$%sedEWuaNL YM]Mc=%lek,бU)tm[RH/8q*:0MtP(\]fXR L?,ƚeME'%uґ5b6.TVnؿSmUQ;R^?gαh@ht`p_~}S$u |o`_lj/KMI*T^-ϷqϜ5qs=ρtE7ёt/MwӡuOUձuo]ve7ўvmwwϝuw}x7x/w!yO1yoAzя7Qzկwa{Ϟq{|7|/w}Oշ}o~7џ~wϟ0p 0p!0%p)-105p9=A0EpIMQ0UpY]a0epimq0upy}0p0 p 0 yn 70 P . 0 հn7p ې 0P pW& Q1R,#~-1X}NAHMayܒCqeafqkaswEqpN1qMPm$rI1 |QAH3Y1HQL K5N_-/ @ 2!"!K"#yi +#Q%]%a2oN$Kc2']&]'I({N'u8r(2&e@ *R**NJ$ 7**2+G/)})±X&rRRr@..42D/O$O00 s1Z p)eC2#qO$)S.!3173>s0I4o4QTs5!/M52גeNNt7}s7{6~s8388oSQR9-RR99OF;s3838! ,*c H|00b$arbr)4Ah8g))8)a?1!8? 0(L(!ЂG D(¥J Q/ @'W#'_#x@=|0 c9K`v/Z'dIသZ$Tҿ36V@Z (_ %Pҿ }X)VPul(E4r]Y nv% (|Y9O?!{5{10@/`ʜ#X (@Xu?K0@&AKA0^hPVeg$ H 7p~8P$@$u—@hOrB Q"AT[Y@@MGGHQ!T?Z7@1Ɇ}d/*`L9" lBưH-Ѐ2߄T$ϐ$+)?k ;k.NȤp0GL2 er?Nvo0+#6k B`' TAVhUCT`Yg0ZAZYlNT__f A^fWfeLV t0"؅OY$aYi(PiCZ bMS,02 gT\C AݝqP }/(&Z@AP5x-U=%0<@L\\ =4U-LO1. f'DrGtuV,@ .|XNiu$xe1 V[m 8/?N&;;;w?+m HxDa aEB3kЇC@H@^1 Y/Qd"{1/.LI yi&(QijqA* "R|r'd9RXPTH* U6I?PQTJe!cCs|e]AT3+!!D0 fkhHxe!@8̯r92]1e%sM(zE"E,VxzQ13x4(k$1L ID_:@0b@" R%0!f$5*ޔd43&GC uV`-$HP"dEcI@6% d[3 a 8gj ɊE$MԢ+j tOC@ "p\\DHP :MO} N]! IBqI+HspH2FĪ" R^UmKKkĞ&t *!L,+NρtLdf lwM>œΌL+ZW r]20 !#;HW4ˊkiJLzEgNF+6=fPVnjkZcڬ5K\-f%kR8%UA|sK?ޘ:AXϛ42ϵԗ-@ײ_ E.[ D4J?`X>mI#r(7EHU @Ps*Tu0Tyժ* P5"IH@ $@ uX `?( b5P+[D r lDs4u…i 唧[6HOcE' D4H9tDrx?8qO#;j; ᣸!-A2"WDDt.,$lXGTpTV8e)cjuM‘ b.0"AK."(» C> lm6%o uBD' x,`l@:;fA,pnV2ΰ݌w٫NBlr1ŀO} voBx)Aq?x?px;y_˴ܛ imM'eJ89 )k˲{&c1$pϑ |>؉,qO~>a b6 񇏼'O[ϼ7yti+QKx"+$ClzpM2 9" MSY0p P'h!z-1ݝcС%:?tpR"S p /c}H&a}jX%.H #_ '(!D*"aa-R5>b9E]& !]}0('H/7!Nc X "!Au"j|W hF+C2#+#rv>G Y|y1$Q$fX…7NxU 'j " D/R Dx(z!Q6E :o P p :` =as 7w"h+{$q0-W-H 0<>Bd&0`=(ր$` 8w (芫$v0V2Q(XRc61$4f5l1\-YDXap|c>k@<s˧$'2c87Qs1 9I 0Bӏ@m"esSWuG!8a=V&x c6 P-]4ٓf0MÓQl“;99vY3vAFy `BΓ.9i9_ɖіC1kFw 0/Z E5D9(q$"QkuJ "{#NR 3@D6X -3ѝ  +  19GS0 /]'ak)[Qeyk`>! x="gQk!&<1pRJ6t0n!)TYP $|+0`A$_ѢѢaL%ɴGLPb5v-]T ]" #3! `*ȥu DŽ;w]`b@)dfZD[uW"Ņ/9B54`1P W $g3uk1[ar_OaYk!3/ AYtjZ7551Js53GXħJ#t3\X @'62swŐ hOꈒJxOA!K1@0*)j3݊9:5УfAAxJ[ЙcEr ]h:^*gTH],ۦ.T⑬^NjA 4d AEfj҄vi&je0 `I hQ9 0˙R#aaZi%&-#yIa! yd0mg;q1P;Es`)v@J>fԓ1Bm~mVY0@6[H[27a304`IA2+4o?(cV@g?W4}sKěmkuWocuA[vts#uw<wP*~Y[P a ;h [KgyP^;\x 0Q ~ y3p5 wȗg3q/u⃽!2<4l!z! E A "Ű!Wg0='F!F{N!)'J,g@WzQoև}GJJ#Ya`;( qtS$ai(ذģ~A c!p~$ 7"5qAʪJ"cnj6@ ({ ^ . hHYb%KRx=2B#əṀA #e2MV%t)=%V) "dna0vׂ,o,6k*+) I ق!"1! 8x%r"&oh" `Q !!/2<s"|>  a;Y;A-˜PA66;Y7 SBYÒ 4SW"01<'0y WC[7+*(ղG6:t{#vt">ic(’Ւ?363[~h!n֏tuaS5:;szK} iݓn{C`[wW6kVpvtV@q@$7!G{|;fpgF`)3bkg)fF̀/)/@)@Nẗ́M<; Dg,z[A0a,tjh.tI,j-b!(͙TE[A"NA qdӶdEu |+{rdݰ'|wۼ نs@ УҀQ:S`#V=H `SQ܃5d3;N1OqL>`jX5I`UM%ujXJ0TEJf= Z>pQ -=KAAO>q 6yQT )1 71  ^X".7%wsݻY1a1J#u[lyhNQW\B\2OO[-Bs^9=M*[umŽN{eG` }#`X !qP8a^bv_o^zP> c~b)vbJe,A8LքJ+` CN~w+ p ^UkL.B]a 2L)> qIV@H0jRZ/MD<& |' Pe-gٶW]˝ q$9"@/PuQ0c- v[.f^sv9w{{qd!qS'-l0wl4s4(;o ʛ=JG;bQ-;1qE'/d4Ng, DA>x`C>hhaFAH'XRJ2EL5męSN=}KMR M>UTUFB nskRc''`i!D 8@OZ3H'(ܖ=ѓb4ޖ`H>~K*i6^H =Rh@ 8frc #(E&`i@^~  "=r<ȬbR%Ud^FZ a, %27ZU:#J2u&0k P: wwQO5h qC I*Rْeq~p kX}z-Yi1zCŠI"*#"-J;R4HP@!Lsr:HQa:e7 ("^Z! *:+(2/""B@) r/":[ 郆 @MX<`#+&җ:E^; [\D*8 6hy RA(JyB$dxMњOBvxB@*% I8p >*{ BT  UB<iHRȠand-`1 B8l" ,5e^qD X1m!JdDSr#* X7b&`X R5nCtcX-0HY G~ $Jd"әH<UdG!6"v  = (`k"@!|yꖼycER`ziA<);S$1Q~fHn+oNJQdmrSKѥCn1:"BeYʉ2>iylpߔan8izl+4:Ò`h`k,KԸ{yqw K S#7B:Ppd)FImm"؀Cp0bLV2@*ؐ"$WK ^"BÊ4DtHZd-Jp@rC׷wf YHKۨz;ݴ#.:IлD mJ !ei(b ՕXW Rƨ. z%zw!4U, H}Vbԍ6 EvQ9WDMvrQ!7BF_t"~?.]ܗ 0B-f(i3V #4d3b8NA> T8*٠2 z E.C`aH P g@L=Ozh<RIdI a X*<0ȄpE  ؐ2ßBB'41aK<$EH0D…" XĐ>,G9K/S\FH ޠJqY,B{+XڊBÒJ`tQ79B#y@3+Qe=84Fm x%lƃؖlLL(piԘtYPFd#QpxW8Ƅ9n!p)o 9HIHPh(/-JЀ ښ.S@PZ󝓈Z"\TP2yU+t,tװj5h\@lJ7#15A3A)+CC{IRWCܥ}(8:Ǖq,e؈TB Kj}\X#\|qO Mܫg㗤ru,W@HD(lу@7u`Q}$uL=Rp""%"fCҜTR2uRHNs^6'H&3D235cR6`ӊxo9;#J TGBVDUxF}ԫc $N0XPPdTxװb0=!u Xz3E @T{9 ρH!ษD  h%09b!8P8P^p>c{:\q֖x]huƌ }V$3P2a}=`>c@l fEn 9 Un"ވz: 傿З%/(+?V6vV LY4hYPzjjj)"fEK F* 12`0Y03k9\mqrcXEF*,6!*;B"M\><(1;VX"7AE Vm.Y탰Z<ѓ0,kP^`<2X h>ќd Dg4gHr"n6ptD-`?+IHb(h۠\pήfF F }l-}ֈiHq`4)8g^ Q`8cefcʖq0( -,] +ة\xKPK)1M'@MAҝB`JQdt(MֈI-:Obk" bR5,\Zω!^YJ,/4"_e4z;〈N 5 YN!w/V8H J- ʯY]Wۺ]jf**VWK EHm:؊"+1VqznX~'w8ܖl4͒ht0 'Ҧ?u0ㅲ#¿ 2l!Ĉ EHh"ƌ7r %a S *dx2a Bބ'#&\΄;٣XTaM k>4!<{QK@\ܓ! B@|x? *tp˜ NʥE>)aҍ+h;R(AC̚7s].|tC9$$h"BBG*Pd?v:voҖP-8OlCO(="~C\ovi0vhs?`AnPzZzZ*Hn)BpaBjE\UB..H! (c0#A 9$/cMAŐ# r:O6%CP=-8pM:1M _) M$MCכuy'?zPЙ@Bi*t,:2H n ˆS×U?<-LvhP}>gz֨ *ZE4(H T"">@&l)H6(Z{-j-6"CgT GC#jQIP5F]xq5AB2nC*L&@ IRSjNSHW iCQ#sLLW9 fJ:.d3CT^AsCŐV(Ea"9! AEDwT/$_SHs?z`D~ pDˆtlbypH Tp|IR aN ;0LxŊ5?p54=2ԴTJʤ?WK2D? TC">$H E&1V2.I3L(A  HQCB@[ M!@ (}ox(}C?` qA C%xre.􂍃NLMFch:Qx3r[rr ":~@Ҁ P-B  @0RPB*Hks@* 8"ҤuHZPHBSp[]B$8)SEBPX v|˰yi#&0s` ia #H7 B@̄<CZ"PBh\E|yę ^<×P!B3"G?$2,8h>DH #+k^*\iBl# OQHO9C2F"DOSaXk8șHTI^APMB#TB;r,D*;T=dwdKEMH) N MHSǾO#[bIE>DBYc0u=?0=-B%%qˍJ@?hN0W كW C.7gE3ALBq0aB##"vCBȠ'T2BT38Lr.E^She$O0CBn\8M *P@ SB*IЅLCb1gH@r]! )F jh N/A*5!.L1h @b(. >n[\: Fb(?X%M@ a t@, "aKH+'dʆ"ܯCeL ` AB&: xq2h@S,73AL,>3BBU. *Bț§A.ΰE+F){iCD1!ewT?dB#! `j@iQ[s3wk=kuuvT ;!m|A^ !H+0l= dDLpl Huٴa`#6RV3cSRH9b1bz?7"B Bg rn '![(B:9 roLLԁ>`O"趄.wHt7 *xS&aV/քu^kB1Mx!:_$v} 7)QzB*;& swѮ$dj' 6$$#lRۆ07oƸ y` ijEosFnHޭBN@rӁ_zZk<'@ T+CIcY<,E=( &&%}삠0,"-]xK yH `iNa yN.Z-V5^#66n6 BH(B]BQEI z(` 5V X?8"$ ?Mp?`" $E%Ǧј߀?hBc40!C<dC<ÞM0#0q h9$8 H tX) =RMRQP%UCІ",MC&JXZB$L?JX1cDH9~%ĊiP?)PEA0W  %^`ڈB$Ak) f& `A PJ(@ar 4@"!C̈́3(58L@"E|pݙ2Cgs=xq <$99$*D9DB? !WAi=$DFXL5'}NPU@!d \@!L z P? uC\%DM8PhPIX^BXPB`R3 9DUFA:FL!C9x(͈(({4CU@8i8R]3eJSaF!-H81RԒ`Ҵ0z@ 7IMHx!D @pd\ $}:MDS֒6M[=EP3 xLz]()i>*tJQ1'F[=l 9mP0AnRE 8rcfH@OՇ{`s~FF@K-Dtx @}D rƻ4DRFWOٔjz釮J&DT^CD .W @UA(Ɉi+^t1#x$CBr3P*Q1UB^(D5Qt#I=pt&xe9gB8RTC^elLX„ zz+.}6'|UB$BB@!Δ܈PB?X]$yn5(i~]ACH?0A]"ĹHUD٪y5D}Lڈ!%-@($ GD B|h0*Hv}mB =CXY}nBP0ZBB|Ȋ)%T?LYB@%q!}W4҇.DdA-x CT4M@^<NC .HF) z9#[p5K({Nx0 dH-27& =UM̄/Bږ Rl`ٲ6 ܒDj߲GHLJN rMceG(ylTz5 zTEH~H >TJ^ri\8YefԴHJ'GGr(8c+0 0(`[HBj4q\5c_+Bx'C /!^tԕX8ɩp45i:"^],šBdgC(JG<@3YS$8!ZM'LAS0I _tj8Sި6E]l?CGjB V@ɐ%JBĿ[kjB`<8l=ah:IÆUri-ҲLn%-XwBjCB5FFOkD 1n^ ^W4=@x _}Ň7h'Fz" >4);$A$h? 5n$!#KD/A+̯= "H>~Q {4Y44+O, SLV@SE㪣hB[g@ /BsLqG` NT> VeMR`X" !:>aI!Wtc𬁠j#G s s%viE O{]g%NxIr9W|\QX!2x==T؉^#:Hdڮϕ rc.&VNH 2h#Q!<b GJ')谷 ^W|g_/]Tji B@Mz9fF$H3T$8n8%4 Q8&]h=J5 )?ԆP&BDLC27PvKD%.MtIH:L K" C QЁdE :R8CI~HX!#czl(C"b$a@fE8 P:<OqCQ+.FN22E  "l!0SCtBm Ũ%%)@z0;$0H "%A!N%“LO,ʂi3g%7p?< ">H\o`D 3`Ȱ 4 V2(UrUjԠ=,$OOZ><4{?i/^Cx )ص=,@bQHDx8#nhւ1}9 0DZ4&+Z%j)4፵艀l݈[7(,od07I<R[PA@!P7Nq>(2?AZy.A TRy#[EcP@rv<Q/v=h#kّn 8zFՎ}+`j""(2g|`koJ$pcY$PY$KS9hꘅ\ ]+H(p; ቉e d1HΜ'e!/!HanE\,l̂h`>yPpZEF9AS%0!Tc ibL 0'+":م:%-vSG`$(6 5A1 HPEuFLS V`G9_nv0 OՍځ0 Tz܈߄-e LMRMXũh&Xi)\*E}5 $h&i 9G8X_ը1D$I4P *rTSH^? |El<89=~| VQĖ=ٹq{{r $ x= -^OElyG ԉ&tG  yÔ!%'S cTrEIb0}!NȈ %Ӊx!fE`@!B@PvCl- , n汢 .Nv"`BbZmb(A n^H{zӀM!cnn 0mc N ­Bݦ D g kLLB8dPPh <$64ns@6'7d@ZcpB?pɶ#hj|&e & H's缐漌h?$"/`IvI(3 a$H̠% "i0!R} `*=jmbTi;@!x UC2"2ũ(';!"& 6A"B}gy",L!`XăFfr1)!!~M!AN&r  a,5b bܘd$*JBа « ⣔\ &a:b^!F| TJ6j0%E p Kb(!Tj"Bd@ !.B#!B @A*!*b O| cG?,("J"J#`b lv/$<B@ z d@Pn7">"2 ,*# ,wll$L-ښKI4B9i+Ptj-3GD˽B*0؋ä ˽z@ZBg J"{HB "0U*`3эBp"Bd=b>!zȍz=8J܇qr@"h"3X9$X8VL%( BfzjbRB&l#j#0RI iqg|/pBANOdslQ]/e٢^x$9)8s2"䶰Cy~V+daA2thÂurB?6JFD9$gCxD}vʏ>aN"xkc8X&$^1KM M,eV- !<6Bbnb{TY_PE b51;6= W8bU V%bLC&$@4B\gc%$r=|:A? v6q YHB "פ "mem tZ$ .kjKAPp b.p!p"r0D>OC,}'s7W!`8J_R (c䓏1Y7ʚ/%6{δu(@!Ns8p mx$slG|JɆuAL3gOB]aH(p"qUgguF,zcDU Mi=z˦u1KvtM6ͣ>rcFh;x\IL@oy-;Xh/pqrCq!A%v#a($恀?YA1k2~0hh=A<&r9:~b|”[!{{!fVk2 JvG*<#8fstH;|3abb'd$anq%)@ 2nt@ pb" L *E}>-{i.#NЁ 0\$#P.Xpj b(R p- V# a5tzHd {· 8C [9X#`c "`!N *JU+#<؉Ň, @E),Jil;`:@⨴NBW7k~N&@$mGv :J""\aSKomT bj0W!0o " $_!!¡C "`P!n:bC C2E}{\0e  h">j M80,Ѧ==^8(eEW9(̥n ,(`4C Mp ty` , )"~ʊ M!4 ;;"Fmpgp:TjTV C[,M[ ?>(zMs\ 5FpX ,ίDAH UU** h<Ǿ+:$(RZ#|]zD*_7l%" U)'\˹dKھUYBH#o$s$P =p # >RF4j +ƘdoKP$Ώ V틋H"<CUk`Bc"ߐLd:<`B ĉNH1F5LIZHb %L8 !X$r@6 P СDCb`ҁ&NLTRcOA':ELScml;K v\i\V'1YJU@A?Y]v^(/\?(S =q]ODز:^<\snvP oH i?,Ys&IB?EHKн zp0lRtDAG MT-_Xv "MJ] x W$O L$Bze$?+hM09 0DxCE:.9ylW&]R@V,0@ZFEy@@8nItdT$AQ9\MT@`i@6zӁ3< $A?=g@CR0BZ0AixbDRxLНAy@ ?HFb @PjC `HTW@ez /RjтG ALW$/i %G뫸0 @EЬ=A!5Dp gƽ2w ۯ,%I! @"+=04hЂ lADVЪZ㛳#wIc,g:6H&Y}Mvc-⫅a">9 \X8fHZ5"X5#VQTNJi7'G6P,xG¨t054lu:ݱ$hF$XBP(! ,}U]nM"LaOmkFV"ʥ+\?H.^ hg01XՐix9 #o"@1&6qP5n hX!g rÜgcѹ$5Dh{#4>HBS,!/0Yxh  A.4;S@6`lx%QxP | -n7."->O"H  ,q$SREr?$~тH3[J xR#i?<  e/՚`)G"^M" 1HMDP mc$xaP 86}%I])"T'ej$c#͌%jKCLڄ _6 EyE#XN VzS$bs%6)^3#ɑt,mK]>B?v |n"@vT4,c!j i9Dxju\WaDC2+W.U a?  ld ݎTusR :RϐR `!H (RsD: , "p?f0M F8%ޝC  A)CRd l*3C0ƜKp, R@.uʚy<}+#a+Z+@K܁o3YQ4 G  ud8!k d e68QAW`45֠5B"7 y&A!S:%N&R ~Hr[D2)8Eڒ |{ |(W&b"a_kApQ<  oYS`kca%d.@?CʥRA@:ID\: 3ߑ>Pke/ :Hjl- ;'deTzUyP`޴d@ >p~)!R:"dDbHkiBpFH<:<˓:O~(T\P--stBf)Px b٬BL M=ur E$^z}<)N jD@UP$!HMS *]S@+Mt%6D ɞ&Hzg$! o@l efB1hT0Af0qAh&()11(' AGS@$ԐG!mM!v'mSV}BU!'vq,586E91Xl48AlJx^/b&B9_1!ttp|_04P]C.ui02""Q,m&1 si)) ; |W0  ъH運 0\&1&_G%!7P0.pAU9a^p; 6OF36@~l0ۨ="rɈ%pFI ZXQP! +>7`ϷzdZ@\tÄ/UR!SewZ5#](6JY漣9}ͨ4#v(0>q1*@%{D;9X Q)VzBv6 R3XNJ>25bbGBqU㳙7̷U6(gޘGkh@Z7Y1޸p  >a-_dq>DQB!Faj偠 B`yYi+ٙNpV0Ud0 DiD?scp-3[|45nm M76+ a%$Ov$,sqd2JIJBMM^N$DZF P4| HoGOAgSHIIwG;GZPWODmD75#ړ7vDՄUZ[{O/MY| Fh>SVӠOu @Ӏ1PyS֐;8&C9SRɪEQC&!#L0Q׀WirAbUT.+ƚIźIib*Ju}G#sQȧ,/J')OY{T!4Z;YJj q76pJ[] %N` a"#Md ^1 $r@^!{7h @(dG&#]0a[2a QbrJ9QrJ]4 ^ 3F 'H&õ[k LQ y$ڒ}ID)Iav:edH7Aa0f0 `f\1{U 76~CGzs g{Rl p"Uye!y#5aYp h@pfv_Ɩ Que*@nhV/i  rJ]ZR$*Ւt!Rnj0ghTl@[V $P RV{Ch']5M$zr-s' sPcWc:הQy}qq&Fq\rnr wz$xMwyE=G+6ZQs%rw1ĕRzM$sgkWO+ =SWsslij`'0]HQI/LIpZM/Yt=|4Z6IQrnGȒզEʊQgWƢ7I<P|DAFq;G5C}<7aR~4IŇ{wY /@~ 'ӌ,iC ^B` "@* 0)aO51  3+Άf;:;V!Q!!" B;nP#X2.fFaW )A&A \ \7s01 A$[2MWr"7YD%G"v'uSE%X"q)OPH@ QqC$I<؈)\0'r%]*I0$70Un=Yg yKp˜ '`#Q F eR $9V8&upu]9Wdg#1 I6CI0OO#JܓCwi0iEnC32wA52OJ-1@ZPsRwQ5S3.E+UUMӅ(]oU(6aPWq>A?0Ρ/i uֆ OP(t- ă!]OX=l1Ij-]"L&om@#zjL`qd#+x9̍QZܴR+@%#ro Z @T bu<<5J _/*'@X 1 MG<ѦF^]>.0}> q=mjj*`ELW7J*."8NUI4L$Jq$-@5upN/OIyx!PL;"%Sj/ԥbfJ,ﱝZy UrzQ2Q-3}j/RE՟ [$x -YpN?3#"O!S7PV޽UV,5DObEVG mO01/jY/1Q_[]CaSл8x(&{\2 `Nq7+܌W!KEq⹩Ѱ10ך *w5=={? p|nYa. =[ϲ"P D+2*4;!`¾D;L2%_;2H* .#˼ڌ˹" 'B:JL-+Aꀶ`4@.:,P2= H0ϼ 20OL3Ui8t>nPK36S[TMWςT;T(Vꔴx%M|U:_muLe ҜKG T8Nh\')현q"^4h=J:"r ;rhWΠL|Nhc8C)7x%wjpt^@ )!`.E z.DNZRz&'HD<%"ĺ:@ T6I5 X%#V ށU 9(ھ a$4- $HK-D l ɫ/+:@P '9+' gJ:'x0 u$C&#}Pu##L `sX`rĸk;> ` p?aHc i"0| [$HD /t7w ҅: ΅X ',X;t4'o} ~3XBi]Hu0#k $@Xw&Xbd./;F>g*RD"zWa50R,AK7dd#]ȪMff"#]T*i1"RȫB%+M&ĸDuz$!jeuRWqH#GJOf  abæ̉?tp4bX/#•LPS`ʆ?mjL ҅Lk 2CkԔH#/"Xl4<!ygvBoUƙ&@!\J/J)E 7't-t9iEғ$%z: T@F & 1^o@ƷeFF jB1i9 ,H5*Fjv &T$8 ccZ4cKU)++p{>0źjޚ,: )+TVv"TBUlTHUԀTT,7qֳV\Bu&Y!,VZebw9eWP-VO9˼cu J=^lw;. :&p |`)}d7I>}ֿ p=acmo7ČД X3ue ~q}c YCo:RU4b" G44"҄!@^DfDtR;%e Nk&8ATpP ׫ GCR(ܘ-SG3{ dQ=6VlR5=ȺUɭA"3ì# TM$3$5xk4{<b UK&52d8'ae)+/mu]s!ul@8AJ?_f!.R'G)C"uC"GB7AC. @Dc  ,Pr@V!E5k#2$ !-̻!HeAvPfZ/)rN7Tb""D'n4ԁMDf  `DwxQ#9|ChF1 $58CIEs_@29wȪ?P]2" @5YF d;u@xRU;.##;Z*$ ByTiDӋ:( "cit>OpIfoִ LڿVNЀ{2 հS@?=`OjH )ёJ(󎹈 9p1x Ī, "6tAP?S h؀qk S2 ȓ$l+S(jBx#4)#\RBuk7uX7{ 볂6> ( %Y(;0AD ș( =4U9r `(åE"#> % I@!B٫"仐Av;>Fsb3TØĽONSڂ飒 /1#\yIW;¿1( ɼ((hxfVeՐӶA0`SXZBAtѓVOU t Vgx`r!>;𷥠 QG,xᙐЄ,0X Œ @U Ōxi7x *iфѻSV 9=)HU[g\,:8 4. с v|h r`spbӛsFGo$YZr(_Pk@:yܟt)e"ͩ!NQȨN a)NɊ\`1a]?!F`c4јZҀܨ[ǁ0S&(˸q66lb?:nN9N)v?Jo&Cx' 8 ݦ.z@xpX9˴R2Z%Ø3n$i"=P5 .G"t?3 fVm.ʅ3zL֔Uumk2Lô9ռq#B}L ˸U.0", 'joR!2J'L;5W"A[zl \R3 |ND*'0Ȉ:m4 *"殎Ta7+tgmcH+!E0b @p]p -vpd^0v5/v}ЎYx( R5!eY; E_5[P`I 0m-jj Μ7nѧ rG'F{;sR URـs;ܲŁXk8sӛ3=ynTy!gdD:p̌P WY݀R H%n"9h`{޲w NA֬WzUePTF⫬8jR,3G[<x ,5L7VXdRIDJ|?:F b"AZauSV~D*yhcĚ,.||E.ePkpꞆtH{EZ}|uk8jxjbkRyp{⁘X}~suÞ7n~`nY&`LKs~4M+H4:٤'p "Lp!ÆB(q"Ŋ/b̨q#ǎ;1AAL<3#H?AMB8'L9!" 00fND%DA'@1$(A@ KD0[P"T(`lY a1 `1P8x q ^-߁  lfC!?= JIchnخ' ,u&(_ځE2H'Sh:14pH3sD0#>{!FZ3fgy}VC`KۣEe$@* R.$`H~o0vA|,B "`~I?B& @ ĤAph\aIb%'HA %'O/FB`TW{xxjbP%4@>t`A@qTq%WP54?8fR>6|F$'y&ŧfA0L%jXzA Dxzf?vđo" Ѐ*֐ ?32'<~t'3?B>\Gbv U&!SbH @P(PH|FR@ =P.hPM @dV<PG6R]nDpAdy2iGn tr:W,?38TZIIY2JYpEpս92CŴ(=Tj=$1AX\2 ZLŭh u" '11.T"CVͰT`$%&M0@|Aݮh+D`f -Aj z-Ae$%?b%K?[DA%sO}U/Z>ÏZTk$M0GADI@`Rh2 Fx& tW+Db=D~xp]L(:"lpZ)J "r%x3HC"p [2)_ OYh1d RD(OYH-aP+"=H#@V HE=m n %IB x gxKs׊?a%2AIqC<%GƏ@S$a Wp% _A[e'Y W Q@SA8 ylBSx?㰠G  o(CdS!ə ? BPb,c@ CbBBOLl(W4`M Y'Aډ*SId)C qN| .qT W!Fբ!Fz~R D<І~BD,»|8a +"xHQ2@1,AEP5+YwWD`oY]:i ,Qjh_G2Yuo%Ɔ 9XݫUXnFu>kVՁu p⊗!T ōm CrW#"Dbr$5KĘX@+;϶ _ KJDCS;) Sqp_# Q$ MمX$ŧ 8 4BFy8ԣ&un1Cd ;f`riA 됉<Ɗ1Fr@2II]Pze}4x6O (7eh cB?l3$@,P)M `_sP(`@X!vȑl&4KeB&69 Ē !,h`v b b?.DŽY56pY|cL]X3bn}]xro$$W6D-P׼/n@=B^`Y @Q8鸊#BG3ޞM=j_;E]Z  u!HA.=~ɺ|_C>W0P_Ћ3(,E' PЄ4*pM$g$H2$ {RG%Ɲ@j1hF8,8B 4@û 2Hx DVpn R `A4`Aq_-tхp;}?pAxA BaTE_"TJ"ĄHxXx/Ӊ~C D=4C /0@l/- MKʇ.D`B:B9@RIB9,G* D] _6,QߵW|Ba #$ -LJah DQ@AbԦAHl&Aw:0A Ljm N@i@ Q@AčDCT5F*q kEj<Ѐ4 ,E4!?<M]MXOp .PK@LUi@tǑ^Df IdhkB ܀D+knΨξ Xe\Z^do4 vEYAhVeT^QZ\ִ\fVnr`lܾ\A@+҆Y`I2ifZ!ek ݙ"C &b^\vm@HiJ%5 į.ר.@mPkr@`IA@@MY6@nmxg~4zҏEuF| $/|@cp*b*ЩhhA\oAJrhQAfɛ墙eAoA(/,8mT!VcEАbFl( 0 +?D)ADܕf(])? }?AnlʒZPlt@h%MBAHlh$GL|*AHl TnIX@FW D?tgAX.D 1ŲΒB|l~܄:ggʱm^&iD 3DI_ť[1 r$K$ѽB&AC ׽@*'KR*r+VAA\a 0O9 Tۨ+1#3E0Q E8PK^2cs6k6ssC5 UADFO,B9A \E=A2 B+&\4?PAPDe`DATAD}[ *O9Yd & b t@EEA$F@H{JE 4K^ T B @p_Q-(_hFuş\ ֤@BtA$@h4A_yHsCX\PJD@|A?a ,~;+yYUcqC?4@7PrX-ԀX5T *T`gíI:b@tBE- LqũlTLm/E}O@8"c:iSD@(Iiɗw{ }IwP(HCb(5$W0T6U 767qo76<ʧě)hc ,/$HL!*8A@\>l)>XC/@ '5K8)PۮLPx ܰv@|F`Y@HT$FnւFdɓD@k,vxT#^ M`4YTm?3\&MѠL`zTה#g|Fh@ DeLʹQ+uɍq\xmڄI{ĔL $-bdL˫+h| ]WMpIA<+WEBJCfFDt;B(q1UnOpCC4gľcDdq]Sv:P5uq@<el-tr4.ˁ d7KPCi YL<Ao@/TQD6)rhQAiN)ʨbrZxٝ X${GRġ`7D3I(ܷy+7ZLyjuA `@;J0$z/3 ?To>S5hAx) Hϡ>uA\Batu,@,@1@ͩKy@M&D ŞLqSCY AG VDx Db \ϳxժoLkjr-E61A?l&[  AtHp3(R $a?I G/ a! "e 1"4.ܒb@&By% $(bBR$-E/UuQY-"U ֵ{o]v耲"$*~K@.vX`}aG.qyaΛot!h0:Baz.\^/ۛ3h1gޜґ8·vm #97x9>|QTWuc!1GH 'G6D >8ha/ ] LNZ Z >|,jC%H1*vzBҺ.[ͮB@ qK *8Z! (8w~\ [7vz6MX.4`_~R@(dT'wGo7i%A$} c:祿yj置2pk&+-0K^x域>/xUtǾֽJ!)ZXA ^ ް хI1z$qG2ôkU!2m9y$ Ԑ.iax=XE+^pA.Q!Lfd/ Ft1lq7h#Ah &uD6D35A 4 5ơ,F`ON\:K74MApk!LAUЈ \ @7u?ܠ!-`iJzkH/`@*Ƒ -hhE]V5e+(H ~vk_ @!lӪڶZLp Cg ?FĪ}wqFALC/ p }/q^@G 0 u * VCpB*ET PLHNy`XojJ2< sl8 S$  EYvBȨ`T|<XM*1Anld.S4H0 H j r6 ֏tx1g0XrbV 1g*7A0Ax)Ёrb8X[C0| PJi$!I*f[zuQ<s/]@(pZkv ]E|*] hS_E6 x# :Xhwш DDpL& )vX">\žWRHMj)uY %`p5&`y@l"5^Ф:0AF*X$JԠ{DYJ [\!7g-j0A7;"A ĺ+$[ NZma.t\M%{W b_|XH.*4 '&QZFdLf|t2Aq Tэp]IWVD/2PM@\ 1zu3zEv)_sD`.u4ob9.8ēPU8-Ad4,`xp g Gx }=`PJ4cx/B9m2< 9:#I/vp0!0sz*ʃ8 uH$'<9.D!`M" LBp/~IbDƘtfDÏ.no^q/jMϑЏ(zK5Bq!.,Q!i89.6(.0Q:AA2%V` "% `0קT }H& _[\B& f^% C(NB.8eI.a\2n!$&M! l龎 n%_Xn'Ӆ.N7Ԏ9|aA ^$J+O!dAF6./ 6@p6wMІ.j7&!p|. BcC#/:ʞ-NXS(r֦>2hF:j7 '4$F& uK;$kЮj3o26xs!Z`'c!sgt*"vLGunZ bw w84bL@-9}<#oMݤc(?tz=/"SAASfb 7A0t gj?DOE(zo!F ÄPh3`TG3 ,Jwd'RTHH(M8D"27#DJJKdH "hDflp .@z0MO""tIN9DbOg+oH@ H7tvIk d !) `" 2a6D5+4UUT鉽"`c>U&@!R!`V6R'DbRIDƔU.L!!ۜYBC 6$.* .&$+Ђ .~ASAA2, $U`/(J$F0W b뷾Pc ڠ,*MjDJ" "(!4WA@&FM(`b*Dk@hTb*DHbk5&l#*bV!P*kS`~j jB>dbban $ 0fbC*[++P [ Br $ i.HaBހ'*kjbVvနjƾL*kT7x"x-1F4*b52{{>#вm|'R;" 08,5Φ:0c6Kpme66*>LD3$"Ѭ0$O:  ¨,!B8!hDHL2Cfc\>c::,6| 'RL! ",0z;>BČZƺ,cP3 Z9/C17Z"{~j<11H7`5Ԙ ܘS pMz!3WB.ݍ 3.MԎP kkac@l?亰VlaV* B2Y!D*+Pnin%2W\W~i6mNBk˵.J cA`A뤯Ad Xؒ9ArAd.z}" . BJY!:. n:$"!P Ct3Nđ"j.0BdB`d "6d4"$({ )@)o0 p6$B(bNN&dJO5x'kVM*LIG ȑW WW%A :+a  K;sYیK;Xպ2 )40qd=#y92 HK6#m4փI#P0~b0tP "F?c'Cj7pA B=5C?W{?gCI/ñ'&GDCӦ?F?"{;?죇as$ :ڌ:1I&IGnȋ7:4DwZ[Z[$'#W!|@KZ!\!Ot#Iz  E9/zYnl:gg"Wf sW.Y!+d Cj£BzaUX,a$æ+'}Z5r.bs7x.zsL./p3m*846kPjm+3Nop#kCj NGPB6X$ܸk/zfې2.,`I'ffJO$X`biH$Ug|~!^>dpZԳ@z:gJxyf :x,L # ? mӁn{"y]!WG$Cs_b!v"B8@CNV:ّk] 4įσ3C%4"_`FYBX'ɓx|1{Mc DGPM(d_<) LӡD)=A^:JЃM)QOPq@S2I a4#EQ*}}: hKz[UVd+и $i$AL;hƿoJĿp^_ a&&H(o 8ѠD<+ł0H"6ap`Oa``f0A_p˛?\dسKTF/T|p.+ΘN'la6"\8.W`^9/ˀC% 0l/A'L+`d_4 `Zdo4赐H+^llpM&>uupr}c_~%L ˽=7`DБ57+2 L/qc|PxׂqXpӮoי@@%EFX'!^$A$rp4hHcϪIK} DEArJu3NRTL)8@!:8Vag|2BqFl43Me tS3jƎFA,6k?g(gCfvKSKZց׺㑇 W(LN Ib@%1mA#6cFA@9b:j`')#|ȅ#EdR?1qRwň/N"/ `zq8LCPD 67}!<=KA&T- ݂uDhB7$\)!`K(rG@059|&3Mq.Z8= >|& ] XcyPPA{ 2 Y`|VgÜ `22P`6Ђiao b= 0B= kHƙm oL8Δcyx$,%a4~>ou6״`~wWߗvw=fE730Ph xc1 ^7P Fa p؁}!DRP ,؂~^4x8o SƃJ P`H71OR89pcP10T\`U3؅b8dXfxh[]31F-.1+@ <$ Q?@x (J,%N'&e @(:((a3O *s~2 @a/1iPp 8NRX'A<Fb`/aqģȆDpA0(g,MpN`pN`qN ` Y %-x,&AmH`0&"?!7:@ 2 O@ p%NU p ; pQ >@YD /B V{D@\TISyD<QAz A{n*/PUPvKQ rd =n@OB@<ЗGq1 lD΂*`J)v H qqjaI!Iɚ4ddd<(,/#np ORIJ `q4BՉ#NEB"Qv4h4Ԅ!46q{.t`i'$w!$/;37 @U:"4#F.RX0q~cj.V1r"Cl13:0R ßR 4!V1rP;aR9fA!f8U6I!%5#0.v߂29>iå^:b_&5Q!52C7l3.clzc0.R]0c~sG8x8[7hX;ӣ%uRi`khvA!ep`'OaMvo?a|g-5]բ{:z=z _vC?0q#*=P3 DLHvyrA< H9=!ٰqF!xBfBAWC`V9U@ Lƛ3a0npa `'ƐxcL`p@<r~R nՠ!!'KӠ=  sln0Jq)(~;GmqC{(AIGtK0RP*'H9*@#Rd@| a&9˂-5>q5'a-^`CQJxOd,GN1tNQ3'dbtmrn=T S0! !;+Г&o8Ni!/ybJ.'AQ?tp@黾10!s6e)@ kpUŊ"ER&ż>eW^:i̟ZC9`>z,~iP4lÒ/ūе0M62l`;uPf?9"2ӲϕAz͓_0xk.WWI ?1A<4aat.xarlb:;~[!+!EP ZHe Owat"di` ɀ;1A-FRS+!;'˵˺=\ETy7L&O$iAꫥAb (O`V|u忘%2;X>MxI@YICwH) Ӽ:!+BPBn7"}іJB`y5KCt n@حA-1 A7 ;f:wV}ڴ!A,H`[xĜ94jsjx6kV(bx}v%q1y5j8c@N?Jep@fg0aAZy- HAz?Rԣ6&.-M<&/|<A==!ō{Eηi)C?z=WcʽixxI {u5l|`L?JYq}5b*5ƫ x\0y`.ei'X ]-2̀6l >6wN* hЙJ n~(*: s@p#;}?LNP+NAA1{, 3,q4; s:0($Xm:!w^K}`uzH(w3EQK ڠB<;N0D,2\h A0SsWȳZ`Zv(0`8D!C' WY@P K@t@wߘD`AT"!!+h$iIQAj)@mA@I:qc9``0``Pk<[$j@:)yҳ2*OQ*oP Gp aֹ0[F? pW*O@dI? WfƇ``1v0A%iL= "@` -F @QҼH r P5)qhbqH=\>AhwsXcca06gZ!Pw.QR2`@/M=^62b\2C16p8WGc60F[ᦏ8^ SX57tP]7344B"<@Oa TPD$ (A #tp G?.\ BIѬds f:^9$@_-=4K3QDY!Hd6F(#TeRuśW^ u _ER%T?>)OY2]t t ATIWm)ݕ-PMwي}f<9In[S8>8l>43zN]\3i╟GwiIR(t6΋)@E5Ika6DlŪ?#Ԩ. SMAPB<M&ɍ=^%*>Q\ޛG(0Ɲ+jPHQIbSHf4gHꖨ`S7J"=H:-:t4/Q}ABƉ&8EAPњ41S2n%7\Yю`DvU!Ph_ d!NZZ4Fk}bhcŤbPnW l [¥^6}\2$||#0x"$m^H5zM Jؽ)9[MR&8)G &C2lj ; M MJ5, V1kښ.kK(N,jiBd.`>h>a!NV0d^'B궹L4^蠃8Ǔz@xksք&5MIq@b'P+S4@A/(&҉쳟BT4^T82)dCWΚ*x{puD'HLE@G1LM`˕HN H9BCiIr |.jp8C9#i D "؀m.D7 D@(c 4&KJVYՏC- t*UzBM eE\ E'Y8b:Pj.uIAluD6ΧtvԵMM-m7wHHz]kzr.sۦycV5s]v׻M$[#H.E:j@\׽o|;_뭋bT.ۤ >k2_Ep`7ʡuɃ5awqE<m/f%"-@ &iDLh)d,XcR#qEbؕL9fI:4r[R)NQH]Ɣ(>E5F'=r  "0Ƞw>r X5@@ x Aި(A,HR_k*?芮™X S@@7։ѓ ((\iQ `!cB Xl0 > l.𯩒 Z] x0C D<a$̉D,DTЍB\*X j+ب *p%ȋ!A\Ey ‹[ 2 ? ŲC08XO\[yrHqHXD Fd J#TJ)S79GzǑ}\'%yD SF##GP8N+(&qRyXH"zpJIÇnHZYXpӄJnRмhA̫ yJ$MjC(A)i=qhTȚsbhE¶X@ TX+.سI/8ʂd='K o 5|)]K rc!h_ I LL*M قDٛC 1 HBY%JH"|AN }VXB8`8  "}Y# *8B &X7AϺ8R"iSL0";6 pc3pɄK:0P*<,"  --߈QÐ-\DQQBZ!!΁ ! (ኁh[.%1- ,yӟ#PSh#" Y1 1 ! 8Ԉ*w+elT攤W(Ni@2ca14ŵb 6CD6Xk&JkfO2Tef@2Ё({ E>D{&kzƉOD`D#)7aol5 \aw:-:8Y݅ phX9@W ;L\Vi颅ă%hK;p@v@jb(0!pNVt&@ _@LP/!"$X!V08ĻsJ3 f_ ?#Žf!8Hyȃ HC2:x{ݻ9s@]@Wx4U -@ѹ*TӅ@*3C 6Ëj6ƈоH *CEj@,Ո]sQ h`nY `(Ɉ)AYAB Rqo)c 08DJyRH  9(*hSE.(7Ev YS (`tsdS*agTe/Xb(0)^j`k[KIn{ ΃[ETt[C1ܡ=xVqs94G0@tGsVX.x  0 bDX̺ Ϗ;"ryc9 d4،鶓8E(e KCH`d@s'H #=W27`o1Hd xXbaK@hN iq!yoQy溈1WX7Ea1B9A],.8Mu+QƋUPEaK GRHu|by,8z߉-DSM Hq7E٭Ҁ#݊}SPE ,@2l A A|`bD ;tp#Ȑ"G&J.;R0ɿб` :7 WeiF2JhC-) $V0T,cM{  vZ%Lѐ_$L&b? &Bo3TNO꿡 %i4j b"LóA>b6AClH"ҨAZU];c􂀜m~jJOm^$$Sѻ&8>RL$FdxR OdVA|(ǐ5HHP5ԇ;&407,`;Q8;/$O0y?+H#A7&.˜/30/}1T%E&q]v)ɴږ acB9 o0]Aӛ3VBAhzieOq@?@P kv`AA`YZX%V&$AuZ)dePW=OnS,},T,yQ/ dJ :&P ykB^jQ :`fT!EA#A*\tcl J/kEE,MsqE@ml3A@k;1k"̯\3t3A =4E}4}dC>#Q5 &taO[}5Yk5K ugi6m}1M ν}7y7}.UyqBRӛ 0"\91vL*rBHT{=> t!DɞPs8m8A3%MW!%6~ O&tp@1E@NP&v_H X?[IDA[s$  d|G-H@ H "e YBD6B$;4:D2@0tRe›`+f A.qa&9";9#!" 4iB#‡0 | $H\e*qJLYQCVԉG HP=3-4n" /qha|*G=f$IGE<2 bA; "H; "FcA {@2ġ@}*AC%# Ȇ <h5ڋ@RԮ D@ؤ!a)ng9 O`ADȑ#CSA2)SL"1`+;d9P?zki@o* `*%H>W 8@pIoW(ҦbClRLJqW>Ȇ)0 '*MPUKU MJD.@pΊ5A2C @Ί<@ITŮS$t"V. |(jf@t$zR2!@! > -S ڂJ"#S21D0@(Bq(Wj&] l{Th% v&(p:\C0 0.AX3!xUβܤg , KB&B UiWh EkUiH- #t|Ȧ4#YCX-I(9"h=iDGAJỈ 0U'yOB^UpHxT@VR+-C$HRݽ&ꠤ2QY$u#!8e.R":rBS J/Šn8 -&LA!M2E$97q-5g;YXl[ًF1{R h)I#(A 谂r5yֶ p9 mH0͆ @|A`AꚒ ,La# 64A A, G썑s@ "&w[HczOh!9X< `9 9i,-!̗#S0 r M iGf,ĩK LPH@ LXW8['<0plg0Rp!S?>@&_.A8Qqri#B.rlD$@Dz$ZEA/ !" WB{,2[PvJ^au# o⛌#MXT6 K XL@@ ”-L{`yF2XΘ(DM:Ah$T 'tFtN'NAlG1TAU4q8h4040 \A%gA'c2hiO$(OLo _`4N"D$DCP\A\e|O?A? s?(g$H}X$8MቊA?E-SlNE=hB ąoAC " K%0|vur AC,:L9,5GȒ܂"KGAG||hCCGZj"Д*zC 81 p;P7Y m.UTG$N(`2+R|*q/S5U}?AWؑ 'V~O$j%DDLٵCBT=L@=zđϾaĕ-C~ꪮFIEC 4@"BGH4/L A|!爔5ӰP"ζ Jy@ģ|:qUH"b%HaT @r :l(եd-C\zQDt VaUOmR?hMOmIVTaL^A@]A܂jVwWKVl j%mThg8  @^+E-xQ`㸘mVQtNBQwwxE%. Bʚfb| `C< BگYX%zF4X|DyeoCPGĢ5+2_@&)|} gJAKRġF^6p b#@2HC ( C ,9*A|Qu[SkCF`99jID~oDZ ;C QQT _L>;!q11q<#0k9Z-+D"[z~#AZGyxAcBL@dA-D1Al=9D~@lnz:. 3=ŷE-GUq]t\T*h[0NbkMH,MlL\4H*PCL _tA,O%эϝ =DB `A縆QoCtjBN$?l t\I^TdFqΉA䜄\A&X"< M%!=KD~VvW,D迹gB^=3p3;Dxz,H#Gg 2?յV84$< .@A^B$@ MueT? 0Ng^7sU)Bp eδVMߤx(l 5U\S1oި̨WHN GY!JCM^G 82g! %$hGdXC$^-H5T)s D_o_o8` C@,6 yGZ8 /Cܴդ5ojCԵ)^@9M$$A^AHJ܄>Qu] I%֑P7OHtQP4C`76qGHFu'G'?t^ E{ F?K\8T_ A"xHUCAf*?TpCj0/:ԇoAl&' Dxp0/54joGO;_,q?1:oG.+Y3F%cD5&*䧷p{)11D'XZF2Se@VxHr(qHpy@⇁B֣0+&B'?1,pN TS Xi G4S=0'N2r{ 0 uKI<R)Pjq\E?p`;K6LdnYv䓁{ k @GT2HH d XX:W@CKe%A D̥[XeelB^w6qYPB:'%=̛T*o _|mZTI9N Q$@4*un=w=H`j/7D٫lv#:=OiHC$r"_MsZt@h ֽm'SQm9~m=O>WX'CvP:8(9AȪP՛!DD IU+2ߥg 4]d<(75ANAQtDH>)nz_'$.)XJSI LJ#+$dR@iDM gH[!5*ȸ%"7SXDR$0䕲.I$.$*˭:(*05 L @*xP+ 3x P)@`͍2 N(F,IGTJEt2@:HU% РCk ( ,`WULz_+ t( 3T+YS=(0Ss:Rs <1,w 4ШB=4ߍ:A^KsFDh* |uj}vW"OP\:?S': B]?R+ TlԖirfyTB`#,0a<(7@vZzE(%ZRw{H`l/ɼi[6薕[e^ph/r0F,|(̊gR&p Ц.4bêrOG."xN 2=ځܰG; oYD D^&aD7rǞ.K)+D(P MTTȔ2CԼ!˂.5HD#JFg!l9^AfQ5hbv0U}9$>,xS.'͍rn\ rB<2nH@ T3|OPA0JH@A$ u8e2x^7 +t4BF1 B O섕  L  @*D (" 8!r$ _HTI܋+aKVR>PDD.O[( &E0MfR:!HÁ2!+g@x- # 9U!@H& 7g/Y̋)I0R $O%F\?Db H,TBQ""wK٘*RcJ!-<@%cz G^)̔v6;Ö $ *BnڥD*HKE*ufŬ=*"`E*W)N[diOհw}N"SݥS TMM'lZꮙji_)UEMɶl4gɦհ5J&Vg\@RqERV4EDdD@CJ""k$@H9֑-riNYHF4!/{DX $d y@HB;82]\S"t.έH@ |]F#"sD{~`DtB|`_MCKE(Q.*T 2 H]Q0nѹ+K_ҸCVre/8`s'Ujz3md@FE yCAPGXmzsvs)< aǁn 72Hϑ8Fu<(AA9 r0A0ڃ <@FXB74B# Y<@l($HjmD :J>b @ijAM"H BP]6D_i66plx u;#Bh ND hM`t]L?GoC#k8\}uBD`h:]E5e*jжVŦ t!أ8pˤk9*Yf{平β]eKi;kH6uNLO*~,4c:Xk̨^Aڸ_b dpG+ԇW`7x/ <D~ yoGH7$cCvͯwaϳ>.$}ܣ~}b]ic|/NzH% Aۯ|o~7џ~wϟ0p 0p!0%p)-105p9=A0EpIMQ0UpY]a0epimq0upy}0p0 p 0 p 0 p 0 p ɰ 0 p ٰ 0p0p1q  1q!1%q)-115q9=A1EqIMQ1UqY]a1eqimq1s3.?T S@@,08Ӯ;Y/?s?NӤ&T;;iPA -!LtL45]fJKKTemM+BPMNeJ>54 tbOSEPNki "u '.5{,SRKSYSvm-Nw|WupW5J3YM4U+YMYEЦugB& ! , r(B*\Ȱ Gŋ3jȱǏ CIɓ(S\ɲ˗-%ʜI͛aɳϟ@ JEHJ4ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KLb@1] (%_K_DA4 ^DhƸ{a!80"a?Y^rg_D`Q@Eh:hE6+ΈxHH/J>P8bi= D2$D-pC+$C )C+ ,!:K $K? \K C"&0)O4hf?*vk;xDe +#UCO1Jy,R {@ {cN27$E7-08mxq(b%#G`ZB$  (+j;" /Xo<Px hax(#Y5(B'1"FGT 4Դf0(B͑bd1^&e=/Na,hD}Eu&,Hm"K 9V &5` e粥<<7z#K"F#CaLWVdP |-?bN>t!8@6 X,D'"wO8QU?ԟVTC"@pA+GWD 9 R֗εh?P ukQ:@$ %CjLlicUHN7pN8CW\>ʶJ"gHղQ%[j[Zw@))HqǺ@ ٍ_uo`m"(@b9lBM3ɹm~Qr8ip?ՑL^?`/ti >jL B|ZAFVY!E3"XU10n Qa5yB+R0'k?ZB'1kPW0%A5li0Q`"]2"v47liGb͊b]+2՘?0=  WY)SE\12$;(iIx  (w !G5r^za6+'f~Q9Ε3l<ԣh*-* xZm@S?TT&5:Dk oH3I\hT` E " Az `r] $ )vgc\F\ˣXP؀sHsSH :C%roh=uO,:84&́N|~HM3x vK=];̣4z%8}G^A. +2~< 0"5Pqvw[. xАWd/l6[HzHp*ZO$$BIB(D d 7vr&)G\HpLF#aH< ˥WmE"=@Co\ѡv n`&ck#RJ#1g%%@١O h3OO$,Q %XH€,zB,J$HevjbEPB5,&n 3qF4 L rAB)b>7*cpcp\H1} vjP ]"H AwH`64heGDwbw]xFpF3q>3,q4&a! ?\d TX# <`ri4%`zCc<,>TkSGDAwO'9s))[a|hg`61vV73{i,#:0{jCV 9caz>qS:aKQV_LS?R5Tu6@4/ْĒ6>Xs3qtg<2AiρU)A(!x $ tFѕ1A qIȋ0 /׀)܀/%{Pz {e`&@ (b8 PhpEbxDpCdrZHju (Blv0O| h1Mar 8%PyÎ&YeR  A,4#r+ /p @VS570cBc Ag0n<Eo%%uQ}T)e=T1KCyA-ρ+e0tR"awbu17PA.E׹hY>Vm#'$0G/ڔ758\Uvw+l^u]eC _Pyx!bi 4'vSAƦjw8_s}Y`:^Gz2% AbbWy{~#z ڞɠ$~1bF#Jb'5ʧPg&%9JT%F$*qsL0 Eld+Z,XvC1tȭњ$RiHsahB~2iMXMz\T I,Iù&Im!,ufl1xwqEGTVkB\Aw 6Y.Ԯ<P0K6oYIGa'jҳh%I{rbDO'DK n!rD9O_gu+OKgZ0aZegg{y *y[~y'!ia0oA1"x'Rf +=aQ jl y'wZ][R8EFRǻ6Z{ț+WrP&" xhI ʲ,A}5~~(֡}37AMB|!YW"s0f+g"D҉da+= Q%1 *7 &AAGqtX:AN0HQs@,0L6^,ȋ?{2:C6 5p/@(d k2Y)[X,D(I+#*Y a BqP 1B1Kcs/$2$4D4$0&x܄qq `P`{F lP65|,tHP h2&YjΉ k vz}>o9Sh:?La@uj+\]6d+H׆Hu[?)6M=5`3Z :730@3?MC69Rp)n" bB/~prS5պ [@dRGQPq_$fSX%gXTxp"*T)>v@qG"Nyzrj`",\SL6x!(*~NfW|k* , ¯$6 57;҂$Q6#P p"i{k `Pn/Qp-Ԁ`qblt!@$$RISƓQ  `%134:S4NI &i@R|A 9?(hMDH4PR1J@οe a j=Mu6Ϡ+]T UqZ>& ? R\x  iO*Q\Wc`ڼ%|+gT^ƜYf!@Z:(Oԣ{F bhc+;,D mw(>PBm=6=}P! x"L 9A Wj/*P<}[G  j4@ Dx@>(;)8\P { p#$ȩ53zB`<F 8iN ;>x`4 >N* *8DLA:N;1p3-OHX?IB9P$2 L@8lPEt-'7i#4 -T75k!QY;MZ@TsU%v&3;qیRx%HP[v :֪C+*.ӄ<$!>X:u jIx ⬚ :+Xy\䟐`G0cϒ(}DtRӦU ^8&X ]BY!yZıg2~ N|j @.zaW^3[њB- Z*- \0o_i5NqS!m`X*s!>|PmA$`+(w ƃmCHq1Cb09D(85ģ-Rn$9bI -N`N!c| E% KCd6 @rRH m¾Lq *F njHSdJaд)F-PoZD=CCAy5Xg4i`t6'Ӓ$(Ea&:1M"Q3zFy3:O@nU)4s$e;E : DE&"X";$7F6nOC7 dpN9\ؒ rf!Y足n _驮?AL AD q$2<oPJ$c ",W@.AR3N@אBiIG\BAG26 NтpV1o "WTu%$G|d XR9EtcIWKF0,Gv jm DԠ1d=U@[2 hdsbLf8 gzѦ|^chJG4$` KUE 37VO TՓq &pWx6&TP13L59 3CeMtoh hE%*x d* Q|cNQY05$#j gFI04J/#HD*P ^Mb XA8eȭ:inL&pshf9:a&"T H0]%(=5> Dq@ ps U~C ,@IB)"|&O*D팦NZwcWf e+CR>TT)*_!*#FDr 0 ,OPQJ@0?9`^{'wk{ZAfzIv/`s+!p8sSrW)Bp`BSP@76ˌVp/ȵ]xj>Vk0̀9"c@;8#4J18-+  ɩ3 Eh?;XpEWJ<(P9r(9 >N MH0 x6K @((6FxhFfl#P%*O: m8+@X5m'#DxM4|+C2F\b?A"nI뢳* 2ٸ*82y+J"8[! &bHȀ,Ұ泡h2Ai>DLڭ`% BKrz#8 %Ć~cA%4\E|r3K#At̀H/GK%бCc:xĹHFz{G@S:PdAIͳ"_h2@(X.)PLXNU7"E4^U]EE8!ThaDPMUČg+څ(cYVʉAYآX>Um- .!(=VR耀 z؀?aH#;.CCT$θ1(ʄ4310,˘a)H p(w= TLBY# KhFȆ}?Vρ0$H×/2\Sx~Ռji6W:@@"-4 : >`8a LH^geGnPA{@u{7 )Xx'ۦe:Yڻ9P;!ePuP TEC:C؈/䟐qEHQٺNq RILX>h`n+9*̕A /Xͻ>gTޠ"Ic-h J( 6jnSuJ;y 8eJ'rtzPHupf" z!@H5Y.'/d%'S)P peY_ Cm.*77#2'tV10!>k" BHFI{PQw!//tO:)XO?2 %X[YXS $|lxF%>E p3`rpeQpq(4E[+4:(kF6@ t 9jLj0@4`?mز۠LYс@11xK蓬*Xv j6P[c8<т6X#iH0|y1Hz,nXxa̐77&~ qsU 'E2@kP'WZ:h +LA؉hہ41 PEhhNЁ(}M\FAhS e!AW?6 ZŌN/!";P=ƓW xX""(C "_ +@ PĿDrI"e R?:<'РB-jt(ɣJ2M"ljĠ͟aYa4M< )UL\$ aM!lB̢UqRX|n\#S\#ef?9Ԣ%RFH_eۼ6_j DИOqnAYWQxgpw2wY: |#rҫ&کP`~WT%/G qK3D߈Tt0Kz$8b?̹TGmjM&Q%tFO5TH` CE$\em"O$v5Is&>?*$I,uB((0$?ndNmcha2? HtIoG ?_tt R h AH0aM$5!b4I,I2Bdu9h*n!R ($LeӶ8Ey@dd^]&Qh L"?3Ku@Ih)X` %Ei/NѸDy9,H!=b?E\Thh+THHB ݈m%EH+젧I+4Mc%PFv0 ךD&Bp N|?zf-$D ET)f,S t|*R>MЃzH!ЫH^MkP\{|Ԥ@A B4kNBmm8vO`wVx8qs!MŎ9uC"n(H'9mwJ_?L&B$?!fh~>_>Qr6W Da#q(RG!2`95 'm?!=v?d2T" [;ّ8Ӿ ;"aT5(B`G85vC ADz" fn). P<ZHe+8Hd@ɦdX@O"(Ɛd" {Dc.m!8&$1BRWT@H&$$\L8PƩ`$ЈBP gX(Hb2t%*O64#jJLȕg1YΔt5#dc HD _5+d#$JX'P.;Qlx'Ti$pdJPyoS2Q!D`mp31& ҉ 5AH7If9(hC(8y)|KR%QW$47>? sDy4 <ƒNP=DC8uP.G+Rh"vY$3[.{kEu"&'e%q_̻>2njWL8J (8h%O|`0,"D,POlj"N؂\Z+XW`0'gzlV֥M hPPdg9Ű0+D (E2'ջT@ŕJ@) M@H*Tc(@I#c7 A <(NxĬPBL@YΖ[\РŬ%K\ih1VL!Lc^E`H%N\bLDGLE, M8H*ȿƌHC `$Z"M Cq@dDEH!dX[AM e <^uS bXJh ^GBtA&yWII` ĜfaA"B$1" ^IJ]0F!bPE BT>KWP0FL`Spaő1z˱\]8CPz|#BBC4)Qȗ9#BO<+?4H<B8q. {P+NM']tš8A%N B )!BO3 f<DQh@,.BNEEǘIp$N\,\  DQЖ@ \D $O$}DD D[dPB HV XNO %L0b14D^zeQE!̿XER_[R0 `^jc fn&e\5Q)e5&NA!/+x&g֦m&/-#v.uOkQ,NgPIqIh`nN'uVYX d-+ $NILٹlgiZ{'|Ƨ|'7Ib\AT\ wZE EUQA,A]?H ITz%-.ЙHN($ BJhULR> ye-!zAUaeAƙNPAlDQN4B\)#  @t^\G8D7 $OU `2tjk?qĦH.A)?5@k,q5?UBA4?M(mVgVHAyHq\|\^H@hJωç(B `+BQhT΋YXzm dzKH@hYYLE(#nE dp1F<D!Ft@EKC T*YHl MhDIhڰӹ8mZ!ZH|L\ Yđ Έ^bRHJX4!J\DE ,XbQDܻeMP]x)UX%nF[cE(hۍاTOLD)BmQUDi.B c(꨺iXmi.nz#B9tg8nQ0?L$B+ T$DjG?ȧ$RHjǩd0OPNǷPf DOW ^%ïMhlA ,:e_?\% Q ^$$iM'D@O%ޭHĂ*Lp(JE9(BH  R@`?dRdbkLF T HEd18gݑQ\cx@ vL6XYiqYRA$u(i!(zgx1 (B{꫱Zu\]HxT:ǥjJBn^[o-K.?#6 d~5AGP()ЎJ% T@30JU`З̴5H$F }@Xꉀ܈%8P j% B1 QA"`E"  4,KtJaN$)P>Z8gSU"OV|/.Yt&w ?du*HYL EGXN /شn  8mG,$H6PYʑ/sr$B$JAH}ĶC 'y$Id*[w$?qA1kNG EI0BP`%mph /R'z „$@@2'Rp$S$@) d-t#FsRY'7p:E r)gVP?4gx, cJ>2"/ѐЅ:I90VNFW|62i8T!ІL 1HbCCഥ .9Xك$YْV*ia6 !\"ѬD0P%( #7sSZj) U l4y|9vST jx% Mو7>gݜZjo1[$z(|9⑬MQ)=SnEY@"H2)qrY"}v?:"G8?;)!<$iNpH|fHj8D{7*@!X VgF=#ST /U^q}PEDoWw1A$p7bV d%.I 0;ηh!ANS&fAzœ+2)[; 1$u;NS4{X $?L ;YA8n F?1N q$nH b[z}$_IPV_K9W^"HYvX@ 6Yv*ŧ.rVB>B7ލ̣0-r P &`b $Z >#f`-$>Fh}NH&!QwxwR/sfܤ6" !~$(%'dB %jj%} 6%(ȁᐺ%AjP+! 6  h P",aւH*kT$a %+(bRը@$ $z"8z& @ >!l "a2"霂8I#WF0$*aZ ]AOO6D젆@؅r),n2FD@B+ʥ@BH):z`Bs}-; @:;Cb jbG*D-*B| ^tR S$ Gt= "q&ʞBH@5I04k?4PE 4>B>)"DI0ߜrd2(j6*`LQI:0베i6_H㟠0b6/ KZIVJ‚Bbz9%F$&M p# b9ba0˸./K2+&P!?R{ ĸKFD$nA@ ^a! S P>gNE֠$ sr %~5PBdС JF>Z[C22E"V!:3@4[-L"Ԝb53f*Rd0ւHVf@'!#lȠRE( b`H)x3-?8 b So D|박 M&J"!(ϼ0`20P&ܬxNAOX$be "&`'(\![)$"cφktb܍lb(` C" Ȱ1:|`  iw;:D?I,mz%+ͨb`wa5EAO "B@~?bc@"@" ] PxHuBgf{6#hN$ENB< "76%aTblQ:|eHQr B$х)s S$5@ *LVALtS2n>5mH5 WPnPUbJ%"#*L/>&4rQj(H I6H$^#F$مe&%0a A&BZ@^ G#5$*a% "&^ @a-j)w@!&MxR H6$- NH @",C?B*$>H)" $RLj58DH N@V'ּ JCN"#%X 3x!J; ^E:#ѭx  e3ݰ9~ \jjbC|F]'T %Vb8^hC˜TZbVB 2u d:Ƃ]|-|$޽'!TQ`F?B66ޅ-c^ ):d!>^`!001"L; b?>B' A)Z("" $D4E!>$$ O#*;(~$.d ! @e]?t+J{8c9Ҫ#cbeG2r77pjcqB;0 :dXCBXBF !<| 2JW|1„:ӘF"TI5a)Hho(ы ,$7y&2Gdh[ )XMH6DALચ^bFevQ.AԞfԃP&JIW%T *T)5FdT&a``Sb \!aD.J^ă0>yc8#dD h^\@'("hI "; @Eu ?Jd$/hnlB)WVW(gqa`r K!,2»Q1PHƽ"!H6 $)*.D B: 8p@$$n7؈_POE%h9fP@"pf q 8AllOĬ͉ղ$TD"p6n 0BЃhe$#C}."0<#hP$Bd Ɇ%D/Z ~!"Nl gXmRX)K h@k7Qڢk" 4(T+"Aot)\ cz>IOf*\'}S$kq&ȚAQdd`nt jN7aHi "mڶ8nkt0IB6HX ^MXU2`d ! i0+L4A\%qZ|s`v3Mg&7qH|,"G Y#g*%= ?΀ks+Ez*+!a^ lvփQ* ik ;$]p:u6]Qb |ٶUmѲPHDzb@)ٺ(Joyw͛ F)j%O|R$TE@ ,ydyP1ȜBo('HZJDVƼzXRʤ\txeZh1A7T2RR0S5te{y .tnM],c+ӾBpSGh6} ,q52 u~$}o  h *y_qQ?C5aK+7$+IJ4㖃CHEh3t);#u)n Y7F7 ϳfRpMr a:H6a8S`k1dwp;VJm!ac5;b8<1=<^!LpqQ1KP=Q :@: sd> SEk6xŀ@(vW%E>$t ̈́AEY0AF*4O^ qMD$qAt`Yl$B(Aa[CT[ DAQ \ "0Ah%AXF())R ӱfc Rpo2 s )#)()'ix^!RNAzB!HDaI a BAk63O7$)EdQ}2 h24t%(_ ErɄ0!`f)wJ5CP-HHi1h)5ꄊ K AJ$E>O"(~z9Sk4BGM љ_ES,Ѳ5ք"kljn}KD.= D0Ga)4ljknKZ80N_Yyji6B\5<TU$A}AWTxF\MO)/!U[04Q-RɳIdP|x :iGN7hJ Ud{?W҈  1wX S@edcg`HI '!ЏS< .y z*%^ 9weG}G^t^Հ`%hg kQ r? P`Gpb07'F`oaac}he[07[`eixP\``faW1`@#"-Q 1>Pa=;Sp`N Qq 02Epo& >pzh F"8_̥`CZm㪃g2Eqit$leCa*5h(*e#7h-ZhBQZ)h2 n9s S72-~1OilݗGk!1:JƁnionIf1 pU)+Jz!@rrf& eۭVA $1-}G pC .q0jv{ + 1[Z< 5YGAģJYTWs~(cZ3 67}C&W*2qGE;5i2yϳp"J|'*{(†7LwU|"ۻɣ9ELP7vG&_R{!UQ}a{iz(r'ˇVX銞3\ K~4T'o)-a8L%,6 a II+DCl5l;3%H,3CS67LE<.;%9FO Q|/*ƀ {a/DuS:NأTh7ia%jqJFz!1>!1msN!A -tB # ;e&.1Iz<Rp( vX0<̃ʙhɎ^fb/p {>>; p.K6S4 ApLsМZ ?Zx?3 ܽ!*V2eB/  A\9y<3N V pHETTj[2iK]^X| 3t ա"2yx.h$1ϲq@AC6;  P WItKه@Be& g6@#Q !xVnv(ϔRv *$ut0lh=,L؃RyhCK)N9NFLgQ i)vt ҏR<bZMM(Tʡ!om*"(RDدRu.n&%k4li%KOR9F R]AJ]s2&L)zju CldjiW,}-Z1 !-<4*ClVTc➁˽]e 'p aYE+\`8j*Dynt `ܪ u׵A `` *\rԄtViG\f i*"6hV*39Hb1X 0B̀*d @ A dsb/Zr0d3#r\ ACfb*d6J)cc*z}3FÓ &~er*h)vd١dU0~Ug(Lp:L3(pBwe1 nVp qggکET5uI+ L(k(,!t LG݋Q IGF=,hDTh2-9ij5%+xP"3*5*ku n&F,cqp/z6!̩*BK Mk*]ߙnM`1Hqn!c['~H _P"$p.(Whn̺XkJ¶m{J.E$soQ|59ϑ\!q9N Oy[$ Rx/\};{3 gvpf?vnʏrDP&a}"'sT:S{Ћ cһ'&F5&+O+ū[wO)̹'Z2AZGCB,XE5nq#x$YFGFLXÆ)EV"L'ρsv(II 2b@}TiFK>pz+PIrH"S5JyV'گqΥ[$ڬȒܿv+6kR$Vo%OJ¬TXlgСE[LG3Uk$,:Jؕ΋GVfz"QoGȳWI>s/.xkj)&H)!2Mth B_J0BW]( @:80pB +B 3dCgj"Y`J?Rʅ^(&H h3JH҄+)`<4H! 0t:aYbt$eH&W԰~DG e)"^$RRbg"agoqH4=ds8E*GJF)y:TӢC,nHHUȄhߌ&%EvAJ˄(4!$ejy܆(!Ȅ#s8!@)4!-Tn"F!" !RCP Õ]DޘGUMx]©ͨ(1pRBڢ G@!)'z `75}*r} `( J/:`An?hNguJ&}#O(4ALx| '`K,;}NY4&Ϙ*`%+ADU@`@o@%hMxT* Ylv&qW Q"ELm&a CH}w( @YN":&XI,d%]q d|֬cW?~p 9 rh@$R,7 bD&d9j$D,a qb"QZ /0 *MR1i` ?$ü.Ydaf IZ)_;WV":R) 3*|kyX26CU&eCRGBLA;p<=GGxkbF Z-퐱ɽRwhEd0rH#,IMH.%aD)4#VdpL# fVmFnFl)ʄBR[N "N*bUt#AH{%P`&q 0JၣbcEB 1$kI7E"F#Z kRYRJ0(@G/POKE|c'V X( @"e:m$];̋pudkV<5`mW @NND _"lDSl)0`6HMCȔ\- S `$.(^6 /*9@@qbjj2x`$6bT0i@pd3v]eZNSavyy{cAώIuRTĖm$#@ GpP *bx/HUȈR@M\ vYe4B ӐJJReAI o:3M,o xk!cMls iA`x 8S.4)$<12K8C J7^@@Y L<$jFà .xJA~MlVK""dmi+SŢфbTy-9*p( 2:J CH^- @`Qv[ȶTۡT"HUqEdD'Q0ÊWT.!? 11]{0~Y<)s籊U ɱax@ b LS5- Mb`v|rK΁` ,`æz0e!C "@AHthJ (V5$QbXr:R)kUp|MQYHW1EV!(8"]]ǂhe5 x qL!?Q xn h`.01v±M%n; p:S܎%9#udJ d_#CI#so4R]0Y|xЌI xӡ-Ij::0@ 67 B-tjA05K^Mc| ,A$ ?.;ZVlQ;' yL`I'Le-# j6^5Inl$ ML AF"b6^D/WF$>U,Ƅ4i~g _ ZuPE0`sHIE;I`ZΒ#XChB$#CSOH&a` &Ha)-#̃ ovVݟ `zmFԟN@o=@ "ajrazLC@ Bx!pE`@aZaqZdF}W)Dd &?aA<@MLA? Alb'%r" !t $H?&AK ̙ DʩJĴD#12 -/Al?A|؍ `: C 8L1:¿@c| %H=)/.$ ]]y*,( O1%8TՍ BHr=?$B@HNA$V#9% TATrA@A P*?@PH|?pT`B9$&|@KM?M$|@P@$BTH렆EDT\&p?eA @A4$B%}? 娂̏30*XW8?4hfA Ǭ,Q EifT "=Rj S.YAv)g= %ŦmB6AITr 1u [VrTQEAdڕw^446a++CHt_?B5+ lڟP\ jiM@&DۀMBpĸSDHWJV kAI,AlA  -lA + TWL&Ļw}‘|mi]DѾIA\cBxH&0ŒDCN |\\0G\*XĂI4@@n$.@EҐtelPPJĢ兊u.Eᒔ.n\]89dHЦZ,IɂrFk6z䤵V,Zz񮅡iMa/т>Egi"" +@txT joCxRAD†I Z"ۦܬVlĽOp^ѻ?H. עD K KBH0n תuN VBn@@4G?L?@nRHy (VAB5*TxFj\tAT긆Z`UmY3ũ2@2`ֽҍV-|wAA q!BsD" Axk\AABwձMocQ]&&G\t@%Huҫ ib?蓿 * lhl&: 2FU$[8Gy[,PD` B1!N^VD:A:KMDGO3;dEC@sYP@I!]t}A0DDp 8/_hGCd _RiqrtEK`0l oJoك0ZtPBMNSyd/i0El"uR+uR/C/mX@W5/lUW(X/X C=@QH=4A7`ĵ.5Ax1K_6RE uT@wAS`CvdKd_c?B6A^"v%JB4v,/A&"䁧2]HԺ,?"$H84Hݒ!AAARL?`BtMvj5b!H<] T\l |B _P7"%KkbA ڴB+v} $d:>\Gc:MXvoW], 4?<[]P]jVV?ٞ~,A_4?|mdAPHH@/lSPC%MA̸A%x,^atٕboՖ30D@8u8H:C9] ?B`"/!x,M۶B.Lh UkA %A i? (XZ Ї@>4U/ Ժ*OH\vjJ̄PoKcP#B D#IDh(IGDT0ꦉG I${5 ɦ(Tm (NKeMFD S,h < %(A(#\JRrH&MrԦAK) D r3 )|F姠xo3 @Voݰ,7ߓoTnL[CGa8ٗA,@X : G޹Ɛ;8A$ҁiC=*9#$RckP}BTxpz"6ɰn] |,$CAj@ %+L| ?$A?G^ $1ê3;C:<1| A8g~3?]h)D@?,ՠ~TG&XDf?(–*:ݰ?@6jo%$nD&MA?1&Hp¿46L1͇-rHCŏ !/H(Gt aˆ3 $qDB I$_M7D -""~o#sD,H/T|I!`^VAE3Oj"hJoBiaH@OKE&|8,6G9E $ȲmDxZD5bJD-Ł[:LФM¯W.UHQ!@9~ 鐹w" ,)v^}[y$B$^B@ 6(4#/ +p(=˻O8 L R<` 2zL,*> D1 |oE"Nz.ѱDJx<4H(G(OM8ό$3 ZNLl2)τn'ғ J:舰&K+ELSKߚ@iT+J!Vt%K2,fm)#.` Y(.!!':nծpTدapE ?kCx1"~J!Η#~h?k"= N[` Ji7%(0dr9h%d_.%wpw2&0s8!;,I=^f"܆.nI5E> nhiyw^!HVYcZXrzjqBh ,`ɢ! !A >- CFx  (Jރx~'w+7P< LoZBБ{ܡN(`qp?^Ew:sGdr!03C`M=|TS|$5N (#@G?9ҟcBHPKIb- P1glg.>]! yW\AXD#шCHÄʼn"qbC*"YE/~q$J$ް ͌>B;,XG;1\3qJ# YHCTd!1;!Z@|%I)x)RBpG"IHp"A Tl Ax0$ [À+%3 0@lH#kF%*"F;*Ә t@tL'%?c9JDJAH)T!/@b@n hT$([b:p8 USpt`~$ٓ@ J Lj*2S q\wh<%He gloI{tXnI#qG2ע>CQH^:=f=j3&=Bn(ED,&R(cDw8cFj cḶwBp<`F$,f!RL JzN0ߊp 2Hd ȬEp>(=8C\-D>!<"$b!b"RG/<FH!"6s,*D" Z9#'#PPaBΦNj%-)%$!V#!W:!,! QBO@ 7H}`R joML!+Q 4qN8d!B"ل &&!]>'nON  .!!bc..6nx!0RNTnD 8v!\ b.!fnlx>,#Pz>=D&4إ( !a&+cz%7&fċ `jendR,_ ujbzrd Dv:+ ~hOM&;2M('1oKF1UwG!H,Br!1,يo(.3"TsDv>j"|,,)kDLyeEj~~Efb~2A3i9?$2{(.$,hiP1thĕbDS@j 3 @#TB#&'Bχ# 2ʨBBGDKD>P DcTFgFkFo@xdV!$T@Th &oT /X B.h BFh V" !J2X?Ŵ!?͝r+ !XCp ba:Ԋ 0!Za;!M>>FBMc-@!@QB v9tZP:^I>LP\JU!@tM?CĀ@EHT`!6a{`M !!0 4!N9ClN3fܼc'1U F5!@H_"Y7|#,%7!Dơ A !@!MD((A ~j!` (*a{ A<^jAAe,BdIEse^?n"̅Fvv:`*&Q!49A (V! !@!$6kk Bk Ė l Z!@.@蕠n!nʀ E40\\!+@*@j0p$Tȶ~P03cL:RL$ 0c4P7D"^Hv,cL(@YD1:BBNN\Pwo3B]$(+ lB `1fƎ4 x$t@"p,Q1+Msl&GJLf>WBZD|/P2K<9j9HH_@3v%M=K,zUi ̂q u,j[+ٰ:_BudJ3,!0zPz Z< V| "% `a:!(1$AN!#ca"@p )!Hà40`a!qCZ(:w"jEWXH T.)/6aTn !$d 4&'3Uo:#':FA;LC<;OP>:@:H;"2:BSD@2BhEhX ;Bb;21I&,|I;N # A[z!}:!AM`fԋ}!ټ!94!cR42 !$21n-B(-bk ]>.=4׎l狝%?v8Md,kÅ턩%9`PV4ʍ%Nu-"VMuWfŮ_!fEcK?&A3u5◾uKQAQ!#%"L#0')RT`0!)R uHa)UY5!LcH"0HJ (KIUVЉ E@E 0pu"tHŋ3jȱcEJ1 G_ Z}(pȅPiC0a`+<9QM%p@C0[&FQ:ՁYb {^3'i1 {[= k`Y=z}R8S$ǚ7moQ n\u/?q2+Tǰ Ɯ|`kXBb'w MR,$ dy %afف{?]qU\R(EMdG))4"* 6QaTshDǛEr EaE4BX5Sa]^E@CI#Qcص$1d$@ #E UD0vYTR;Uʕ\ZT0ǐAG>p6E BMkRh@$48Б14EVD*?5 de&I&ǐ>"Pv"s"T@@pd@9z4YPE%YDv`{MAm%O>.E-nG=Ij}-QꎻQwO/]sY9v2F0~R ԓ` eQ|GKWQEOfH{!@,EOBr$@ixP?CDQ xN[8@RVEb2Ut%SWR1 pIW&l4Ҋ%:LA }x ( {G`<4\ Y(~ DEڰ #R `h:"+!(G/G "zx򱁄HTGR* D# CU֨ \H4_QED+$ZgH XޣM'$M:щ$K=*9Z$}$,kYÊU\.G3:hsĤsJ2:0w,W+Zi #;kհgMW|tE˵Mb{<(!}@_)FTT+I)bIJGSGr4+B/2">"\SlE؈F'"*%KQc(byL Ǣg  1Mԥiܣ'DTHUTx9~2!mQ)PS@zũ[PA yo1CRQSig&.'0:!S`l@џd&6003R[`.KhcU\8x \}\y/\IibZ ّ{!"97ёqђ/028iY4p_K!aS[eP>z9VX6a4Ͷa~83% .Ռʄ^c]m6_xwHF`I6^I0r5Bu!20+#i`# /aA2*xjtz1HeGkJ@Y&/mK c*-u Ҥ1!rg>feB-rF Q%xDź[iv{&ȻiX;+in՛ڻ۽GF3vrqSmh!97+42֬AFF) G;f? <q<^  C ڰ,#\|&q$ pG  5  >@ Mɀ# B!K "p  hPGDRw#@iAdRMWh0,~tp")v !)c ꊆ~u/YaFm1EԚw1G4oL5x_5v 5o7\ @ Vx < 0'ˇ` ``@Xx p}PPP/p0#aFPZP~X  /#qh2R WSm}1,4 H!/1Q ܌5go{ aqv胜Wq㉋!b(%RdR*6(HE2%i%=}8"61$т%?Px*>2On8n)2"`{/@I %" +"!"`! $+"KQ","BQ"')(GB1cN4Ƶ&}b ш0%;D=bbE;/t< ԰1Tiea,% (b[LQ.x*-&J&{}dq0\="AзvV2 a!JИ6 ]l2:dee&C r/)vxwU#*J1z.E &U{,3"1_B#- Nɿ09P2  @rF@|U>% P LFp @E |е lKu= /b 4a)ztiH6$ [(/Q>INuC4~N>Em+q!>_!:*$Pq I$  7pT+mҦEq:>Y(DLPel_O1#$dn>ɠ@.qr>OVt4#MC3w/Zv+?q_e4!pG  Da0 LΑ{n A50Q_ ya`BU( qU zb A”[Bx|)WcqGPh06`%LDS_/J@;XL:TND##MIX:QW^}4/FzRTUK;0P0L4fΝ=}1mBAT ܻ̆mS05p!.#:b&dB7,H={;(;C &C40w bJ8^<:p (xMH->0>،BS6Jh=.$D1Eł ccgEqNThG|H:p% qG$[ G;1G+\J ԲK+ߊxE0/D3M5dM7t p Σ3PA%PC1j@tE:F Ђ *SO?5TQG%TSOE5UU$b+ eV$9<:#60Ki` _AAGh[ 4P/9@BI!IK#؂ 渳Ӝ/R0d%jqB!C'0~Y  ?(Ȣ E^΀O ֠%H*7bc?KBcgV! ztakCE 2>/\UU="ZnUт,(1bUW:J#$R2 ZmZL>h :JL;P q&d0Ɲ(h= JA>J:TFq5M N wL"fLx6 AjT@LUSV"%!W!2T')kG4sb-nWKz2(To j7D&l=Њa]AfW 4\4NAӑw2y \g3@p$aRFz3t:G t"tC)O$ %0Hm2I7 @:mK+ s(Gޡ*ȅ );}6d -@/xR(AGKZ;ZQRMd(uI,;gFm?N<~v.q4kP?t9 AI>(5<]_p8 dtY71)n!>:'\86,^)MZ~yD0 i90i+E0 h@ĥ1UHPɈ)T/6ح3("pCH@t{ŗ| :H75?218("C'8'(LLX0~))24°-6YiЇp`hq, (PPG&h@ةwk9zĚp49AG>H`ꑠÿQЌ x.3#8ZK XRЌ̼ ڭ iX@-hȂ J2 q*I⬑S0ٱGY!BC2Qk5ˍ#J"&#@h3L `A"x"8Mx;"UģL368`S:"20>ҖY: D+L8 $zdjGd2=  )m=|BXڄIi @`;K BGY9B&n2" ͢ ȒCҡx2됢Ȃ*QQLG³1KؽX9(>( QԘ3IK $#]&O%m HO+a"K"2K0A"۫!aZ]iY{+'!'a<%$P)S*\iTX 8s ! CYpSB_; X3m9ٵYk`^5E؂Pق͋nSA ̻5K60xڄġC0[0aۅ؂[[u=5ۼKjܭ'p*R10;gc_ȜߝEifvl8ighhFH`h i:Iƌ Y1ʲތVV Zܾ@8EM4D%FآFBL0pDa}D@@ CU:,/hm@CӮeSMHȂZPZЍ,He /Q0`ЦmXTG RIM8'.h5 U82XVI>`B}=WI؅,)Vg`^w q8l &h\0P˔ð&@@5"V2$# 4U%K x7|BcIx Д. y9͌8ϮK぀c3o 'ߑjyO ǂd# zp Qxm)'`ip^މeG]^ D"9,kwUB"pl`y%24 e$9MX[8`i).Aؙ˘I2|BbQNOm~ =gH *+}Q={Ȉe{Ԫq yl;idT8Ȃ;ߊH΋Ta.w00sÑ ~A`BԒ,0zO)o/ApDW"&JǠE<08QÒ&iBД ;VI Dtx`ؑ̿:8qD  "WY9#Oձ E:FBvZGI,l0Ĉ1=\FL%hDbq5 :P?E5ivޒ;A ӷ`&M/nإ&V0[tG mB=!F T ÚS6:io7UaC5IKaAx}k0x{)@nsY$}#Aibn}Cs/smlIbC7DS8~ []QTW\\̇d ~0gZgJD*&qKq7FgLI%IS.t6(uљ!wa@T*vzvfZM|ZdP\,GSY|ں{襪T3 hbvݮy0#s@Ƥ*A7ek^Mz#%,ikyt`nK-+'+6?A$ChiC\MW [h=(0!\xu D*QzJL4FIt?:_llƏ4IS[Ȋ\XbD2jG?"ERoH I3&s9CSJ9?Yk};4IfŐԉ <28dܳ8?5(=Q``N=<4IQH];DʊnSCԼ? ݂@T -P)-VBIPP uqvؔzïugL.Kz$c{G;$MTrsPvD`;W,4YТ-py\'H/q9 e.$:f9Cҁ %08`$%!pL"#xfDHS!LI@XlD e&f&ԩÒ%PE@Q))vcTXL҈>QG ЊÎ@vAMJxJ03~0#, S03 s0C,&>1S.~1c,Ӹ61s>1,!F>2%3N~2,)SV2-s^2,1f>3 0̇@;,9ӹ0`YϮ-AshЎBJz5TGGәޭ?mFL+4*UaլvZ-66u%}jT :oaŮy h_:׋6{_d6lԷц]J8 ^v-D8՝7N08opbw!zӻ&&24bn/ qP`muiwD 9-/loR{-&k|yXqLwNW`RK]t1$b8Y+0ŽT\Ӂ.bb׺d]F!%{ MbuNӶj/:ɮ${/߻x]"x<#/y+|G>ZLڛw#K:a~$Ma`;0m݃Cn"Ddx+[-Oz.1G_U7ݪ˺Ju>zLUn>~@5Cfֶ{uokU&. 6K6]BV-9 `u>t~ i b z `a ! ,t|4 Ç#JH„ x(8` CIɓ(S\ɲ˗0cʜI͛8sѣƃ@ JQ8vɴӧPJJզ=^:ׯ`ÊKY\Ӫ]VٷpʝKӶxv˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M#zCRĿ0_ x@?)\DG.|A "h/ OAGT{~c "yс}=  h!QIE2"G- |0%&"@, ,@GD4QW qȃ"/0`?"*0?c0}fA~gq%]x RW~?Tx䂛H˄$DQ"%?qJ qgUG/@('G 4<'䝮!I*yʽjJB#!z!} `HGmtʟ 'HUPҪ+ 55$p 5.5 p5/(p4F%)4`(F%4)| ? ǣvA4G)noTOIA/C Y'\_Ϣ\'@ K ' 0p R1y eI^2B.$+HH >I QJ(L,xckaA't1MBl ? O@VM\K hG|#@gd-(#s %zfT}XBbd+$JH59o$,4#$H 0(@ G?';be SMd0E k$f A(fAѩo1 3Yݨơ&"VҾI0%kP̾JS vR66:M1yQܠ vN˴*&R)EP"Aؖ($pc- 2dM"B@/̯M&4d9nl:c74rbQ/) Gf-8](2wIQ9bb _>p,m0$E74v="aeXprdA4yWIR*l@ Â@A \e9< KT<1ARcC&MvaҜ87%-۩Bڮ}SDܻ U/x1g9ϺqjC@|WoElXzyGkf$  H !BP0(n#1/*F!2AfN@ )N a V,4apvGJRȄ?Zf CFF[) eL_XF43C#  )w6uQ֡>qA:hC|S[=P;cQCz[6fZ+C;%C9t`:9IgFn7:@R>t: 9Am"p, 9l9r"C>R;`A7@i#r@P#TAdٱ*+F*CI)> TEu& /1  1 df#q%T^cq_6V";z㖖,IaU!b\-Q1t9[;iQxsG%a8z9tt$QB2jD\`=\\Jnr#7XtgY`TE bqP&B:x!l *UxE0w8+#mbXXPHjNjZ"a `d(pea#+/6Vw$љ@1n ʹ^y-!mz0y`;d+ĩ$Uъ~)e6AGoB"ߩ 5$q 'q Eq zLXV b)LH+rl$trc! l*`[~ʱ='BxkQ&qA*D#A YVзCG5,e"Qpy#DA#tplaga!D>7q4i5ZI$!T; _#*B Żgx 7)"2;e= Pf[t=Y\3;8ѿtz;ak lUِ :ap9L`&P}ڇ&>IAryg+~A ~#2|Ga`!R'Rz'ywXR"1z $0IV3BAԒ`b;l&!~ w(tE'a!  W$*\2C)rG0 pƘU2HP ơ"' '`x3`(KгY !b 3.pMpx{t)Y`@B0zv !b Y&Ci `KSU366c)!4Mzf̒P/p|5"dH7(جdkFhwP )(71q*-KX7ޑ7,ړ:B/, E\cq㷑:g=;p=&yJ̓D] Q)]0Aͣ^kӕe$><f7K<&[,!yI1n&ƻɮ+1;{bAdܶ6l2Pq:l᭓L ` fJ"qՇˑ0w5̏xGHP⁛mdp z' cIiK8NY)iNPN83B$* P %-C (am{Kdw Y6;Pl`I P1M^0EXN\u!O0Pp[@>֩a#!#y!35QzQQQl :7>|U-r.$PXuW,kEdXz%IUO"Q,!!$O<2iQRF+1Bpcx*8suƯcBOЋ QOЂ4,A7>Г⏌1x9Y-\#De<"J:tl]ť@;*oTEG{ׅ VI ZQ*S59} ,}YNͨeّq9ce 4- Cڲ7F;ENGGuKd>3vXBv N_ {! i %Jf%[UXM%Šu<%U1}9 ]`w mu 2 1&N Mtj^c0q,u6&D mSɋ%A;sw']eLI#Ni$TX(V2 , q!6fsG~r,:g+/ OE=ʆ"'"?4XP>@PB"(B$A 7R4ha!E !(j ر`G K(4ZHe$C 5sܙTRM>!D\5V]~Ae `!RS*uAՈZś-ۥd,ԭ:F`Z?Y}^@+= :W#u/Vs ZgjgƝ[7D]WiuKuA8Sy2>W¶q^w^<݋"0z :lСCD( E 0@$@D-DCRHMD*(& SOL v E"A B4'( A C'3鬏KL .ҐĩJ2,Ɍ@$IaLb00ȉ 苂lj- -!"tǃEբ%h"0R!C C!> ZĢiW)t'-ĝY+飜y$QH(eq DK j,p@[&CdDO4`K "ȏ@o ߤL,1ɠ>6@qO:+LK/^@-ڸR/0x0Ȉ[:r0xC &E!!֎; ),(̝  F!Z t> Rlc]y;IA7x kD'2TGJT 8^E'$* qCg EW FTY"kPiL}'|g R{Ue=dhq Jzv&~'Oct+$ J4x 4² ߂PL# n|:!G"V )+jGԊi>-F- pUP< `*&#ND@"(iP>-" P |HXGlX%`UDqxUR#HP2H*rd;(?e\#:NRBEj BG "x *0p!, SKu #^bCp)󝭘+'ZD$I?6`D FTbI"P)J UŗAII`T9+ (AK D#̝@Gli7#ٕ E0Fi) J &S DAڋ@RAʒ;Ri> T <$JAį.kIb[&F"FFK%&e% hB*8xبЋ- @GBd@FDY- !2@)7Y,;Eq~)d YԢfP> 67ZD%Ƶ;) ͐T|ɤ*k3Hc,6"ea Nf(8Y ? U-ta>^ L3+ 7.AK^ oz YPVHС1ݯ~ ֊/B!*Evj!؉E4,1d*VH Kމ%H+d:N1 M(Aj 3`D+:Ru(N,z0)"MXn1iMѠcT-U: [>VUn 0XB+qg%9֪ugNZK _xR8 QT^kR+3(?~! b :߹J`KJ闢I"DO6>*$F 2 &%f}T{ Hb|@&W zLl~o< e9p~#25QqZ*^ǩ̝)HP&#~dҬ (2ӳH%42+\ҿAˈP@_ÈAФH;u`AH' LT% 9B90AB&4xK H<` "QKB-t7TpZ:, x58 c3@%EG lC>TEdFtD)6$&AH#y O\ E8E"+TD&  ?A %qDQBp\ؒ.g@Bpc,eTÐ)@Xr؎`aPbHa#/?)"EZwpBv9s$6\'qsLYtrJ Y_3AH^lX+H,Vd‰%Mb ɂ@,BPPxQ )a8؝k(^*qsQ!" 4§94)"xxePʬ$)۩ Y o D(7PI`))AKT=WA@)6F蘌`ƂXkkc,tZ!۪V1$=茳ɿ%0m*gl(@l2(j?ÕD3_  !CBy):CРI Z (#` '̳0:J!# j#;=ʉP ض H  *QN0<[%5鹢괊3§?#EѠQ * #-X!zyĝ?=ĉH`dQ BX5X@(Wh;9P$8 B#Xj(. C겨m؂_.aPX(ط700@Ɉ+@Œ-X{5|BNJGA ~t,OO(lxh̋X2 s}5 e/p]%ӵ= MثQ D?г۬2x/Z !"3Xd]2ڜ Nʚi;Qz3* m@< x ()c:c[(Ե$LMۨ4դ@ ) 47W(ׄ8lSK5V6X$ Q/~<U-_xk_u_. Ɛ_6[5@@IvDj9U"/xނX(VErt7WHSYW8( XnMAO(aqL-Oyp%v#CԂD;;&;XKAx\Uͩ$xU)_`ݪ 0Va+^f7'&cxftVhҪ{ރ>> ̃2@ H?؀rdr{Y&S QN ? S è eQk & f;3{K `fL܀:D $M&p /u =I^L? q49Q?d{i%'2P_R=O@,r6 {6 @gĨ/}ۭ~rFh)AT@c>!BU'@ $ E^ &|V" ^и(i6脆ipB"aw ! B 0xyiV p^N̘g=M&o!X:LIu나::ak`kcfNp4Z?mvdžȖd RlEX m^ETt1'pE " OLMhicCmH0wrDhM'_`H,T)BY"8 bo~G&D-m@ 3e Gm^:EH,DA,PC~{͂抄  6i@87p0|R LA`LYΐvۙ$!(LEp臥;M\"hO&QUЀk1;3lj0&70ukH9V^%󜄨0MMլ*A(qC 袧9υH+݉ĦA ?B%֔M`"#"4Pԋ &[BXFJx< "Z63鴍 R17QՋx*$ nQ#u3t"@ ?wGaJ 2P&OOc +@Qd7\n􎗶< MnR=mi˽axOIj*%;W0(ESS)2vXbU- ب9UDTHZ̳~.agI݊Tu9^ApՎj*.|83U𽒺$iP(hkpԌԝNLaK_:87LtO8sD2w=N WYR}A Ѱmh5 5<Xbr~h/_(!@FrmPo-ڦ@2 n5, ɯbk7sDx},'mߕKtA?=h  !H'M h"gҬi&Μ:w`f?y-j(ϠC!>OfNfA4,XĴ(sh`!ek*ş"(3kdoBϫ͚+MԙBk4jy"N'XiЈiBY[tZ)‡/n4cp?3!@E$D6cو3i'yj!_4ym|߽Z|+}BwmABбDR&8`sڐ@4 tt VD#DuHI\!b5gh5޸#=^Ԇ O 3 Bdo$Jt?5@T ]dLBL?:cZ4OE9\?Pi}p?^.sDxBt-CxhT R(DP#iAh𙤟L1ӨE׸&+Hʔ4y AY) +pF&> ;{pXPAizOPepLBdBpSN$E;QEdBETTd^-LA mS eEq`DNTP^dg8KsSA$q܂vf9 KZL4ZW,?x,=3qeZeR&\hK6jgWFL1ݕ5 46HS_e^u6mml}B53AJ3mQ=cj p>D~\s-<=w̳3YV9饛~zN UCEԴ9CQIP)fhnTďͳ zp^c딉=kPIgVE5f"$-ˆ?vC5aWPQ}v< cp$/&L@E Ȍ6 \&2HTpDg!d28+)h"'`dhPF1jTG$arLd"7Rl;)Fb0ni3& "(H~ZSQ0B$f3& -y;x(TA<$ !DXD aDw4h;LNРC!`FE,9q"i($p$@Nd |rF6و-B/n`kR7MDEfB@@R1P&F`P@*ڃ Ayaw"G p u؇Dq;KG{y? ~8i}Bc?F %": A,"Ho(T.bLyTJAуHxK^,VsߴLݶfv)'L&D$s (D<`<& /ơ$@jJmhԒs)vE3rI0Ģ  Cewj>9Mn "v",adb@LD*&,aeD^R&[RYg3 bO,:8 Bsd%4S\\4dS1TC` 3p0-x6g]|{j_~L.BClԬor78Z\FEVVa=pDME\BDYvIhEG!dCQCEDB ? J@D4G#C$vD[^D~ ڇDvLHL ^09]ରE$Y8DB婝YH^DAYDMq:R$D(0]A:Dh?4@}DDtADIIÚڬMD MPNڝyÞ?}ە DD(tEPEx%T|UuL ?hB&; EDLĒL$XDX!TLA=Ō0?|E@l@ |ƙF ^@l،"B@ |[Ky l@E(Z ?H4D6Dl° xE GBK`8"Qcl?d4G I|ΌPL8AT̈́t=~aOgG qո -[lQW WB%XaC:HEXX> #T gGLC1N#M;dC:TEDEaƦlfč:\$tGn/Y2̃S(p}X{qBDܓ}x<\#o>&xx_yE|Ol'ɨc ߈mXmEP]dOt?,'&.(6hN$25LNhPd?DԜMV?t72AD#Y?,DLA8-C?H!&,zhA%T0,:AMJ }xZA0Aђ.LDZՏ(AA 9R!y$gh`EP@(сn,YD(vOREpQAyfQ^SwZ'$SI䙅jxХ.]] LTeE5(MK T^LB.x [ /Dp53hvMtݴ2PMx{$j=n~b&E8Dј;++ d"Y ٔANOaGQMDa%2]6j\b8] nTD yTLAI# y#D`RlB`O{͉eqjiEh,\wH(>F@VY\@D>E`т||]EqsA{Tv`{@Dզ]oO(K"KPKPz-`\C|O]yz8L-xA|-{A4|AAlr{F^5jF8-Sh {rE5-(DT@$!Nv, +M-?CD:9EUHxZj;D2iLt LHD6u#>l|[hAۇʈ?>v}j%7M MndgtWF0aEHExELbbU@ !B<A C!{ADD ||H; Gz_7eR9 \|PH6%ZhB?/(b!ы@4"+ԈJ(R"mٖ#5v=؁b˝n7 |0M!շ %"#=tiӧQ7%hX=ZBԷKFTg&ҫ8O㧵ڝ=N6D}^M.P~)PsN׿߇ J! #O3O:,:Y*Ha154a0";9= $qafn JqF a&X#7\Ȏ8"B7)R/-"J%#S:,),J'KLS$D7QM/1H gҨ F8/UK5a zH㩈b|0D( A i˝i(>考b|aƍ"˃~1hn# 0/A C(rba|@Z:_ `4Z= %MYpcuYf)S 1&$9Q9d#Tkŭd{60m5[OKcٛ^F<=OoFj5` 5:*&u..Ss.K,m;#z/<$;k35\5kdw4tHgE$[AA`vR=0/P԰0DRg7 RBU A:r$iIh?,4O#-MK_GL.j[tH)!:d!T W( kl!çL t {`d@|AH kc!o#IT`>&`KB @ <-kR \iu'˺`R]UvPD]oN>IX$gJb4"BHiMBIMq]5Ԝ4"xJOz 7ZDr?j^Њڴѧ>mԊ=+N€nGAGtC!APě Haڵvӡ|ƕRNs'hHTyNІgdkы7K@^S>xR4rӪ=Ve5Yњ'M`wF Iؠd!K>P+&h*"RN KA^`?ؐ;С ЁdCc,YA0c5[G1xI p,ڼ c5E*rr f@Z/C "DhAtP)@4PnVHnD"AӅu2N0 9F HA\ 22dzfU Z p96xmiPVav*tk5"nD)h0= O xÚ00QԅG@{T ANED۴&̚žNK;"G_nA񝄫7DbFF/G xa)oaZjM @oXQ^iG=Jї{H0XK[A0qM⃜Af]."||D/0聐z c/<9a.>ߤNeDa|2jAF|í"ZDvg眡3 KvmXK+%P`VcP eC ο%d]{ Pa]$0a'څxtÄ'nB}cR0g|`cVcXg fvqhavbV `'Fx(l`Rcf#VV]vd"+x44`G!&G܎sbKgctpPc:)vf1r% 9N(F|篮0vwB#F#@#b9+Q~/cCجfͣvb0c& Gd֯LC #v"yt"VxP' /A a{"\" #@:0"Y(&4[Jvbd ؎jxf` GÀqMGTH'2 Jc*!V&/_d' L8!^CJV $K.oDtO'9O~/"b f2,/KfB'\A"b|i"I42""Pqʲ1,*y):d l9Pl6 8@ZJȉ;!c &B/4QOBPq$#6@Z"%* oҋ>,pjjQTEv 3-3i5]x!&j'j`vQxçZ5s8zad`:Ajn[ij.8;vEԨ bkXv3f;s>>>e'%v ,4t@d& 6! "AGPz *YPt¹vb4 `?a|b!R a/}`N:Mn @$&H t "b ɱ c@NT@H(6A^^q"`@1Z9"`~ 1 PcO&8/ @` b0"m'. `T3 D&R d ABd`V-EE@/  T$0 " xA!nd "!AP/"u$T^>ҢTa ȡ`|B@nE`( @^h09 NL Z"0Y t`>1&@$, $iG}"N[eey+"$ <ᱲzJ"X:`jnON`!&l"2>*`0>RB( R@P zL#2^iq/W}z%")&"2B9%K$&rjY捪x*d!b0n6J+]FVbt#*@pГY(.2/npMyR^'-nt0Q4` `C-6vE.*@ =mM+B"D9P&>ct4St{w|wm#b$xI cp'=5L`=/B`XjJnWmc|5v3%v"jW u++◬`LĂA =!M\vCr2hW (!1`@hF@ 0@TZCj\)H Bo[\45B\/nT dD RC؃RȍXغf1UbI֒JB eͦvBSM|#n/M\ASz i&8yK 2kd&Р&h/^e' a 2RR:%4!HG!pԄuN_"$ { b'ޥe$Q&e&`n/"p' Oy0-^w[n1VVgD O`X/E _ Jb@c8kQsdǣQ>.pvS8p4Գre'DSWqf ⥋w{)4-R '13R.pvP1<$g< {wU C31%/DSnWsbT30fo Ā[ Ǖ%0:&p9 \ AD: /a7GÊN9qA&_&|` %f6Y/šsaGcf w9j 1~:o&Qh1ex-A*2"(GpU'`Xb!uX6"D~wAG@5"!4CʵwhJ!CVC;8eX:b&),VaGt&&9]aP]㓮 Czs41Ӷ*{CT~O=I^+a$ӧ=?0Va/jS'p !5PcJHԡBϘD'HQ[?=؅ v!(G9caB:Y}=R\oc d}ޠSQz} Cf-Lz9ғЋWw%2|g#!%<$:<=~ ^'_. :)D4b*˸·CB'&0t'6&(˲+ =D8p'H+~,"T [k5xG%;HyDN#B.#\ $#D;Xb" Cs &^a &^o-fU&` 9p }NwE .wؾEIQzK!D/DpsA&!Kv20`4@`W rC:(!f$w@b"vV"@s8Er$NJl'SU4=UZu"?yʋɬ艺A^a@BJB:@ a!!6A P{10U$ j R/;8KCRx)/-Iѹc?nXܴ:l7cPE "SRA8E' "DI0iRp@F'>|$y&i)&T1R|F.N {5s),- (57@]\tu{zGWDt->Ӂ,C AG qcRFL*),`WG6 wUβڐpGAX;yvŭ5jѴK`2 OҗY9"P?(W"GlKDyH^`b|A@C* ]z4\IG}$,pE1 $GJeax!PUCOҁE?$YXm$b&#){n]D" lV8%%0. 2  ']Kl#%Mq$:Ny2pOךCI;ɋIs0QalvLFRk†#= 1t9$A^0Phk*JrD"KJ2OR$ axbE%;61O'<@q0_W!($G@3Ȕ/9g!pI18SV#a<nPkԍp)@H5":Rq#JAѫ,) I`=< ON>,58f{KͨGx{/A/Xcop#s?H0ab$xE a1'kDut"m6A*FA$xt/GaNr_4^jkԞBT\a;d[P ??O}N/S7ӁT.Ei3 90$`KwrS e"^a'!a@q'eR"@#qE 'pl"f %'h&-f/D!vq@Q!-h!@gZFD_%Doa@e!e!be_T' J8sQGX;0űm};%&fT1$W#ofB_1rKx8Vd?H#|S^h8(O}[#k$q?j Հ` b!,` <ˢ HEXO'B{vt #S3 ) 0Gaq_bv"b`/R W7/8+討.3r,XN@0"At'yw 'F$FGgw*b0vtĒ)r,p`% kD `z?Aq`GCi!1KR$1bc07/17`94 4)u79228G3$ qѐ s~8 SQ4J$}y"@4!IWP07 !>)5'1z]6H=P!=;]q$LyT10"p4I`7 qT] Y F}EFY4&ќmE@ar$ݕ]CANAy$K;eTDf!Af_4A2C4B B>WM3ՂACKXBl$[ih=4CBACUeBZDC$%*?I#V70%Z^vHYllaH$!;J)9G2!fyhi+kySqNQ7Ytbs$ѥzPքHp`q5R&]n>`z J|z劰ґA saJSnP,T8"_ (T(PH-0.OaQDyX뵐Pu)3 v ЉYn{ o}^ RK_ z˷x_xK!Gq] . 0.ob1(Bb84kù#EKw+o Jz#D A" s&B=O+O%eW u2 +2.Ѡqq_lhGs t#29 Zl/mQ0P OraHQ?Q m7jmspaV pD@7= KL*FCy@h‹ 4o1{! $!,4C>'G0D0NѾ:k;+@hW,vs\.{G+v~@O!5,ۋi{]q[xTwQXP- `-p9 ` QȥSYqx|0`~Lȉt@ǾRH&dPZd04]< W9B{$> /qzGz,|>  n*30n0tL )3cKzwpwՊPY ]xm-R`BQ(DuE7Bg&}1u"V'}=GQ$$͆/ena`Q$1QU9Hၥ@ R[T޴Ok\D{dGAUQrGt0TFKI!iڀ)aD\!\QYm)iQ$1[YN?Ha[a$+V^&@bPXE? YbKxF:`=0/`J1r tb`N3D)2.oAuG6+#,`3  0 |P O1 v/1Q[>PY.t0G>$Wl!4/_ 25M`y"4k0@ )p " Ƣ E?477 &!QbXs0{_}+;.30Fvv@#A.pK^S>k|2  GrD1CY9ڱ dD/= ^\%#X ݥ52|ЂŴ0D@pСD'H4Cz4FAc1_takaDKP;BeD\$zuAAB՛q_1L5$tCqy$G§EBY~ Qp™#?B71䃦Q"ӣL:i'ۥK#$15*LŘV c Iznu.`+}έ,Iac+$$1*: }_0Ȁ;M/ /Lb'~ףS׷NA*;2 炗,ҫFz6 s{("ˇIr+脖5V%'XXa;&Kr<ɦV-u  ? lP`a G,X)B SBШ1%7bWBRr`BA%ZQI.ejTJ3$LBĄRmZYV Uжb JWۻFJ̈P$:IpRk&6ڲn۠xwr_̙5o f/s&]cŒ +rM [mڹuRFOə>7RTV^uٵoEj)d ?"yϧ_ 3o޾rp@ 4@zfg8c)?1*d0.(EX0 ȏ#9+JUC`$!AZ$Ѓ$p Dѐ.Q@ਉ𠥍"I XzK /v@cM&&`tC- 0$4ԒFl3*P&h XRH dF|G@A'娘sQ\TW@ 0x202(;!kAIF3(9zw)EQDP (a(78Rx!me(ÕG AW>h^a19(2rl([fqp#s IN?QGk2'H$`ae["f6z[E ђBnx07Jܒj 8"Gn $\ZR`'؜Ю]$B],6 M*X8 > *%*hO & 8'w!%79@B`v:@% E- #2;~0N*d|qĔG?WDf(D PP"Gjcr-'Շe% 7Ql2Ù+j9'5 iar X*<'5':F$(k"SJ"4 ADlZ*r8RģJi(X8"&LzHFځ )) NSO1z#DA^aVĀI0I #r$Hl!2\4(Ã<$Ƴݑp@`A $ YWqeqPjHC☶Lf#SHH8-#UEy/Zu jgݥTUK\^72Ó\> @y@ؚ*7C#G5{*cGw< @d}.S6IT%Hꐡ U$cJ3Y<# {ej$Hf,ž(M8Zĸb:9rP\C$/G :%DH.K\ dh+Emc[d=3Z&YK*YXC7d=@YY@U 800ph'; BQJP8.RMIo H[EhŎ Œp қD  ȂZ@@Ƃ8)`D0 D i̚Ђ6P2$ؙ 8z cpPuʱYs|阼 RӡNy $$y x/H0ȍУ0` k9ɘx Y!) Ѐ y >O"l恝ib *# D+ð :͠ /J891b\P PH ph y`QLz"i@cIQӍH#𧻛  `c$PEX@T ;MGMJ $" I~* @N(N "}Qc@(]]RFbRDїE$CC[LOq"X'05Fqp8@`+ ,ǝH*“pQ(F@cFЛOKpG M`cr-6 pkG{ xxRP!ѩ),ߺ162+7-  7SIS1)r唔D;L\@*^DAHܼ:XTXGS$ 4tєJM8b6 Q݀ ջ4PNX,ii h0F³:*Vb@Jz :: ` 0xe ;0;~s  K캯 30: @WFҀwӄ=" ًc0 W} L RCe۪.pIJԒPӔ,21#Zۋ:ĘS9 5N1 5f- BF YTH+` c $Mհ 4ZM4d @d " ЄVHgѳa)iEO$\D|NeN EOn:R ;Xʨ !6]H05X%Qޫ)q<0P=:Ί$ N'wjĀIQVp !%5H"Uv%ݖ&I6{peRP//x(PN:98C:[zc[ 0ɸ;EXg ?s#u8c ˠ3΂?z:׌@Q.hw1Ѱ9`lЊ .X;ۚtE= { Sc:m ƽytK8Yˀ4>3|6|>95-=G.$GN=nR0 cc4|x˳Nsn Ppnu]e<c LUYH2e>)JXzQsJfnfof=b1 `)>G+L 0Us*+ ??(7 :c;`e :؄d|A-I+A@ؓ ??4 /J(HVA(ZO`5@6FfvG6x"~0`>H@ `>@R@eAvy)fqnBq/\hEqE`b=\;̵W 0DDԀ R|P^JtSxF/@^[dMP͠=ģ ĥ(saM$E5pD8X_JqiΛ;^ gTђ=xVH,QQExOX`u9gڛ/-#r,EPF}G^|z6udu~ǹ;L@ LՉ"ס ԡ1ȡH<2I`(Ȇ!УמpbTy$W~ ɩۣ µ9)q!ء.qJ;Vɤp;|]{Q i˩`K"i ʰq 6摊 韨AqLtXMG׷ #.$3#T4ID؋c P_ lҬ^MڸcR/S7u, AJgT KM'wK s;JQaRP>dG34'mA=(1WsOM'=Ha-,B3؝PjNF:,I81TZ_xxT( _s_,*G\k`gU8pe*R+1;R)*4RZ9Yӫ ث + ( &- A1xWHE8Y ,M֠>e1m2`ƈB + 4ϯ_HU'`퍍@&(6z{8Y$تKD*I(e,Vߛד!Yدc,/3r70Rᡋ:ʸN9ؘ=s\ӯ +X; xo "Lp!BBBĉ$Aap$ɒdxq $)ŗ/Jqm44˂?wLt)Ӧ _0b!݉ȪuΑ.BdT46lmqNQb< M+ 6B E"(:l:0ְ4qI Ho t5k>\ ֶMy 1M+vA*)F{hNP󶷿w`ȕWuvT'TBl;'4~ B)?/eI qiAaz8UPBZtl%MP 5Cm< }h&?IBD32C"Zx Lj+}8 aO_dDa}9* 4? <2 N}bB?P ЁvR?!4`*2 d*j@2D cBXBz a· pфA0H .4IfX:iD:Bhs?kgI1:B*vQ鸢wk #C ` Q@Ȱ PABZaA{0)|-l^X A@8 z1Q/(܅$#ՠ?b !5#B*&2;$%LA*jB%z# -S"z$B,YHĈ i (J+Ez&U~r$1Xg\:|a0ftA?|$rB>ȁQ!IVAP8'TO#PA9B -  wYN,Dխ XIA X%`_z >qhdO[Jdp*$?@=@u'QU sY'j}U Jh@ [  f EP j%ylBt1 ${P:p[d<=2Z<@{DP)TRKKM6Y97z9M"  )޻6XFЦN@Mr 0Po8 t)oⅬx 0DDҫG"& B0XMJ|LC5#A&B32T( $QG@=@EA!-*!$"k%2*8i!K@@I@J啙6uX279VH*DY 8*!LB숿@0TR;G$`g,N !X.f}Y">k)d_M⁀1!j 2d3أ]xg BA{x;T3U M$}SL*kesrh8(Kp@.PZx=p [ H 0 !MXl&P hCp4y܃tD"H4C&zw}nnÂA!$bHŅ;\X!=HÏHMACcK(J&03hf?pka^v=" xHr" BR6mW@v$ xo5M(xF$w N(" MzҐ $v aN=r?3#:Vx 8f<Ѡrr+>Y1:&vaݖĤ#Gz mT\_¿brUmPDOȋD EG|R 6H%(@"T?FBB@]E^EF  %^ FB|\OHDA\L\Z c`!AB" X}MM p̎GBH:@`6(gD, &hG#,Y*E. TL B !8H\4BYX!%shak@-%--@"B,bB4"BvTaPZQ?l%6E%DYK9b9B5؉8[)@{POmHH5 ?T?9zɺ=drUJ̣F0 J@ `XJe]BߟhWaXG)PmEAAޞEK0dYDdJ@X)@#1Fɶ(D Ğ8%б?MƯ V: 9LX_ WnYnbᒤ݆]e\;[`faͼB5TL;:@/(59<&SdhfiHX.f͗À:pGҐ[B}iogp" ĊՄYKFA<@ @ guZubȐev2BdÀX*45<@A$H?@(lV F"@xcPBCHT?4Aɋ$F]&Aب(Ϗ_ v%yVB-|lP̈ Y  +}@8W(AA 5U 0@,U CQHE#C~K%H"h" қDDΑHt l-־0yAi?bASif0Zd >v@ɐh@CA-HVB2t(, iB H? @h:@HtXCLD|())z PHfhSDL-5fg)&Ҍ0Ei忦OE'.9vlXcsҿ؀"hf UJ/ i%RM%pX?6YE pM^HV]0' EjE{`&꿄`(A?5pH$ dt(BUJ`"\ $?aE8Pܿ 4R.$@I*0K߂z2(pv[- ZM Ȃ00h#]∪0x+V-G 9G%ɱIkLl,R ,E.:4 Hx rʢ(@# G6: x H,Bpꁗ^bTTJȬ j  SR8-' 6ͩ ӬHP R!41'F`+"x=V :R)!dmaֻAȭ(сHɢmG@@~ +9uRbp VkHP- ``\B9!@F+XR06j :HN `P.1a9!H5}=v-BR:h@냡 :׾bz !X+|)H@56x +znJ)*o^iVC3*;B@*Ʌl_-oѬ!2@$E"P7,*49T.0'Ǻ́; &]uM ha "~ ;8%nv Ҥ?K_o$)I!JܫwnU 3I'8Y @: av;nq!‹Z=BбS9J gE+1>osIL"*DC8d|d J:8h5 #B!0όHC /D]G*pu8(HWX H84!@xhmA8ʾ D",$CD-:0!6*oV4s" ΰ$Eq̜`Hʽl'sI:+ V qG0RFV \?O DFkDߤ0,!NqcUth@Q2Vv4 $@KA^*6ꕨ^-I3RJ^iDEM ay$' OQVGQũ&NeHYaDV5[@U$Tyq kp( 6Fwu-p^ BƜ4Y#lK Gɽm(cِQZ0't 27!(8R6 #*_IRwD|S"IhG6UԗCmd U8S$@کEIh%WFPb!dBӜ(~5nD0Gdx$D 4ց$('U' e& 7"xg!5E P/ jZ4"YGfO^dvEQ*GdT>-Q 6gBd6%!  R  aH}%d"T֑+rEhxR"`M"fD*bbfOjHXfP* @]e2C5$OmIMf$-"._n@ptMj@nV㫄c-(,l Dbm^#R6,3ld"|6$3W+R1k0-Gb^A&\!9VQ L"Cw 3'v63B$x}̨ T@8i9F"F00 pb"tA@.I#>6,`k Q˸SIq ԴrStT#+, !;457T"4A^>3zb a PGGYUGFzR2R6bQ޲U[Gՠ23asp4Zjh2JhpQQp^!Tj2 d0rW56&bMŊ\5K(8+_;^#\D["*W*[/"N_^ReU,nC$VV F+h6OGJh@h(BTq, 4BL "̔+0a4 &&hBtnB/0RgJ,.J5G X悺Y\S9Zsw;pZB@ j@`$.`Q<; B+I!) C0ls 5;MD"}\^x!;b.9{.@j, bTB1 .Mrb8@x}k+ԏt jBiAA} jRM[1D#Gd>Tx߃K!D rvTLCluᇅ]Bgalb?i hdxpEDcbFٍ ZEGH t$$p%HzՁ_]~e!^= VҁPL>,!ng7&Jpm~Ps tCaXUSL`VVu T`%,@VցZrQ[t*Шl`Ej+G)-FDp*DDESDZQf M[f@ ! J8fBf a 1H K L LˈR$+ R$` 7/403 @/( o m{mM{Bב֔5aDvD? F?+.f4513t7- L H0˟_=?_s23pǝS *fAwqPT@1>,gD"`rc\a܊B! FlkN!D;&i{eXzD?йX`vp[QQa?gahlyF!|Gf A u @Siz@^"P Ӎǹ:oya iED3"BEe I*àʖ8!4|`6T@I)GC2JG0eD,Cu$h9?``g B*"U6HLXfLjɍHFO0" s@!`b@RA\)E $Hԑ) ;@ )8 DLD23$*E ڑ@d16JV"Vd,8 +h (HSBM$O,ِ4J)2"ܬ5FHBPL7Є`XDN H@fX²,h `@:KE!.0P"&<2T?jp ;ဋjh@"^biR*"X1Aiv jM #" :!NJ?0.dl/Q 5TOj̋^m DH@ԕ8`D! B88_j*L4@ǜ Ҍht@vp`{Bx"RaF d-("a]2qXAN_*Sh"IzR1cndE@O(=DPAe8ukŒ@4m f@ /QR@A C"?Χ@?B D@KMHSz ^^ vT@X`P\8 {:S Oѵ-3=@ <5BX #"9TM)qF@n9S1]s4S 14 Բ#ԃpyyg߉Q3bà0^bNzgtM6c.$ Hr  Db _jZ>Oy[6"կ2,XíI-]dDPC!_u0e+U=9ؙ(f$P#Q1Ӣ#RE#F/X-`ilV >_;bOL)s~m(@4O&__UF-zM!-F0`lCAe‡o "zeRėybÒ@G aQazE@=!+ !YP Ė'(='g0dLO*Ԁ,@!7! Ѥ 4O  [qo]3q  [yC7.:Q+!` cao 3!p9X'#:\ʅ"3.{@:.9$)}8{DlGp4cXCrr9X)D!DEGCaŧ؋wF؋rB=$tF|x } b(tyD7: t1VEEzzzfC߱iZzؚںnDSOr(PA!t`Q4vX n*@ @{@:@ ttA4ll5Q&@S6@ spP|Oȉ {.1{ N1Eup\ `ѱ Q:5'*C4l`r!S!DuH vxckJ$QpL*TP%tDQH SJU]+1 dYᘘ9 R*V, [M rNpF`V"` ".cUj1VX™s/Q@Zj +_Z] baoP q[; y0E\$rq,P[ˠ@" ! )}~Q2`bPz @`.3_0ϻ`@XC 9A&\y tV0/03NP^ ^ @ҹa\1\v\7%c09e)\2mfq!cAg @" rf3q(zBOFdhdȍaWLvj+r&@&<Q) gƒh"k1u\"@bKQE$9APƜVi!f"zSv>AHYȏm2ȣئQ96k}҈NJ =^;DLld܃EkQ?z2Ozo8!+!yzChW0ؕ9qcWXdy/>*?˩39_>NdC6f\E{Akr6]J:my+Qa3mcWquqi%']zjrA +cu*smc20`yq 5  @.0 Oؾ2$7[{]!3qݠr:Xq/ `05$ v'ط`q n{߽{ i4&S)` ᇢ``" ,pa$WI\OO"ڀڛy? Q 4Mj<D .8UT(qVAhX)(1HBW(X?9m^% "$1"aMIaq+P1m=BHFBSgW18T=j C xiq+lqiXƫq<gK2Ҵ12$rz%1BAvv!1֢*ޖ!1aߟq!"$.+P(Q$>q%;OtUMR~9nA"SLOW{W>v\'#$n9w5!N+@H0##Vf}lom3B)Lׅљ%"zR_M/2Q%ٔMyٛm+=4'4-@oU:P/M-93=r}}%UND#w{=Y;3$6?..| y v1׃SѦp֨ݔfA89:ҳ$S!j vc?0=QsH3q@(@4 =Xi= A$h@:TPѰÿ ~HX!nj>VxjPʃ"męSN=yShΠC>Qaѥ%j4)U*}T՝UjQkؠ\dj3R@~ՙV7YЩ W`… FX1_j-hĆ/XYfΝ=I+hҥM6Vu#!an޽}\rA`39_Ý?]tWǞ][HA!K20ͦ"eC6Jܠ " Р@""C oA"o!D "G + A(2@I Jq:8D2&%3\ @2(+HmbgIAMyBVBX X\-&T @Rjȋ\CH #  S؀S#ss G [ɕ0hhȀ# sBI!6j2"ƂK*9NE $* M&"vSR]wQύ1ߐԀJx|7zi0эTPeGΧ"{ A%HlT7*h$CD %ܝǗ#<,)L! `! ?п.}EB>@xL̢$w})0A@.Z6,`}G)4o" +  %BqW"I9pFjoZ$8F#& %1+ʞT #$R;̥$4!G9=+CQa^n̦5$g\lvĵ"]Ldܲ,A]ZSFhp+ M4͂ ?ɠy*d 9%7a] hA a q0:Vq+$@P\C1N!^#"?TqBi5hST%GKcd 8&k #)j 9CJxւtB{˛A&=+vk "&i-cVB*X&|Z$lqE#[ jf0ޒB ViAL"LHW`hOiW @\ ĐڂTV p gY!-m r0!-1Hh΂!H QL N`," :L ^2l(U0Q#ba #9]0xBP phB@`A0I5Hq뢭 LlZ*c{D ipgPm;'Ѓ4Ը W*6قM#F݌UC-*!TS :Z(> i9E{Z2)d,!e)W(T<yb>X✐Q.)Ʋj$(v. */y[ [>ϩ!w =7FR^Cvw0E.(wj$5 s7d҆@洴-jm䆰`FAKdz`K܎d8}9A69# j?$\7Pa"NdHm1pz'`a9[#59 SHŧpkJ6(gKfr Փ^ZׅՊYFd4NYł rܒk;xvu4D oğ (Dus]%Y(B߮}@@I,VrpcHɀi )͉=g4L# (2=G/8;TNސuF UW3"@Dʳ;{_2n&. 5xA V4:MCnҒ4KlbЫ Av3XI)WQ{H%D 7,RGr h4Y@@\+XB0XJ@j#rV"[z$\ [h 5( #^ x5X'H Xt a{BIRj ?i>BC)оYB5 Z0CRP;\ðB? ÝHC3:sr=hd?1?a(35)`8&4!K8{ʍ4=Df "`rT1"09PE0@؉#2p\82hi0بҜMD r~)(E(1Wx "q΂8Ή2,[N\ѕ̨EA >+X4wObExuxPgxxn gR7vhoI%!2e_8I(`I0SpԫD,!D6*d ?HP-K9e"8H99ѹ$,:XM@xM5XȊ$pNE&^N@I^?MtC:SQH hH_<%➉!8uߙ!Yq ~J ' J!@"F!  $*(Ԑ+>"b0 i J 77*>8T!i@*b^t5'3 6e3l}@+ֹM+ ӥ; J1ԂpȊ h@5 ?B[[SxpB$$볌x/ 0(APјÔU%wѥWeّ0 S    @KЋ KHXx? &$ FpPnΦA[ +yxф'%)%+,S='q&W=(;p[ &@L\,E҉Hpipd)żDRLǠOuRk g8(xg0BT̪DtHtP ݪJp'ꪆh؛SPtQ`9c ÉX*-=RػM @\NaJ;@xa{Q]SNlX赩l!3q ?3b}UCq.lCy2HȬZ!nsnV2CP# Kd 8$ ;%L.txd=(?!b dJ@9oR˂`m<3H<@ `Km1Ș8p-ss2⌼Al 4dR"* ۞,PAr!P Y5*c@M>\;=+0F8C=N[ #9 I[6#HR. [δbjE5YP2 8ȐclVc9 j!#93Cqe5Bv4 U P+mVᵂ@-V=ͷJn8nӂh`40V9k.NNdCi8aSʢd=\3bZ8HX#LhJb7'xdMJ124'7.8. ݌D^U}>^靮HDh+x,AZg^ĚP%+8撧@[X҆XކCY;pNU5E,T\[W@`,xU ] \mUkȘIȅ] ڵ]م_HEѡlShހ^wHmEAhq_]0:v@@pO p?}AnsU{$H i^F6P}טAqω(A58V#VZ(`hvY r#`bk ^l^@WQJd<8|m4wf_< AMIe131J bMW 97G 5fjHI׉WD_JNtVER7ISWUgV"I ZYaYr\sx,a/v_< vKLd]H:p '.X1(˂P#qaq9 /0y{OHq|'` Y' VF:؄8HĔ^Q)PK ˟[C@yP."Ά@-Ƞ:)TGz L3PQ(>Ƃ\$kgp`Y_PjWa~z׌WMȖ Ѹ MqEݼF:+Չop5<" D`gfl>ਊE &QN"{! p!#AVPȐC;I[(RR|CUQ-)שQw % aJuvpPөG|PIQ) A"^$1К2\"ƌ7r#H ? Eɰ=@@PC Jt`+ w PZ'[h@%+Pɒ鿅P B*BM(*-d{" 5B$D[([v'"v$IB֚P2ϺvbF([v%TNdFӹhwfFOUTUT˖riE]ad`#WH * B {nOZ`1ۅjA mE*QI!"pQ} U2TTVҧb]vVBVTTpSF(8$FPz`|^WދD2W!E8%e.H;mD?/dĊm%0&CظtWbG ^QFUByѠ^&^ a E|#¦Fo"ר!#̤œ2? @ )|,,1vD놞ơnPq H2Mj V4DaY @  NG2iBwPbGϧ($,+B#TZ01Kݡ"t@CiJ#>LMx !M^C uQb|Q*4?cxMPQϡT/EG5wQԊPd?A њ$Nъ cFSYaLX `;Hŀ0W4֧֬ LZH\Tb="ݲS(@]/ȻZSf## 6iA@X4UV%#zA([,)#HB2؅tz޳2DVC;t%c@XB %J!8Ж!{ SZ"}M%]Hn)=@il7RL  R%bx t0۰`+gw -!̤:Gp]zֲS9n ! caGh % LGCKXČxC"5a` ҉صF>&,1sA +:=Y5!ol,)SV<;#ITH~ì5:f"l=v3=y::^+x Pp abQlJB:(R!fFh4e 1$D0CA/NyHBцJAa@1dCu0(" F0΅ b(kDҁA6hƕtyz=N(nm $S Ayp ~E`ߍiBE@'h^ !H3mB=3D֎0 )#"]1rDC}ֈ`X"i?p | )@/:P J Xq tԂ5BqDCpV!L[a?0Uˆ@~QSeAo`DvRa@v`Hŭ 68L j! *.0 @ Yj@@te(PW{3dXR^pI !>#Q=WPgJ"Pu +Jbx'0u" `9rT@'%!PDJX2iSrÚl(EYD(IKFTSZD@\~vW ʠMoTEX~Ć @b4Gp@@_x@lF]hp^z `D[Fck<ul  _uKMܐv` UV|Z@F[VXjGI9 K4Yvh]PXp_j!`JqsHEl Y8T֢t dyD!FlF$]V\ԗd{`CHfMQXExA"GFhFFP@g1bF }" fimudqDaͰ "CJ7, 1H\\> 6 (>vBK@8aE(8W\ĄD3 GGI1ġN$1C ԊL6b$\_ݙNp@˞\|Th`KK CtTQKJ ]I TAChA'H?1hU JYQe4<92A`hEfh쁓?$CL_Ia99`D2% #C $]VTCO$;GpCME@*IM(5 GF/tOy(x N И ,?$ ́pvMbR}dlN(RNhM ?$Uר l!%C80f-OĘ9IGHF@M 4FLAaFf.D|C%["xOWO-ʁ001VjPQyQGs0ҐPVumyH iEL T@O|`IvݐbgEBXl6hUQYȔU_G`u i]"ٌQlLx(R'F fejRK @%L;eHy$8! V\ljBMBf4؈ebfrdސSFFtOӄS7_~]b  {W 5itRM0Q!\Lfnwϖ;]*z0FW*|vU.J222C 43F# L55EM)821M^D :Bڔvb tF =?;t_?QjL) ›UFU%OjBfU1DGbGlI>X^eJY%H&62Fk^F:Y)@2 5^SJSZDT29 #^%VW,G,E-eV~A!z[*EP@J2"h"{E~yV@R]PlqhMPmvȃmЀVHfXVSLpZH8F譅6Z|1}VDͺ:P&H9,-x/~ďB2/e#Vlo@NFoF|EУ=FAYFj+C|/FlI7?TBV_COŁP(jܐ5h_DmFgF `E f06yWL M!no EO Dg >@N,Eys]HHhs,n0x#ik0C7LFdmǡ&vb&0ю,nDgD\Ѓ8_~C秙t exT/G(Ib-q]kȋ-/ΪNE?o GIp䂉/t;C<-TE8DlNL9Z0>> םyp C(,ER)^R CiВCa]LIU$'dA-?A VI' vVKMM?hFt GFKi;w̐? CLH1D5\9c,R^DUB)W>0 D#{>jb%C(M%FHF]YXc&CW]jnAݰQ hMh$ {ػJiNh܀vaq&nfe?,EDJf:BmF,& ?Ď#^锍hD;O,,IF *y*VC4تmg'V&Ր$ɀ>{P(~W虎p`P'}$i' jhi)K=KJs8wk}ZMy)u։=QB(( J֪;FF\b xw:m O?\fR|,UWMy\gE??jOx G4,r@?V`b<04xc}(z _A jaD 0a fęjtIF?:hQA_<\ 1 s`sP)(9j {lv?M!$NX z0 S[dvqcFI;؎/Y"Ӡ=/#izJC &CmCݶ'%MB p'hbMZDFqQK3IYz3Tj 1lʰQNLp(t灋L@A*p BX" <7Ӎ8ZK"*hB (9 J KP60qG ۨ D2Ndh-( !5$P Ţ@`L@c( {%@p|F* *hßLF t +gCsH|Q ϬRzKOA}#,O4EUSrPYi]P֟:(X\UZ=@u>W%zbViZYxI#k5e\f%$Zȟ6y{t7vy6 ZJnj s5h:@9-RAYdi! xc: 0^+iqY~A L9~2C Ή -!tcBF*R")"HX&"A` )&  -ε\!`+0Y<6:>0H i`l{8mvZ^h% VF‚6Y(*I jHdj#˜c!3QɌ1c!aj5` hhl$X N4cbԎg!52>A|EpWTa1$Xc2 F0hX @p-`Lh!QS^ MoB 29/,Kx6BDP X'UNӀ!6?P! |XD 4e#"cG&K%Rr@$Qi3(C%atjH{>$?iv=_ L&Iz؆D0 $ 82y>&L `(Ӄ!|r\) VLhIjTCcDRe3(+q>E0Pn)lnIn &`Ucm}Czhh U"֟H!mȃ`B[+ZjpuZV~/hjQA~ H!jWSLT`r,`8 $&xfMg@.Y|Mr;l'MdbWSjx#-S3$B=Qc^dKMny5yH}]e?< Jr_C(?$%~ 5P_D'2/|]!z~R] _枰.k~ ~d X%Tckno6"%V4!$A6p  ܈Ѝ!|s. jnfVŻ>6$x"~ހ8b7C,L5As a"羍Jbb-n-.C"Bɾ. ـ' {\o,& ` : t<EF$!o%"6bA&x!!n &4RlUĄ#8bn4.`C"nC fŠA˴L8V ⨖|Q.@ c ; N", pB"( 7 |K,GKҰΖb x)RCmPHվQģTp-)v-=|-= C" ?)ٔ "6c4Z ,Rp$eX (N~%CP"`*"~'O@MD7dDD9J$jzTe ˫OHZ#N~'$)Ee,%A:Kf, d&+B fbd-@ H$I Dp@"T"D`BXd!%rRQ*BB/a/*$e: 1,pTh2"SzO9*4S3(J3-We3bd5ӄ"[AX37us7?%[x[~E\3<8y#`! !ňb_v9;'3C7i.6 a`]:9!"k@@3?>KeDsb*Ff\eOKff?tAAA!4B%tB)B-B14C5tC9C=CA4DEtDIDMDQ4EUtEYE]Ea4FetFiFmFq4GutGyG}G4HtHHH4ItIII4JtJJJ4KtKKK4LtLɴLL4MtMٴMM4NtNNN4OtOOO5PuP P P5QuQQQ!5R%uR)R-R15S5uS9S=SA5TEuTITMTQ5UUuUYU]Ua5VeuViVmVq5WuuWyW}W5XuXXX5YuYYY5ZuZZZ5[u[[[5\u\ɵ\\5]u]ٵ]]5^u^^^5_u___6`v` ` `6avaaa!6b%vb)b-b16c5vc+Àd6Gq>T sz A z@9 O\ۣx%Oח "PƂR0f/Js^?sxs~5p|}_ 6$8ry"@MYyOVt`VK9EaAO`?:Dk'h\ `E g@`' Dm4(@!TE)x(F $@F!Y40C2OCHCOPOH`d1!dFA \VBR/`"?S @P4(? $J`JA(wX?ZUQej͒dZ@9 i"4L BWivfB=UN3Ѥ:=.E]xXY>A@guDቐ5EtZ/h@'$m'Ld.4A V?[ JS tSw!n) @iT p3M@Snm\Q#Q- wURm@GkK @Z@Id]}@qCPXMu&M  jԇl DB-S\9OhAd.Ւ[p^kG$ݒ@pOoKWEB^ 3y݇wj$79V2D8ض| ]I:SExo?:%ALM)~4J*`A9 A+jILB%6A_#*f X20"3G^@8#FzcfT5B$D" dR5\U=+nUC,Jkmy1`& $ P$7R%= H# p2) "6.1 BYD(=HV0@IQ8P@դ KX ?qh` A3- d + I  BNWpA ]C0QqD%l7ͳB 56\ "6rK |.,2X0+V'IBO)-۽HvIVRZ4;ܾE'HG?/Ԥ'PJ+hVbR rQ&:-Fr T^?:WQ0A`ׂ\.9hrҋj$@PG 5s/ШqPIAVyxUP h )3Or ,H`$1 !$(@y;>؂ 2?%A'EI|$ADVrtmla֖ 5C{%vgzwFG8y;ИGfA^ꭑQ_- ov p. %p hUR^)\ krd0M`2ƒU v.BprETa*3""DG ц2bgp"%*np{q\=w Ɓe WjSPu(+_4](!@-ⲗ_*cd0o̲ cl'!@@vA|ig m_Arݥh) ВNhPM4 ^s*F-RԓSb.~~M jWC[XN#.Lq1A$Hlf;^dxA#<։AKmt Z ܛ6s`. F *zB!Ĕ1 RxV -T̠2(?6'33cx!>MH ɸd(SiOb D$?sz\iDA&{x<ȘQ TkKf=3 |oxaVdA:#ЌN$˫G𠫎[$Z\B`dEKbijGW>$ T j d`'@+n@'8+T((x'0M$ x / uj> 7N61m@OQz|-}aaEuD'Pe% A!{rm56@.@s13b'sbIL/ʳ0(/zx1<.#k(1c{C!CrrAREQ1 @4 b1 r,/ =5/rR `OrK&2/N Hq7>6P"͈*!!5"' FTs$93| i5c(pe&wJwueLH?Yl:F#)=N Q}W`8"7@p?OFU^j> Ɓ)nXv@"HSP@@pbm,߅ N#`'"J "ЖDn DCGzEUT>I\CF Zb!azSyl./g)0"ILAJYHq*i)&><O%B@WH4`90ru#E”O}>NY$l1 01 OOpEREj CTY2ex0PaF!TUY0tSyU2gTkgŸ!;U S3TP3N!027 0Bk{#-u4l`8F *z)$ !Q !] LyW}euCz[0yd*W i02sXAxGhNtZYiqww:ujmQ! pj Ƨꠄ`I"}i#7T:ɓ>cT n%t5`RIE ,eW`v0}/csqmք pm\rCOb֥wEP$% EgO$?viPA,9!lKl`:evfu%1:{tVʆ"sVE*5PfRs&@<2in9B/pjO,;9 GRjeoaf.WQXF-BkvJPW_)jnrCZs gJpp'A+ w"GP[pKr0/:;RP|۷~pbрt!Atpt$b=wcmWh1\" \u7vQh11KPRqqVl_awOtwazzba$1 gz |1uqd 쵽oѤXt0.~p@S"a p!QQ {-P 1!t\BG~ 5{k?1tqr 5r yd5 F# u8qp!~#7vA![RD (|-< f&p @Gy&)' 41   A+!!ƯrM+*"0}) a1A3Cb1 C6.BBQ {RH0%4Ss*/ b~Ba2ЉE0d4 7CcV1y#">cp/t،`Q֠\sy1Na')@8IN<P V }0 RTb,bb-W iWAC 6 ] @I 'bn6۷l(` 1|C8 p= a =pQ˄M`)e Py   RR C#+WP3q7H0 ,71t1@1">МϙWPgq [k3ǒZOqǕLOry`ϜdĤ|M500TX/qK.>`@ d LQ- `m t1Vi5+ àɣZ Y(BL?+eU30P o 5iLR#cX09.}R %j`300.qP=by4^f'7 %ks,EV61ߥU@f(1Q=."<#&!MԠ=A`O;b0f ~CWT{` a>Zl \ \u`˺ف@YYW|UTֱZ f mQ@Y`UQf׬C~ A;A]=j BA)ھ(6)}7V4TǠ2E1fW!A|`ـA9I^E!JThB$,J(do 0*$Z ڃʕ?\V{ti[  /VPpJfHo?C8k0Pi@rmS?`dd9A=aorFqZ/S@&1"i--Ei  7Q-PІ U@ @P?>QD-^ĘQƅ3K6HpB*ErYL[VlA+-|Q`ɒZSRM>U}^ЩP>Niի_'b}i^(.c(z5d:RX… F7H@|1 -fKRBaij8V/ſi^8ou(Y˽}Bȅm\қ !xH0CNct0;G^zݿHDQHqRI&Xbb3H"n ) Jp!@RH/XAdAȲ4 ! qb iFQr!%ٱn\( )3j aSr.@By!I)Q1C'A 'QȁX͉&N$F F"Ur: rs DB+!yF)"3I>gUVKN4IW[ R*+ըF5A7\;GNdG$"IMDxRshexz0xd+>\1oX͍Ab! v FeV7(]ɠI鐣hXI! x_FSRrDSb &7UxI`Th ^I'qGA ?x@ +$* nSx){!]8@* 馑³] *7A: Dȼ*(@G (O*лi|<9y^o Aǽ $W1p?4/)xC< yٌC G")H$:tȊ4cW,'A " @,Hl$ <@~ A> A`n`+ W@yCX D"ȗ@"؀L0OR҂} %"P2P-'bԲ NN A IH|D'baZ,C[W tVD@[!tzI a$NK!UW0H&qF8Q$78B7Mhx9Vւtce}CV ge"X@]<4(SH)V㴭!v@37-nT4!ǁ?N1ө B4|@8j"a/&Dˀ !s`s! GUэtqϜc$*xFn~ўq@ W* P3DA_"" qdihN 萜) >؂T$ _hRk3 sP B}G*+]&B 9̓ :$B u@nЄ+Y/i2_*d A b ,o3|H,t4qd]?,%N  ,f  8Wd{0Os<-qG'99H  % z Bx 03:٫* G*SLx:$  °PZsAk#L ȌB(B uB`7``]X6tCPHKb ;:6|è8 %4Óx+q?dFtGHzy3,䨌%8R ҥH))ّ =b/700,y&TQT9Fa,egtcȝ`(X9*&yEx 3@JI:'h7Cxv )E#911U9@丄txqa>d$\ )H몔;)G"').)/P;|̊}{?A࿕̊*@4?HɵWx0Z)rHȀYx(uXɱ(9u0˜C~ CPC[@MÏ`{J2xCkEA J,ޱ' 2Q@!O 4:aQ \%<%Oj{ 9K!9k"*9Ƞ!"!Yc렀rṣ@1 # ! - %(IN*сЃ@TѣBOQ[+Ɓ)Ws@D<$,$D%=1{*3aJ3>jJ6prV1}0E=%Ȕh(BcB \ %`LN60s*w/x;'S} *4&T \{pLOPUZeoUPʽ~xK~ sĻPث ʿvq@L˭A?2L! kЇ| "ّ˫A=P T袇s -R) Ѕ`4 ْ ժIZ+v_TN)7QX|bTZYm0EJXHƃ@X"C Ҕ2I9:(wǪ qں??R w!+\57t3I5a`<ˊy ELϣD93  $Y 3ꤎPSx0{*B8I{m--M <;Q%E:c -<4>!QS&ۍ3t \=정䥎5] 03Hu\XZ#m ={H)dFIcb=d%afiAQͰVOB4=bpʽ ÄMzW|WW]8U MNT=bˠ= _6؀S<#I_g޽JY:8=Ccpᔥ@> ഥ^V E 0лc~J<*xgֽW[>@@`l4@ \3P3OG>8{eڥA;?մ0xhS |\NA!ꅰ(i pܰAP ~٫ ^(dꋅzˆ 3 /Lʴ~&j! ҸWn( $lJ82a3\޶VH @;d֜$y-FLlh!Rp ! m@G$ΓlZ׆m!Dئma$kSR4HE w@8EQ9|Y9F0Y[Ff؁+:Rn,q䛃枕vsEvɁo&_8ѐȎFb&0X= ʑo0ɯI݁pZ|`|g',pH2PhpL`ϢC eʁS!˅3~x(}dJnCF@L8L@CJH8W8x872spęnc~Ħ، ʙaLap̛ uK_پsJEb#_utFDч8b 4*a$B` } *)X㩏) ((YψqhO/Z ty") aSPIb#xʤ ¢QH9ҊxY3 `?jQ RRx C Ƞѯk ~ΐ ғW igvƉbJ3Twu& .6Ijw'ç*Ћ ;*60?A!WWzu=~=b.wrψs)\.hIЄCȩ'Ei|,pYG>(RU󐗉 XGUwC-k6_8M-[%މY$.ah0bqhxnZLڵ=.m(%цyj-$\^0߄#"@; p` G7vg|h_"(h'Jdnl =Rj?H @iGBb-k,ڪ"@8X]#pH1l?  ف"&*ۼq r۬}!wFFgUm;t;.d!^gl?sjϕcOx+N 髿>xLE?lbP&kÄN@z@D ADXBCM$`&DA~1C E4$҈t`̈FNVC)pѠ0?bl fƭHˑ0  DE|dޡ=G`gK`G ri8!"2ܢeQWwb֞X@Z\*& q2Od(Y)C@̓+`0YHt/D*~+ڦ\lB߹£Te(1WpΌ?O/]C >|ěr2<{I=${HgG$;~L;`$>*qĉSyA$j"fo Uh / b< Q6$0j6^/$Ĭ=Uy;P@)Y Pڊʵ1k hr2 b +C%/U@Ȇ0H" H5>0L#AF$Q$@:6S@Q  "J4D P0+]1X >GA!R@Hp&dh}98 @A"dCmL Hj`A!2Y ppxŁ(@nPI]z l/k`r!k=y:x#Wlݐh7I-A™R6J}C׭M?1q\l3WVm{{GrD /{3[Ԙ10z|tPMMnmh5@ A`! Aj &$]pө'i} D ˅?`@zˡ@6=H} "iRO ΋M[@S|(_Du ^  ޕ X`I8|B"DGhAH!z<91ݚ D 0@Y ; 3$ ؎Z6]50?8Z@@MZXiC8)ړ@xDژxJ  Dʟ@?H[ B 1!JA ) =FA I`mQgWTUUU?0T}n4NG<K-o GXL˿}zYyE9E hp #WO8My Qft@Dbh Y}WQj֨%_fPMY_W&bܐ ;Ԁk 1\p:e ߼;dփ(5,Ӣ Rh*?h*SL|8B;}\IkQ&&ppܐ!3Ճ$@NGr`;I6%>>NUl bGP:D7.g '|Ƨ|^!`h[TVYeR4mVUV_Dcn@Prox|fnhx~(yyQ2]O3%O GM( L ?% nA^(!EE% 0\FAv\FYf U@hي%`beu QUzǀJlcMEjx P'`8yT l. F3*DHGt|AxgGK@G99x. 3I5`@C~AEH^OxMmFoODB rHij?H@Pr\2}h'd&QN&ه@}h]|,]?\vA39n D]@d!@E3`#n;wڑBQތ+(5R&8ptE$T0+8Nd/vpIM5pIpI\٠ 30W?p9f /WU[IAA D df38"0w1"JGDnؚ@Ę?Q 'G%ɜ&&G81Jfo`G? Π*/?(j7z0"P@@l6igN0QHz@dAŌ^A2@kw@}RLl%? A(p;يR0.XCZC1+)2tCnR9x*lMNB+ 8>"1AND8yU3,NxJTJ~F g@x&}eAD[|ZI( B(  *pTBQbug,\`En::ּheo +n{ WȭIH"vaRmmiRT@; Ydݞ*@7 &T8Pă2 , B!th0? DhY!‡!o p@FBvTN&̈u zp4a "$|ڢG+$U_`'A^Y<0T‚5>La]`kʱ/srqÅj"k[$SA}THN8A+[v⣔yX 4Uvaij :4ADL(ÅVB p<e1i ^(z 'a@;|1Ed ;ȍ=@[h$+3Rc "JM!/^`o8Ф ̄x1!WCF 0 M[Ѕ8. * `4"RhbĈDL`ЬbЁP@JXBnh¼P0$+. . `H!3I Y(dM6ɪ0Qn ]5B[Be.>D:@89HX //(:b዇|0-"VmjBWaVjQ@N BPxnIXx)ۂ;C 85i{4U'B j-iٻ򫭈2TRIC '`z*Z{8jR FJޛWJ~] 450;+ۛʃ(I%'?M2 uz0N=U3Y``)"6!bJFgiD!:Z zisj݁ᮎށ= _6 E 4-0= &S&x UЋa.0@O2R`;LB @ Z.R3p#AVt yh FT"h 2ACfE-Q.4x/T# D)s7yO׽?瓀>[5爣 D/Caâ@&cbGAnGAJaPVT@t BΑ&$ˇQ hSĔ:`?UX QT.5u0p{;ȓe5YњVm=&"7ЋACJ _sP- 4PA?A ,\ysވB#2Y#bVBʤ0b r 8:C]@ nM+*b&Jå\t`[S&M1V]pa@!h$"` xBR hf @ !Ea[SJ (KQ.#&$PlPPz^H.bbL4H}Xtpp`6 8L"a+CCk 2V8{h (CM?<:5(xqCD 74Q4 mmLf z~UT`<! .(j`*L!&*"42fQ- P &)t1#) \04NcҀp_gx!гv \ eDŤ#/ ]e !!}:ue&WQt+Q+Q VJ!PN kzl&츩)+-!(L! d HO@>J -%Z0WCWC#@c k\Z $qDۋ51+C)˲ZrZbBMIKoGLJAMĤH@L"lhO 3 *JB >h +a;h/!h Kx t! \L\TaVB8dbjĬ2d&EG\^…Nۢl< ;c" *2ь5D"bܚ"x8-t@J*jBQr8u7BI7C%:wu="چiBG/>cMCh)4#+k(iM2&m1^j2‘s(c,3ᶕ:$`/ ""+A "9nc$D0L!O~kH!,#*#0w Ot+`h $(^!5$HFb.LtIBHO` Er!AHUHT Y桏4AA RO Є$5 j D zN* Z #m_9h# py|׬@BSǯQt+!&#chRXUW"0*ff 4"%GcD .u0;Z2y[8#U V  ZR:Z7|"X 'X54%2 w w $*$ `Zw6(#$ wi^:^]e 65BHX';'&1mIozqp'y5d9cF+*g;::Q!45Ó QpbAs2&U1UM*9X Ę5'csP!Y1r69! Y !PY05#3_oVD>r![YA$ ġ9 aOTQJz ] 1*#ʵ;!x9Aj@C. ֊)s?&þ8+$S%r ^P8dRC8]CE !#*[juiD(~lIJ*KZf2@JAFB(:6&j**rbk8;$@tb<-=M54'>JFI8%"7 Zl6 !P=x N UI7P,#l֡ 6)2[$|;),z+I"x4@JB T4!5С uB Ve5!hN-~Z 5 b/1s;x!}E ܁Cn Tk *^SB4|Zq!-rPYI=jFeQ1J'w5]uJTc Te! $k2j3!f>o!o9i T! M0a|S u߾( x0ҡAFhh+v p0 Pl@'y{E<\HF@l ¡`!/}LS^w0TZ$0aD[^Qs@ 5!Ph>>F!nr BY YtX?LM{"R <P(ADO"Hąu4PpyAO)oLDDH)&{ ( *|P TQBGP-OD@V0+A"=`FJ$BmwG}"v M#Ë@=dH!\IYWz(\:5F<#@9|@=r qe>?FX//ฦDAF%I0{6f%$M?/T}5tA(dOGl:2N?CODA=FIF#DAC9I*h5$L @nsh*8/5W&$ D$oI0DwT!U$*#aF,ңΈ5)Q2 nN|I"&&J$Dp&exbȥ ~|_pBS4?_bAIbƺ. 0"$BESP,) 6k/5DkAZ i[4RCF+ ł*uAt @?>4QP-;(in?Y?dl*i7dE06N#}0D -p1 B@u@%(sHteY]N4T%=Qx9 qt ' RǒNh?aC4!#B 4% bB`&RQ(sn4bӆ`Ʃ*c Vc܈[dpXnD8# IH 7s»M4A'OFF2?$7Jdn{(dZM-B0̒d~ڈLW;^@K.03E RHHSc<6U'U3Ywm j4t RB @h(7Rss [$G!o@&c%KPi'C_O<6 Hf>YO| CETHR :QVJs#9g ,RӍ$=CjA8pT@bBSܯd~zZ(("u<iiJSS(^JF#ur P9L^7T@GKu+@4g# v d:mkϥ14ƍ F#o pmz@Gr-u =֨{T)jww;{2 ߜrUU<ܖ$\Y$Q| u}K4@3Z%` :t0ZD*?Ju7B Ao ^h! 6.2D$BjJ--@D|=l{xt;/&Zm}$GrEo&J JxjX`oT0^qP mZ0!H !R4䔧=7b@! z2XH.H!Jws'hRin$ $Dp"X8.U06H_*kz?6r{tWq(0Iӳ.K{@!bgB6|2PB| KDfV8,CD>Q"EA}%QWHgjAX1YEY%!:T>A=1%d$2PeNrgRluJ:W+u5*4_$E fkp B*Q+.qGB& !NS[F;pN؄r`+G (`sۤ`psHG^qcwC,kC` b!0,$/*T 1wvfV/\GZ 9A/yjx٨Wpx/jO|0 ,,)A*ՐvE^ R2ҰcV+AX9pqlS4sq7Xs E@1 -@5ȋ5$ $DX>#)aSC9t!zQ?x*d#ٛ0I/tQӁC"B_5Z!$!ةg=XAQ@P] ԐP _T6t@)S>p4QU?i oDh"j+` wSf$i=1I`XaE6ERI]vV{{P^Ra`U !P1j)$3qx˂&q ^!NŠ8R+Հ0+u8Ǡ"qgy,@ Ќl`(]褃 Z WZV(0"]Uq.! qeii0_QU8^"bQ@!qVSP5uVU3-rTS嬢."2T+QAq*a4"]U4.H$ycZCPRQZZ@7tX{⪨ +& 5Э-R\1+`)/)&P^ _,.+,kC ɰ5% 1۲SfIJ7=Q1^+DAS*QU {)׀[1B`$_`]ѵ }ڳe/? jO ^f@A>DUa2VoE YRsL2cΖkds;d>)x˹B \JxL#&gV+"):ԡ6$g RBn3I$ 8a08 ;N0NNKFFd+8FͫKgg&AQ/c)K| 64KN8fP&fF[G5 ӠBR^Z4N+5 uhpq4Ƅ@!<@?%H8 5@\ R `L"tФ`W&0[kA'doZ~BtGp [!Rv!vg " A ~P6ɰUQ    $`1G5Ƕb XC~~a\!Pi'hz#VHt9^W&z RyªD5Pq !Ѐő$ydPWN! эU A1 gIq Hsі,R҃;񮣹:3c~Z^ABLһoefPD=@HXveqw O`! ;!rL(aQPSO%BIWuGQxE #zf!HݜDNa WlŁ+6q +.s+((vOo,F0Dd=᧱Kgy  0 0. x5`L$čQyR!ܤ-a|E\m _#Hڬs s˒Ʈ ƠM % )B+ZSeWsB69㽥3)ZI-s@<%9IpmKx/:M J[1O@7 P!  ! mE[+t"^ ]LP^ 46QL_.4sg?!koHwҬ] Wi@3>3S1ŃW+!1 $NzC84 {b.DV! Ζ?X=1<.Y>iT!ZTq(Btt=B!z=JC'B@X"./dу? d/ ?)K[ދ#$T4@-3F?q }BAcĞCGj 04=aI$SI׼3$;P1Yt8@}>@nkVDICN36*$1ٕ=tz A8ڴ$gRW1iBn 0 WMܥ`o HbD21H2iX ?-'?aq˦ƖK, Φɬ ّZV2@qIlC(v u%a^1%n!֯$YŊB0 "Y  `wܡ )9 6d٣&|0!)\m1`u >X:GN `h>@иAEX69 3DLcB& <T(&XP*Ѡ @)&6BPi9FX-3.1Hb4 虂1ȂChDKeJu^Pr^h@n !AN)A 0(H7 'nښIErEH;"#h8^гNӫRJ7ʍ8 8 WFU 5V 'ㄪ҄98UUcA (hAk R6& E!:6㟸 Hw]h*u7 hM/ -0A~ǥIrW/geA 2 j:X!@:Yz $~$.x `M?yhVأ 5~  le#9H"n4`kB8) X惒9g@xh [!G^1;uhm|! RY> ݺd! >EPj V#Z@ȄIB}[I"@4K(WE ؞a)e $UpɬF QA?8`ejG,SPXA`@HR)!X'> Rd#&'!}HW (( ( ` AMhA,!8)** 2D1#zmVQ?D /`~M+1 '"*Pt/ҁ_+KX eԒ . 2`"!4"6rH,3c&$d>H c# ۓ{YEREl!BJ ? 9| >ae? %72I; SA1 E_0;@1:(GzH 7BEX')H )㗿 he!J>@N@ф4I< zc߾l5$L`=tλP VVa{7qx+믯&&qdv'\#B pHdkkc$LU<81ۢdB̳!AvD QH< 4\@ @J S 'A5!+@@5`HA.d+H#Ӟ& C-R  4 $H13F!G q],Lq! f'0 a)aI<0A`j"kmH5)$_q ,%dZl7#3aT8_(+D?LY˰ҕ$, !˥-1Dch 8m?%`ׇ "g*[CzDHcJ 0 %H\vB+@n,T@S37`K <_!p]-]TW`&B@|Q@l1@‚ηKئ|`*V8uC cB&n]u2xmNktSd#GieugL8Lwo( 9J Ģ\3P7%x-7qA6plቁ}UmK+gyh\9em\;y:urќԆ zž0!u88?8 1&HA9 WuiN1&f(H.(U5 M!)7[H bD15 ' }>}8#SEdf*&N)HT9fLK3wBVBԳcF9)h@ b)j.~TY -XK""=8d蔛5HD8+sEJ lxgBAPPScp8A  ö@v3/ح_@R68H*+**<ȗӗBmI,v*KTYVQ9?@Ȣ}|{!1&u \[ 9:LAB vAЙ## Q1 <*h) k9:yègR ̡w7 E,9`FbYw뜸7 iBȀ F I0Gr5FvRƎ4)IR@שNJ5 8@Ȃd Xz3݂K,rF;`W`F>p "c8۾K[uJp$ ɆdYAhIs |0^vj8"K s+wJ؄4TAO2Ȭ $|dBJ>5VI) @!~ sBZ26\s)ܥµ D@Hyr/'P(1Y }Jix&n ̮lҙV#1<Hryʹ>I"AߨH4<@X &8 &<H@ XbJ@@$ (qxqp(CJ S)0(a~s0-wy+:زKf PF* D4s \E!lKƍ+GXit + g\/uh13!my ҫ( XM!ӂ0T/8NTl.@ 4XNT qJsӂWN݄ ,Mb,(K1`( 5)Pu+T I 0*ۨKTP֦a|` "THu13KC-"A; "H2 [50Jp$8!RɆpIV"  TɭIsץ4K\JpA]PZT7` l dL%1B2(3ก218!A8 S㳅G4x6TSz ؁aZ5͈nx‚6U?{R5Z\ AԖz$U۴` Z@YOt3-\QY ;Ҳ -UGͭ  7R<[j5=] N}]SK؝݊S* ,]2 ^e0`4ոi87(B[-_=_= W֜ѻW;pZ PQ'_:-Ю;XNj&4n :.1iJ!!10ĂHE4pF} ȂZ)@iJ՗<@X)b :XE 3<Ă0X,/0%" ~A E8 @D[!4e${7cvh# ?=@6 g:Ɇ ,12`dE=^;#JX% >脏MhsaaƊ jHX $LJ$T„藄A1511B~Yb6 0e1]P72&k{U}83[7>Iya90)8TV82ET2%ӠDjћqT-ЉՓiй::A ajZҩh)q!)DQр%Sł0S)Zb}"PۓclYQ*i(W[)\U# ٟB6E@[Ŵ5u-[/m;4R3 \VLV\ Hm־l XJZDȃ([Zd1չE 0 ,WCq <$]՘J)W;HeZ]I=%v- +zeñd++^HH'#m=k\a @Z]sN a.-Mf1qԧN$ipA?NX-dU$;h9,B1{`+ˌL%U j@Xj<>2§̧p|`(d)lo)~` @AeX`0(( ئ[snZ84ЋG1"@4h7@3F7ϰODbA5`OM-NB]V+BbTVmLhPѺH9-,k7*5㈜7 ҰqՆx0Hjl{ p ]v`06w3שg,,%}:,q#G4(0ŃCDxA`ÿegb@Xbddt)S| uӄUM!kӮ^*A҆bnvȲljp+׶Ea֩qD]#Nx1ƎMPi^Tnu{3 .Ү_Î-{6m5W5|XowE{k8ΟCϞ[t4у"QϣO~=[Dp c*pyCIH!Pq(P ( C D2$ `@A",Q_B5C"&Pn AM 4(PN, 3@ԵQMG y eyAtdOIbLDB#ƌMC-|*Yf=@Ŕ(W4(h:ØKā "uT)yV@13ͩ=$ ňԜdsa@?HkHf? /`O()4@1\`FFC@H $ `BA? $! j%?"uZC~Y$@PE ݝYU(?"!dG ΚmJ& eA`،T(y 4(a!Bjp7Ga24D2))P0eE~lqPq|'Z@^Tm8@?  Hۚr$T* D|G Ԁ209/ |PeTHe@ P?@}܄TzF)PP@ BB0V[d߫/THĿ|On THtHo հ܂;ByR@@l¼ `}"Rpqg+H2xO+HQ$x0*-$M@ qp  Zg*T>@wÄ0D(P!iQCJ2$?6PxHF>@/l@Q`TZJ^ \!-r@H# q4MiR !xr D! $,!hH*I$%)!0#G2# AQ]L-aA,0w3i]X?%pF*:H]GB t$ 2A"67*tEi6ȃ"Iܰ8;!H2M:"i!9H)$XE`>vH&6 j+?7QX܆ZρmGoS(@ z(ZŽ!#ؚc %H?~%l:4不*qJ txJBJ3 |:p@j1!v t8 Dhj Pb "$F!2pLA&%wH*iļ@IE9xCF[)E[ĉ~A6p ,xnH"km?l L8($`}߃n;1P!)ğ|~ a B1a'My+`4-mX`v WK_ݑxT~yG(-=VQA"W+O>Q]vwT *yiJ*EɱBSIdR/gar%l5:/2Bn,?c Nj%gʏ ]I@ITc94g&K2TQ!iNRXj9|ЊtEƩvʜ17SUpի@q׳ثY#Dt=c9@Kƻ$y@Go}.cbH\ ˫XhPņLަI.YbWGSEVћy+aY{&"]_D6G$Tz/|Jkmb!HH ¿|7P#cW9}uǎ,4aܘ|qFƑ%S5* j__tB=U` F#XADX OC ™` OU޼۩ ؜=Dt[Iı`JGVFyd@U@UdGQaatl(ʦ?KFHEh,HBPPaTF% nHK&K>B} C$\A- ݁@qWBdtl 8ILq,@ɕL[@ltyI D"A!@%$n?$ lBR BdP (v%J:T[#8A",Ɛ DN~?:&C ,/d4«0@0 (DHP@xm5[A?\/f (/dCS6@hýH0JC!@$I85dH@h (Ha aH%y|I ,!> lʥD?*DP˸⸁"]"=MYNMB ԇǴ`2c@!H6?8h \Ap J!ڤʬMY WhA&f.![%p7I LC0>Ԁlm,CgTNXB rG2GU@F2N xy d |T@񔇣 R@$̐}^>uHxY eEwFSV|@Ah@߸udSMDP URyCHOTDTQ L\LJa QPTxT XE>h& D=P*@pROx@|}TT@~"u@ Ch =x %QʤFMj8Y.1(x(DDl!ZdA`Y% 2UAQ@XF<YUޗyT$AT`^#-ʎ Qyx=Cޡ $@~K@9}C 8@~$NJlfMj:͐T$|BHCؓVXCBAeA-0jjq s!TFܕBF‰ ZIuGuLq*,Mk 0%$*\ PU%DNdd'l!R_U'$"DfTQ- h@0@@j5ït2M@pQ4uE]PMv!dB ,E%pTj4N>ą@!IT D b&PU@-%NЗPV$fj`Q0b A!r~F $tQ ﴉ@BVPZa-H% l¢ xT BXkhTEV@A#B́%y*Y! M D#h?\@NFAu'ʆgEE$[*EEJd!r,sƒٳu[DD 2]cYLFv[[3!DdA/QN?cFgJ!d1LC3;g*2݄;ݱ$I?+l+\ ľ@T@@d|D4PC 5(D? .R`Ah-BK-\B_J 55%&dҔf*lLJmuޤ )RϬAl@[ /Tdï-[͵l~+3FTX7f@Tql?spx8D@VhBDXazx8s~tcjZ-Ң̢H/2H*C@"@@Bh$oW* "8AoߣI#H"-KD@X |H2Š@5ac͏/bS TA@[c9 ' 8+<"(쫺bAF,d1X0$%g@{yn&φBS.|8 AXБL5(Ja| {o*Byhс#60LfCTae]Ktӵt76`ό\#f%VOfO,B4BDK@X<;LXH㿉R1E@ 1A!@@ - T1B9'(!-XҩAC ~L"UXʌa4A&AS2dn\/v7)@VX)=T@Aj*0%$VU0_4,s kT t6*h:"?5z%sԠ=)0rmİ 1jELč΄KuڷL !܄ҳ)KD(DS͈PqX 8Z5ɄB^b4`HY+^zeb8'"+?L5*ʢB8)YC 塟V h*M 0fz t혩*%?Jork ,WM[C1ݍvEd64aI]H6""$2z48b#Q4~L.w^M$bd}f7hns֪hEO( sZm &R·-XJ^1G ~ 3]j$9Nb/E *u1d"sd C@^smD3JgMpG@C "h  D`y8}(KB-Aq 9H1IcA8t1DzIъZt`F5QRGQ" H!L Ih`{ s345 ѐC6q"FRNrh[t4eUbZj'.Ml,Hb؈` 2O   @(U ( b KJ`DI@(au@2b@J;ahKt.ĞI r孠UBJ,S[b5 DF斻pj127)UJYRpǐ>Xc~/rZ^Д ?MOӗ2d5PQT>Q!foQԥrī R쬬RUZVqRF%)m|[WlJGa'rU'DHC:(aq't!hЀᜇV%!`ke!.8Dlh dA"Y"Pv `'A !J ֍n "p s##bF!Zs/&Pt(IҥA%H$ h[Dȃ;Wjg@!IfX D %SXDHEjX0GZJ' )Jjԕ6ԱaO^K4LL p)VJHB?, Pq"vjr Pt; P 4#c'v@E .HtPDD!uܠWJA$"9?8trAӑ@C"HH1Q05P^W'?I/9јQ7 $3kE P?,3)'ؑQ  o˥ y " AlD"MU"p,"B>8 Ҁ`(*Rwyq*T R A?/?FA0}CKAgQ8G@4]!W+bIadv#neA^ &p8 2ŰD3-}(cZ(`iIlnG"! A Lp0<EA*xtF!&!<pm &9]raH^^9 % "~ !*քY: c < p `GLa#y |x կ#&Mg @jP$t/{7 !8[k-0vJ%"p|f#|u#&p"O^- " M @Rc>uL!OqD`"R@.tg (-禦ߘ.' R:ܦRim j̦a+rϷ2`# @e/Įdfn5 cL j2&B.nxU "c `N-,fO?BpyM vj/l5!.$3EeBRӲo|oD/P8`U"IFVX &#~F`.& HD4FQ#"ZrOKjJ0B:: B^3FP5BAUXIV@ > ڨ5wb7<&*;s /p^A 14 T!/xo(x °ax6v AHa5a 0zm Y+P^CHmDCb9eJݣeD#"Ә[!8bBsŀqB ra_.zlک*ļEFx Be߄N4$ H%5$ V)DgZ&&`$G O旒iBiqK+ VkJ1@D;4, 2K_3 _+/M@bRISLdA[  H\U!+DVmp\ap fq @ IpJrJT|J-|3mszn-hiZRj\־e+0"F&7*i)jD"#O5|bcg“sqަe$=2)2 48hae"&FB.jq0OunXJ&&#"F&ϮgaPVża S[Ztt;h-ݐ1B "~S.H\ b4 IП` .%(6J^\ Zl\3\5딲&% LpL /Ag>9!0G AMQi?39Ŏ3#" E&132|8]J ZZ0B>?$6m u ~   [ 'ձŢH= 8 [H;a!8?XC @$!P TB`U䵶`!<+aߢW%BND 0?<5 rax^ x)^%?縩JKr%T\S"bEYe눝Phl(@>A P&(VFzO!@D L@`? _ vK0GBoR0Т@!M 3cF%I'}aӗC̻7ig4ʁ3;5cϝ$`ҭkνËOKNϾ˟OϿ(hFC$M`8ÄAf!`I dQ 8b%x0Ap% @hx zd$0-GDDS@G pCC5KEu9G_ԩH X,mAě:lhВCbTϕFjU ѡzj JORzPx.:QtztФĮ@.yY-M ʂxS"*PU&Tm,çK?pXpL 5/$p4::?8P)EEىszb `3b Ge ?YtЀpqi? OSC8?*8 K9?ccͯY<"ETsBrgp11p@~p22G/ * B1*M'!Bhr' I$3yF>&h ”* b 5y@Z<:J@oN2W 0mi\&^!8DA XPp %'\S>@ L10 65ȁGz`1ShWA GQFg{ȍDsarLUQՇ4$Ņ ;1@#`\h0D@AEaEHk  (1G` 0!)2ekUw5\S6S;! ,USA|<01U<#\8m" r6 6ݐIE!pJ6cC !6S@YeY`s,ќz=p Sg|kڤ[Q։ 0#s3q Z{6{.c<1wDJrތJqDvC~WLWoxMh̻B 9,qpzTuÄ}Hp [8B0@5< Irɇ"txP4t@`3K"y HEF1YCލ+aJ&s"X@^Ph5DD ˜bv`-iN.t`Է#8P瑦u 8m" vR2?9e1ATK!Pz37ť[PX? ׇp>@x3=2rB(8g(087&ܪBC)BTHР  3NJT,"!4(`Dn|J+ Rr=R@pqj*(*Q LV 1nȠ[ 0HLԄ̂.?ɒ Br%"; D > p:O(PRPrR2(6`*9(&`(`!ԙgnhbNz .-yg s!a6k#k(HZmFSlR8-W͈%/T,DA$ +1([dD@dT8'"Уr.Y譠44\3^&  H""X8 DH Pc ^0$zr7 C40#(cYmMBAdDdfRA&ɠs$Դ" rj" ;J$"n6c!>#kXDe<`DRbh5aM`❂0 IO"%>h74!F8QB^e j_ l=(<r@c $B% ]ع^P2 F3 (`N AjbS'քPT:e@Ub _$A搗/oR !Nqi?ЅX8\7GK'tYn+@(5v@(Y1\0gl?7F#II % H{T.߅4Ѐ H#T\ S4!:v*S§D#8ԠY#I* )=)(]@,QP= >%Ƅ*U9`U X5Z2k (1 @$ jֳ^ЀiV n˔\Ԥ BH亜ѐh6z~+g,BuXD$3n1<(IF+&q,2}u&Or '$}F>X!+*OgFsS5P&Ae$v=i;13 _x"`b Y sa) x@Z!M$<H%;$g-" ,BlR ##z?k`zT?[C^=n+۳ADDзblMFsj^t]pj$FrbkQf YH?u9 *A2(3{GA^lT&[+xx _)]K 03deoG #$PJ (>M:>,BD `EnX+j9y mh5yjXMHH,*<+?#ূh;JNH'|,p<P?pӽcKbmbWȋdiU7 X 00 h q x걎ЎA@h8H H1*=d (ġ@x  ZD( I2H( Љ@4 Ax(N F 0 :bt )hA9F< GXHaˀlt"" !CƈYE[d/ íЌ5(AŽX ؀|jHh)p@э"#3QŠ.Ȓ#Q!ȠT(.@Ȏ|0N(:``8.1nqPf@0*ƒ`l<S0Ѓ"DG1't#?)EP;ȿD[̂ߣ Oْa苂> w`xKZy\ =a:,s??ӫsW@kK"Wk}A/΀?6 8HPxLA)B**ԓD *ep`AJN@8X3d(@Z!%`@aոV"YK!8##Ch+ 6 , L']X89;S0ь&Xœ/ 8kѺI؂@  M(=3 hE-a;~"3dK @> Ȁ١j #JFB#ఠ6!c"hCC) J4"'rk$ (Eh| עkءs-2}Bp=Ũ r| x2)#{VV"K HZO˺m2Ճ!!SB @'$H21"Ⱥ)5rEjC-Ym -8'H8Ji(d8D7% eyӛ9 ;UҸ%2Y+=F1H"P`XzZS V+yնa@ZRaJn[WBځʠJk1!HɤD*JpKJٸtKQK9Y$AȬτ?')BFޅ>vṔ MDMTÝH{؜D,ïPG@o I^ʆWK\CpO T4u=(*X3;/讪܌r0 9ٯ2ܚvdY )TUYA..q:܍Xb/АԐrC)Η j/""  #yW D"4T$8  4 : ȈK,!R)% cE*F; @P @^a-]iE`$4 pd7+21CZ[v20sjOy]\\a&bu Hdhecvgfb&hjk+aF(@8c85\51_`6⑓d#:f{'}7 9:sfY>'LQMđ=78ts  pP"Re#f A5: | " IG(Bye`Vj>R+كdˋc>@(h,e)r悘nkxKۻȔ̋#Lº0 Bx̫8<$X 8V>@LT>蔅ѣ_ a@>)R=W [cL`|=$d=p 87 0ټ輲XshTq5 P-HY  Z6NzȽ xPR WtYlYKÍ$%8R%"$I%P7E3}$piɱCa,h „ *&C1>@G<t8D!@PYB+ca/Y,)ΈeÝ%XtNXe9&D]Ճš]j-h@d}/ŏqw ru.Llܛ(R7EV[R%Ł*ebl.m0F?50I10e! |ApJ7b]l⛰5<}lp >i/)o97?E%F…d aCJ64 tDw@A,q+@]mDAVPbDi%Є}T(]tQt$Do\q@]Q*F [#':i d7&4 955/(F.)`0+a t!?$EY i% VKpfBOlid i/~=itO-;B8 :/TdԐg6ȜAOnXb#An܂@]gTzUPҁKJ 5 'Al2 >95ZRZ*AZXI{ih H0- /j1Hii9| tPA!dBE&< AqRAEu&WA4P($mS!BjmPJ a 1e&A h?!d͔-TM\m2̉QРAPnh|x0 U A|u]@X$ A !Ɓp? dBCWi;nT5'4a3J&h;!V\h(y VӚy.;K 35;+<;n͹8O C_P7TxF%7n>髿>Aה?e(X?0ŀY#( R|R R=d IЀ Ёbdq!BvG8p!t20 5TpBH9PA QA`@rDN1,sxN`'  bC'`D0H B:GHVCQx+0@o!A$HT)Ld~`!AZtL$B- ,l"Ft@ fc0A#fXop&G"AD#,(H4 n}ݘӾYfA@R`|eH5JS YwƁ @8@;@t|`Ļ B "dࡋ q,D Px`"[W!PD͕Hh Id<t%H4pDa1Ue;1{61XLbH # @jf!8:uACI%^ y @vŏCLۗi)Z24W*D>yZ˦X:Z20{1LZ6YAe,[XRT fB" -ȄD+@Bs y>B&4r._gc hBYd GX@ E<`Gv# RG @Z7]%a \<9qq}~?\%` @ aVB$@rv NL‚ lA$` a4b.}cC(Rd'Jrȶg} ǬNWM%eDB}NtsɏyFG8uS3 tHJXY؅DFפFb<@N5QDOAEP[`DXD@L0I9iCd՜AwxqAS`oDѝMYGA pBPG`E5<|FڥNQډE\wq(@BX R1"MwP~EmuAHQ؞EA IX0D4x2D1YɾÇhZ4C5 ?|ZBdADs6YOA1Ԛ1 \Į[p [A B.rA4|ɶuڨ5eOb LRR |!T5#AL8]#AܾL?b@L|: C<Ԅ NC.DB<4FBȬBԅ4P!DXLX8#AD DFP4M]h@xߌU?lAFP@t̡PEX@\bDќYĄEnLXȀN$sVi^ЕTPMПDxeb~ec9NPf+d\, BBi&j&HB@A\:hDH+DU?ܓ"hD@ApxAt#A qTP`P[Y" HU9 ,UR%H{-A$`A'(IJ@![I±&C 0/Jp"1bBA8X̐ diVDh=+ΉR2D@BL Lo! vLڵF;ۉG{H] |tXX4!D FKtA@@Epe%r qH?<\TȆdYG:vHu$-oďT!x?S\C-Ra0xPX?0_NDbLQ DH䥅w&DXLVFٯ Rxb$%FVT]D_`w\[nW< rPɜMdn@ tj"92fkJME$fyh5qEw[`hOp4L"Ao?^93Dn$Z&S+!YeBDK`pBA`:F)K1;sA^!`f Ft;Ί;f lA08'|>"?\s,BHȍ~A ?HAPw]`b(95 PdDPphĀC"gP 0޷QeO_BR})@t7h h4x@ tHqȖ 4qG )BD(4EIR 6. :E ѿ} `4LF Sڴ_;,A~B0(k6A^GH>T47"1pUd9 U)u*= JX*mBt C48 nuHg+ DG<;R`WkMA 9dhMz BP ,ШjT*{j6eźZ,Dz@"_ ֯ S!ZPl۠? T&%+@> uG;g z hF:d"Cwc<50i/^w"2ۼ^ł^4OmD7+گ8O"K#x(Gl *Ϊo {z \}!pVU4tN"m@,(h|` ]  "T@*PB PR !/#|a)ؑ;f QbI~'9x(ChE/F\uD9$r8DLbAdQ`}GUCx ډ6 h8DP5 :ўFsIR$ "BMXS<8$B) A@i ~:_!HrAL-rE~ Ҁ$+#%":Db 24l$GJH,A 17hB.# "$1D $#|&pr H XtA!H&iD1b t%%H 4&EB68C:omс%յɅQ]en4^85gTUj  ˪>EQ=+(˔JTWW*/Q4NVzэFze=ZP +,YY*!*j~J(J+WFٍqծWU*յSQ, 1Gps G}j9])tg+YFv%FSfN!òOS?@T_s\D{^H$MWĀ`siE %Kte)OB7 I->+wA8h8M$C5\"T(TA*H(-Q\Өq-.hP@rwGW60Pmupv++ox+}t8H$;5SvK#O;lxXh  l`q®w?T@6>3< q/#QD4b=w qq"2Tq ?0vь}Z@WRC^J03&VINBW,ߴd +85,g-_6@nXvwT:gg/^hfF]h7 ݉Xbx{KG J^@ LP*fـCm|QOh7y7jES$&fM|39j4F\n#zYף|Aӭծ<:G/`yyYo(MeTmww'WKkǘ qIo?N(*^1yo x.-ݸP}<<>@9SL >ʖ{7~@K$?SU{o~7џ~wϟ0p 0p!0%p)-105p9=A0EpIMQ0UpY]a0epimq0upy}0p0 p 0 p 0 p 0 p ɰ 0 p ٰ 0p0p1q  1q!1%q)-115q9=A1EqIMQ1UqY]a1eqimq1uqy}1q1q1q1q1qɱ1qٱ1qbq E0 2!؋ =2"r!%R")"[P49#I#ARn$M$Q2%!pN%]%a%K%!&q2'uډ&%v2(qr4(2(E) ('k*2)(+N2$,-% ǒ,-.m *r60/-/i f121$b1n1;')P H4K4UsD-sZ36e+Cr6N.7t7s63q8s8%`7}3i3f439m91397ksT3 anRq>%v 21?5>3?O?aS+@ BaBB'B"hA DKqC-C5B-ڤ)MD`dtFiFAE5KNNxGG HtHH0ITIt&_'FmJeTGmEI"'KtJPGIMJϔSFYI޴F 4,T 3Nw*1 tvQ?{PKI :5a5R!uR%(QktNovSSg4P" ! ,Rc b>)]4# > 1")Gæc01@:.T t%P r$cD?+ш3Tmt yۍBՁ@ %K7^~8ll*%6wR }uC!@918&4o6;d!chld9/Kb8TM2F86AEPy:&;PL-u6p{U \5jp-s?xq.pu> r)1۵+JBH*H_a߼ aCݨO`zA(o؅N{Ӝ^@jy%L7j&NavV">!Zʼne`xM卋\I@:et?xs]dǍ-n{&qÈX?\E+yj" 4YT`rjh8 )0Y 9ط7 v7_3D788NQvD5!dRyd}skZA$@i'  0 Qw@$O`Q#spCHг.":>t`>01 gn{1b'{p)`7"ŎNo%עY+pH(+3"Th(1BGql\%sHZc )!  PI "W0Kʹ_Q17yOb83vFHG+G!1Ivq!> rjxSN4qM-$mM3/0!.u`X02.#6 0\U ɐ3<61+6/O\ô8?x+<-op3,\(&Вw(8I!ʇ9-fq2a$,r_' ˇiɍ1auуccN42zuw6pi669qjC.3QvCJl71k1-Z!cz7P k gk {d* zz npأ=OIX#01>0Aaq2¿ 1;5]% -$*=(; 3)90Q哨+1[|Y21>!{B~ "^sBإ6ε @ ##41`RWC@),5N @z@} P"w \IhMgdF/fAp$b)7+_PG𘟄WWp [hDFqcCĽjsrv3Ew#GX`9,nbEpXtw_@Hܩ9ܢр"0@n𓰕0o!&UP)0>YpI i,,#CQ4QL]ٚ4`67Y.rȊm&ϛc/!6XOS;Y"ka8-BOa.ML$kY,Z A4= ':+ѡ6S=! ý`܆5WPDbDZAW0Aqs dWs04!&tG4qJ(Eet mKB )Ip^ D cN/?^m  2 #%-!!m0v 0aD"(M1;]S^ ן"$prVSq J#zvBC{$RA|!-=ו؋M#ؔa<bf8T%v! Qk.ZTs#^j5P"O9VR#"*-\Osa8cDoN5"IsD.9cbY:.- Yȕ^.͇&(U?6LG0kl9~![:UrMj6^-"9QD-^ĘQF=~RȎ' $;L 68@iS=МdPxaҢ0`D)11lGI ja i! /ZH#jk! BK!a$!M@ 2*06"M:Yg *Sm,܀ X/*(%N LJ\x]8Kb',(L]4 fxln6y ͖  aHw MgηcI .kd>Hx 6Mb+ %\x;j^f<_W)M(6\-w8~)в ?pNBiQa e |!!?4s@&#HА)O#BF:%^H֠*D 7Bv2h€ $ B4r@݀r%xXU"Cņq!9ѤC* ^AV F$"B@eL*,T K%T1V"<\H I崋 Aˌ?sC*ˀ !s@ ZySu5DÊ*)+F@x0 4 !Rp bj"@%҆MP&gp1b$T / nd6 SH6(")G , u*:",XH z,ma@:6  R9Bov%>n@ Ea2DZ5z╼)bؾVLL!,mUdt-B&V\W> _ LeCԌ-9iY&(@X0d.a9MI*00QD0 B4\ƈKSL"Vv:DH@:,6†q+ M=4g!I2 bºxm5cu!y}!</8nqh`:ZDJL!Ez88PвNh1VX"dH0nUd#uR!pl*-ݑ~\A -FV+DC#HCILmqjTIKgJ9Ë @8m Ѐ.2^21X@WImM` (3e{ LSL E&ȾT!WFLf2pl! ^<)vzkB4 awN 2PQJ`'/` ~?l}u7}:PasH'"^g$=u`y]EҮv!%>wŒqrw+`cF13B} +^0F=8Bfڵ })`Q!4:Z3L+.j0[N_@ jDO~|~{7_+Ak:i:ZB2uWAo=@x?pE!XP(@8@-9 &02)`0  `x X ؋()P(g0#E+r(Դi7 XA @@Phx:QX6 D8B-p09ɀ  O8 XOh ?OREl?aiS$(#%i'Y- 1asG{ȀŁpɸ\[T_$]~"~VAU5a=:R<CWWx =Px6<#`'p*y/dsdʰH >E*#г~1-b{?It+R٘ x9/c+ Q! uΙyx$r4H8|1Q IHyٚii 0| !IA!B5Q 3vA:۬i鉜?|l . a/y w`Mj?< A܈29㞖#٧°r"+„aЎ<=SS>]@5T@-C]!D PT+˰9E1#0 {")h 0'0 (D@(C9Ы + J: ۊHtDF4Ъrĉ;j AlͭC;v%jWzWyW⡭-2, t?$gɐw:ªVu.LŊR.Z>`0Mh28S 6 ;axX(!YP Aي"311 1K@K c¼ZZ݈&(cܼm4ȝͅ<~ȻĴp4 'b<,TPg u=bZb3.c8XeOcc-N b5( ~;>>>8 =$V :A! A Bda?̝gOesP&e&%U3`-Mp@d% m ed@GTXؘ 0B[3bp5)~1*w t"t WA b !Y+BxЁ&Hp01{}&- p :9+Ȇ^jp!LH:Hn: X@Oi@dDq~e YU;8QY$S+`D;khAxVPƅjؕ5QX{{Q 71L|dBAD8!G.5@8  58HlUH2P)qtpj6` }5AlUHW&N̅RVpmTANq*;Bw{H)88L։a6˼ўӯhʹA͹zʁHl-wT ښl!БYá!ћ0xAAxrs(EFЁO}"8vk gТB%iK"Bb%FN"=RX5"ҁ(Q(> gǁГI?ȅu8$IE!5W?m?N@'d_v1)J*.ԁp+ВY(8ӭ j"Y\tA(8065_h5\*ȁExh՚ U)#(C 06)XP'/ |BO[/r#r0'kKł?y*3XIʂto]?|WH/*ptjXOaWҺ_!{2D zd Nv @0vڸTpA-ۦ jnӮ ( _Apw uK< 2~ AQVx 1V .'8NXHm"J9Vs.3OJZ˵Rhf`gX:Gg M??L ?2c28΢AUC/4O9 d͏*˙G9(=OvHhpTE$O 3^Hi_cED%}4dA`2ovPb&pR 5T؍:aAƷ 8 NY') ۡK.Ó@1P <`QE_dZ'2 [+00DE*a(w6TjkMN$8 :K2D ) KLls uΈ.Μ QL+" )>C>σp3>*CQKf :dq&&  f68ˬcem0sR"5!I'rŚ쀸sG"w B?gsǚL'sV!,.UU^ܱ)@Y9R (=M\8=r@diaij^Й XDxBvckSH &B`R X%SKLO)hȨAvP3T;@J[g:. D u&0&4'g@ 2hRJ&x?HBC&Pq`~ׄ vї MȚB쑂aGܵsiW0ŵKTor/ .qA&3\Ml  L(hn _!6Ϛw 6i|$Ch\d}F=n+(3Pa֣ &ԅЃ9C2zV(<.QerGa$/WhBm%>CC *a^©lR 4a5z= MYEqi+ $tHuᬚצ P@~Vm͆0?&;//4 $ v7@ B倫&@-|DcxA&'GbD,$ ̇2#thPm`ŭsy}mՍ\m A%R/dx:'ʷ2"@EM!J-za#_UEWSp 39$u^>zm]k s:bׅdgB $Cl*%bG喷sKv M jm_k ڀ#b bQ~FK%')<@A@iv Jf nn?Ǿ(Pe8Ӆ`Q"P }Ǜ5B6,g$ )W@K B4xjX\qDiA @B-"OqS&Y"}.u%LO(HO%]S0ކrpLc nXʏ^TRi 1DlėNҰD@oBKhjY*dP@U]@Qo0E6?yGiBT.NƜ`}bȟDdׄYjDpTD娘 2FEDZDgGGB0+BH[D5Z&)D2<@LR989HՓ$9C.D^~_儫MK?PᅇǤ.ucTAFa*RvQAA$CQ)"qEAg,Yx 4,@l]=&@XBPI,\E̦0@̂ Ȝ & \A4,dҚK,D|D\& " %T>%=#|}AM LB̗`–Z ׬\y*E AT@v ێ]ڲ?-%mDmIyF4:0z>~)C`Ǟ0y nşn >.~Ʈxnv״SzR }şD0ixZRnm|E%NZ>V 3Vǂl\H1.Db͘D&&EѮ/P0wC, ݇lA\7U\؎A|uFwBEFB=`u?l"iHmSpob1@+jegjNejAA]lAgIJ=P]7?A4qSA޵C^_Ij)EdDxvҰ@rb G(C8^DxE1FSx`Kd G!TATgZ Q Fa\`t@*cԚh|Z\dhF$sVDD*w3t2r V(&K-cp$_3&EmJ'>9U*}L { =b|rb3= AE5P󝌢S'"Z?h^H3C7t2ve.4R(%S5:QK^RvRI5P.!γCKJЮcjp",ʘA@^Z YLYLK/5S7S?x 9?"ʚ0AQHsA\Kl ",CzLD@ @DXPJ jH!A8P1 rgPGM#ӊ;s8`ˇ/t5CDlYgN@b~c9$]UĠ)T2&_#F#pq4 {/rd[&K#&A M_HĴxcMRma cA TLW`>yxq(%;EDMG-g3Gc)`'GJ@K U܇rjCmr 8!cH9pr`Ȟ'0r&j6{l"c^bܸ$5&$!(<"! jN%B=TA^j") Jɽ(`R5@ūkh79/hàB#82"`1Z@8ʭBx> "N8#KT)ʢ'͠$@[SUX<1K`O\MVٲ:S"6f$g[-+l*p]\jtmw#(kcH^^8(1娏|z^xn!&`gcudР~(2s+RXiv *)8H%ZL8 ~橩꫱Z뭹hj!iU4:,]ci(:݀"( AïQ8iKgr"ŝA{3<kW1:ݢ`Hn69iba$Ȣ &УOਥ $ ^HbcH!8~,~(O=C髂9D08qac$(C^) P,$ ēC64G#+ J2-Q*K!PBgCq` \H`   $яQ,:~CYB0˸G*8A0)1 ( D>," *EP)?C`9h)fpv @, H4$@`mObmI)>ǖQED 4;b vXSpv @HLꆕ)# N$1xB-%+:sҪx'R@ҁh" a"\hKD`OTv("`HT`3C8jZdyrhr;>p @)3$?2 K*J\3B8r6ʲ+GrBmoJ8P)t;0{Dɒ@-U J'Ț:@!q9‡R0 +U D({Q\5:чEc4ݽ&c"=0 nPjH(<PJ!d JMzDz1dLj]ȢeXh řcDdF5DmxC9!XRЊ8?fCo~? !,Gnq H!6r  N%ƛ_c[B!K)X9H=k'+` ҖxXַ Ak8M\upȉZGJ L@p5n!?ǐ[SHA;*QjUqH9Kd7WZed$X PyRrA õ[AH>SRq3׮Rʰr4-C~DXUge-E02s,?!jm@ MAtC$ _g9~HeB $!ƺSBm<uxU3Ձ9Bφ#u|=`YAʑC,w:Hf>d!N#?2 F=P@q tP3{yd ѽ/^Tq"w/!F=WHR~w#2z 1GFLڠT !(!*BƲl .f8KxL!ؐBOl.Ue ee(`fM\X|e,iT`!$PxJ@XBQ&CF%JdbFbLRd@!N`lB#>%((`:tl }vJ,&K8ު& ,oN)p8,)X.] RFq"U()DȂg,b!w,n[a^ .Q<E@a)Ԉ!!aj,a"^^~Fq%f`##F& A!BXq+!1!Aa1,C`Bqٱo圾*:mg܆*頂z)6g 2!r!!q"Z&(:(@A#W,0!'8x(tct@ 4Hm>@ m1 r;Б6nb!D'2(!t"*""a(bD|`!"h.TC8 @R6Q%笤U4p#heM$O!~Q7b6-kiBE ]J/誐s"P.E A1XvmB/HvDoW$Qa;;Z5(\!oYj%`=E,ʵSBX^BX. 8d1b ^$a!ڕ!F& h(! #C=kɀhދaA<!>I<Ē("cATfK^,!P\A0/8$2@hcCs,`hW,D=bCO^4F3R` YPpBBi#jVwE!>y-NlGf"{Nz`(`lؒmvmfwA>܃z! $_~'=)"aNJZfZm2conm&  c׌!F .!F!&bF5QfTRr#&`TP 1bcG08B"/v&a,B /)i6 EUX%2*|bUo1"%hMI.S$׋$wRB%%FBS,gt$i& Hǥ"NktXgFNdጂq%br =fw,` X-x7(FO`u6ϕقvܢ7w/B 6C{0"BB1Ɓ ۓ6V<| " !F-O[QMfu·Hb qxB4H ~Y!@KR5+2(\A8cw3ʖ9k4(ehYS vhM!6tey)"lekaY>VWAk8P$>0E^O "8BW渟M VJto sb P 9B#%hZlBbէ(ZJ8BEZy'T֒"S"#{BQ9N"P)4K^/_ vk%%jZ-rOՆ.jԸj,v!'x j>Aj@;agC !,`Z!&\㜛[ee$C,( ' e[dm; @WP_r-<`!r2-6N`oU !`g䵨"`S5ac(5;T+P "~1& %UH'DE"bFpK "Ԭ2Ȕy)n%ēY?V*x[gn K)X i%ީcЕ"|M«DB#y;+ b#N '_b0J&ص"e0{)%c(nY`CeQI!.vAH,7ZFtBÿ(/ }@& #AA$:$fO gQor7UKuBΝϞφL6*ЁKs:Y}1T 3Wxg|:;P%RGe:š$P7b.li%CdX8?/)#AW+m<տ^b.$p!&X/ȂG^/Hn[Nzɗ*-4 v8ibS` US!p H E#586'T1+]RW$[W`J% fn½@ o?& ¾e $A6 >`ϝ,t n,T`9 C~B0{MSw<̃\V\8C jRMr2H,BR`S$i@BJqT Q7DI!#zSЇW`wU,c(F7 |iIL=R;HKQ* jo k‹5q$И.ސK3žQ:j(V{чU\YG!P-K!$ yo $fQQJs,H) tM 2Ct=%\!4R&vS"< ^ 40(#! DP=-)ct B (/6t(FpdSkN-D(H Va&aȞL<{CȺ 5%ME:jc! H# . RK@$Kr.TNdJ-%eN Yid bN0C^dh:' d9lEA#8C4I$%HKE98°*Q $5:P3r4F^# pD'dKUb%dXEr` 0W%(LjlyE˨섹 W{PaJ Pk9PTрi x/XɚW-:=$8Й2 (ZM˰zyuѮ  @G @t9DrJ")/YCx6Ķ#&*C .o-)7A(&A uɒc m I̛:I8!7} XGxVe*$}C.M1vD'$doP m An?H`ȑc%z2F|lC exxKޔ6<~#WKe mH?ndX-!/0Ĵ:ӤHtR| =!u co#>Iњ"H8LL9W7Y I"D,fbOߌLANN{s0$BM D/DU>9BE $$8V (YE?#Ձ1sG0XÇ37a1 q5)G- j$/ErE#P⃉DQ(ВD4N4"<d,wv<'*҂dhfhx/D)oDl!;VomO-b(7L,>{d*'E#[PrSy1{Ԃ>AS}>}U7 a:WW3,8/qR %w 81Xfߥ'ybksHtfJNAGazLRWu1ZB} !Z!S?s 9Aa q/0' ڠI :! pr5u0)5S YUV!Vd[ ڠP::s SuA_t}1?/ XW`e$!^XK`Yܲ1#bPo:A )pAP oRaRK 7:b`8A6/NQ!1(pX3Tfe0]C UkA]  ` B -xUF:Q`qp3>ea\zAwKJ!6! _ !"O#sv&o XkDƸfg(yg#%#BaqVp/hP!Grm'|YQv=)0 K@=kj/1j 3KxI2&ARb W[p64r:"L@5![pkHIW*D ?IvP Kn0MAud%:y M>t@fi<w4 0 zZx! r:d#9M0#H~Q`RDYEz0.k@)3@7!8uC}5ܵ{q_V X{x[}U> [%2W| P ҠP#O {g1/Q|7@%ZjR81Xq%|L_QW/!S@}ЋҨ#L+{&'HOrCBZt5Bk2L@}bh7)*N=b9lA֒*A8i(9E?FIІ+)RO'-,҄ݶ{8x[1L!& yf%Sx0|% 3/C#[|Ir+ jIgLP;d98bXha3v!ta đ6[&']Gº!?4Qf:7lƠ<,BLĎ'v M/sKsR#m*3L\Վ=,vNjˑ`6;j3`0D;0؃V&S[Lc9~Vsiripi81;=B '4Pt-I!R<s]iq'c*34xmIpc)5=1(0NCywMBӒ䒩(ml=͗@Ph(ܣBFf|une`Au4'w4ם]q{燮2݀7胃znE(" GP7Nꥮ@ T2&2SX@GR*Uf_5 L|$VM1z`˸ "pa->YخPJO%u 7qb =X@CD@/Eɠ~#?P ɡSyGd+2';z;}զW|~?~WP%xh DcvpnL\HJ C# :zUwL0QCzJ$5$:aq \Bd  CТ`0ee ' 3B\71Z1Q pP4;ZBKc?opRcn""#cyr"OtcEP 6&YP f&x2U$XsUB `  P 0LBq1A t40 ` 1oOѵ߁g}FoTnp5B#tg+g M @ TA"6T8Ba V:H ‡:@BEvంD>iS(Ar℠BBk>s*ƁӀ9 j9Sγbpb ~^pnH@nz<ިi,\H- C^hDlELF٣IW2kR0IG~GʸEG$ݰ W=r;hHJB Nkűt"K( 4= H#Q$#2k4ȜHRȌ:69 6[m,:)"ЃaGfkuas~Z!9Vs]g=$N-t^H@KPь0nE#g);0wHt-wUH|;b } |")rPR@dRd @>8MK̝ª&La |آ nA_قLr"V@# VBToɇ&D0w `? .~04 . :`<8h֓H*UQ;=ڢaI'`BQiwQ "B;{@. ,ݹRY3 K8"눶%u {z mg=]@,ҀP)Hu+n,ˮw{/66 If̤N7#8t [Rn. 'ɺ곖LA WG-3Ng8#*Ɯ-9+Y^z2$ۚ6&c EVP(>QZӨ8@0$t 0:PRMrƋ4b sFC8b7?:dVu:MzԤA<(PA$9@UA1ƚX p +c1TQtHK9dΓ'=ՄOxQb+jdB5 Ṳol7@]}uZ睴zk>/S {X5I\/34ѠZQ!(8Vn8A&V%miM{ZԖvpOk2 B/`l/} /*6%nq{\Vsư2nl&Bp75iZ'" TWW%oy˓̘p^ tOQo}{_^I 00l soBllM 4:vk9a#D r12P2 8 `S 1W`ㄚ &`2Aſ(DKa$XJ*8CUr.-]"СDCH B,6sz\) 9?^ƊHJDqM6) ڤZ& @5](r0F@Lb@ "^a?0@0ɫd'q&JdD ppVh5H-!1fײ}l5fDnvaC7KR2Q@v5C{6{ʟ!s!)|) 58$H)N4Y@ & ^ũHPql-q wa+4@ ];F( iuxh^tc/t)E,JɊ}ra%lYf_ޅ'r:W(bÉ$e[($"vo#<h,^]c?qD6Ѻ7)IY>,3( ul(W@A6:dy]m]zD[8#eWe%ew8+0w|@$lD@M4P@/,qSRر"-EQ>@:S# 8Px ( h H@0@2Z -~ >p78)X~9|~7ȍHB!3828J R*ț8M;kRS!E+( ؂К]t HB T 0b26 s1T1㱱0yV8D!a Ory{@%X3؛< I#1`špCx H1"5 2zԎ\H@zI#XDr=#1_ms&yITb8ځ2 Թ x bɲ

AW9 ;88H$%6'uN⵵p+@Jm>N2 !6 nQ=H9#] 0HJ5$ũ œC@$Q@P =|魉 iP`8 h`Ǚђ;B-X }8Eşњh$ذ#aWg*(Py0 C$h> x`0sk>k0= (ӛ( Y/8<#*]&L.*s\1;*)!܁8j6`j0 .꼀>B D=Ti*PbK;`D>؃a^&͍~ Rӭ %!VRtd  F/؀Aᙱ1ɚSFh2DZH*.ͧ""H-MS ΞH7ɋUȏ̍0癢߸}KsAvd0Z N)QH)2pH4 ~@ mA#Lɰ+!)X I@XV賖jHL aJ6PhQği<ը bꐩR=B6 ƒ*# fuVJ r#z~!cnámA p4PM Djnn#ZNo$FNDz}u->Ac :^- _C/ޖNSHj,`}q[߁ȤHն )y_e 6hSpXjê=0kQPNWk!$"5Rױ='A𿑒fVΛ ( '] wm +_TO UUP zTsfӱjqJkUEޛUeF$sU\{jaկ/ wM+j0xXO|w|p|H-|i@j8]Z||зyy{G6 Ⱥ}؏}Wasxҟ4}}_@M0rY EY%YKFl~ Sl d~h9Jj܄Sy+ "j$n> 1 -TdP/9C4I!?1 Tm0t*0lr2C"5H:L ACb:HȒLſJ>hTHMYX HR5;Wgfe e)$BɿP\D* {@ag+yV4'ej4PrrZ0=L "an. \(OU< LP#qP$iK][Bł'D G0+ XA* 0tZqHf!CхnYaa\1G A`0E<Ѐp8?05Ձ  Hc%"CpTHЀt^yC@#UR uW X`V}U]i&Ca?[WQ< "t(? 0Rd2E\F uYhem9AmbtXHV *C8yQ_ ?8U)DЖ@)`lxEBhPЕE Jz_C"tu_UX]0XEN^ o`Y Y i 0_jAB@V6_ EKgREZpU/po\ A`]Ȏ[r6񵇁UL#F٩S5ET01e)[ҒU Ew6nE_vMѪ*l#0]/= »I}LaX-~dgNxwXyN8|%wE3D*y' {Vz}sV&0AgۮCYiQ$DV+0FV yXC-] TYE-*&@] e#tǀ%c;Cj!iä*BЇ!^X?CLX໴,A*x48W|"N⊡!#D.5NJ 1$H4y#P,$¬TH6@gdJ['Xtn iC ŊDu aEB, ǎ mHC@͝cEnl> `x`HI-[ؑPAD!tĠ H6b Q($#ڰ8y$sbR$^H0'A9 Bdd"B1NB8- e,)Q֌K>IaSGvɐ $H\p;ҟ@59kiUW04m(A tt(8)Qw J?-T |@\AsXH#ɬ8$)GȎ1Dr\D*˼Wg9[:Z"DStn,լ&ؼr&]m ޺tEua!YoUMqz^^3+5UN2kU#y깦V2E+SVD,i `ҥGkMFT)C"[p\V<d%ɂRe!0 ˸՜Fѐ>YQ":?8kH= z$;q)><;y[K<`Nl[>x B2Њ$ q4"ɠ #OS}V.z a^sxCKA(!QDYɐ zt㛓[DI8DO`LQNtMWhuaӌ@Sx$?ܓrTTVHXhֻ_E`E☠%DC_ԁJhQ:ąENhFE<.h\ߍW";Z:&?L&|$A9L &Pxw8'Ed?H%?8(8N @$C  VPW Ȃ/A$?#RU"Љ Im,p (AAPD gC"?̅ EǠ"s.$X䢋L$Hɥ ?|bVybEP|*V,l + eC49*X|i E`FBR CI0ZhN1 \ JɐJD @M -HDΨhĤ d@ԍT@kM i4Ͳ)MXDJILtHxGb|%DX:eNJ@`lKC0 H U LOx[NUg%g]uWA][iUlS[@Mp}EJgą,x=0mqTÂ."1;B}PD`B6Ԁ?t?XEwG!ġV% E4P@1IF~td$E,ŅQ+,2+@@((*h=?`DGI $C 0@*C} )E %4R(?hݚnVfE%`ZVƐbF)E`H І>Dh9ҦC8էRCtAVZEPQC2J9.R$E2 -E-1R"VRBe C ]<@$JEPP-NtC((E e%eYTT@J$%m '*Y6 YĀLF=|SYUR@ՂD@&F((Q~TVpT;C G\ Ԫ[RB| `WA\jd\eExBhe@?B ,20x@\A֫PUUUֹTUSVni@ؖh@M gE\pB-N]MmÐJd@LUd1gME`dϬ϶-RWq\cܜ݀ WMU\uI`fP5]]$ܔHVĩK\U#i]jFY$RD%U mmػt\|mA|ݗiWsשʩܲ 2zeEd*CXTFa|ReEjUD@A/^j?lhiECtA,Tqo[ H;?'EA#fwQPh@a$£QDH%2Dׄg9*脐xc%ڢUDwRDt[Q %Ơ]x4dI9 ;6ytQ\`Q2D 1/CP1RDi2D7 1Sģ2D341@a\`aNJKXP̝dE/utiEd PS ی \z IFvA1CGX_[ NNĵ,pIi=@!)r1!KF$͜2k[ @d\|:]8Uc]aR. Fhr nQ3ӽ.ih- :1w0r8s>>>N1PR/1eTds?3tC;CG DMSBA`8A]CEBuM@$ F- 2`DLtAv7̈́JKHIEdDT3 RXg YVAt¾h ,@#h%ȪCƠI4eA|jj@THd$CV((FU heEhhN|xBM%B'Udv|߅'S[P#c ?h \HAn$8S,x iLW^0GdC\54V 8t(Bd,c*Abb I/FzS(֛ɘ9qI\ |d D$->0$<\L:XOEm3sC0LX/q-UN~pkTK%eKUUyJ ƟM\V?JE"%2#IS=}LGS*Wd+gL@+iGHk@PXĿ5 P9T aB"@i8CF4 At8CF$q`A9₰_&p $- r!?D"|u*BR.z$V>@Qlɚ!UZn#DU2t(wY4h{2aŊ !g[{փUuWee"b@9uoEi<}޺GDk״|;,fz -4]klwҚ}[D{b Jpp"B="!ti""-La "Bܶd MH- &Ns(rP2ꛯ +@4' ɈZ2鿈~tH>*) -=vRJ.Ųth\BQ.2R => =D(D)%5O"! (DDo,jѡ"@N>5a!7,)!;I.;\@]#rcN# HLtjpH2VANФhCF-*)=PmVDyu@rѳDƈ':Ěp!R!gE<$,% ܿ=)gk` bS[f9,<@}%:7MV$k7Lb<X 8p(8!4a;)jok=S]H߼s4k+OO}8#<dD.kzc["*9?0&D @N'A !DA䄃@Pe4L€_D MP#Є4q'('N0DM)IqD*48T I䢐: ##(" @`Gm FHğa/ d:}L#1.kK%!0.u !ZDpǜo[IԉeR ^PAF@8$#|IF2*#<@ j!}4!Q @`Ȑ!)%]LS?2Qt@+mKRP1)V*"5`/Б2;**? DQIJQ,^Is`-q $ve!p p5T«H . d  w(P=I)O0YC$,֩7xE%d )5(}i*pkT )_٬& S@e<2D@#P<@}Ktc!~՚,.Xs'nH+ L 01 RTA([*𞀮*"y~!,u l͉L7xq-m+LЎhX$0H4pf7@ 0o*&;K%S&yyN yj d5r"fR]&X U&-)ZyEb,+ o-$I$ Di)z+,2mCpR1'X!B9CCj5[5"@,!T!-D(vV*6F(@WX vTeËD|qseTn䋘,, @@wȄ\Sm"mh'AQ !v9|@ǨT\@x#RIBϞ !'kL$J BFF>LGH槗</3I9t%[(GfGR@$]eD]b.H(Ƞ:P$ 7$3!L'RݐK hӉ6; ꅃ;Ɉ.hm0 jKm `^X,(PR- $^ 9£50up` $##",,",Nrb z!Ho?T@b7%bc!RAW"@K\BAT&{h1@ cH"$ ) [7Q]v:q7'.Wk).\*KxG1Q Xv (NPf](F2xf, `"PD8b"%h/$8 x LR1,kc$o% 6@̂gnCbbL'Ѫ΢2#MBq̆gBfre>@ [fɘFJR˺,^d"iT:",-p$g2"-m&a/#u4Lum84pw$2"1/wP1F* R,c2wD"d4F4s1LP3W5[5gvC5U%95sS7w76M"8Nu87{9S9ss6<9::/*Ɋl{H䂵-"!pU!$ \D 6!d&@ . L@hfhh *On D n``jN~ .!F=.!'hMxN ! a^ $"ރv# :(b ʃB`YHO @$t7M3@!ĀT4 -ƳCI$F*Tp:HsR`G&abCŨiHN3"N3tNNHXa:ma&PJ)%\jࡒ!<)d `U$*UjV! Z!'E AnjE&a2 @x!rxA *~nbZ$5mm\١`Ё]!`D`!VJ \VBPO$`<"2`a1.΅'` &J j0!XV D Qv>Ҥ!vLT F!HzJڌJ @!/$8ɵ +ø !Fl2|P~Ch,k2Ҷ[q..&;lle,X'*& \ĂdOf-jo-\jlELl+/RBL%N,kD"Ĩ6hop$  7:`vT2Rbu#RѦiX "ʥHRR~K+kfTp+ \b"v6xCBs&s,/cF.ȫP3o~Ц $:G (xfF-Y 0f]b54vTS1m. !Xf8L8"PXkTNԪ@U!aTtcM7Z d"f1 "UK/*jXUeŸ!8T. Vah~`A (0X CD.8v*N\\Xj!e[B,'0ZE.P8\ގQ&j493PD!z`Ajl夂M>.,M tsy,&,KMhsb8r“~$O#}0.M~$4!(֓ΠW &l2 Y pu_H4"?*a!`J /#b-.֢գ(n m &' ' =u#!F( "&dތ HH O-.`ND!0$<7p!PMc񐐋49-ps2SviY80/870"nC2`2740yh' h8(îmТwLC4mk8bp05`AO$do8'=Dl6.CǤ#ˋ2L-cHcpK[%ޏaob~c@ E)MPoD=˙/kc3|$Bw Y )N6Z8#))*@ *N !:L<'>B!J""C'EC"0D2=ap;.{ jo|oN!:Bc8#D nl$RQ8wÇu8傈xE69G]8WL æj#nU.X \"Y ~! \jy!*E !.T&MQMBP~R"-\6' W*'~Mֻ>ҼҔfmTMce4r"w,S-31?:-"TЯ9v诓'G&^^מYsޠwA<^c8 zizNM"$ǿ d6$@TT`>k4?jk@AР24a$`(sz2 i{( b!'Ac=aN!*"=pA@?0a!!)P" K@C <HtN4LTD&tK G߃D8PG$#q@b [Д"[D IMD#-NQBE9Vx"ϝ=g4(3R.OJVbA؀?ȜB? /өJ{'h&i"R{k &b  PM咐K@K&$-NY fDIXe\* |`RIU ?"4Dq:3i 4|B{>\0{ oPo[{g28BXjTrZGLfVflMBLh$C ЀD0X*^MPTq #[{Ф$hM"$H?A^!e rUCc"TBUrfNpI)tC`A@PqPh4Z)CJhh r'u'jRA@UTСYX&=ЁYiSȖԴiE I@=A% 9?Q q!@6.f`mLf@ZZ,ŔAVN1zVVYiZm:Ih""A™;ɲef73؛<3ϐZIUeفCF%I\+pU&BA4\!s]65'% BzNP l34=&O&~O&Mxu\CTvu~R04aJ^t\Sm=]!$ n O55I%Na+U# 'z$ F*4N&dnر bM =?D$>od7Ns~5\USC`Tq `C)Rto" <',iTih,Af36cc%^Rg*z@!BТ0F)a9jLc>d{A?WhSbhJO*&.1qPAҞ`TDbSV KDEFZT&Pv+H:'u;Iv`pagx !zO$#m}+qT*S@(5ѭU$4]jP.#ښ'1^C0)pp3O &zF|g]ݳDY,>|&?s>!LY_*_CpR:59H€{%A8}$?. u dN]7+R 0!|oA BWH' `Xpc4I'v4_Rņb՗'(!b0adcn> C$C`i` ɐ$  -1!-& \h !"3k p jah…^!r(&!vi@b8 ZT~01 "Fkpab n`p> 0CeC*aW/n0 iQaq  }!.QQ=R3"7 J 'fBYVraF ka R!VPOwΕL07e~6zʙ'@uy$v_''9"%I8Ii*)IqW~Oq+NuT9u&Ӊ/_** !S%1e*C'pX'3H+6qԏ1r]U7V-U>9uS F/W|&X!~9y. '5ғF2u2b1MN3:S| %_Jۺ r*zlN*T䝘*xb\4s_m;_z(5GI J.Lx33=q {c@ Z^Y!zЀO5q2R_&q[3st}XG&7\VyRm)33@6@ZqT )=Sp;dRᇳA_!&BY h#bn'a& xqQ l; s 1װ6ø?  ggRAh֠H*d$K:UP*G'a;ucB@۾ͻ& io DPa&o9 5cI1"0dD|jlHPl19ض[ůFqa5G0zcmhnLa4qv@o[~B7Y7fv7m \<6 42$iZP lBCB 9bJq&H3h:8p屙euH4<#Vt@N:ě7s1rMu w2tϜ\F kS|6x8<\9PÊo < |oG[g)XX\} l'A P7 !sR;vwW GI5S'aRK+ZJ4k_>,W0 ' KP =1 q Y7sRaL  p @O*pP $O4)(Cͺ؄ ~m@*(['&=7Ci&1n}/!D8w^I ^ ug'$Sap;  `P ` !UЈ&q hYxaaE'A$ANED=&mXn&%[@^Y ϐJP&a p N x}" !po@0 ZxP | znU;%bC8D"?5L{KM@q1@ /xRN$H}Lo['1X@X?SA!  .z$-$Tn/ l뚽}'*Xp\u':+a". '*T@+&'lB2ɂ|V&BIv-//12ѕ)*C26,SdSLS)9S"2?UE&Nu&^tS3&h*(±աV'ҁ,.)n@Z9B<ם?Ef Tau'A@ ځ,xxr 㡼oP D>Uۣ0&0ؓ"?d6'c8@ w63؀$X 9)V]a8:|8[[$Ŀ7:ă >уC-6l :DF DHIswԖ<}VmgC<5lux4)QSt;0hD }A  '!Ն j()KtADI bY9$!G [fq+ !K-)6:)uR('b"iHs"l϶7Lɋ^h/Nk"(*b(:"2 D,U@>! (S D ?0OE! ĈDS"R:""E9+' Ɔ&ƫ`, $ `L"PQk|"4Jʄ3لFPC ,1:/#Sߚ,σ6 < ;鲤!^պIk ԰&g Xee5Vׇh  ֈׇa2ɢ$iJ0.!6 !+Ҭ?@@4\t:DKZB7ʣ$p! c÷$pUQX44T1R}[q329R/rE=YUf&u!,Ȣ6PC!؇n!7XvjoC#[j!_> n@w^``x^`krc&vwW@QVxQo\ࣔ@TWTpqLy$k:@j2g@b+ЁlKRddG=!awhE-:j*,ya5dֱ!!<7imV^ ^,H2I" Q8" D*`/!q E,>=!Qsl&i8  F-@6@/p8e!G*OL!T#iDeX`GyQCHH86$>?L~IGVҒBGtGRZ" liedDth4Je)G"9 rP*K_ӘDf2yM,f6Mnvӛg8(B'u> g<9OzӞmHh@:PԠEhB Ai$.,BuheȨ!=?B A4&"CCP '$B p8HKѢ&DPh%m*YN:l]aCFpN9d~`CΠ%"`Cp@!(4!lSZLR>|e ZE (҇K8DE1?0p%Oo`D  or(7-i7: mC!Cb1[h?7d,B Ta[x I.h`8c#~( 9` ¨lud  y"4 +CeL! 5.i@@e! p;b J~@H/ |g :VкeD%ո,x$I|0\$xF. ?W IQW#U( wgk ;VP`$ ռ(4&۪nyE~昪((H($0z"p T`C'd xZr[ KFK/2Dbd%O?J# r!~I@i~lRC,6&Ud cR"?!{^rBٵ,BܯoϷ!'+ !!X`h8lAMx %EjM!߇HGa}!SNOTk @#@0_ Z@s!pf@ vt؜Q#{Z+/(>XZ+As10#5X!?vyۑi4ipdPC`X4k(< čآ MN蟇h c0SȜ`UPx { Э:1@`H2P `q@ōʫR`tphC)X0IЃVXd`#PH"H9  hDDiz ,3838 X`<`!Iߙ@ HM+)?1nl$@Ƹ:K7kT2&XBPf@#TwCA% 1X:P:""!JȀ&B~zM:K딦|pU/yKdYHR9&qwJ= 1%\rj[ \|)q'Ie07]q`=SrHB[qvZ @ MȨM Epxઢhf<<Ȱ;1) @@ A/'a;`AvyAm2R=;{,ۑi`4 cG/j-‡w(Çbs79:gU8bL0!x>?)$999Y=ʤddc"z4#Aـ(R= ) џeb/=!%9$U J6,,$6+$D&rE@IYS C[!>>!胑EbO>%nzEXqJ?1ʵt *ي˭X%IRJJq$T(9,ٔUٕeٖW@Dx-1q L6Zl ؤr9=q]6V8 >yg{޸xS~f#p9{J& ֭pϜJVKJa:&ы ,2O=V62L"]P ?Ye[?eՂna6"i`>0!ji(pkhڢ?s4ц ҶsцЇXd;H@Pw) %m_a D0 ?2hy3/V!5%*n-( ,ء቉N 09e  M05՚@+I;|`'>wɛjx١冀{:Ҁ_+A(i!e H-"GIaՐIlu(Gt?錕XpE HNcȌVh*ԀR-u:9FsH}X@>v!@̃k' ,6G3Zt~͜-T i cFAvR؋h6$d Ȁقmk\8 ԮŖݩ0m`>!5N0sZWpAn|.0ӄob&|F4Co NS7.` HSt78w`pp< ǛppX{ȀF5 V% ct}O RUN B!Z"J4p:lP$iDI,TB * xZ8b.buٱ/n8ʗ3o`>TV@GR计["5d|n<׳oo|]VKq5n//NFWtA‰V{ * : %eS x"y>!!8" P4R5 4L f}SVV "MB7A;}gVBѐWBJn%?Dl@[ UD Na|?jqJH X Y?R41d9M@ bEoP^%gRxUQp QZyt[1(A"4FIW `*BhJ葌TBA"B>$ss@hF"PO=vg AUNK?,2:)(9/45?P(A"(O%4p8*vt(l!w a'!KBg g. +1? v0|,IM@?Yuc -Q?qLJ@np8F%XT`Oh{%Oo%HqL@&*&V tE UT5 _u ԐdhT@d ]$HH0*O@<ਏWH>QAT,9N `qLBD$@th/@D|@ IXQ (KBCp~a pD@ _F<@x)n0 "%ib 8pi k\eN7HlqTf#^"H,v@J: ,'Qr WDh %,-Ҕl$TL (HK(+0M2!1q9ԊɔR9hj%7P^٤h_LiH$e1BJlbRY29+ rK NЮO ?>xOxXA3( B$كG F. @P*jĞY!Wxp0AD0?)o-B|6alTS2BQ*080 j'%4-caDZw2.sB@!z@jWbxcC1\+A$U#{A z @21" %7u5g  ЈbD N!SG$Ilp`7!CBHPg Z-hh@`': @H_x "8@1/I|xB` TE1G$՛ `Y` k3N' ^?:8L`~P @ \B2D MALK% GP]BdZ+H1NPAf_({eǠBL!c(l6R2 ț C@6N"߰94 .r0D 6' X ' vdޤC[ n8Bk*U31BR p<%Sxs `B-L*6! Neb _F[ɼY L Dtb5% 0Pю e?.t6VZ] $x¶Mb * b@a*.z !V]-2hB'XBօXeksnX l7B#7f. t }g6{TZxlA[d_PـG "&^b(D(߂YLFH G GLFD,EEyEgȄWEC`RHDJR4 ieXJUJIaAE Ѕ>DZLEUH$@ FEdm 0ynD!jl kPnLV0!R[P!FĴZ&dv|#aD5[pp!qDtTKi~ D$Hw9 DġXyf&n"'N|T}GBaC_<0?L?"ĎU[9a@v."/Bȸ`  I[X/63>#4!D9;]%DٴE@HJxlF0EA.w6?$oTBeh|^CIB́Gt}I ^ z5dL `E` nVZiJ$-ưӰ0K;zg T@D5 4t@d $@!?(HE\^ɊR< B@5V@h?`Vxl d!D0<D)0?tc` ]A@H@h_"h@0$h VM]??h +h@TM|L ݙ&j~&`%A @p!*8^EEJ yAy9 v[T&bB$&$NN&eNnЍ; :1CҼP$/D@p@4@@ ZRLYL *V! 2z@^YP(`@Ť`DDdh@g@DUI$]aK8LKQZqH'qo'Eo<U) a .`Qy(1` :T\E_R ZL[h!%`)oO7IE@XdH E+ܧ*-1XQ x!a@ m7qܝi.G0Bī^R4J*Z[-zXW:>B>6[ $[rE(+xEF@"#V]Ch&3K5`Rȝ@L#H U &t$bN[a3UG&ĽI?e LiP%mRDT@@HUʶ,^޼f VI&nBwvE a @U[=B @uJvHm&wE'|rA-qUKAa1C 83'h9@.B~9tO}_Cϻց= =JWDq5"B`I PMS@Xׇ)zΝ )ARDCЄY,5-uJHRa)JnjץnV.ƏmhAFe@@ fZ @ Do 4A$Bh4 0]B"0C,0`ˊDmGooQd]?hG2ڥ ! G jbchXXN-9!6jh\,[b"bj.YSEʨUuH5-Z)aD_ K0q_dIȵukyOJDP(ۀY)#FE1fn"WDn4 eHJ@ù%K{|GIt(*JN$ˑhB2A@2AZ4KrBX AHDAq qD@*{L% /Ax؆O3+0K\Bl@BT ĹȺY2׭@3p&8" 1A]?hKC4@ NB&tHB9mD?@$.9`̴Dp D>>)gů El!rL+nTxn1!K̨* aL]^^^ R8qa rh;G.I<\W D,_HHE Ȁ*4BUHR!n$gK.qm\7vJf[{v#fv/`CG#{w{aߵAG BhG"!&}ۭDf'#I.")/x3.$4j]Exl,on#D*+"AL:$@3jHt4_x28x3ڄH H(`}21渋9Ɗ38na0TA Ġ8DʤTt 8Ү8 XdBHZ9Bn!A$dξX4@` >&>B@8"@? z_P#B9BHƌ ĔH:nE =Dȹ ēЀdLo8@Ț0CL Y9I#C8@;3CP% UbGĶgHC9hpC42OJfB+ `CG, TtBJo;N[;|dL 'cvt ^Lmj L\ $+D̲  IL IjUWtVZդDNĘcƅ[*rRmI1[!<6ت߼pM> 7lS_MZ-AǑbj+̫!Fj `C [&h -MP栢jZ8w`” |L'~xpݫO<^qNJW|J~WZU*@UU(uj*䩺5?/)"H=I"Т b*$t *<XD8찊Ī\i8#CV0*0Ha*04&44*SEhCp|*Ѓȸ'8;AT FQ` ksn~dNN ?!Hh[(;GJIqTKRqϫ $Fă[ Ca 𡉙p*Z 1Q:#@$`#ޚ XjnbA,h CZw&)DH`) | wޙ|آB|EX}:8Xp28x K J"6#Ec6Vٯh X`ê bc1nb|u#֏HC &$q^@qDrHH q„ XL*hF親JuD/3뢖V6.@ g S çݪЃ;J\/ 8L1K+i'+?# 16l : {1ϥ]?Ҝ( @])?PrG-~|K5*jAJ?(+G 2upV$|42ظ°laV!qՠ#la\G*J3p/3UJ&i\Zê,˙ 0Q%`V:$|IЅ:t٘$II0J m\ѸR#b*iˏ_+HL:t#AC<AXx<:~J @JP'E,*1S,t U l+IYbh+"!Z@'RABP5w$(Q.i1AMKHU31"jbyȀN0 '=Eg G4Hu)u@QNUġFSJGb`yi(rqI~<P@b(IG-}􆣺x*0,0Xm!cNӟbe4cIKu@cbU UWE" 9ւl )``s <q ҁ n誜p9ZƵ5Alc!G 2dk\ HMO([ro]ZoAzя7Qzկwa{Ϟq{|7|/w/PLշ>(gzl>=ߖ9]ۿ:s?򿳊l ~`9&01+.!P+-MzAM TWa _]eDma0vr B ϖиL P PҐ"ܰc7Kb)  0Ƅ+.2qw$0,1w-=. UK o/`1liq\ٌW-NNq xݞLX-;YӐހqaƱQ̱1걳! ,*f H*,FB R ӿQ7G[h q`pc <#"@ 4mqfA&<NbK#t`)0H/:0 yU`4L )+SA?:C0ށxˆR(M ĈdU E8@`evsY d K4oƁA z8(_¢}5_킵oX[X9^Μ cMⴁ oWZ tTB_l7U'[]@4@,A STEb!?mpUP)0RY?`ApTЀtT P3xK-:`DA? Jd1UċY8+az4tP#}AW5s@ ?mVǦ@r ԦyY?5WP.]%JF *U@"nyjTVF\VY A$bԩQi(e:W `v@Ju[l<F |?3!  aHP1eA =A+ڡQA~G d^Mʹ =vJSfk3h<ݹB9&OwA M@6ț = ޔM*HoBzQ@65Vmeq@`聜^nPQQӂM AI vXPx.wmg8O%/,1)P5e7nGݛ7@I[ը9gSER p)A.a## B6'$,9hPPj+ 0r"7iP4@"ʋh璒eg%j>mCA@ rQWt[+h1J 4i[#|ҙdGaUK+Ih)8Q mO<e|n 3<0 / fThn'e6 @PU_VP#%q!:@ LL xM 5n+"%4- ̴vVUd>$f6w 7 Jww "6ȝrN|@A~V\ |UPαw"9H#CM|jհ9 (I* U.{iVSB-93gM|i!xDL:x$b#fS:"/e i%{² =JnΠ5IO&EnBd~H ZX C#L"NhE,%CĤ# AD,[ @@ qFtXBDM]`6 =ilȡa% π@ѷrNpfڟ<pWX ؁p;A^'HЀI"F x6zRHm! ,C|G yLS%SQ@VT bLS4QyuK'"gH !+gHtP\/ On%BҕSe0fF!26 urJe"q̇wY۷6B+,e6-201(Lj3233U)+!)C8C*ug#Ta"cEqByP4U+4_2u_c?ETfLrSJ2ud=#W2&p!5Tpu>Zq+0@c7HB8g WA=Җ%7FBSMѸk&(Nl!:t@Knh7f!7@珇sx@Lahq !EQQ V +)0рRi3alfJD|-HgnTJʴ}6 #%I+قL-]LQ)|"abÐH!TIôof:JR6B-?Y._ՔڢQ +H(> +m`AO8Ovh! !iD9K.8]432LXP3&DA0I@t=a@Z 3-B`(%gFpVSUTPp2dCN5wgbI]fjqlQh? 'e^DO™-rjN>(*E]R]xمn-ś4Po[j/ZhL`>6̓  RAPVFS .a^2GBa +U7/},z4~Uid@}׋>ͺ ͩ7!GSb|@+Vz<WBZ]]MӠa> -O R%)%:/z(d7/w3[QZ͌ E4aT LA_58o_;^,]Tu4U-\rD4[Dl|[S+1:ESͭQ \(N$APEEӉլRIUJS(ߢ];cIkM_}?ͨHJCa*p)bpN-FzȩG2: q Pi1vQѸ4#$V`k۰!P6֟Tn/̱0蘉w!+ԧTI5zvTq`jm9-4@ ! "480aAy 84!Dp&"q'8?:!bl1%pIPլ55!bb=A35]40 07 |1䟊*(`O> n1pNBlUD[PK\f iFbQF6ɵQ] \`kځiJ\0A v\~0$Rtm7?'GSD$@LsJhP>rc܈C'>J Fdx!ԁsƫ :.n"gqZIr)WLzCIdã0($退\y;RAؒ1gKtE)!  a =UF)QŚ>O FJ,Ȥ? Cdd7 A202C4 L3M'>: `W.F>LZ-h֓-f \m6]O(W L 4 K"2)>OLv } Bs頀 P][uB f-*4zת6ԕ]!0W vɅeWh`OY\n> jbָ}XWfNmL ό v(ݷo.@W6kk|mEaQqBpHtfڠQ疃˅o3'׋sgd Noug#ۤݩoj! R=HAJRXq'UQ)(A Z@7$ jO=A"a/t;MV,AЉ,;A'nL@4M)I` O~lt@@EdQ ǎT mG' +^! HX+tB=<C2AJ$;zVPIg=s8r$"D+1[nsIK `{ s" Š?ƣa?Q$A \-B <\08{zd &AiLH77)f`V, @޲:$b@vKſt G,!,q)OH4:;n0$"$?g @#6+ Pgh(~7h ,97{ e느W 8- ٔI1$wA8 kZ3:YZ]Y)zӸ_{9 0 9W;^=јu19D9ר'9 /`Fl`` \ n%wzُPztFgܛba*H($?3"U$hЗt)#;841r蹆S[18aEq*A *0"Ɍ́ӟ 1$81T̓`r)PE WP R1h0C3 =N{!!@MAˬbIiRǡY9kQᬨxdn SHyL|H)R\A qa҃FR\kȕhliH ЗNZakxnxIe|N3#^.[TJye{3# YVn۫ƞ ۥ-S]˱oe螂?*dFς1 @-cX8&VTY`,1~0`DL~}Xسs ӉU] !:s18@UKI ۃ=q_P W`Αj%!\( >X8 OBA6O`U$F xNPRU)0 Hg"[ꀙ>a0 gumH_@Z@ m9KЈ@U A@vR(LOh,e`L$a_@ (q#kJ'(k"+$42ޞ- EBRcjAEC,vx)+VyJ"eV41Z7LQ2;3jo԰lc:x'KČv2̚7s,V8S8t/5Kg=SA|2r~:O?w_8{ywX`w):ڷ3~\-OAL@w;TZ& #}5֫OHndOA=ypSq7aASjj!A_uA.X"Hi"yL'v@dЈO $^[ӛAV/yK:$QJY\U9%Y.V+s)SjQI! D 2YLX=QgA4tMDCNZCS?IA? 3<NWAy6UѴN Jh\?}p SP1$%-( FD4[C ÆdMM QM!4n2qiY$pԂP)*TXu֛ܵ!&OЙ!iB ekfRe:ZE`FU? FdArÈ1\ S>IOFEq?Ԁ9PP[-OC2<SCק]bAlt Dt B!z-D#Ge+T:e5(s54 9]y Q Kq,$o!DŽ#"J [}ג4d/BJ^ .g!ITI}/y . /$Qb"H"oZ0ᨬX$Y2_B #AmcDdgn82IZ%Id*q2: #^\ ֱ29,A(9DsWUΎVViD`KkhǴG-J`Dh-4r V׷H[Y›-B-8,VE/^Y;QJ,tBUįmVNs;OKLi ,C!(A zfEYC瑫($.V %L:(ԧQ*DZ4/IKRʈf]=;D LL Lr MC&FRSt*=NG0zPF3L$"D/b4E%P9vk/(:5 (d^n5Se]H"FIT/ho3J Kܕ$;)c#'3 cZ铻(̕g5wT",@D>L(p}ST5xRtbDGD0(?h?LB.h+4D,# @NFPN[E~ LR:mAl\҄Spcc^ DSLDTpEF`eP+i1BG@ԥԅW DEc4]]E\?  qn|Rx]0mS D\CȚ+ g|S< YJ|EbɶD{;ƏbmH*q>u71-Yrf `WW9>e0-;YkFԁƂFhD44R@10A B nTG˄=;K(O 0^""3^Lb9@(@&TaC Pp& `!U`)J 8o &r[#t(1{qXWW„ށ̝3y"l P0}8Q>熈~4K 03:K6P *P )0B |h?T{<15K- °A -Q)FqQ%ȑB!!#KCx+R-J ʠ9GBaC!<ѡ4 R]^,1 TA -=x:@A Z8= #ӅTCz!B:ȼ{ "eϠEh PC؅IF)&U 0y(wA2!LtI h)] ghgzq0;CB%-kn(t$hФaJ1If&B:c #c] Dր Qo e3H$ %#Tpd-F(4*F.@O f 0Φ2p rTcPd>N a! `k"# p`At 1\:lC)\Cl0C`<F C\4ƈ9O"jl@$lA@}=jgh 4pņ4*C/Bt*!0#KD2Y?Hc;Bbyh9 !v"?60!ze\./B b~E)f#HYBxx,t0X""@! BfN B,Y1 ~@6!ȁ/B`#B@wpT P?,7"hJ&đ t%0@9uZF?tbEU>q>􄐨*p]5"=i?X:I As"D-ĠA &Te =c, DlAXp-Ոvȕ A *j&p[GX;@V#b@Ё gB;Fq@y^@. q4buJ|,t o%A!:\F 8pbZ DqXF&?a%#2X@sl<É jpcn@ 8ǁr`$ A.Ӯ)RD">@ l2x ?\!U@/Ïe#eq\\GT :(ļE9EG"F=$-HLgS14Aȳ2:h|&3IPrBDE|'G"*ZUx08OJ.x[i||8?0b*PT#GRw!URV Tr+WyJ6ၜLIKqD`yu =hL2k D UBǢ@FSe!HC@ W&3r $"*q~d$!PTTU!U]j?L{p$LX2ֲ*V֖ D "vEM|-Ae],X"i?$ҕh|Gn'5h |*C]Frđ6q騂s\F?B^Dy^'6,NFoh3b C)!t3T âVO5O0xR4^j4I B_8;3Bh0# zRtOPB6$4"!"j2¼fe*C6IMG(./q  " Fv/.&J5\e,,g >wr^`0!j1ǁ,< ,#촕@Ma ڀp U;uK]iI[/ps)D*mwA T(\ ٜ8@`n` .&Q @;;`2XV ĀObd B> b< o&"`+ -I$+!HG&ˁi/ ۀ% M!#`$@Am bOO΂n  "Jp Ǥ`,@4fBhnV^gO#9lĞL ?E9KB<#if|1C#&QR-w?6l6d$вcg)mEEpD˲l^2⅂-ޣQW/{G@ίDu9Ll.Jf! @nBlE^7 $e& .> )3rJ 5M-I(s!P܆!b9Dk+@l@ KlCqy Jj*N$8fÇ E#2;+ W Ka3bj12[T_ +R *A\-G`pb %Vog.yB8'n RX'vIq Y15foŽs%#Jf]d2Pgh6L uĖdΉ'Rn;pu$/u bv]%>B!r?*3\*[A'3Hl$e #y"J5dMcO t 2 -oW0ĩٺE6$PZR,T x7q{b,m.S\e"Э{% "\N ! #T Ee ` " M.2&D`6wQ!^M \-a =)e .$u[ H`3rH5e-@:p@<dN!" oi@2 <#`"C U<'e(;R{iCڍ;ij><*RR;y#xQD\f'CKZ ` `W([ZT<i&ԖxelGA>f.&I{4 (B $̀Bһ-2t)v =;[% J3Dȴ:2P5@ 94՝I]>LUMIRyERڠCc}[GTDj)`965J> ND2 !Ȧ1<.p1`ܻxzꧺ"87}X*&KRt|'!zdT"]b&TTtE tN* f 'Ǖ{Kc5!+>1 TO#=$ԝ Zl0*$`_]8xxD'?x.&.Q#tBB? '`baa&,<%MDIuHN, z,eRPq1[,_=%8B Gҧ> Wj0=#D4,^Pɻ0N$KԠ1KS?wwَ>a'Wz  {rX@7\v ]};x%;?}AFr_Yt-4Ԡ5߮#h<)Ч}KP3ID+ >0,ut/D[d-2!\l+# ӊ2Qa^!-0L7^&]D,]a-љd;#J2kƴh2JCg9ɋ,4yDFX0$`j|q7=$!)Ewl#y+& &ၖ0x$r:Х !bBʂL% KL#|'1Ha)O\H& r 21yF#4(ErY|WAFak&6 MpcpBf0lҔ`Lpƅ (JgD\` BH#I WLDJ;eA$ְh 1(J2 (`C$VO"DXeAZyrrQfh%%,:C{JJA~80XNB8Ip;H)@Zo!Vfa'S2{bAam{ F -.D{IX XA^FP%(F"&arlFrO  eDb58gIDP Сt/[R)}6Q,lbXB:5(E0.`teb``&z fPì̋4xB@;'AQjק%/,*#8)ڃJL2`A6dyGRĬC3Gr4O,r cЇYD"D~$zNYr 䮂H%JNpUސ TkW?xޖ59ʒed ?e]@^ v[[\] w%o"ʖcJ11 0-ѩ#IDCz)BD"ttLT̶B-ZO-U"d0wW8'g)9/CN -,U/d eKKH6ˠl}.8l#KA>+RP X@X?!5tM>3X6-d&6R06&UNA. -H.wKd%_%L/@!%^)L8b)$/N}j'lC6t:j8YK n{ A@$%Hf'q-Ef!fHlʸ_G8_?ߎ^yCv0G[kerz-\PjT0YYT",Ql"U1pAa  ZR3Fqyq@p&U SQ[:U{ qRDZu1 w ^Z hm/ְٵ] =yjtQxQƬ j5!3ԗ`Q+Afgd887$j!bc%,c!6b D`I,+0x2[)5gqJ!q ia`  [,Pd h! '`d"?#+GF 9y!Or=OR&@eDऍ諒"Q'i!wh*a0*kJ1^`^; j3Av^ԡ7h-6JAV+J#RoqKlW w&]!Qmy Yx"E 0 `.j{}JUA0rR-qPkDæ\ܿvR.&ʻ[w;Aq!qD<)@^ŭP5pp60bV"0f':h7QxR/hJ%4"Xŕ[)GyYI,X2A@ )p rNI t [`S)ﹼ 91aRq@!6$]9 A=0ZP5r&"#P sUzNz'af|O122!gw_ L~9 %|Bf!"$aʱ`_cL1A~PFܻd$vV'#F#$iDRF"gVH~tЯJ,ƌd\X [!;p̚Plj !{"D1,a-Q,A,zy=)yPC"2-1 Td,%jlKbd.Us Ld{,ч.(ՒIˊ +t'lD[G[ =,¿jHReA, ¯lH&=2!@5` grL"En/:,ҎVCh)Rч߱T$!73P571ife@55ig9g}h@Ac`:h7cc7b6G&i4`"1$Bб #ecK:9bmli&,,N@eI%7e D@b {΅)t'ʴS>jF, .&hBUTNe*ydsmfoqAp&t-11"`)OJu A )PR.NyRRHFR!<(IA%j e4 l^- qѢ',"EѢ0:` @ѝũN  A4U1尐0 UռA=U.yl 9Ԥ=xEK:p""pTka r@ 0pUU *P?udl@,&_FE-nT{YP{i^Ы,!F#3[ qz8j.!0T` cc[-H ꥌ]EO˰/Vv7u=b(^&EA:`1xm#m8I%2{-Ei1_Qc{c""bDSp+I0^ +QP`KpK+"]Md;~#BDMh hsf|*hxAW?A( .(~xF)UdK1BhIIS⌹a̝@o֌ Ėyz/Hvjoҕ.~Y#NH!e 4NGF@: R+o#ݙO)AҼJ;:3 ro ɆJn@ܓ_n`G?!QU œ.yRS&:!GH@*cS`AʢdVKi*Ha8@^JUҢ RBB R0xF!R1qud!{@3$+1\b ՜Pi pbL@Lʀ/'O;"89>X`0l9WBL3 `$!nh"peiN5lQ*Re(RԥSi'HdI0!f)b `T,* F`B%j`²PJrVV/R>L*Sس;Q)NG ԗ!m:q"8ao /ꮛ bI቉XD*2VOz:@'jy"rJ0m*IV6$xHdXF W _9+YS QV)#[J냗6⮤8dVSx5R"g{qbԐ˥S/*,#ARrҽv I[,%S)X| BV}7!4#!LK\́:v!_IԦEQLT-ٗsɴ\~ܙ,!U)3LjTbV!NIMHć&%+$aK$Lk+q%ޣ7X0*aJUhCbdYM/ O++rVQ̨=dȎCbfqIE:9DdglI1Q%( U p&GaO`\#D3:2$GIJr~( (p(D"UB!њI xn0TxHT )Ik&Ȕm 7˴8@5$.̛0g,ި1iac%'_RM lIfQ&7hS%0Tzn0!rM?#gc@ K#i?CSHIx?2ֈ @Ès KRּԔaSJ Oʣ͔K,0{CM0;?$Px%Χ$aK#yj#_1:)hxNO0v3<p#FtyEt)Œ$'ùH'q2hƞXD_^OqH+@Q-QuI0H(,JH4ц'ḏr +( aKu/ҍHc1pJ@;0#0כ ռT1Pa84:pK`R. ,W181ЯcF  8٘QNA<,y *(u6U[ iPhjHJX"Dŝ%ji;[c,Xڪ˶-U K 92P<H^ rH X0{ePnQ r uy L (ᠶIp$5375Րh}#kDö+apVdv{_#X8A( v{ R;S݈CAWˬDU-B_s{_n=^A^ jdh aK`r@@tгiΣ@** ‹n  %.sThᷓ M-`K` Xi S&> xM>D)qk] QK q/W8B58n㘌cDNHE=qт/ ᣲ+We,` e ͼ7ֳH[{ CD@5A}"Z="L F^TސM/=*xde>d f"`棡F&ff= 7N3ʎD8P][ xX?ƒ rXU'Q88ڇZC-MٯQ cPrr-$(-)l 9ɵJZ ~j '#89"u!X)hq$`@SÇ:Io1j}i2 tX`1YS9@@g[1*X L(`,-JaX44RFƜ F  GqO9 pTapM#& z{IpƊPɛ1j ]H qt}l0tT6`TWlt&L9K N !QPTi@$⚪Z 2!G[!, $TmCw(߰Cޞؕ(oH X<_΢i`3镘ƙoa+Ɠ{(, f Caub.** ,!X=hcۥ8JpqE KRrVro!-$.b1rOm ^|0#e(Zj ):W lI]c(2Є{`gxkF rnLnp3grVlΕc B `+^ S^SW $X HxM)MŮ& 蟇UM+!"M-к> |aC9;[\u0ϗ>HvXАYqa!3Ҟ15a4Pe1x,xՈm9D[豮*‡!NKS_y2M>xlO kS^Rx*Byx]_!h u'0U A zz+DwHz{{Тѣ/ɂHИO!<H@O9`Յ;(^z2cȀ6ՅACP>tpt֕0B,S :S+@ TT^ոp<]}0;^Z)U򢮓Jtv-p>B4>/H$AR=?6^rء"@@AAZ1$]ZD@9A@7Ayh$|V"tPoA^-@Z<!R@RhRDbj>@sOLXyy9 h=hi=Pmp?AАXg@ۑZ:"dJ4Aɜ \KBy {Х57C=mq`i'q%?w[[7i¿8qO5\j?qv޿UPBux?4]ZmWv)e> ZbF?`r5Q6s! CTq0D, 9 2C0B `HQ$$$# i|a b5.ZJK p lvZN 5/:(hCNa:~2 !NsAĐ J "$$K7ȟR~#( ?-tt`LFp#fdq$\";_kA*! @ ̺{1!P#% xɇD@H  pqa`?D#u' B /k Rd@3XWZ*`Bz3t+"DT-6ʲT,bLt̘4 h%:h&浄etNȐ+,9hN&*TTbUC5_NN32._) R]~^P:%^j(R˞rG#/!4 հl!cv ]!KHF$)S "KE eب@k1#)2Gj1gP.{&R) " q։J1xɕ p8Lڥ!%ř=; E`8NSBc*k̓PLڔ"ōow!M8m)RCbJ jsɠ|e:)eY =v2 *BtēUb!?Lݕɺ1|v>=}b:ϖgck,C`J!ڇ0Ё0>6 ^AP)a$~eWl!RBzEg:BՃM % dSp]|lپ_.]̲ @#ZH<Ô%k"e9+M hmIEg:q[B']j z`n l+  3К]5Csmءz_f(4x+OLg=%.6Uʸ-U<Oj.T) 9_5<29kn<1d%pyV!1ؠus|vv:իnc=ZPs!'oB a`f]#:n!3ԀX;֢n#>_<:\x*04RHiH( bI({0X  `&"#, h >u$ @F"аKsr:C@7N/C '&ab} 7@.CGDXp"6z dCH߹tEuEm1=9( p  W0L_(B] W8&C0%(Eu)E\Bw% ڜ3]AB1:I!C مOdCPpS ٕG,L`$A@iIҁCD?(]Z]CA(L&XCGAGHfaL؁HhfmpiDi*2h(JCK$p 22@*8`4+C%Nlrd@R aG)w#n6 uW|jNt׹\ĹA)t.K"0LC9:<B0T5t LwFH0`/E C @WTz`L|M@ hJDJn EEW@l$L<P~I=AAE|qWP ̴D$ԅN @[˜ȊȊK DʦHHGD-eW L @,}dӽLd@]hT@ P L xPN$e EdCeHC O1C@lEM$<|U,Q|[DIǬL ܜT"j\SiP1`T"(\Q}H[0!@ iܤ\EXDQLrv{:TA=*EE1D:ԅC0 2yo!R !wPg*Ca 0p,aJwH}A X[#0X$GTϿA hS C o&~DTQ} ɐ?8َ=NJf52ILJHqO]Ș~ˆ@C 45aAB53x}P "jס1luW]@o % CG91dU,U-|Q@ld 0_8WȤA0ߠ<D\*AACt 2DR#R"]C߹V;R#? 쑵4R%MR(?2JkWb2QT#5>FI5Wn=ltR#Q8}J3Uxi0E 6S,0x@Cm\}ACa퇴mXNȘNSP=IA%VQd$ \1.Z Y}2DKL[L[PK[KBFH4|!Kf@vbm ,k5"CdkȠ2qHo)E%}`mϻ+M |Q/iRD.†XB"3׌]ml~lȢ,zH} 6ѧJ$CBx'?ŒJB68iۤ?P!%\cM}R| c 0؅(TNC0}iB0K]:4i´2*$)=\cvxYO<[LhB29 LC:2ȟPN})C(?*01(9V] |" mNTk*EdۅLمP]H\*{]\  1fY)Qۥ%Kr%W9Oű')ۚf9Ms,Sp.̪$(k.EEA[3CXs\Y203D 3%{ʖUc=\ .K@k'Y\B{=7(͝HAmDU14=D6tG{Gt/ ul^Hh[et|1=߹pV$?,[H( Pʵ\?`e[N2P6sL^Z3OD@o6u,B%DZ7(t?Ez?ZkDDVN)$ eqEF=DgTc Z 3w h#50&I!2Cp %lB=bA%<# b\"bLE|"8?dD0B `@F+hTDG&r?Cp%N ,.GICK(B%%ĕI(~2F+:Dgr8A8u0gjN?8/x;D;T) G$/Ĺir#7 ClD"M\S¤HL-+}Eudgc$!bhBGGg!MS_ѫ8rפYYmcI4IS!.+$(jKHv"Ä.UV|P3h"`MqB0:?\NhqU׮ `5n̷wu6ɪl=N͛=xtZh憎Y7ꮶYg{pwm8IẁpvSͺX ,㰓 9+-2)&`!!DiZD 8"`Cc1<ĸ>J ٸZFjʒHF0RFn/(q!-:KHpR4n? )$ `T8/D2:R,!FJS 0IJ+M>@ '~R$éN36@2V:-S$@Q6T@ =@ʍ=ɎAzMZAT`Zqqvڄ"[$)"**p+$4OE5K`%bgjד)ciyAV UvamJ!nS,1^ 6ӑBh_,-k3(a(`5i -'Ƞ 1I"aA j&(~[j K`}@(~ik)ƻ I"\,>ﶮoL{$!ʆNV?Mh߃Z~`̝+ })  5ǾW? m ~^rV] '$ n,{XA ^ x05ndԣA)T YEl 84$tRO{LGD(!([`0 SH23H+X$ }tЄ (p01L"b\0"ETo`d,"$:H9*&$ '"<6)$;erZ~\y r:Wd:o\eiׅݮFVԐn˺epňmc+XDIBuF$,:@-#e:-iAZT[AlEϠ#$R:Nt!M  @Cd#Ir # y9!w&I鮮#0!&ļt&ڝ 2ئ95"8_4ya3OTo5ZS`#)O9b4drVC" 5<Ol$d>ebzlùH/~i|$`DC7^gbyHyZzzn+3O` fN<ÌB 2B/֚00P`+,W1JJ*E.'/C !0/%<"Z!b 4P>fL'$NBB# 2b#K0P)wbeʦ#Ac$DQO!:$|:6.$lmҢU$-2M1*Qaj,$f`%pJ؄uL d^ U`ᨰMT 0\cZ,Zɶ +fZ0X@$2msbVop 'nJvXnxJMrAk"F8fv?5$@k42h&l_)i `$#:+3BoO#,O:c()&,),<ʋ.+v,&K|C:7Ph`>jT\,WmV!p5"`Jjӽ pU jK>W4wNJ,ɾ̋iFIgj|3ѧmb(lrz=)9A,Ub' R.>,.20$Sl,r36{w'.!,ng8 '$GLFœ^e8Mqݤdr,UN f,:5+͍+%, wcj{ T~8!bc$]bQ ,AvMj`Pµp '$Ғ8y"A׶8e \:bs|6R4/-`yBc,No,( owN* *P0! ~HfB+.*X`a L,!%2 "$/~F-ԍE`QC4~}RdY"&&D(NvĠ+V bb[D~ 瀢bp(҈$LSUbXd ~fpS^gSy[w{ŧCC L?,k"H@) b6h JG bHBj>Bthx uɛD€"*!fB9k!d-6!W_1!TrxBco(t@t<*d(IKA*>Nt`B+YNG8~ ?)'[9}BcеfBR/u,..ڣ+_@"5!~8_+cy'rV , dI`A#z:4@ 8p!hgPuPT 0H߂kdæ`A!=,HbfAwLJH``)GgB>jqOUSaA LܒqK2Vcd TRj.`X7,I>tOͰcό;2n܂ @ vx!w꾲Hѹ}96EA* %N ]o!+Dτ^$CZRЄ5EWAPZgк-53AҤlTs|@T~_e1u7$:@`F2V;B-T(Jl5b tPT_> [ @IAuUzSV9b},FxQI0Po"6\/"ܓ$Jy1ϳ$?MDp𧎢S`x3Uj,$ Gp .G* AɌHM %Jtڤ[ ,d0P, 37*gp)YR5LfV6b$І4,$\I:нt$mcl P + LAIH8o)WB  o"^0$a{ц Py#"@Xc3)\Ib0ȒaC{k$hAv4I b"لPjMPIx/N[Hil<'LL"VAEHs9Wv@Q-|E^%)`;Z0*aOUtZ$?@ L$ w`%<^v<ZG`M?%bn3]I >b4:5ay-%J:Mk+8^M])+K>jMyIlamk[q< + >RfzFUAPA(E46MBxu/OAJ`Xvc+HX`@S^60 -hmC`3MJՂaJ:`,(Cԫ,X*SԚ(0%8\ZT*` x r#E VGr=#RD$uəm3_Փ^Ʀ,DHƲxcKMNi3hSZynOKdaw΀Mi1;a7$e'MJ[Ҙδ7]}Fbu VհgQQ)"IܸPΆMbN0ՠGuXLN&YHPZd uh$KA'H$[ 3ȼ;n; $D&8i KqI怕0$Eu=cƧt ZR QWЈXD`Ph BMfz  m G|!-^ @03X:TC- tM3)jM.;4HI4IQ7yA+\ 0 AK ˝;`9MdbPT] 4~P7>gL2Պ$F0Z=Af|^ *x@JOqi~|%.Z h#M)j-T= R Ld53HBo  D0a PYV  0sV )V egaz$0hs @)`zpQ[Wq{${!|E˷x~1QQq-Y$M! BOq'1$bR7N\@R†p$ )HEdAdiG1 R *s!"ebIsXƁm,J''' -O'e0nX1l/( )7x$X!4!n.b:rciQ |)iEͥ!TKB!qbi%H$5Tr"~4QGˆbClM]0)gb-^'{Dq! l^$byb5%e-&`0IbVm+_Q_k,%Q^05@i-F(I"f`7P2@S7'AMAtٰjD 0mp^$ZH'gaf$zIV$1QE{YvSz)1[A1`THN -$ vh`BP3hPaX8 2`  hV Xe4&hV5{pmU.fyR$hQ[ cn|'WIYd `qjE\9T.ВƀFLqC\tP &&:T&8At6SBBj:+$i%:R,RpoKt<cp?d]K6$0śX1bN"I ?( Ӝ@V p`#6;;]Q9"t:qàzM;$a:8f*@!Jtaw7F8sxttNa<*rjԥ,HDFB7iQegiFJry,xgu&yIVH>&Wp ]$NRqmG0HЃ>KoK7D8߄M"<`"Z2f<Ɛt6 xBH*HO2Hj9_!'/?Ѐ +p )Lz0F]ޠ>0)IRY F8qSS>LCk@?$\gW|NUk L[5l8ピ,Jgӧ5mt[m$13Ǘ?|iE ~pw.lL@L6̋ζS,B '( HAj*,0R B; dƚ& q-B)b @iT8@ΨIħ.Y*H^#8( D $4Hʚ2ӫ@<8k"jixzQ1*BbaDAXp).C 20+dy$"@ =JIVFJcG24X4慗bpJ)8zg= ګ{D,H@k͐9J=rGJ*8ڄF0x:WVm5i2ή̂@ʹY<5s Ž-745+& 6Xp,X LȄSʠd0sv'bX?R:'zCҠz@ϵO|k{0 | t BpN ʣA# I7*@P8A2nL̄8 (D79# ^4BhɆ S9 9<}8D"шGDbWTGֺ`~ڣD*VъWbE.v x.:gj^DcոF6эoD`ZrwcG>яd Č?Y PmN-0 NHDvDHI+Z'D Ёq1p[2`SH/<%bJZ DL hMFMH' ^ᄚhX7Pp^r(ˬ,SP-8;4 e(6G=J2RH%8p\H H'0C#D O@#c%YqJBq@ "I*EVS&II6ݑ30H0b57k[$Ռ`8R4;,b!AT" @zKDh@A laā^\H2xaÞjU.qaR3_ :R0 A ü`Z)vV Zu J q(`qት@ U#b5$i bp!%tWs(pR@V?Եj![27­W 0MlШIA*v<4Q p;<>lji? }ɇD ˙ @ aWDJƐ ,6L\+OG@tE>I3TO@@&I#1+_,g=Q  @y(>Rc pnMbhp<Ðdq =gEkɱ&=6ˣU´ǰ t{" ^ 9/%\>Gc#7!70% إl^yнrP$#3oyd_p:0,b@A 'P:d5Y 0Bu_xؾʹEqCacԃޟ!!*-ìVIqFf֢)}P"GGMjq8 ^ hmMkqخ >CJFmUwPsHk*P4΅nH0Expa!͂wk]eD ҟh#`hrO`p&);a )MmHD0f0I?Q?+ 9a,H@I&\鄅L1ϖcC&I'Jֲ"HOrf5Tt@w& ]iH?P1L8%7ɤpX!ؓB2x<|!<t"Z(<6AF)h  84++@r>_"@8X: :q!!ڎiM+n:$ Hɶ^F `ǠȈi=qCK H牟yy5x { T = [Fc8L22Ep\4 ph94J6$683^R!{(bS Y*rF``G &y0@#/(ǐp ̀'CE?ᴝHl( AjT(@PHШ3tBd 4!C2Ē@㰚k#|!D@A%T i@S8Lujj163`Ik7IS4## xG, 8 LED#_U |*a"6!(ӈ {!Տb'׌ap!1@Ѡ13г( ڐ "K ( !qk;5 ;zR6ͶwqwIiJ;SpFք C^3 )ː ݡ*,_ېJˆ XX5O~5Ġ .:sHf;N G`Yu`CI;;.;,+) ;(Xhg E|:f@7-;8:7i@#%dPεţ@ˬ@U#εBRM7EBș0C^WWDpY=ðdx(q8(R*Z8à$X c>_ڐ )B@ r4X:Ȋ/L ?D:ؐ4!I?BHh6!+| :ၪ'n $4 !f͜zA{Y 0S*re3 #p. )@CEMhF=j@K+ҍ'`6`SP.!Ι#n!0x"}#B3y4 TOF `EelR!CCxnu e4Шb.:W SVE|T~ KYyq*.SC](b@I\X9C!T"z" 0 1?ĮU|Ǧ-0Gi8'Z܊+x :jĐ`<nۭZ҈]3AImn>>a0pH[},:YVyhnlSkJ_$a!xU iٟN:Rݬ=Jkؕ:,sͽ:~ޚ<#Ћ{SK7.AiѸܬ `Tc0ejtX>u`6xjqp-v( f x k){{a_?O[ޒ p+ԃ(3ݶGٜMyaaNe{ ?B8ky6ڷG4u ^P( H 79uNdYB"OdR'[##MZCȠR"GïhtH>xpsWox"3Y>PؗA]X8 npeRyy- W~=U{ j$.I7g탈_IK_Dص 2:` S&,b# ;'s",9"pP#3F8x" lqpJNyO&8$(P9@GT!SN$V-HGvm۷غAP)L4KzX}+FS7 HcS U8A@l `e(}.4f{v`ˍLb6\7YW㠺 /+$YU_$`  l,iSF8yS:08*(`*4`"#`>P?nš %?q@ 2?O%D%Hl%TM5?^T0RP%)ARBYUA]QA֖<WEVhц[A@4<#Ds (/9#?w?!\-8AO"x(VSQS4ZtPOa @NAeVTi_P.QAj4,B8, @xZ@~VūPU0k|@ AX=TSՔR+0U$"AE ]ˤ\nV&ZpJu F)8Uz@<]M-!\AA^I$pʽrp!Ԃv{)ws!?&AъPO'C.9ֱjQ߽zp^č7U\+ .喥[[+<2 (Ys9ǀH?쒛AoJńDLfPUUTŏP*hNq"B fX~D 0k? 8qE!$ CL#2abAA& Jd% &nc3̘͠0ALq ` c7GN GU‚Se1 rKYȖx,RTX ([#G >#aFcRӐD!( &B ,8Zm G Cް"pR+gpGELjoME^N _4=:O΢Sw*B5HF 2 IZ tXTW$Ԧk JU<ǂuaY}@Md%K-Z=%]JϞSA3,Q$ƁTđ$VTiBbZ*"h.(͏*\.B.Urٱ-d[0.UTˬX>;BJt-~+q*`|bPrh 0QX \RG?z)@R:Ӗ1OM[xT8dJ9DڛmHeTA }O:GɑcA@*RiIVQ!`C%E¹,a_uǖ@b:*@1LylG"G׈9`;@MqXldY@3kvLjYRNHCqvt-{l @Hy\3*&_rׁ58oG LN|# [*Vg{L+qDTYp>^&[=m8:+Vc%B`z+(9Ѓ.6O@^Nzq?R1u 5X=Zk 8ג[pvN]Hn;.w/ i5|m?k !0֩Gأ uY!5swyÜ.0Z<"o9>XӾ=r?pGQNܸ$  Ld@CYPĊ򕷄>sZJ d!)e->pd7hI^bdyDBJ.L! AyOPPA`ps}\`o@T*q^dBF?`b B, LD@8VHCJ&`@4BUN"l1DèFlaH4@8胀0Ru ~  t}TKLŖ(p%oq` CdC6TBH?eH `@pHI"HtUpč IPpP@// Q|DdN#><,âA&åP<ƥpJ<FZ)=0QDͷh`KkU@YVތD،$@ ^iST U| QIE@N˷@` ̬X 싼 @Md `VT?FAcas46DC:ZEa2HldڧŁԅS9@!(!hCPG&Dh3C4-E6R SY@b[E:ͧ(xXPT@@eP1D.&@N+BDBH,/ =@RG=U@GW0zUR>W|VLE?Q_%@ŗA59.\f$t<6H!8! ÄIMDPE?LC9~@R^ST<ԊD`gadՋZͤ`dr핷))Tŕ*i_YGtLeq dNOUXpU`]SJUEnյZ9UVzgeEbb@_ 6J̎VA /iATjñ}БJUϦr x .xĔT% /~:ިohÏʅf%QEgE! Z <ǚy;}Uᓜ}@n(@:h[(*#ɡ&*> 5q,9QdDBCqhXVAT@VXVŪ h~T^Ut2.o+\h@O@[}Ͱe-Xώa5=4s2ޤGl8'`E HU?2Z:7k^ݳnF;7XV n{Xs׆'Uvnt-tt5T5L}yC'Q5\a AA\=L*}E״Mߴ;P B85\]P@P:Aԃ l]:׽R{ݦ=Q Й>#@dLL<8MX5Y\J#:ڝE [,adqR0XSUD(`嬠jE݋O=ǯ`~cű-(ٹB*,jT'[lW:Tqc OLD$hTKS&!|Lpd&" B̃I\ɇFPxqL:8B8R'=ᯇ#xá n)dtx l0ނo tl+~jpB M `% ý"VΡ44I?g |xM#8C$JBJC.@+PIa.bhhlDpC~q qRцK,s pC m>(θ7p;+N 8 d'P*t4;S,QӅ^z = N TAƐpz QA](NU{ @U,ʹOUMs?c6?:[6?%Sa"Pe `+WC]IԴଠK3HL+(?!v5|%8٤Au U[Р H;c/-1 7 Ah ڔ0 ҅|ϡ# -:q?DFF"4sqD+.E3c}PбJv)Mr:g$ zATxHEҢ , OjCNݝC$:Ř.ȕd!=HG hXGJsU4 6 7 QDbb&%#CwJr W*r&0R~-p\b&/s L_s571 GBГM,ǜyt!q `1 ) *p!7bCO1B݄DT ]H ]"TY6\s !n(p7B!": *pI6X a*@0mh |%KdC Ÿߔ1Ay*BNR4Ԕ4YL})IN&O /Or1Ҋ<'E3iy`%ʬB3 |ǃꡂv#`5=Cȅ̃/'6b`!8FhP. uC7voF-Dxq瓔( -H;a e K R؄@ ;IOTVlXNGz( TF4<بK<Yj jg(dC5YњVd[WΕu]W}_X5aX.uc!YNe1YngAZю5iQZծuka[Ζmq[o\5q\.us]Nյu]nw^񎗼5yћ^u{_Η}_`6`/ v!a O1a oAb%6Qb-vacϘ5qc=d!E6򑑜d%/Mve)OU򕱜e-o]f1e6ќf5mvg9ϙug=}hAЅ6hE/эv!iIOz81}iMg:,jQH7RZCuYj:؆ \ֺކXUϚ nƻyfpo|;7.nv'W Ѓ۲sZWO<▅=kI8Z1nR|}%9Cr|8\U^Y139mO9[Noodϵ;,(uOuj Tzԭڦc_/qTic۞fw:ؐGowl[h/< ʕzbFmxgbX\0Q_ U찣:>C~>@y;Ԃ ~cX;׽y}Z҉I;$]q/~!WAUzpB^_wkn>0Jap%]k'0-}V+5p1<B1!8PPpZ0#} X+Gph[/ekg yMpp  Bбv .  ap հ ِl V0-퐲! ,*f H 0]1.@E oAXD:ȡq`ʁ#0P/ $%%L&g$ȤХB(ՠ;"L Sizʞt,XQA*:E!,:SZ*F׃ Cf{3R!}Mb?PhEMCOˑm(bȸsM2xBS5{5`X`D8iTn/E#z&KǢH@?܅^P 0BOSQ!/M]Wg.7ZD?Ud!z)a@jQAF"x !F(x|UӜ@hBM "\ hU@)immhP!TAHtYuL TY 1l:T #iP7LJh@8Alւcn48!A]@8AP)!0`"PT@@ZZPtyMQaѪM=KgӰZlFzlJMh@f?d`PM ,쫇=P:DP@apJжyӹ:{d!BJƊc.!<@$.zO2qb$S99y$ 1FE]A~lbܘǍ|Ӣ °(:F[8 P pAdA}N @!TKP8Quc\Ժu{E)= zp9f T2$?x3<~YY. Oe0lGY건@tۂw x2`^"eA夀|И  fNDhAm M){h4BeKNLΉ(dϤO77OU#ԍaL+DIzW͈`u2iA&pDx& 4`Lrt`)W3 @Q/aBGjm @Pg',db T=$ (B-B)> 8!(7hb-t,pt<@bahD4hHe '8DR&B,`I͕7qF0q~Ɇ ؒK ?(3 QYħDs㻧E a E!^18"A셤@Q ;Y 0x`#Ap; Oԓt #9.}h%k"']%չϥPhmLjLRvSTs㝽z% Ȇjy#Ho zhA{{DA7/~3?'ϳeB@g6ݰ)AXC` lI %6X K44Vy=#YZ.TxD`@F `s, S$aG (Tlh@)7%g/Fm.Og;H0T9!Gey2/ C<=̍ʼhA Ta'Y xYJ-\cC@*2]E'g=k h4F_1As[534)[04L54V_w7:Y 9 Cd7!fAy'56#^x)P,y1=(GB|6sa+p @`bÎQgAefw9(%`S8V%`?i"0pk @'Xe%ydc?j!& ÐS X)pAI@7lM`YYT1Q(-rQCd 9$+ЕqiS Az_G`F1qa tq4@ - YHX/dC5,DRql$vFGe@P$ $jjp<FFIupa+)QiCvLh<7Xx{pT#RoS^h4MTOYw8Pa*D757 L`Yy{LiML(DJ\X_74IԞ(Ԟ/`Lr3IYbH9LNQ)S)lDy9!U0a! z-i~gdVEY0pQ2l=c!BgRS)Luz7 a)9`m)Z=cP!QV̈UPG8~ Wav XiAWzSBXȩ!a6z 9 l%5!i-uY40`c!!-35Y}⠓A[!!$a S1f+`=E3^Х6ڕ4_ 6 @_<`I7HD a* v1^1O4 /!=&CFN[[2+{[UwEhq`2aʝ&v8pi8+ C£!7`A3CVP%6BAk+Ba)d7[V{3=+9 p ][_r0ePd/FhpBW{xi*A>@0ʁ$Ɂ lV$VayiE P09'MvB$iAѤ;}s n$MghK11FFlÆa2%QC#KpR4D1;nFn}nǁqq ! PK$ {~n}O /O pD q$p(FfADgA<:ަ)D@ScrPW!%p`)W pfA,! j 2! #ڰj a ` Av-H@:'|1%w. Y -6= cv? `YvqLRM5AGR+U_ f:mm@LbR)d y='%p ǓQ(7|' Vӑr * x\Kx`"+ʜY/"HP C=) yU&G P `aS- Ϸi8̀@j ltd>5l^,Ӱ{P5Ō3,]/жE*pJ):IIcSwn+t 4S!Ւv2,I:[+5vrh1HR8;H%oFVQWo]P$R@3q*,cP c`1L*RQ8QD3[p^&-qԞ9 0:h2 [6#n ",i!4F)!DbC&) PsuCH*[J-gU04HD 7:0 6SRUMʊFE*)|d܏Q{SLe@sq@^`J 4]7@g2>;#r` 3\{RQW=a;SHA|8o q( u> a9)@t <[  'Q`lP  ]%$s A*$B;Yvthy+ -0Lu 5^OT[)x|PWP< Odq}_p  D1FEXvnZ xIk[voI95)LeGtyv gt0qf`rS  ́ $k.@Ɠ a[wHR)Aai->{]K:Tť+XX^bIH+ܙp9i`ehWODN&Mos%5dpJI軴Q; 6t1?Kd0Z*5h脴fC .!B>vM`S5[]Q1a2 FT`:gPծj7krv=P!9hR~@]q@k!w辜.H@]PRŨe*}:Qm8❨w]oU0-`d ֵq  ΐ1@~Bό)N{+W;ktxu5?!wW ͳZN&?U Lk#&Z@+CHEXe͞E V_~`Sÿ>:1` 6THÆ;Hڙǐ)FN>:3 ~pPfF99nCLM@a%v`pj~VY;s^|o^}Q:+-Jk3|j d@!7C?xxIO=^z+_6RaiԹ@HkBhDkLܨnj2J)\]4jj.)0Gv!1,+H{(ƝҪ&p&ؚtL?4P P@nPA= 8@4:  A "54(IFOE5UUWeUWcF̃b!6,D DbB$ Chc !9L #)87zaكhýA"rbLa D 8HĖM׃ĕjY-j7"a@3I>v LGV3!xWx(/>s"8 ER .FAįƆa-መ)G`դj8mzۭ`r^&FJXF)XpҴ)o a7C5 7X\"h6:˯* 5 8`XIvyW~UIQLx:!.!uJ0X&FKR kTSAR> YA"P+ 쐽}BO8$FQխ&#5%0i9n,m#\hJQ`n E!b_R`"D7@۳B()E$D$hBc@~72 )B:NA@{*д0a? i SBŋ 2/e-]Adq#SĔ0nх2e奒9V2 Tx$"М q )rF+ ҅.M^)XB%BD(|IALfj+9$*B e =ɒ-))dy4A8)c򛂒Ş[YygDSC 5,, ^hAO8<:nTriHͥ@HD⾈aD0 ƊW|!G  `_:8FH1QMAс%ՈH"]@,a$Bh`#2asQ҉gB㓪A=! 6- dF<{`7B."? 7!"0TUbip*BPTypO [Vn%rV֍&!#9@lH r:"X K^E ph)زঠ'mJ,+IxG |V mH|9t`I#2F?;)x 2re4>QN)m $Thc# "/a5|:tt)yT!@=@;-R!%d0Gq> 凤J\C+ft%E+^4C u%id h+>VIy*"QIP7%juc#EafGBh`A&0ܥy*/lGG|?C_* 3괃FL Y 0rm!)>H FdɃ#oҐD,n#e!ăhӸfa\ޞR2#dLM!j&069*[T1,St!DT VpT $uj r!P,G0[pVF.NGHHEKIG8t UzsAi#6b sF@qEMĿLuf4e7 aX$UQ)GJ<@8TB#%#P?%zf$A@ =rؒr-#AW@DyxAh)$"*psྃ=y$t<*(wc@(Ҫw D0$p1Ex٢*9 Ɖj{$6 Ёf8Xd9=Hq,ƩL <;3 (f*6DZ/Ap+9/TJ 1Q@#ۈYsX yyTye{:{qPK$}䇝}Xˏ89!15<jBLz=d pIȼXL" 9 a[B# 02x=s05.ؓ߈GbXd-*U6B_[:#y$P%s#A 9B$Dȡ&h%HPBЭp"XlH"` X P& *? $PC@1 88MiOt%iK٣ȳ @Pݐ *@QJnBL4Ҍ$@ZV{]& 9CDHߝMXpوqFss @Y\{+:LjGjRmPmt GeG@[L nCWgڈL1:6sȐ􈅃x\UA:XقM6d9d:ȂthU@XU軍z~9c_my(?փX:c=.iVc2{~X}* 9Tr{T }4|u<ז`cW 2E)Poi ,b Z H {'{2{* O  >(; 泎5M#h ;S p?>ZLgq t { H?]|` u~Gӏ@= " "z JSFk vhA Zs9`z=m蘆9䈹\*)v-Cy8w6#IA! ^ 솼2+<qGP n qKeЇ j|Hyz=!L@Gx @> N İLͽJAl|#8Wl>Q !) iWIB;vF @3P.nTEetUl>QhXK>oA sl[xQ|ٗggz pl +AN;ޯђh@X)8!&8_Fahr4'؂XM8M"^ ,]maXp[n ZZĨdAtzw፠Dl $X E`,2,չ1oぇVʦ❃؞y˨ F{2wV8,!k<3&W8ԭE  sY5-X%0Ȃ!mjptR HJP 8@'?Lp`_0a,)`yx{m2ۈL}VESXOhK 9fנmf54@Js"H󋇐vvŵl h%t>t=#:`8O=` yB}Z #:h#eDbwX#O( h#jf%Ѐ Ȗ8pCE𵤦x@m $KFi"02g둎'pj ;Unٝyhez~xFgiAUUrqo[oAզ_U9PRݺ`Jհ)@ T) }uXBW:<+0((Hsق`m!r3h x 5kwJu_շs˄@nf?̯Gښykl[7fCxNOt[<4^P?kէAyhrZv]P}=c7[5 hNv")"-_hjO]3~10EbCUTBEBBvdApFB)ٔD\ 2!8%O,5Mf3d]2%M}21V|QabTNئrF:hl X2i2?%+%FPP7P?)BO 9LZ=zQ?#NvL?LªX?@AvɄ*CD@Y@Ez\[q^lAo9 #"ʚ!Р md!,1!$ԆlPm1 IY&p\ m!AC3wgCLSm?L':5Yf\Yf8%CA2ZkBvgX g)Jr}ȉ+8!)[#dwW7 5(敏ޡ=vzޥ>;)R5 x#;gW iU # ?W WG|֮xsAv={L5g92BӨ34D6m #g#A7 ]m :8=`RE]R9L@N#|A\CV:p4)T bx? U F2L!l"PT$ ,A>Jb!ժE*YY!0DEp ܬ<'g6D>`q x;# YNfGHlmߌZ@ 9 lSHLE:>ID~* XW;n(:hq#`2 "H`D0/ M!X6 X y c1D3-'Hd'$06ZA1۟ #KcO jz3"tM_C,oIegBP: ΅i@)]# 9m[?s nõ6[(0K5C "nΜDpq*Ut.pE ss9u6E m@RI= P y8KV@5 :LCԝt~NZYAG%{V>NFn6U`VQPe -Bu zL|px!IIRS℅h-#ܫ] ك2@P;FlnC"VA3DAb&2L`hdԘCDdS8 -`XE d%"g^m'&;3LQQ(E&ePPz}O),U !I?N21+IX#52U?6:I'f'EqXP̝+?-D+I,ә-D >, ?Σ4-c`Ip=̏8ۭ[AL9K95DAC0i"Xٛy UɄɜha5"DgQG)&k$# !k%YSl l@t MAp PMAM}M<`?:EPMn|#AMt( ̬ ٠!%] NBsPLN`l%Ę]~xNtD=ZT>O9Z>\ΥLN;[vFO (5VPDq ?4 d`cD!5nAfd]hC ĥ$JԊՀc J \`CP X :F.oYi>'tFpeoblLT`Ztx_xrwڐ P䔠sJ'}֧}J HVV_ާr2e,LԗɄD`=^AEhQ4)2m 0@?˄9a"H. \ JCl†]KFFC eQ%l(8DFh: .B(T")Ze)BTƸIB#BL0/OgB?Xy(DA5j =yNZaGd'Y=dHb9L]TTA,Tg B "0$`+ȜT9XCClOB$MuC `2tC\?CU:Dzʩ$BYUV)@L(0мVC H-(N`vBPa|]$(H]D?G!KYH+h( ު\#v7`W4d|H]r=[_,?$4F dTmP$h? @wXPf FcJ8I@FX # @]mk`y9dX`Q6"W0-ڞv`Hua CdG؃ou(֋,mymkD< Ң \gqǤ#V̌FJPd`JBʆ.ٍCZ|Xn̍B=(QX)`GdA2Q~‚ i@\!DL i CX?4F3:T⯩ʵ7 9g: HqpA8ĵh۷X1?\AtAXXD̍LT,k?Pܒ}N#x"*N'RpF3%?vibEPD$ӡFwD/B4}Axԡtm\BHD,0xPH ,@ $H$E#GH!Ba)C%  T@T$/MU@ Vt8JT3"DԭWbDig-O ]B4BP!Pč^+ lv.?ǕpӌԖ5c<[Ќp^ɇo`8DlHlB "G-T ujgd(9{ VYr GbJiʶC͎EBZp`# żx"4ӈ&cK# \#в )&hI$v(!jj9@H /4ij@*4:.-*Ƣ+I )<-BSВb-> !oNЩ vIa 8otۨYeVwYA,!k4ܣ]L{ٲ/;.AsFF4nQ%TċVL9Ӗ@E(IսXH"<][( IwiZ%j{(eK A4$1Rj(7PL+7h!3d 71)U9 NihM#>޻\b啖S>?Z'@h$P^;D5Q k4!pm!1x +"P2 YV2b}!H G0"u*6VæАMp @L 7DD,j(VJHԅ[V3JX`K(G QaOR\% WUǤew9&HE+T#!I$ɋ5*$GGA! FLFeI-ߑ$aKYΒް 3FLf-/ILC\)p[Nմ5cD$\n ҆&uvΛQ#Q OyΓ=O}c  xEY (`PFPAԡ#rD#eKe!B2m0C.AxFSїr4)^Ut$botjIUW@@j* g֞ctLdD#lHҸ}&U)TK58*8j2ꟖEi_#R]4BʁnI4%#:"n` :`l"d%dPFJFH8"UPRLV,G޼ Z'fH$`( "N*"LAS  S$,bLʹ` 'b!4`6B fh ΅`m l`Hf Ba$&t`&`ϊ(3Af)db,hp'hb8fb9;I$i!.;%Z!Ӂ'Aгxa+?4@ԁ39F|;t$7ܡ< C$jN@IDMi]_2%(%2]Hb `Xr1BO4HtH_V/z39tI'I 5 JJ/$rPx*TA \D QjtE& Hv⤖$n="ʡqB%} ' Obv`" 5@>52"JKb $Gf/Z'I!AS{HFuBRD,m$6̬$$[,L@,W ! \u$@y!q$4!H,"@l%v:?@"~%Ƞ \~aLFT5k@xBOIB얁&d 4`ӫUk%";S86FZ_8p Ad tT2JjC2$@2D@ H&` @`$. yQ-\G]E"XO(G&*`* ךD&&p `MJ!lBH`FK[BF" B$xHbfys!jHXBd Mt"G9'#`&@MݜsWI1b@S%t W,.&r+wr4#hIv͞h㲀F^jy"Tq$ H#GBV V\A [0l1Xa$7ȁ|a17 ȽƳ.%o~&Aaf7 46x+GTn}TP5#St35BBTzE-BV%@œeE WFp卖pJ-%XGo 0P2>U@#*ytBFao`LoDOFBPNHK4 "S@Fj)0*SsX/j΀{y&O9-f.M̶y2 3A8Ր\-ti(0 ?.%0hb2H-]\ 4Z BaBwkP \<@fmG 318A T]*X Pejf˲iPI0fdP5©!yy,q2N$JJbИ4}C#OK#!aAIIb fG R4bo°eCiu$,^ 1u6BFbI!M΂ 4@DHb# "rXX<5B)5BAa9$XupIb"jay"D-4Bэ#Da*DE?AC5Tb$CMtI&P]M}$FzGNQah;S"bcbhpb"ցBػgM! E~[@Zl=Mڭ=۵=lb 2"+L"`$5" P #W!,Bdl"b$~!Ba8)".kkMXY*`rS$\kV$ƙ$x dUk$aHYA.x &GBTypªL uu%jެ@c @`1):+VG \5@;0Y ~E*(hyz (A#"b-BTa*_˹@ jbI Oޡ@  ²t`\ $ w )$eUbZJ@-B "jd3 @V\_X1@6%>YA#A)Ύ<B؏v5J"c"" i@ĿA( < &GB>hljDx2С0F "C*D$d Ȇ2e1'%c !ǠV t"P@l.-lPɔK{Çǐ ,hz =3d#}PR%Iv4ۡV:(]@1ċ?>dI$9r6 aʥKGB?{?'^ށpeɾ](I)NDBxRT%etyl r^aO Jرq$\Iz)f /HH? L / NDDND4 =5LS?[:?B?c3&A!#;:C1%CZ" [0MC Q[DC`KBh7I>8?[uG $KW@ʫ'iFI;Q,~l?rBApI,L ,yR1"0O#ѤOSLA^Ҕ1@I;V3w*E06 \@VPyrEyŦ7`ӽ bÀ@#c#6q'WbAPmJA=^CY6M!$UWZCZ!\{$7kaP1BDx!uio|&8e^Ge`aoG&TwnkAcejj'{hxPn%m >/D86P ? rIJ=TeVșM\\T,c^)Wxb$YD.3A,5S ,<(z젧59dTtއyC1PIp!+QÓQ%QPc#= E$#QP r0i@ebԦ:Of*I4Oze(D)Q1 CZk|" VUy%%*ca稀x$g+s3 k'Qcɘ#s픧QO`nbWi%(IZP\R0eP"M*xk%#]O 8`Ř(-vB,O{! v:b6B&!t2%~Zz =ILm;Z eG&#:E{yS( шvpE oq(c JGK8ПqQ$Q,,()x5x,+uѠ UC{,56kuLR1(t)4P4|̔8pGQ  򘲶 0dUP1?ѦuJPq,|-,>=h"Ol#,N<eX@d"H$$pfPDy5k#OŮv+kko[G(Hwv-.űEhf#O{‰Ԟ M0"*b/% s@EA9gmp?` t%a10HևbrGPG&(*qrL_"HX_J$b$gaD(-(so `Xp Ӂ$2nOC!S" K`Rn0t؄_eo @ &}wV2FK8$cBT|7a"v@tAZ&?L l1>U ˊΕ0,!({1J " ( ZyJ6P#) ,R+, Kpц7ǫbRMR5E7/W% H_: 5 C7x4];c7F/Lh" lA)y}Dt; ER%/ ɥRmǁ":l< ajy޻t䁒xChrP(9 ]Nԃ}a'@{K(~mD - t,-:iydXJ)/qn`q9$=r(Dpl$1g,kQ && '4$o W' bk %hhP&9#V t la;D' ?HFbql; 1A@HwC6/ >ӅZHBzppgi@hk98UTVXpW*A!I -u0>5o=A=1B" "35'.Z^vvffR/00 (W?P6ivЊ;r bJdQQ2Ш=h5 0/0È.G-@0Z}4"89r;%*XC~'1,M> a//c/$ |BE<1Bq~tPϷ91wv[x\73Lr8.exoH#se)JcY[d6sO7@18%!P56);f%.ASn'a'$x Z>65D ?]pWB$ith u&A4@cs.2yA!V"|9,ҏ%ʱw0]piD`|U5qGuXd@(l8HTǦtX9cR if3 &݉$ 4+8UT i44X$1=i~b7h68f m)R;JI\U AfX%Ct M =t8%{؇y|it[d~1K'X='Ii'Z'#Q*$ AK)Rh!r3!,1MqWj>[6poy!1p1_L;VՄK;MwnJtЇ7E9u1G =i1[?Қ7K.e{KV4BL%2)xCvb*EƸuRٓbAdR;c4nLpI02M@ 1S;TF[rkjhgBBwe0O5Dr%Q @l@ `m&;CBZ ` C|` "pD|Lq h@yhdK8\0H ASPTGM h Qh"rhD 9moA8:QSg?8P *2 Po!Y07\ kU@^q%A VAvE|%X C!I0B$" 0 76bD$|x&0/ WQZ8@9J-wgz1w*QQyavRS?qG%Pd,fiȾw }!Lpnpw\ 36qpQW+3oʡuq~ 3yX,)qݳ:!ZvD1JqNa}dN!qOwW~*}'᤭$)d=:V:)64;5!p4"0bB8|[W'H3-+dv3d&qX3Ӥ |Y5 PUv ``0 K'Q;[#M9##\}P(h[&>B/֟)"L:Y'q*<44 EE)`'=dY ^@mCTU 5?/2 Cp B[tq%)qSd #dQ(iS6X2P\b%Tc/s6D-%\ۇrB@PP`"0GV_킎/'4.(o{֏)--$-" e8. ,-9A4?&* XҝyX;6D#L=+!) $i%sA}<)dZK6qP%٫"[<~DLӓCS:O&7` )ts^}M>sl_0AsE G@  S%qޘ,ABB-'!b0W9VH{B1enO DR3Ny2B 9C$6mk8ƼOwl@1B>>]"ҀA7x BS-V=/o 0 eRAri-#zNjP5EÛ0?-m ߀n ڢM%Q'0ˀ@p%'aEyB5OT{"'Yʼn+ =N0NUZz|Zzۑ94M!4,TQ)P >c$Uܔ$TUO*"`|1"/1O~xAq7)4JCQO5`Ѡ=ʫqս*k Bq|EݠS/z 0'kX/Du<7?AQal]A]Bш%1]-u"#HPN A(w N,(E鰱B nǔEL#MDa(E!1$d)1â|$ PAB#Eu>( TF%G` HYP3Y =yD\%_PHqG'¤pkardzK MEPRA3a8.볉m &r(ҖMo;/\pE(R`nY l0iN?Nڕu˒Np@)wt:^R8@t^` qR [RpVB-m"74 H8 Rd 0*Ъ: a2&" ;,D*HHȋ C (,U"_a vն"Vوh$/i*$ꢚ  ^wB^: o/&] a$;)@X%18@ "H7\F@Mzjp߬Δ71j~ ,Ǯ(16xmd1X*إ'`ש%ۭJ!MAs؝ 3|m}{4*' AJ@JyCuVܷhO :r6/1AG~GʸEXEׇ*hO\5=~$'Z|'-."'(,*I:-qi"_B2 4$L)XA0*lC&R,dp]bI(6 xA!(D6|$s)`>d# HA $N2a  2SN&b8!@%A ȣN QJ@-uQa8eS"Ih:b 4f@x 4"Kwa$dr,%v K,L&(`B$1\ +3\qHC:04h"`MA `X9H ө!0EkTvj=`nʒud2`>VTh*+Qbgu4ɜT#D4*)I^,riĤVcA䭗f0 ̼H9Ќl Ѵ0K^__QSD袢pGI4!nuiThH P֍"Gdyto-TsI8P#msZvEF=6jZ7&F/|3iK4),iH,HrRs;i,A× 0? s=tҜgi4xʺ9IՉݝqN$Eǂ'8RND\o"[`@ ΁|КNI8 QO`X8aA6_Sr,L3L* @Z b&!)5(M-b l"- YX`suYj1"Rݔ: E\k @M 0u[b4Xq`1A8 Y@t|TB4'ڳ U4G4-&-Jэ4ZPF $Xމ1!L%+j\Z׻us}JDʝǂ1 G)W/(cv}mlg;SZȨFeH pa'`ܯ6@C i[wP?S"U'w ~p'I 7? }xԣ{y(#LBS$Tv!"SA訧 gB8I@Zq fqWiU aY911ܙ jBcÞA Oѝ);2zg(0YqK(ط Ƃē(ӈQ XiC)P1H,I3`9C`! H,/ЈAGǩh M 1#YǬy6G#4#b&È#GQê\7.82: .k2×,~ (xz;= ȅ0Ց!yL\ .X'Eh/@M lp1  򑮔Rp.) H=)@,q  L 2Ag|‘\ T #,0wڹp7jm(.@.$*"@h#) #@" ih1x]H 䞆J n5L,e5Ѓq= `ɝ!r=MU)HV4I(4 "H E$CYW؂ؤ$G@&ڊ ,Iȥ")(3SL<֛$X*&^bC#jO/~$k6D XTkkH8Y]WqwFp&HJj#h-)g͊K @4X S*B}53-5*#Q'S1 UOg%al*Rۋ9MR0U|sxܷ)R$7-v}^Ŝ 9߫j[ѱp3'nSGB+]W9byb'ͅr1z&3}ܙo^!`_誂ȪUAz9uQRUQHx6ȃ!՝8_n_%u_w/{)O VhMOĸ|d}ssWd1P֜)g V}j+sp p%]oA^uDT䧈:dbNЖ!O Љ+/pɈJh.@ɝZZ#w%qMc,!r#$Wr'p P/ GJ@ D)pa Pz6!D $FE$~ѡ=8tgR*0aQ`,t鿤T" B$p(Ӯ^UTDX!X07GҎW\qM@I`v>x0X a L ;Kbĩ]+)` d9ׂ' "tB`!EqD| _\JEä!ݹ' ^ >,eQ/e[)bp x <7WRzu( )؃^u^e]bwa]iblfЅ$w.>8@Wx8CYG"$S}tLYRN*94h%P0Y_cYR#@ fBp^758&4p٧CvAgR",:RZJbp<)L8$?!RT@n\@ BB7Tti@-p^+lDrCbDWHDz L4 M@wCEB @j@nQ\M.? @ PľCL5l+ qdzH$Ӂ7"B>l"ZAs @{?t(E0DZv5LF4J!3?˚( C0/,ŕHf@s W~ ?жa@?`6FBH+8 mQH$! XqVb?6i KMBҼmBɼG2*&@dQ v^ @Ӧ$ jT]"(P"`"#-`?'ȶJ>-AhAC!C2%)d7D(D9^I *RAtCQ"A" `"8̥. !U(SRǭ#D=j?ECbs(8X9 u! 9jЀ3Fj(pJ0Ԕ2@@҈B DDf%X~!`1`@~G>@<xxDF`6HT'n;F1S: (Hn)!T&$$dXS,D#A`e%A`bcQ#\U&U$D%#0H?%efr 73 l8y LIܑ(#"w6LH0@ϜSB{ޓ2"*@O}) G`zDԹ,q>03SceT6!h'X*[Ȕ )utͧv56^qW>IRկfZ<"+p\9MXԀZlK8j܍5Ti@%hkW3&zLiAsrĩ t,i:\ J(F$r@trۄTQφwϳ^PU qhތ>{^HM6 *"`OD).iB&;`9|di9Tse*FBb)"G1<b/-n%\%ZY#LȐl+(B-:"DB!|F#; 2`<L |[X,*|A ǐe@Uywbg뻅nt+qe!"<b,:BE bp1H, ea rYw -A'm!#+~x2`bÞ0i # @P-R3"p!_→cqbD©SHB3RS3YA3i7NM.Q%K)DHY"y0'.|vrhWHQ>t<d@yTs )!zȒC vH(ߥ}I)@`B@&8BW D3ڥf2Z@K"K`g6;:2gu9dVt$7:G(-;&N2.&J>t yրxwnzޥCBxd;kOȪ -g? &8dB*  JyaB;.uHjq.>i;&sݪ~ tˉ`b]Tڥ!'WhRW`Bā ??ЗP_z&TkMƁULxHLGxFDy9q]S(,EXVlE D78 D5]ܬLK}LEkC]*Iyz -DL4ʼn!UHOFpGfmԡ[`$^(HJZ!†HaBP_Fr[ D\D i,EiD!FJaRVytXUpNaRpalX} HD, \,6`yJmJ^ȍ(֊a}E~01~3ZGX#yH3z7c8Z DlMRB XB8I ) AWܑPYDA$RL F,E_ A0NDDPNg `؁+D?,(`vN9*HP6T5WF LweND0t:`ô U@(\D$Ct@XBE#|Q@XiEF@vA@vLC=D D@(3?CS%d#T%?T111Uvz QdH:5ƱƶUȪS,uE_+ (ɜ]z 0XXIoy""_3~q͢H:pF5^\Uͺ߄\p~bWEj7"Pbt@{ZՋ!F**0_h0^#73RbHn@qa |xB!fG2d~o/5*.LjwR0yEvcykCjH:&T@}oywEXC4`H{yTOڝyk9*rp#z?c?AM/0 {D,/ vjƋRA+֐1 9)ee$ī],@ܦZ:'"L p(r5XaK=,ac ҞfW&Lfl9 ެ·%,g X)(PHynA J>)@@0<@ ̡\?$}$bA>0T\:ZA#9 @6 (-M@?5$T\b ><;76ŲH &G#iD B@p {AC`(` Ja:<P@ ATH`*|@&|PA?J,Jg pHjTS Ĭ3B5dĪc)^$;+Xj"6`x.޸z W,B&7h!$R;+W4n|Vv`_F|iQETZ1/c$R$0$*RX&q6 jAPbcH/iS'YpA%Ef1`c>;IEw^`rRX oTp@l@Ǎ=khR0$ü#9-J$aP"rÕD vBho>P\H`WtĪXũDzE \0`dcWV{*H &a |Hb.XAEERĆ$<[ #K#Ac!ps#(/@ {%h`tG=ʣ&eYSӶ!njbGРFb(>KЧr(UC>Rk!D:s X@H 8b`j(!C:#e8L+2􁚁KRJj}٫&z++x! cD YkΪ+ ӯfvhk@I@,0- ʌ쩧8ty聙**R+Jt8 Ī,J:vaXZVpg,Sk`jVq\Uy^W@'?"la 4pW>:pDCTf)"6D 0ف0a@mjV+ʈEv4;88 'f py$w }DVKdd,5": 4^ߨ@$ 4JW :-!Qs` {KV~P"pP@$ dQb/`xÖ=#T .>ްv{woMڕX-.$lHI+MƩD&ѾnR#^IWR:XF2a x>@hM p<_@2=%;ߊR p@Hgp4PJr&1{BLq]F{AEPq :pOXA;Nr Di 湲y)FJFr;"Ӵw\ϱ<`ԩо*T"r?/4sj.-)XxbW"Vu@#!ADVP.lß|CUVzRCE ;o"yrH $˼@bìV+c}a - 0>mCL$؄Bт*0>֭zcM"X!DhGLD'cnP! L_ge_Q@tLKeR$"aDk &` h ! jA~K|g :#fb>B`c b .1&!@>H,Z j% U`Ob)X,fͮcgP^"bhwaL]1E̜%|qLb"{THP>!n1e1bb%vRx rFs !.8 g8p @ &FPr ~26!4|&.+2쮭RJ"z!Vrkino4tjfjh*#(DI<2ip$"jn% q-ʲl+j:lk63>H8`g0Q"$ RO":+P\bBh)V r$>(86#Ū(8XIfⅆo v t!G_cڥ!e}ƧE1{>0 ͂Ġfw9vNd=|fUp,":|G!tD<( , !&h]4 #F0ނ>2CaPfp"!` pn7x6M؂ o8>TF7B ՂLdd '-%PS), /(K @ @ *;(`z4,&b_F&ѭÚ8`ix]IJB3nVrΌJ,D@E#O-G\k"V&5J>h !l,@FM5*"JwB 2IhMCTec6*V͊rfU(8Gn3ZRRί$r)ق 2,fl2KZZZ[U[[[\U\Ǖ\\]uź"lh/y3*/7a$`U@rFz z BP !vDav DؠS@kL vD]AbH!0>eKxM ^ 0a>dLAJ"&@ @'v t`Vf$kD Jd,k_F6&",` ӂWau`i]#ă 5/Rq RYZi*7DM ~  !`auo$B:T5<yaBZD@}@m ܀ m(,F>`>s C3J9| ċAt~ hs$fÍ$,td C5h#fL4$CUPLޘ#=ڏXs'$.$"$,$,-nD6f}b|J+`B#&+!&$Ln+@}Gu$„!h$(F, @F"%~B uN#@$!)Ҏ'G"&(ntrt:@'>NB,l)2tN&$ofY"'댎8ߪTO#VotY"O*R"+|t:gI !T1PY"{8"y**:1"Z4,C[GX 5*z7 >:j6sk"]4^W( @ bk8ZD $$`A`Xb C ·z{a!yD!ėC~~Љ `<8H9^b+:hćC-uils B`03Bdh6D?+z ?a n-&ckΥgrw"x.{$V`|P+URBN;1VqF"N@a..w &@AZR `cd!1!$` UĠqGKED_C81rL*] :^%0C <9t dqGk ™2>vZhXnkR&\p+)C'Rg&-Fh8c-_5+V*j@98x"Lq|#sIj`2'V+ljp?*iS\b.5%tPGnNSC,g 7#2/#"!5 {"F`ox)O#G N jvH ¤E`^#8P1?e+S@],];oGi!%w-һw,["*;V^ %y!Fh !*#/-D;DI!4R/R" }z"Zu6G=Z;D:rUxbA=qZyz!=ZB?W"Ȁ +`v SذEC:TT+zd՚B*+h+]"-tP/# 6襤i/KƝ +],d_I%I`/!%h4B9œ8`0j$8-p6 MGǠ;W$" ?qLCGe/U.vb>!"GcDլ:GjbKGN-gX2NfYcN?Y4b}Jc?ӕ*SdM>t_ux&!G_#J-fRןbŤh@Ɽ"jP "!N-*\߄b @! 52ȐFÄl62L 2! 4?" Is0̿ DweN30ĴP8! 1EѸ )0ՄAg ;ܜpL7DBhy$,$ ?ܠ `ƐR!X& Tˎe٦gfS$ jQQ jX}!Mԩ[3 3!ɧBꃅVu*GR Bv= ~ZBүB]gf{:jjLuOaHUmME6dXܺoi^}z..;ӗ@bK,QkkYA4s_fz+ k\ M25[)l6eڗJݕn[XÊ54-4-YV11>Lv~q!*{҄@ G rp3،8U<[rg|3f<q0AK@"(wNǶ!dptd-7mqBuțmwCĝ|' >.҈~z,C?/D3zlW'/+nޕP]Vo +ݍ]aG;N6gGȲl8bYъOx@/(Z5䳝gfW{7{Vd9i1W4͋gO=".NyYbaB>R1 BHc/ XT#DŽ9D~B¿X{G_Rg_!4 >L-M;Yn2fN""b )P "! 1 $zuF(/a 8KO fuqAz V `"pzP P Pу|A@Hpg#=q/җ<P}U}4}tD@TDc"<DKOg2>y~4p& dR{iq AN /eD$Bgg'R `tR.CD8f'C'*HSpjXH'Hm'm"F0#og.O 1 ?Bw p`h J`B6 gR 5D&)r @$ѐ7!  @)O ap*r)q! ^U ɠQAZcG!F 'rt 1u@1mdnP"c"&kb&k+4,rwxu-R#l( /u(:uH77 Y;6;CC5!.@.SVٗ&c30r220n r2r!t Q2QC#5V_,)!`0 SQ9~#7S0"$@MS224zK"/}7+/U!6ʡ4P1s4y1CQ92 3..l22#Ŗe0P/swv v=iYo'u:FF!;`aq Q-8s$//jZS4?::- $du34SԖNx ў3Vѥ-/ yђN:#4gx<Ջ d bJjx Ctz1Tg>1btҧ F*. &&C׌"ROOh P@p A QO"R 0  ` L Q\ xP50`}'!B & 5ZkZGBlJy˧ 1jvz&i+64'O& q1gӁdW@K U70QUu ) t^9<_b'Y$( !*rJK#aSS[N&cG; W>eЬp  UPPT+֕ ps1A .&$PCb"m kLC<1&+?i/!kWM֧eg&4o TBNTUhU=[!U`'8\ 0WV X Zw01ţS_34XY E48>1Cø|.8OúdeVbYXf!WXZƛ :/|'QPN? Ab!᤺J/vc _;|/0 c@+afJ˴]P \[J|t OAAa UQ6# !+ESE ,^^=bRaa7sPp цN5c^A5sLm hmK2(,(^/& |~ pGQ7qt ~xصʱ !!nB}p xĖ,R . 8:y (O6|'CyI FD*gȦ"O!+U1'JBb,x0!-  ! %b#"9hX#n#:o J1 -9t͸|Nj ~(l21 3A _4@ BXK2otvmL#(5^ *:'jBv0,a54#.c Zi7S"PҖ\ɖJpi[C[S7vs616v@9' 22/0#vRiB0C001l9SS•$ (r . 3:c\^kn_שV2B;aE/s0-WX?r7 /Z/[>s3Vth@ ,&7 (męf03QpPP |KS !zr$PM%5TGI!CWTk0#Z -.^{?ta3 ǬIR``*(YY PSO*/tIzEO)EAݸJw.8)"okd9\@6Q5$/x H2UC4:HMciC !-: Žj# 3ȍ=+(;ov F"ܚTuF.I"g j@2HrpHI1+H r+/24(5Ao `4pS-"欓G )DL/:3J{B @LBB?I ,2' 4T @+=|#5J:@I&X*A! Ȓ43ȅ?RЅ"Q` P¿e"aKuh`a= rHK:dt1T JU*KM 4p؃8F/.,ut%:@2x@#(YJ!*Kdu^aez`AoIGnO (f2 .@SOy%.Vm^~|@l~`pyƓ3m rV7c xu$(ؠ1fbA*Ã:ܸ'EWH{rNh 9ݠ Z$9z# w2!QPJѻ:ߴHUDRs8#.Auoby-!Sf ܖO<6C@&ИE y(P\hjY,TD pnMS`5rh=,̒j I9DQd=oHr(6nի'Ffɵɍ n jms\Z3vj u_XֱɵK1+׺bS;,^X6ֱJe9XVֲlf5YvֲgE;ZҖִEmjK׳ֵmle;[ֶmNS #$$ >d:ܖ+D愒-E)-6_Aˑ:$?^.%z- i E4*'w]#jU+DO`N~H⮤ A'Eg=jP'i[EJ ߍ} tI-?`@4C$ݣ?7%B yaI3,*f(#"YQ ?"0Du G*a;E4-Qc'6 pVZgpZ$ sSh(/icl(%L$ !Ht( w@! +;,x ȫ:9NK `%mh"!H`4 TB9J,i>^,O@Au$-#c 24-!7g@CэmbFH+"U5tƹ\ L}@20Ktxrsti y ͚'vP`B̼%yn~B MC2ulH ׇ́ef63'@Ppƣ]cMQ@u%лTҚH03D{NA 7g})HCZjxX97]t- PG*LEA:udIb&a5-zrD&Lm-U Aj{^琨G$2#7>F*~y]5x(P)"H 3w830"*"`҄kӋ@&U؁ *_HP_@]J!8 ðhhXd ##+27P=h6-Rp%@d 7U*y{k#i6dR*$ X+X؍p? `gc  ;-T2Q2%{ɜ>sˁ8|s).";R?I# !8Î\ (3 Ѓd(MJ8U@Bq )/زl%Ec[a `D4! `y0sty` ؖ!HtG𓬳ȣ D , HN-q%A|L(,a^Ԭ,n O@ )*O*R+´ 0R1*M"1 At8}X4B 4 "84 q+P6Ҏ-| pAxk%z7WIC,PC^r*<@$X[<8Ĉ;E ^HV7H4IĂ8լu3ـ p@y"Ύ!swRPÒy1U@&TؿQOSON'ԅ'>'=ԅq&4 c>(_U{d [ QVT mءS5$K+MRØq5otpBW]Wn¾(1ȡUs- u- h>~؁%؂W`RЍ3؇؈X~]7OtP`kX׉ِJšeٖuٗYL@qh 38Kهt4Z911h.3ԥ/ /<EЄqZ ˊk< ..K͢m 3 暒!@c(>A܍<6P5T8,&@u)`c%LMM:JxUӄd (*ˊaI9*g0h(H0hSͪuh>XizX H^-HԴ30j{80:p_pSf+Ͼ$qc3 ]78 \ <]5w 3X$@7 #6DӢ_`4$ )P 0,X֟9K7qؘ3"؄H7 *@߂Q}(./h.Ik68S#(r`IoS X θŒrÀYs(ePPy C03j au3И [AÛp@й͉ĩ+#˲9c 33,;:Ceɑ șUY9He(xCeAf@:ek8l^T&\{R埑_,vl5 „LVS<䢰! b  $NͫȈO %N'U)Sev>-: P)*HVX|+ iVc7H?Zv*TWH#E ҉P?jT5 X\@r\妫ʄNJ{ lPDpP]ڂ`hA \Ƥ* aQzQ4u N3; Dn#Q6Y65$qz#'ʏ%$., hiW*bH..ַxTI(5ND`$Ɉy8cc@QG{iėw5H$y&(p&,̒p & !{MGhK-5JFIFUKƂx8Ipiɵ@>q3ޠh 9jІҲVUF̸ ,(bfJL[ׅxPU{K!땘i m:zV -hVaqiʜBEKҨ*XNDdgJᚧ.vpg\1,O8 GKpD`Iʼnv!#*[t3H=ݎKjZ.晱]<hǎPqa1ς(isȃc 6wBFeԓ!*Tx~GhUj-zhhDņ]ӒM*Ъ:QM|Q=QD6?mQ#zm{ R88V&NnXp*ZEƿHIHR#b7 8&A* ^}9/ϭ - 0濨 ,91j֦?wu}  @(ovDV~3d*{bw $:+h „ 2l 0 ;ThѠ *|%B#cMDha7 ?4p0eI2m+R:6,S_ N%-ܸrۖjұb jѬ*eW88y1̚7s3Кy5Fh-5Sp^Pcc|‡/n8d,MhG a([v%<׳ Q:D+Bڂ= 8 Hy*$B9?fSGxD~ !"BS- G5s&Ɉ 8pNc",ASAHS0$AlDlA tX? cA4bWya;DS.DbAb e#0!^&(B$,% ^8C,t",@D"Oy@T@hT8IMc3/UZ1>sPO9GIf?NirH^ O rרfsthG)4ǒ1AzAYp"Y=G2Qh`?DFHRN 2N 1L ȁKcǽ{n!r s$O\)1Eiz`lFU Ti fA yIE-$\5~DyaA^`M %E|t  .܀2Q !L SA԰4Pn=!cP72SzAt? [A?ʅP1a!lAPAAT8? @-@ ;EA8MmPA FE'ApăR.g/z>))yMO#`d1H_R T aR<@ VzjNYJV3 JND{p@% @4pY(Av TMe@TAA*D ZI!f)Țptz[dK>!i"-8dYH( )X`+؀6PH-H+-R=2%)H,U3˄#w /"+D5cXrhTVfD1I+!0W-+ja83fr찇l=|Ig4SȠ.eKRH&0l!NAHHT)HỦ.rIަCxRDHD|Wh8 < ?!\F=C47ˀ ̂CS-H R~DxW Չ G,~h84͠Hn<>(-'9("(#B3eG2 1h  `A ` R-k]Ir 06dLlc2  2"Ǟ9HwAp?b (B{`k@ z A: #( H!AMP< HLF)Y5r@r d7$qt *45+z޺'JZJ+X}1H⣑8gK7')eE+ˤ%Q 2 ď'F_ y&<%)$!9QgMC8"--* +rQ2E[R~b[wE8rU϶|٠"-EU>ZJh8F 1AzI3!MHRL 14[1H@O.q=!!3|c~`/KL  &LD (H p8)GiHY9  1ByDeŚAVzDH+r -QD8Љ쁣~t;CBEh%N;b\4L-H~A*?\ ӥȴN: -+TVyjWq{dK0fV } ` *zxN '!I)޽P@ ATIB;% R"`d1qC8X0pP2̐0AQhz$`BcȢ̋IGf&Y1Ft ?xKl1 A+"МXhlJŚ5D rL`b-<BTY)^  .cCYC̝KB@~K];84CCaݩKGWDmm>!cC s8ADGwvğX8=@Cc0uXUK甠hLB4D!HXREBu|[Ra6#>"$!rn0Ki5°0@DT UHA`?xIA<[AAEA ֭U D &?Т lB|&@@$D+zHd*&DIAhɕNIl}ADlC @ ?ؚA&ʸz|#yHJdʖVT*"t0<'^4%3DCc1CiCAC9OGH>KiC@ 46R2^(<(*A$B+lӼ,eԽpAc0H5!D͟$nD|>zAhp,W( (AAar̼LPc TPVì@La5 SATA,FIN޸eAA2ٰuMB C E D4M1#HLC"Be0@/02؃ 44?\?DA>(NzOOT\`:!y@ ep@%yi_XGMK(aXut EU'!EPGXPwIe|POKE(_|A@ %I7hBhivETU 9i`h@f M_ELFݐA4L\2O)QJXvG^\OPufyqSR,zRA_Ap߭i$`Мe'ED X#Zilԫ`˽.\B誮*D߀&BleBJ)W^@'IUR6,tBI %۽TM N10B0B3@ (NN2H8yCdVNUaV%!|DNz`B4T6hH^4¿D"DGh]ՈRPDjb?cƢĸ1A@D$|j"Y a]闘~ Wֳ _YdHZ~AA WS\SwA$_שroRT P^.@cMJ|ֺ~|?,@D\Ah\%Ct"$5H:@ƇCCNV9?Xu/,uXA$qXnDGHZ FUT P^FZHh CA$l9\ PYY $MRX5Ι*/[E|iaيTSaTMaK$dWyڜ χUOqT*{R\G#ĂɄ"Axd&/zpA.0~IJ05*BĈr,3Bx_Q[r=bSH"6<"!DAoAtA 0m 0p0r `B9+OmQ$(\Bq|k&DùΎөM@'v2@rhAZtpP[j") ""^ X҅Uʔؔ#q~A. ?Ht&/NX| 8 ?,D{U' E``Iq‡~y(G < $ѓ $쀘(m(Bҥ.vP8!'C>xq[U~w8 [n:"QnD^v ~(wFo(wBmC {h: 869 8={/kH 7τL0'yHR.]#@ʎZgV! !B4B i=GdD$ /"{L.EtC0IsȈ#BBP#(dȇU90 M(P=H?=RhW6`DIr1W0cřTeƫMVQsx5/~'NLJYho%Ѧ+̌X0I /:w &!=v rJJ)Ѣ!C P`BFh/9E K_{8 aE5nOgcȡ `!2ɢ8eAF臂fZV)v2y^;''!wA7[XȾt7^, CtV!# 406܋CN2@ σĉ">nl>H`v2;[Ç f"XHD K~ִ!81!,5=-#Z{4g?&5n[Ja**!y$EP#2ߖSC*H8+>VĊI)|bUeGB&2")z;$p%ŕ¡D2\ $*q찇hTH`S$oJaPS2BAGSl jJD z2MPYXhpu DpG2K"#aIu=HM׎V5qcwpR< K xv8yIR90c-o]qld0e6ќf5mvg9ϙug=}hAЅ6hE/эv!iIOҕ1iMoӝAjQԥ6QjUխvakYϚֵqk]׽la6le/vmiOնmmonq6ѝnuvoyϛo}p7p/ w!qO1qoAr%7Qr-wasϜ5qs=ρtE7ёt/MwӡuOUձuo]ve7ўvmwwϝuw}x7x>/O#x+oK>޼cyw΢g|MOśKZd=Z]=r߿a}Oqo.>?{={:;»˯o;MO?l-4Pp 0T/  PPP0ޯ#-׌bmaY]p$@pCqcDP8lPs0% =/ȌC o̓ІެE%   ga ː 0 jP$P8p|P$Dg@Ɍ B`wz<| WK8Ϫց,/191uG88qMQ11LKGQA^C tqhzЂQol6n =5 9C+koќ-&}kqnaPq%I0‘K1 2 %у ҿ QqC!q"l/ #L#19! ,*l H #5a$b`4SG$/"O 1 ,ء!O{\΁FS 2*P¿3Jp!?)x+ QC"}ɴ u4F7 ,0`M>@@N;t&\{LpH)pPd΃_43K;Z_1j,v |b`=Pس$W*  q"@ h"qL$GP^D uo ?YW u q@`dZ0%VyLh8;P s4!#\-sBLKjŠIA|Б*|!p4}&sɟPO@F@?&d-w|!y }Q~$A];fs 3oSw QkN-rv뼛.+c9Ċ3ũrLE@o?0R{GG`W~.(mlT8PoLF^ACQB$Tx"J$XUhH3HBМ5gPI.UM;F ܠ`T2!qb@,\YP F 1JR?`4 d@ 1Tab*:.>_84$A  !oh:ǽI@)j6$(:d3 xP?9 b 1''EP-T]6,LaL%D4ͲД H`4\;LD4H.h&D)FKx3(qV8"t8, 9@P>O,ay޲~HbW>2hy+AМ2у+ W7Pt@݈Џ2 )DQ-`/dvLe I27| f2Tg]HY %>Gn# 0C1#bIH?9 lV `%NZ*H;NBUw--j~;,WR-*Ks 0 "|2ƃd@m DjBTG=*r xC?w-zӫ^GBt\nBT@ m k? D +4* ( i'P :H)B28EPɅg; ҍ*9,#C C#~JWYq:LYUCTLr@LB+@B 0Sc>d Y]q+hBUu?[FܯH݋ X ygMIe~+ ܲp`p>r?U^4@ @mDW7QU'F'MJ{#H+.M%SK t1$9HMMZBaT RG 'A 9ْ UNo rW]h{L8BB4!2ws\ fA 4A QzNy2 Lc$:QgN(O9LD0(R-CD=#a0,F"D^u\|t#)g& &}A:p'L#N@05 tWIq󁴆sJPϒyUJ9/9H\D m@ndP,;Tc 8,tp2C81q,S}D~A{J$e 4#fs}yAz$UJԿ7=, vs?>:Hΐ,[ "3Ph3DZ!RI+R^ ?7BX#Ap "HBS*eaq ``,|B o0 P(%(L"'(S@Pڠ@D$rЄe~S4ݳAWBQx(n3 1 s+a #*p)*0/@) Psra/0=Dk< 03i&!J#0 -"6%!12.*"I%7H(ft;0o/wo.%f0U1#u8jg2!1353c5HS8%$T Sw;R#284aA"`\PS6jD0sc7>'3(0,9pK(eHFȍ'43F? )"131(iE'aqgm&k7+Ci;B3UqIejxb84ƔX&c ""(8 8Cٔ(7g =+n?fX1&*p p<$(bpZQrfs7:'n92F1?S_Q @ e`(U #}P(@B.y;D]&9UpeQ@n)N xbq bbTrC!B~[ EО!&Lb<1`(P% 'iE! pՄ8xS4Xtpc awr?tqH6X0I#nG18Pl(QVgVi%Gr0LkcW2YP ͅLYHnuV&N>v0x!I cu` e2  - R/&@% E! E  IFV20.u/R A81T32[PQP"7:cM!qE-8A%rS6&/6s7%eDZb.QH0!3/ronT=!0;%eVF`*y14Xq*7ivXb" VWViCED78@pft|d$tRtXZ[Kqu%9!O PUU" -'Ш KZJPUKdFAQi Quxrsr [ו]cW@@A(њ˚Iq1@?~dZ@Yd=2)D^Li B&Ci iP)ɞH)Fw>h2P P2@f.6 "&`4:TSvddudoMm?Fe`f$c6p`fq'5keH0;Ma;c*ehU"v6=4!Rr!"hk [;u&h{cFi-iÏ1Si񢥌3̪Bf6 jFU:.,@C,nBP#} +XZoڱ>Ubm0 WbAj<ojV mf|~\bAz`BPd$k| x1 hnF{mD3 \ʦ,n@TOhuL:;|˸˺ڑ QA t%"(;0vI#&QQG  uaY#H,g +44vUw"w$A`Μ{ ,lg *p _0`Gr7kEB szAFz~vAovl|w1Wa'|8z{ zց{Q![YPӣq j@Q0(`nѲ0  t8C<I1bC~ Rp4~`b1%>("7Q7":RQ{ %L$W`I='./ظk JDg,W\`X2p{*r 8y\X$M$L)ύ\PSPnpU<%ch `+Bd `1\B Q D6}$`}(  SD U*# Y-BٍO8"r1ǧ^iѡur= Cm.Bp2|';"#411DRw4X4 *)7a6x#%R2I@K ?(H Y:Cma-9$^"&K4B菉֒RF6AKJfp7PBxÏ8?I0 /P7.0g>0j e>d`ķGjeS9Q:tl` üB8 " ;* LSځ9Ab^m6vCF DB;?lj!l2A!'7sE16-_4aq}p1``õ F+iqb) 3d6. {=y1^lf^de\@!A{(!(Å dz?i;Q 8ANDn=6@ 8e+q<&O  ɑ #P*Yh~K>&@WHW!H+JxX^X5kY708 `V_uP@ϱs;s0Uɐ*=[pk6^J I9 K17 H͏1 pJ rU= 1[bI=Z P!qeؿbu DBPK "0Q]h_`u@=D<SL5A @=a>AMM>(̡SB 3CR* B9ѠDdiN8aرk J+7 v2=Jƒ'>yÞFc&1ڜ;rz԰#vfj߄#W0 Ŧ&Cŋ#, /"4wHƭ_/aHm3F0o|~d$QQeTK_+r OtH=H&{h<"̡$|ȡ5A9%+ꈬ<ʯ$)T/TL?(dzޫ D:Bd*XT GJQa,2LLJ5|H:6͙"N@BFc4{#0q"Uv1 ;jrjsd 7Ia_4 "#"SA=T`;Фc„D(X&Xy%(V:&-a"gn[чp-Y!GT9'mqљLcř8A@AUx -!_i69{8a`,:`֎)Xnțb A3,H >=,72!LUO#D>O3>F|`$1iܹ..6@+0yL Ɯ~ڄ꟬db^bz +oO^&J6*֞r7眩<¬sBr-WݲHg=FO p>xKW*莗N7!k>z*?cZ0f ƝGqy!7G'!^>ߩ؅`I dӰ pzK7}cA]`DL8C֐MhY۬23 ky;aB4' @`, [p"D.vы_c8F8t /X:v$3~3Rb jK@^!!t'4 @H.a7xH4* ;h^vzw887@ q0ȑ< (XDeCM:mfBPl䚘LH69E $Hyiab"}AA%TErAP'$t#p ԁ 0SLȇ=  Lΐdm8D?8 ?P4Z(H8mXfu@T $²AAJQ0!5h?!U] )+AVU& h+S%rtJ)/!PREJ^E VFT>vHV <(J7DKAkV$qƼHk] խwy{Nk:8R|-+cAd 1 aCP"  46Ť40If *^LI%:WwWœ\3oB |%+D)D>&H^P%$xCZp>*pBX> E$cӁxI. 9!w2a͑w 1V'P2L5 (@ "TQ"K%g\Nތ(9jIj)sE̹9P@gnR)2ܟ<< 2L$8j|U kL6ZրA8P5&u{QmLI bM;[m|ɌߨT6R'= &mt7@:<6!/CSgSBqh zfgTATyLQ F r(E1 @ǨSB `c89Arf% 쮮ʯGarM.@7?.RP.ç4`vPLo jh:z!/@Y53L/+J|f7s W| I@>a))H4nb$hf2 o&+)8' moOE7I( P"\=% |!+%BЛ#l$p?A/yHzC2(r)LK p="$3@-& xџ¸ژ/S !`؊-"(P H`ȉpp$x h # ھ3 /#ېBP'*ڀ TAA'/Bʘ"<BH‘ "/x>$ĩAC2 Xx7`1A"iRj bH;EȞN+#RK!:0YCxFX?(Y@|%1(H@^zX`b $ѿohȕ6{)p{7x}Ġk.Q!iw+X3#y{0:#o:|ٖK[SHƘj9A!Fx fR/HfьIȅX,H*Liȫ˺uw8H ;șxp9s;$`1Չfo{I||4\/8ٓ5ʱë(Zh1!۾&ÀP"A:Txx!M8M 9 ii Ѣ A 96ꨙ^ Q›!0BC )ѐ̵, xNI 320C OcO.;Șp!POn챆Y2wṗH.ꡟzxXP koy.GP JGephXG R> Jc ck Jxnh| !*Rć@).Ќx1 Q 瘚  5RXX+@A%BM ȂZ;@Z  Z :)S `P00MEJ@)1( "L0&OzuQ ,)QhVRH(1߻`lJOJQ֩`%X3$3 "XՉv=FY*\ t#3HH@ '챎9ؚ_ UxjPMJфdVՇ$>DLM9*(ՙ)r-h, 0 Vy2 ;Zi`x "ws{xHňڄPxHyȑ(yۭpJ0ۇ+ \ XÏ"(0+R\*b 8Љd\-1]Xc-s}RΪVI,  ޛSЉ䕌51sGI#9H 4x33!-"0 ^؉V4:( O00ᘊ:+ #܀`hHh4#@3x7[3a9Y_e"}a(a'ە3 @0AK` xa9IA3H ☠Sބ0!O{zbg ȄΎbLͮP6L 7:&A5% Ց+ HԮt TiuَY@zv$yS;{˂b=Ȧ :DX}*l8r۬{J1OeZ{Ż%ք e&ϊ#ɘO]qy2؃4YZKؚ8(F֖ z  qvŢ{1q 0G  (8y(ZhyHxNYP}5i0$C; )ȼam 9+Ql |#{SsP3qІcbp%r .tHmjʅ80l,M$V3J03: KjdžXbXuu0#@$AM8B;? 탉K^9!( 蔊NUudh+qܾvM1Lt$$n\t %B #6"nپh&4b*QB֌LT,U@¿e;D||,XrZӇN2ԘU`  Dh=VG9jo%]qnm Yȥ0c0|-@")Uwk$Ef?6؎Yn`XjsX RGZ4>҄]~9H`E!]ևsLuHq0st8ܚpf`Hh8@'~X[kf>[Ҙo.z%d9߄X͐e)Oi(jigXȠ0hEh3M ~] F7n e' OQ OAa!u )ٱ|2LىY0$̩ OxP1 8fOoj~yxI`Q P ȶ+)Uѭj{XЛu{}wؙe{wEHZаχHҧx|,<43 Sx{>(^9%꣞0bb(B" aOĔ1 *[K8~8U cp@- HZ_j+ „UcOĿFR,`V.aS hF /?_?":APtǐ&5ZAAfHc/9QFΏ4'[ ).޼zu \$B)0-$] ChPpA/E5NTgX&$PZp,H ЩeSI ?^! )hMA^}@:յ._#|b!?l ,'t5 GFY3t&KL2mBvqD"OE h $B!GHHABA7BTcA<?N& \)?%zYA U^YD[`VЛmA`Qr" 9e&LjYee_<t=PAHPU8ek!=ӣR-e)U95Wgu@f=AB$PZ@g&xa[~PfA!+@?V@¬VSVЊZOV]T u.sz(@i*@<Q2lAP@W@aj񑎺1!%A"eBUW*S%BP.})^@b(BX Kđq %BZSVuR#hc9Hb")d?( %A9K v`&0  ^ua,cr xW 'fҞeQd6^et횟ⲁ -A]wi(6-͂%_JP=Y&qiqpJӳe(=hISi|ӜPMUծK<7R&\8L+4!w@W1̣ ;"gh)?U ]0;>7: \BdrDfƶ̂$@5p8VF| "d(6;<#D&W 4&?*4uAKSMX(.Ӝ=*P*@R$rDl@_D#@H1:Kd1Iq$$&3Mr8?( ܱ @v,Q U̡,/s`ouq J^ 閺Ҡi[PB I2)r`hdVAH 4ndBL@7Ԁ83G \`\|GA\r\>D2CA@< A`WMGH?FDv9utB@ OHA)0ăxL?XN ip]ψMg,WƜA &ۀ_ Yti NBP[`@b`tE ,4lCZ4i MIɣ2=8Q[Tc2^),#T@P@ S54ݵKd@@=cTӤDFI  @|@K@JT@LXrԣI|Ť $IN@PŴ$K?JtŴeZx;dTH",=US18YAY@Ĉ S,&?<:>Tp"Ĭ IApU@K_jXBH̗ņE iXptY.n@ RF֬] %-嗸̰EDŽ0&ye|cb٤XFֈXB=S\KMF, f!B ݌hFc`(dXB؁+hPO#pAALw` FGAȎF`9G,=8AX AI[A$LPJLR9~mlA$$BdD!섊v?$$l^B,Qao)D6&DG4BU  EC1 \ QF/Q Iߞ -G.)BH)(ōvR`K!0fӧ %f.Mx^RLj˙˜L"U%ĤҸF °$2I4(HDQڛE݅IC`5axL&>SI밄&@0}>kB@LASC De-H4 EdRRVjhBDBTB%5i*&@!)hiAtiŚ6U C^QRMVfA?ĒԑQZAS\d\UAʾFtfp US$j@!=i-B?@قlA&:h&B(p~X[q6 BР0y$z4Bl[ av94*g{\|'BRu9gACw.D~ơn?W L&ThB !!DdbF8(RݳiEܦ fL0pиEYl<[[R:qVB!X[$Y@x*\dTG|^˸FX0^O-&\+D>plpR]>X 0|ɻ|kEeS\%_\P39"A1->0k&jD=@|loUIYBB@׀7q'?fBBf1\=+>R\LC=\#(Dv"E⻨2?xΜ 2)21hp<>-B̻Ec$] B@4LxU7x)1] Ic"+2?g4 ٵ d93_H0oA0)8#DqPRЁFlXp2"]BC RAIa*D4̞F^B?QB\Bk8-BK sH=TNAFI3IsU$h35e,hڹ2 LED !:"D}'?|IJ߹nE`޼mȀdhf4$O&0n,Άǖq$r 9tXIGu-^wb5] vtvB;WH C5L. ?Bpn#,▁+<@%zn@Gf")V/cB<`LB xȭQtzBDh\DIp^CdنpFS(ft$l pKG77`T@/;2oYd (@@ISHk" ) BɄJ/L@4/)1!cDDn#BĘB RDCxKA R-0|*)# *Pȧ>LX<3?l*[LLN|$C;24Ky>IFXeU5ԧ^qM Va-xcS!,jRhH jq!g ZsT8醶n=wvgQ%=<;{W}=+8! "@ Bع9K):ߑI.QNYYF9nA1ĭP$h# &* ө2FHv@!Zrb#cE2CK!ЊaV-+)G+f+^1uKd+P˜""hb-zE1Os1ILӪ b؄$ Hਥ $  Į!<3:z*#/^,P0qIi¨#MK-â*ᥤTE"HE[cA-Z ` !@ \.@ Ea=H?C; .jWF3 i@/h?{ԠCA&>QGI&Y@$H0$;Cs3 {HЁ ap@UUxUBbH `CB %)A+j'$#Є4ZOfNPV.z/L#bL` ~!28+V:l #J@ ?H2Q@@. 5#Hv<ًǘ—!h eQ!e}$r>+SbF @,!vBPR*P@,2$#t`'"x>Af&)L @H%mJW"T`^Nؙr@$>I)OP:@WYhYQXV;YTuI@Di`'@HʢPtEH' PΜS%e~ SXP)K ئ(IX`v-N ,(*;C,ArX$-ֶTј\Z~oZR/!DlbS?ab'IZZƔk;̞S62#N!]+4EoDG|.B0`$V9hAs( 2@k,FV!sZLE] =Fqo$Ō&;tϔ0)o "[I@xWj.m◑@@:ENc*\ph,r)‡4XE̮w EFPœ NӶB+]LMi:"+DD>I|$")WCSP!. { x&`{[f&G[R0A2),`blAkfMC0h-ڰnN)B$LZ!`^<*98 2Fd'> Ģ/tbIP 2'r^$^$b-naճ5sՈՀ2I_ty0aZSzjR,Lʺ " \Ej,"&lEX)Zxj]H#‘Ȅ@sF(4BHC!B[+`0sC!BA^ `4`90 C x4ȁ[PLH1"az "ARIHbE%JJ!@E Bd(?b2tNAON NJOOCBN+.lBj3|) lff""4HLF!U8"8\FJ_K+#M""M>0Q.@b4Ւf' IW_$IԠ"tHP# $c*1. z:+CBB()W+*NBP1V N@tB_W\D0kB&B'ʉYluE8dDkTv*r&Ⱌ2c*id3#įΩQ(䤢TRiNjCFBڊ ~*B"QBƒFYBk KVX "n A*T o䴈)R,_*_!E 4aL-ppLcC&!n;;`54P@>C !.$6 ȡFAˆ `H t*TR"/!3!́! p'($`Cd U 5 hN&W—.wJ |P9"K:)^.@c   ! Dx66cD:dmǤ&xcB6tl"  " j/!bז'l$. ?-ĭ m#<F3=vx 4ơ>F#"' z! B2bZni < t^o8# ć ?@ H<[f.piЀ3 3Œ#x :ؚ !7O !SogB d'B5HGR_V_曔۪+E.[g ŬxpMndpf(#%Y$~z,ShE!^ )4AhHŅ)>!F2,j -}ǭ$Y"<=H$G D~3aaİa!H !\{<#ē.") Op{E *o ts-+E{P,13k(4Ln`Ds3 /p;6'NAzr1! L&ֆ  "A#B_@5a)Z3"0[Z@5?0ƅ4"3"A bB#`}lܮ6H05=g)s{:$A,|#rF"H {٬wR+;@& "SU X_\.?R[Gr3Co>(Af(L0r@Te'f$$R$(,? Z䫲N;d]W+j+ 1G`4: A;@  ,:TPD!t(/! >xaaA#"A %B7 TpXACK AM #04z3R ;L=n=*)ORE^<׬S }įEF )T2   6@"B2\9͜5|5\;lZȰ& rlp-0Yig68lg(wyu+2_{{g qc^>=\hT@}mQ~QE O)?QTJF|, +A$DAtO O?% u?.OӂA2$Z#?H68?|Rp@!sXYͨtld_feV 1MS!4$@QĒ[@0?M3A MY,`e kiJ3VaW qRtBS?$!L]Ա*,O,S ig4$6h)E)ٙiNdTNAhC´&P-Eb% H~GTKo^噅!{pIxA>D]! tA [*)ܞhuӿ( |PxCar4ppEvYWR7uI6'tIfwT~ k1IA{e ̎Ŵ!u~ưQVzPN/пe{i*SM~sfs aVgƐRv_i+؈Aa>-gmAIZŌ8]%SoACp-!ԕY}QlAhǯO*l'7ɔQ!ZgY^HbJp8AJ; q)ȟ )ZQA\2u[ݹQ R fࡣHIb$ͯkW,iF0 &(dHJƊLOVWqnyc8^j)Lb=zc8Ҏ5lj:"0ScxiTA`Fc1'tb0#Bü/}(E^!m tZxB*t4$`*<#lԺ>J$k; bZq`(k1D!C "*fP>xw TPˉ_%B饉L$nd{е&}D(AO tQJ ͮ|UJ4 v͌$ `GU7`<`:Mt pxH&℃l!,IEL Dɩvhlt(@J_A4D$a5Yn I c8dB; `5PZ/AQe P*1JZGT8CI+H";@}d% Z gHg͐0 "xK&̔ 8LH[N*"3?PcL$V$n ýSI " 8ObS4 6o" |(?I7A@FPeU#3ƅ .`XpZ:/VX}q @/O+T@*(= R-ɧ)Ȣ ,[,D1 @25";2 c&1!+$,-b<|*CW(F3yo3 mLp{c1)q"I[%v2 `6,ŀT,xaG5@}S4:nL'JB8t7ow7 154|k_wZYU6UL$2r6*i|qWXѓ1MY_߁{w/ QwT{Hz qu)Ȓ2nMPzHX@`b S[g0"XB]iU CY sC2cCxtzEYM8;f {+E ,6%ޘM~ N`b(Oj [?MX81 '0 !P,:#/#qN#0Qj,h2A&'8hP CRA?AX0P9$Ddx @0 ]"Ed$A)TF%P4"Eͩ|4kF[I1Sm}E!@pԁzGd'f#/)`IBjI+ e%{="PGY 5S4G('@TqȠ M|W4 KD(ZmIWHsKbaD7҄7{sM%6e1LBᦟ:#LZ }}!$h%XphR'ji1\֠RܐP:͢.ibPkQŠP  nY `AS#J-`ȢJ"%C$SVPqҪ1TA׶tuXWLwR*1UWQ6ň!q8 kơTM.A' ⃨´1k7& W0NZ֓D +AyX@^NZa1(rP%~ڵץ 2\#$b/}`,J+6w9": f bP,aC {\ }11rY_^^3p_#}@ p?0DL j)btS!lwR`}ZKF-ɠP 0 S v]5,`]:?PB, ^fx0 PV\`'q`'pfnf4a}-iFo *`i:"ۺ$#& 0i&fC0=vQ&{ X2Q  pÌ% _Z7P ^̈́q2R1_0!>>~>K<~< P_-n2Qs DJA?I.G@cSnb9NX}#Lʫ=+!]{1H!nAM 0 `h׀y-KaN!w}0أ]3$SL7 #m`p % '!!!8չ'p B!7!%m}P'U77@!0 o>:gn/}$@#!#EGog-WTY_atts${tq2C'N{Qp2{Q5!{6 4ղ)D@x 3 0xa=)R#HC n!|@"A0jLd@tXI)U|pQ!+KƔY$(sT K/[ĹaP'I$ SΘHM:uh4UYnj*Ư'i; U]՞<+ k%a)A^KߋӘ$J J  "6Ke̙5wMq% yk7s7k$B\ 2;4oGF:O̒Juٵc]fWjL蟐 s=º +2 + .4DSTqE[t'w^XrD(.$  qc/$  r>njOFʲ/%1.Im$q$gLbIJ~ AH$$?8*" D:(Ҋt(QEB"'$h$h$ h3I Έ ^%^!A$@ TM"a PHC@ !Ln%Z/:Ip֡Jc:OBy!IyNbr\jaƏ Md{hyJ'!<4 Z XD2 5BnZ i x&鐀Iv96fi^NUֹqGziGұR>h`Q PABxJHʈc 7hfP$=$C`" 0Ym;J.HwT5 qQ`)c` _] @ZW]ɢ d<RVOf ދV‹Y^w-0vqWh0fqGbrjeU#{ H H6"(@ڑHdHW\`@XA (Ht"#Y"Z@: Ā* 9 p, DU T䃒y  @8X& )9`qDW(G\U=dHKC4pD{@H,$F! dTHs2.0@ $Ɗp)SADiЅ4")%F֕ E*n DK,ԥ*Y&#KTI, jBɬLhBLI@PI!$B[ PYÒsOYK%%Q1qc7ΐX5lxc*%c"H" ±0 o-`(G"1]i3K :wq' p uh|pzj0S4<3+EB "! ↖AUqq098=rpU VWe)E L$ +:uv-3t— :WXx<0 Qz4CVc;" 0j7|C-AZS;D9RTh#IS"1)R&T&9pȸKI|EI-Tn~)HZ؊@xeIٗ^(qI 88 pA&1Йv-]=!"da'qq.t9 ߀CIFqfI}Y: %A R]A ;;t, 6Dg ?VZғdN̎ B45񯝖} H JR%dH T`H#J8D`MԀ~!{BJ&Wr\EB20 %cu+%R<[Ю8؍i -,ğpxB K '!nKgVi)#9.BH@ 4@DŽ/?S$y(! h&sk+  8sK&s ??ɀ }5 JۊIȉ+c+ 3B_K$`@짮~r6шʠ XQbp ȰW(iz&(B;C< pxpDwP{=D<S!'C!CNN4HAEtlp0ET45CÈ #O]Î:E]EY6MX['"FtEe\FflFg|ƚht0H7#8%1C<.+1{#D@3.)o39ؚ:'0iPЄd R&yB$ICIHHહMD !|,pq%`#u*aI@A2C)lVtQjI tāA0J8κf1) y&LXx tx+q0tQ4$1y Ieq1 I]@ Pa`٨!!@x(/v;*yF Fx >X[iVXhm-9&Y/84r: G{+A Uﴦ𢤥@/E - C ЃdH d=+GZyɍp ;|0}Ę UV1x1 w{ɼóm1%"k4c9gB1}Xuq)s1$_(H_pf\hvUV֡ȳSVZ3_I3 5G[<;5TY%E fK`HDZh$:5Is[4A A3p@?W_H6pz¯}s=@bP5˳0 D6B4sa ͅa@R\cU5 OLB%P+KbaŸq^ԫ7DDo8c,4o?<9(Ì 8a 8|&)T@Z%]Q+<TYK-(L^SےP9_RШ_sJ ͻ(8afVV ˂S=Vh)Q\{~ : uJ؛>(@w#f*e+ k ->u}2&R>; 3;.%;|pe P8,`֊!A}eŽ&;l2[ש`bM@/mkD- DKycqB tmn1.iѦAm*9:EmnnQ,GFfSVlhCFWnol+A Xh(Fb.p?^yh_50#OG W#޸G0:AZsIq0H֓8S 8 1H!_1D.8;9!r,euJ6t0G1 Is|I4&,[x!xf'pspgf=t1أ8w X7E,h%xYKhKx( UPK(P$xǐR+1 `L19K .(J 8`3#p0B}Y.#*<׃qгRp-G%Su V˵θ༓Ԃ /hiP_]wI*hOgw(%[PiiVRxU[OХ";w&$ŀǰ8ɅY8t-c*qo P  $d,r():#H#WCi3I$h yRTcTil#b ȠI  CRX( +%7 72"rrZs@*xqnS'(#("U` vUl/rir!R"р/)% 7Wg8!xP{ {aʊϮ3 Mju}6Β~`~ȥ@r;0 D5|]p:I[ |go@gaB BL"41?*)r$ɇ\ R@5Ϳ#E]i a (BhĠ"ibdt` ȚJ0 ДEBL& sJLC 4!F |SEӛhoR_-y_[!><Մl;pBPd3f8p/4@Uka,/8o߼>RDOu`gIzU!piYR2Qߗ"bSg@ E}1UP>f4?[0DGEdha\iLXiO 7P n#WA8 A%DA1{?xvDB*IfmA?0 - Tj)6 5'Dh9 8+ qADП {vI C$4 C"L^Q]4},6E3@rCD5 #iQU5=4{]YACEeF.ZD8,}n%dPje ˮyA:ny\tFnqUFmBAq6|0$,2eq;bF(+r;?r [FWU,@>m&"Qq-n Q/$A|%*# 9(Fh cj"m(ПYhs(?> {zyBҦ) y:oBjDC5w'7ɇ'D*5 kUNEq TD "mB tu@a"Z\C1}}Iw`JYCC@E* `Λ8 #t|ӜL Lo9!A!SG'2*Q-@VPjӛ4:)? o&I[3ʡ ]+@j9`FCqԈ&BJ) BhZ@`YMtl4R6A9:Djܔ?U[F59$  ,CAC&,!n La)N aM1B-$I۪ϲ"cbCЧ`T 0?\>1;%pk?]r0Dpj7~i3BZ5X@ E/AI~*`8`n ;+@`& @ q0 K_@Ό@D\2- *H+p5R#H4dҸ *" ^0+=`LR>El;=H_aFLe8(44ΓI"WxhbI #=N,Bh*u7YI#ia„>,|T \C ) PrnoU 3Av l\aG2d  Lb#l@X."@DEֲ *DS.u (@+y(c ؇.r9D @!Z8:P1:>ALjؿ@rft@LM,?؃C=D0$/cƾA ф $EMfA,xe/Ӽ(Mg*Dj,VlxĽHbRyjV/V A4=DV#=3_U 4n556G6MH0$#EC`:&@@Mlp?F6.f/z 付M-Kn5?,`e {hH@E> gHs\J@tJ\@ Gl@ND<0@, 8<`DT $/lH\ AZrDDXl-48A6 ZMYJ&ŕk{Ѕ ^M`$$~HxTM\HapC~M@%dG+ԀjAZ\)A(@rNtj*v %#G$^RX@؃C Dj )r" iOXSpiSݑAG_h+(=D{T\hQ -S ,ıDŰRL?G(k1 $mC $"6ѼV+"uٟj$%f(u L[V*QeKF`]ȀT^NdF$²k\SfZXPy 0iޥOͲH,O٬ |T-eU(SEՀ\;oP'RmZ՚t<ȼ?v)Dp IEɾ?@`;.iamzS#C@eFEG?=ܦ-I ݱRnbt^BT iLlV\ZnꪮBT: 3@`BQ66 f@aL\p@@@$O?? RC „PLVD me,DXgB{GWU8dddAT@\@ܯL@lzև/V~J@?8AL@X$Cs,BtV);V #"ohtU^WDЁ.QWrp. N@<(.#DDa4B4T0@BPW?\q}? C-5؃zч:;`)GW$@?ڎt5$A E?BD<2ӈ2n_ DX?\RH&Yv 7=]Z&7ұ fd5=Ջ]nf25/ ŭȮcw$Lp$ۿ4'IS1 $1Ǧjf,o5A$`/70$F\$T0@*"$tOnLϮM+ ] ʬ Z;S?È&8:a0z=w٭ $xZՎ{',.҄݁W4uY9y!.H Ǚ$c g@` @ Lʻj*PA« #*Ŏ, L(E (66!)c1!A.>KjI;_ j!=Ed{ V@xc b` a#ea,Jh 砅袍dI)i=묵ޚ뮽:ֱ"84 *'?*$8So`!f(kaK*HB Մ""JMn`(1#b hb!'h-/ C`娃0~Tg""'1ƚ=MgS~ = pbOH/܂) H{tnA CD!It2d~Rl 00`( o"Hra IpN} V85 [H HA6.=o_7ܠtk@crG/b &@vc!8%/!+[4=OWD4`8pɽ`z vD(c _hVjczWV I3$PV9u@VOƵ2`+XoB'5+^KܯVOQ :"MFXd0nK hbCPaBhJDб%/ Bd.j FX@+!(S KC".rF(g/&'e Pb: #nm |` ~  z4an z tt}~>#P#b '!r I Ad؆ ^,A>S>BVf#m^@Rdb=shŠ> @;*9䬌A!4lA/C3TC7cI #8B*,X b#0<#vq$bxQ%Z(( F&Ά%A ̀8BvRf "Rq {gqG&TPʈE'6'V r"q'GkqN9式"Dc‰X"vBTlmH dSͦSu%hhBx!2C$HD )|NEn} 5h&8 (.2`z"0 !1 #3,&a:^5)H@$`T@@"Bia`H dau=B٢)N#hXǂ@e+Pc,RZ*oš ~a"a^di$itޠ.͒bX4઩ #1*jp++P$2kM%VL: 7!#L h:f\ЂKrWtj􉰊CGP>$tk>PfJ%p `"rJwD 0P4M,fs&D,+\ g LU)m5EZfCe}-t&FQ"$+%H ǜZ9:+ 'ˎl6Э = ǹC NݹT<;!p$%G!{b\BqYN@ v#x/Jn!!y&!Gv&2xh:6#2!NQNk%J@-DW|A.v8$1^9bL%xHZ][#|AZ'%FŸ#D\60Dg'osHn[&}Aqd4! "X 4#"^*"c f%B> y2)pd8JTsR"s$}B4@-S%,%3)@M7gLDz)[KNK@ϗKDG<5kb1/d&;,}u3DEĀ?LD:e%C=ĺde 8\Z1tdzXv}ue8VQX Q~D|H 0vdc ( Beb>)t)$aR`0!Hz'&!M~Dh""Fe~xvPRh@@0*>$#8ʣYMTj 8>*bܡ`2gncc#c `f#)m`䌮4YmAq \, L k?@G G (4I GE7s"'DAA 2gчT@M.:I@,L4t#`ÊK L jB ӂAC>%!F†!*XpS@v r J3c4>vc:(p>o EQÿ!&xrtH!eWB ơ^,)x PR}pwi/cDGny_Ht%x9]=?}gxpUf_A#8E "~ed S!Y\X=$Y ?! %a99X`}V\DMPFGYPg>p`dXGAe[ǣ*#AZ@!4d=f$XzrVYZ$Tbюq呖)DTSV O 4)T ETm %@A4. 4cU?$ՠr #CrDc(#LAS".HmĂoN14@#ܼЀ#@Tlo<"a VF=GnXUdSm|ElO5?6LF9o43;t@!p$㤠{lWQ!OVaONq?7ɠeuSnOa,=a_t ?St0Mn]S[~C0?xQ+F!)D 1 $HI?]ԡ%w!b!rI?&~4L,!$P,EN]P [,) oI5D`CD8\Aifo;F"P Lj9c@! TA@"maHMhSOIqqBU ā u\nHԔn@&Gr)H/w";uvc8$K1gtHiĥ5@ ADZ‡>!OHh`D"SH+ 4RY;eq "@fQ~SL.e\D'> pU jJhOh3!NyM"S"* Dd̡,7)Dz X~JV:h! "HʱÂ>wk^׽A0~;AX KQ5XŀTz. HSQ,-#FbU5l/V,C3F<-jzSnŽvD(mgKMsTmW[JV HQzDXPG v "νRd@F=Qc.,PU@v䤝6%"I0; `4N ewUq2sP3x0w;2 21'q7 ,! 2($JQ~|1#>Ug!2}8p571?o&H_dG!DED40$vx$9ґx?1v%"sr3X(`ly"ͱ"ao)r*Bz1HfƢsHFWEց,AqvXA( pag1$6ӈQ#31f6+GIy9q3r(V\BbK&b^R&aAt;Rwc1^Vb&f$~|"(mcFR'b,)'4BHua&UUЌ>@p,,=G3S +`d[c`w9vYgŷzɷ|2  zPu YPcB 6V&&#tW5qd`@z)*ZO Y3S6XYu>S4f48)c`Nו`SS5z2+$^-  &gÃ֑fCsP8%9[!+3R@/UɁ(etm9m FyQO AR$90PYS,0Ut?>Ԑx T=v ɂ5`D: "0@ %W NC>0 [pSY:@A;u %%R҄4ART9J(M{sֹ n0;UdY^Sy3Dwc(=x7tQ4ģCZuHndI\tuFEF`kqEF_CEqf&zD"!u:E A/rJ`EDeGхGEHaee5Hp}'&ɧfsaj*( dZK㱨j*&<*NQ?o,$>֙K WyV֛AT 8mq2Ґ5ћ;>bZq`oUyys' .ʜ, eTQ@[ 5[ [[ [n m b1VcS`MIk0);7x$#(u?x!C@yc!deV1YY\ v FTdYgp 5pA`Bua|i]L\>~Oɘ54BaQJFTY0h8!#%֒O1i\m.6*)Ph`1+x_b1@_A}w abYe!pu$qD&E&Sq'&a& $ѹuA`]=f^&P%v&$,bkT[[q 6p) &.}&Pfbc %lc;j^gq,ikl6[vgd֪4 $nXE#i&wrf*_F[* и+\6|8-p9f9B?3C]೓^xץ6$ (P@@BpJq#71t? wR NY;L78C1% nPO4??H#)j}CCJ>!TS4ڔ BeCS;B%GhK`܃>e%| L߫HrE*G*9m $ JtbEqGC^ ;abk:VR4uJı)Zfwz4FllJX6Sl.߇ A %Ămt`8a'8Q Gdx!Wl8B 1Ti0F o4)0 A ҥ7򘈂[1R>ll/& F~9m9}k)VڵmO|IHL11N n D7@JJ(K~Zσk*NgQ{ɿΠ s fx kyd9: `{ThwZǘ(ȝ ݉fPc4/Ԩd73 c- -ZHˎ 9j,gZ; (ڒ!7H(;,j@ڤѠ1˼O? &:*ƲdȂ>:ņ(̠Lˆ´ ,aA7%UE[K!D5.6'YK007k3a225t`?- Z Դu Z (L)B]-HՁmM76hlM3!p* +7a`]ha6:8c sa^cOF9eWfe%+Џq']wg:hy_">i]k^IC$:kk6.;,=2 ` l离n;oh2n- ,Jj!rhltFr(:"1oh['bjLj]w9 à0:^$1 F$f"I 2c& >h.^x  }Do$?D =@NeȱnAe!\A8HĀ%sp9:4lv{U,`"@] :s HH DAA`L@ ;B{Ro-i ?9UtnI@R!鬡D&dZ~/Q. '?0S(^a G;T ![\- {X![PTP~<{A4&N'>y"34g"BR/ Ix=qk| O @Wi@}$Թ- ; ; D@q$̱/`ѯ,A(+/pA.٫ b2P! ՑY,@ \)A/T@6/i)˜,2S6٘ذ= h  >>ޡm BB;x c$P'pNz4ZkX5U(̩hd|-6!&w3up7(ˀP FCe˅zWX{3P2k8Ȇ;~r >Ǩ8#k!!H> T[TX9]6u 4J=)`Ӱ6hc64[DK(dZɣRn, l%˖r L-3J,wQa-H0h +)C鳅M\,!Z,· A : LbzQ301L ʒҗ$DNH$͆@OO=0 t\CyOϫЪ)PB" Ad32DȒ 4 Ͼi1y+mW-7lj !%"5ҽR!DP=()*+dD3I8Н XڑTS!c!1(YӻHS(a&8C >!AȡdTC$́C *8BIBH\#LȄH0"UeA V-:+*"4} T+5ْ IRf4>BF@H@/9lVj -H>1HʘӣÝ! ْ]- +¢X#2O4P+lrɐ1:L@`_)HjTQz B*=ЀBY#pF$\0JKPՉ&325 #3*5m']q1*=A Aୢ qN1t(pGzY[ `]p :ojpsEb0'ZJ]C%.@AR58ٓ:8H*(te608/>(]jН\ʕ$mOjPp =)5zA ʪت1\8 !H5Pg-L.a‚/ k [Ji @ j +!܍1(UA*1+@jҒd2r&>8-YAa-+䂱ԂC۠AZ*SS H KBԢ+KbYxA<,bNêB+,ނ`;, `E/.C.`)O 'm3ɱCOD~@Q- ,4)Kd04جAqͥDbH5obK XeL̠Rm,!S@@LCM-2brp4&0XU_;}3Tkds19THp@*=4]:s2>xnxC42h=(pfH GGx$k"hےkIzze3:7d Zzf'MijB#> ]f!]ѵii]#m4KPOf^g0ubsJ6Hh|-I`(U؅pp CIh> Ѓd  ơA=E$$ٵ{ Իt(˘ˈ8-qs ىai;@4 b< Xꆋہ44賾hމ2A͈(ٓ9?F+[<\B\P+>#id@P$ ‹r4$p/i&uXҪ,-&.RB0@Z,P/3$+ $ g3ԀTʤB OK@0|/W %̭Eμfkv|~ƪH -!Yn TWD [hB /Pu@c,)Po8v8( Ao?0HGP/'RGueNfI{f,iV#K 9$n*7_ghKtK3sw(xof쓦zhfpQn`hʶ&Dhƈx[(p. NSCP^ VJw2ȆN 䍝ĀIg@$..cX̂0f4fF,{:v0LYLk1OĄ{NDG}l-He9zbFAQ |sID: JqTMқOpseBe|Ol0ו˚:&ȗR(P b4͂+d}:pUAX0ϳ2r~~I|Or5gt,h „ 2l!Ĉ'Rh"E`#Ȑ"G,i$ʔ*Wȱq `Ky$ :!B`7&4 F /r [ai ` ;pJV'@}/5U@PK#=P|tiMа4qn8#l!-FkkMFe 7A0]MP/'Foi-zhPAEXٻ/ `؉sG:P0+ȊB,+xX P@\$r<(!U%>s@Ӷ% $a"k#Mf Ă Y6ȕd\i|zU NA&DK9[Y!`O_ִc M@n d WV ],ȁ(8&M`8ֶP`[h2MLL8 Fpt$ i!6 ѩlbgtqv)x'A3!q 9xXX b$F2;!I-`  cPL> (A@ ,g.SWς@o)g=t}A ?p;*Q~;3e '4+pm %ç9!tY;A* |mn4\4Y4G<&oH+ 9Ffdb;P)2гKُ-DyP B,YB$ J -Uٚd I  N]J`2`D@ SĿ5(@pY!B,e,1@(8:nfn!Qv!!FB`#9D %A$T kl@k!jQT @L_Bp`e"l^ =% A(^8[fAU,1 jPH?@TF!rF:A[BR@8k_B( nq@lQkms@;@ƕA # Ё+>?b w?4U!@JH6t&D ,@#$%O4WYSW0C4|;#38$ ƒr?C85ÇɌAL"|܉@I+F GoTpNN?,ՔZԊqSӇHpE W& 8@èlJ P?AP\TETdX%eZ ?0L dNnZ<0C K`4S9r}@Bid_P;a5hcjDʦZ@*8C $1C 84 ,5a:|9|5t24*@C `8DCG4mHI XxYY, ܼEd΍=]ONt@ lMnP XN|٤NƱX GlMPdNT @ ,i -Q<@T@u\۸olL G4$׈Oh B$hm)nPePuq!(<9)4Ne dHټE8ŝ-~ڨj-g>@I PMp a >)D`!bCD^PAQ Y |E@{> CjAjLt?A*jAnPjbypc4o$Ar,ELЊi AL_".8&? /C J9FA `/RS`\%`Xm6'F]mB:pG}@p3aٚ-A@@Un̟:-uN)j=B AEEyP=j-**,9}5m)Mi,5pѧnPd5l`YW_PߚTE2ŀ GqR*lOl_HH2: Z 5}!pN`DpC ?let!vgv;@dA AgAL:Ԁ\;,*Ea7TBC n~7rV vDF8`;<qqHNo8wDXAx@Ju?X{8xlgv@/1S0A1E"`b1U%җldi4^9GSCB0!~?‡<xF{9J,~"'"dO9jS4T./D\hA8.45?\#U^'YT09"A# @;b8?0H"?_‰@t;?CU?0A80SlDҒ/;Dw:\?|9{ugDY  T] ,PStc &Y$ 8L`6P\RtDd(Ȑ*S\>|'6C`@8R3uzS pRw 3 PPv0pĬwո@`n~ lπP~?Ē&MP \ 8p״k社?)tOhCxZUiOLjO]6Y5<'߭$` {8w$),9ܝam'&r u)fR:%ʚ-]۷. _8~QW+rUu#s9A&@;`/Kg J>ۼ~gks?>/rr/$pcǍ+c'' lnB)2x/A }Bv"=C'x=`Ʌ䪠Ɲ" Hƻ  /z)%$I&k$ *SĢ bL/00 *Iʰ 9>Jy F$XXqF T9& s9*~("8 !pY8%H5Ph*- $/Lơ T(2#.$ v <`ŞpͧA [ʚ @㟼nXgi`vw&Yw7 6gvZڝM v+P+ $ `&|eFf$=w pr'0r`9 X /B5@ru0A_rHHmnmHxRg'BgSm:Ijꈪ)ȂrRt t2SC;"uD}N+t*vH2.x?Y!@2ڝ-ݬyRgJ`{/(^}ݫ@B*#V$V6l5 taWDsv XCɲ"&dxbk/(N@(m'?Юn   R,_>Ih~`"ȳ‡}h_S:,(|wfe+W,!?܂250 Ko h[qA R$pAA8HeQ18NqqD(C΁t \g`<ΆU3$>*PH1aXql` OA =1$M!W:Tb"x Sx0 CE MчKdC0R>(F&:莝/X3d5'!4TfhOQB 9 .DE L9ݶDxJȷu2ӄ4TwYֈvbWB$H@8 jR ars# * $bHJ%Z  PrF { CwCς1 @P$l>*2FJ"zl"/$s8p'!v@'Z w"('%b#4@1 Z$V "@Vb`` "/v$uR >8+ @"-1 "]`"B`QplPb ʢkB jdOB$N :~Vg*rV%^¶ H(h|tV( "Ǯx0Z>61. u㴺"B*-Q}N ױE"|ܢѵ 4+ $HC4Ң*4'NXc-h''R'<".ꢞ#::o"dDX.bz^+찂 udZld%'@!< LP/|"5ecAvb ))((S''˖j1v'@H'' |'@@D+ʀ$aNba X|O"P`T,< "( 8IT<%+J;q&ie'P (WdM'`Xv ; &0PYc dj$FeP'>Z "j#&\:_|(`Pn w< Bxhhڑ'b7itk! 6 /Wl`c#hR !D ÜXЁ8n%p'R!l }gCsNv"tF$-V+t 'rz/hV Ls~*4Gyz$NY|!fM$Og-'t$FRɬ{4~ D''߆) I ̄0*GLx̬4!5Wb.(YRt'%*@sYz""d@''4 Sٌ'M9"\Qr7#.B}-0A[…@GF;UTWbʈB+?B:Y DIEAEE: lTXpvT'~X G*pt''t'Tu2)B9<20@D'N[,'wr' \ r(qm)+|)i'r(/~E>T 0>Ų%"U)RD ^-+.-/j'+섡S$31#2'SzS SN3C FS+FlA$^6(%,ǦsqbR/ؑ"|4'1 ʧ>0"+"G@d&RPEjx'~ Q@4.M|TH:bL*5| K1|!|+K, 5^4!QШ,fG}fN Ckf+FR[4 g} T;u𵼦! (k-+Y]Ϯn'dS+SdL{*D 8N(ZR@ÞV_8ĊxB  !rK,'H8O!9jM9y`'Qz&&^|+`B@`#q *eA9EyAlCx'66dMἍW7D1-^0#. ,nfx ,`tB`CZ%/'F( (ajīᄁ X -! ''8!aM"NxC0 @6N \'K |BZ '9z`!o%F& `h$))*'`cc .*BAV) b`ôLP[36] z@dE9 ΃ aZ$ Xn9[   `Ǻt F!:" x/C,阃 a'TPH 0(iCry")"ΏAǫ&@`9- <@"9R'D0x0(V1 n\IM+$+D9("i,uBG0I$x[7-NPsJ$|%* bˁ˭t/& I@#Q1:୔L>нb G7B>҂#!] 3a&NB1 b \'D<2"Q'*HMYtF1dQ +nPu=vkV}u0Q&SU+jمR}uv$n+(69z7,0#Wݳ r4KkƂk r!zi 1:N@c9 $,#+:= "n+Ԡ~"Mԣ9Z$Xob*E;\+B/@n%Vj,VDB,4DB> B|#$cN(B##Cv"DPfAf% ΁ t]:(a'a(|. p@i6d֎5) xB2&/4hXns-,,h 2$ ~Ze( 3[y=s'Ŭ`v6s /HX k7=`DUFE[}g a`f`Hf:nj_uN%Uk\RBj+[tv0-۴ ̐Q?m!fduשZ9X*eLFP˰0@ 7Yq-7rQ|c^=ÿ$Ab]c=~9I1~Mp{AtMQD_P?&xG>P7ڃ:}^\ %?M Y$S10!D'9Ȑ&% |]h0Ek1G $FI"tA n3n5s44%I614uQ]AmiE]^fOO 9?PH0-0B|?0"Qj1m'gʹGXCYP=ɐ{P+5 9 z( qCdġBR {DRN )&QGQIqJ-l}&v?E2+ *@ L B0tC#CJ$-|?Ot ?*琪iL [?x2dc A ) {eI*_VrB+eA 4^i= E]}]AR[UFzL+5Y]hVyR]mTl3V6geP[ ZSIdF3O FA;X[U"P9i>5r%p `_uj{U'_<{n</:g#tn`Wo;B$ȋ}~OR}Ad qcGBUu${p[v?(c'$D?f8L PA8B @6 p\A@g80y ,Uy8V}7@ r,$I=C*rl# HJr%/Ljr'? Pr,)OTrl+_ Xr-o\r/ ` s,1d*sl3 hJsԬ5ljs7 ps,9ωtsl; xs=|s? Ѐ t-AЄ*t mC шJtE/ьjtG? Ґt$-IOҔt,mK_ Әt4MoӜtonGƂ|Gx9# G.2Dn[@YQ[+WyT2S\"7Sq=%r Ef讔#نq{jh8j}k[z) 2NUNc Zt5v"9vYVSo{l5qit:ֺCmh{ɾlqLL,.4}R-B97H~#>0ޕ_|6|$7 M31tc.:p4F4wHV8E%^(/QtJbQϫΞhoQG(}O):n_wXGý\T]wu/A >.B{QoixN>K[Ma!99y|Ksg>6pWJ&S! ,*l H B`!|`pBfNS:,ׄƄ;PDI0Aa"0LTp`D+Ǔ @ 祃8t@:2ha7o &ldԓhg $Hֺ($^CD>\1QYm}?f6K 4g)ؐ`#sͻ@!kSq1 ͿĹf*&<3e'@gEt8@ꠁg$I \4 ZF` HD`yFi$@QPd C?BxACXd2XX"6z(B-&d@(v¨!p  B`BRFK O l!ۈ`(  8G&r.\ J"ECIׅ! (@Fo@sZADdBbGQ%j҉"& D?˔MkR nIO뛗Tt-I{þ,UAWĺ=S56d3*OA Bh(IL@vk zA'3<^kPG %Fd QGpxh͎6=[vS_HQ`@᳟Q${ K`f%Vi3n+gd ? @3  a50:$E>w<9ɣw³:lƢ% R6t[ :hi-H;0bC|DRt9bRstLjh|7)9'Gm ."l6uLQH@H*p*{\gq?Tg@k# WsӀpjPk@Ld/JA+]j+5^OܕDp,Q,--;!(,!00K=6)ł0168*+8B.Q22J!m6JC8 W㲀-_.2*Qs>0r%22*Tq!9\V`/b|P&cu0V1X2cQL:#51-Ts]aI54GS>i\=Êf {3$rdq Z qV0 pv4"} e4l`w6c0^, :v%x5c]{=P&Po l9=%qs7Yusq`I  1@S{Mw~o&נ `'AQ; !VFa7SIqG@̖@[ @< +(l!(/k64=!K]X GFps _n•E'UIP;]x6cS)N iF"M9x)E PIGH u!yx4=iQ(P ' .>wLecQĕY*&fD;c۴f,ihby+Ks7)LAմRq*ؙ,xd.rdP6s,IJi5L^&3BM9F$aiQ!RT$>`IZQʩ5 -Cǎ=#b*"֖!a-g8dtv?s @30AEa!p;H`XA :@(X[ȣ9!PwH@')x@;JZ)uYcCѐ"6DQ ɑ q 'RgD4--q9jGkF 20LшG*9lcS ="=b4hR$*t0 h.$b,_xPesy3)IkL0@b*w2*1"kD\0p*Q1Qr0R a/u% ddL@aDZȠb6O㊐[aI=:꯵(aC}OT} FBǞ^V`ll[,:^:js qq1  "$ǰS?t q>9Dhܯ Py"  Ћ&I0@&W ,zJMfq%0T(u7}uxₐWP'I›pS>{'Nb0`!E}RiN 挲 :R'JGj57#0-[t?{rO0~< ٵg˄:< B!|W~0@ a'(:" b i 1$I@pi'ɮ-dH@!kDp!'i1# o$)g* t,I&:>@'1ɈJ> #cIA,Bd0рΑ%4h dHMPq t"K$!3hR!C n4h b)2P2HA5@P‹ = H-"Ѵ8dhc :@ /J ]" LK6'*8S$Р$( W Cxc "RRSxST8h$1KfKP 00J9 F: :N%GH/8O:‰hcQV=# h!0H78R p J& α O,"' f)7|' p!h- !.\  s  P,`|9A   ja/ ~@/iD(8g(΂tR diww >)(נEЕIA·!nz4$T *6Í} @(kI3?깅-yaCr5 t1ݢ(X D^S1N[LC>uȐ LSAb`"_blP='R֌ĂD 2kE$TB"2V2#Q @B%; vXD&8ړ$r(G 1 3) rD Ifs$CmLL(ghc5 ؗ*xa~ɒRB4x(LJA4/M 2xLd$R;m)?45n!c @$2VDH) PDT ( QPx0 cאQi >h\!X•`B`QrAЅ(T0ʅ+D! &!6$Mf,kMkr ĥAR"s0Jbf?怬.@9eY0W8ΑuIN+(?&i:lYIE氡 "VB@^82pG`, G2HN'yH޳6+K EjG*JAO׈E-S C{vqe;>-ߥQCĕX#8C,Knjkgў 4HID u0 ͤ6DC&Fzt$<-˓Q5$XjRV ҫ >N H\|_*(Q`[E a`(mh0AܐA!C2 h,_2"d?Lv̰A6iN`TCqRc|A|mć" IH e\hAqr T)R,H CX`# Ub(YH ~+: AutK,MjďBµKhry藛dqvgo|rY˝'[JSbk_eHɋejs>v/%z 76(aёy;ۛdz$%Lx<}x#`(/@Xv7^0#!וA,kdA+B?$2&t|/Ro E$Ra\Rp2׉rӎ-jÇ?׿$M,u${ >P0ga 9X@, / сZ#9/>Jq#bĈ*!ٕr8 oq6#Ѓp:]Q`La3w:HXyxh hP@#8iWS@ 4+Ed,F؅q`NnM+|2W.h8؋2e$NYkrE@pppkL g80lϹi+ >$j /I?j!Ml?`#䲭‘m 0! a&ٝ` )9#Q@II J `>#X؝Dq P@ !IK8@ *؛#3 }JKPP#`>/< [/!!3 {"͓8 h ̹ ͹๙X( لKbj#lP5H9pD[;##tm#E+35`Gr7I)%5%,UƆ| X" 5F!  =;xA5)&ƒ==Bx:W1s25~gbchx d" mzꠃZM/0{ e3 *8'# Xbi)ȁ 81%)pF1"k0]0,"ӆ T)H@b<ё:}A;"z R8,0$&舎ڂ}iԅ X-݉pi$kDթU>ɵR /:鸶Yz x6 ѡb5c!.ꈠ㊃ $Hf"x֒05# /pPB:%R ބׄX/7-<6i-h:[%:LCo?U/R)̡-.5%=f!,ؑ TөSA# XcjK/Ο81: PUظ[MȆJrZhLt [a #UO4F V8'56\RB9EFkHۓ%5U9 зX5WL؃/'JС$0qwϸ4XCm|K鐝h}MfSS$XQ8ȶ_<3p+ AAb.;/uCзnKⓢDE p87Q0+`7Ez#m J %L)Vϥ8뺠X"i5s ".% V_C_%I{MS`ZU軆@`p;m3_< & Iyؒkbu86n;ޝX](<˼P2]@ü{6ߕyw1씁a@[M.b XP)3.Ѐ{6ƞ,VbV(} [/VEeGрn& K&>bMT@d ,Ua'$&ViHMۓP $c@m6g(>(B`fkA 14UT21h/@UiWv@ Б)H@)XMc1+ƴ Ja&cK&an\Dfs@ ,X׺Aw!ɗZ0g e uț [_ Ɠ~`~PȆ0{إk۷eF a>({bx)DA.`$8<-Vň(H-@#2,3UYQP1 lyRQ`&ybmR@ $.3!"DH i!|Mig3ɺZ)Llb::2 ѧihP|J!7*I'_@Dd>(@̟}R\ 7 R4.JJ`c EN!®>J^k+x b-պr >Ձ>Hq ~ߓHM/0lÝ".d`72n\tiDB^1&$~ g@< Б>6eQJ K[ s@ -[zEU}r`m6|7 rGYS,&z\Q+U Rq%-  @se hMQpقX2x/MS%U- x^:UںmծZM6/LW:#idʁ.,h%UT~Up{J.rJ vqH& (=KRQٛKg*2- > :fxrpΚI‰x58:y[x X@ݿ, (.  @b1"7|6@./uwn\$Д3-4ZZ3q83%97j=&@_’:t^R-Mb$_W1ddU TAZ|ȰM a"02$FpA!:x @5䵐!,G7:,mx)֬Zr+ذbv G!v¿;=sҌ{`"-wg/BPBֹGeܬ!"E>PFƼ>8ό7uK648gPũSMceX3ț,Ǔ/o]"=ɾěֳ{Tň{G]WzٵC[ `\I&!j!z NT= bw=N 7 ' <-z O ZՃCьM2vJI|U:eYj%RbWM :?79 fQQ# ? ]XEF 8jٓ;!\TP5X5%ZUbpj)ZAHԚ[Qt F.M"$!yupV9J+`h HjoS)-,J;-RX-RzX(<~iޚnC$Py[i=@nF4jF7!M5GSa4 C 8"V:^5Z(̐0)0c3B35 G30*"_@e M9ݠ`W(0mCF F$24ҵ5Tcܐ`S ECsU a2K5w}, 9WLHpz!$Q2"p 6EO3?9!?x0#d';>I@OQzN2` BΉsD3:/4L4Ly=CAC4MCΜȊ5חA|0 ?sء?1!mG] dLAj_ f |Ҁ~ qU)xQ,&7Xy8p\Bv6k"#%JfUJ`I Ub%Ezt PUU10?@A4 ~ TX4Dk*I*',@n :7be>2`~R `!d@Ԧ6)%H$+b[]YD cNZc𨒊E+t&dp ⊔?M#'LhZcnY&.Mb,4S[MEV=R dDH?LF Y54?aCy] ~!ᦠy]IjZWƶataut|!""JI&"#f _VR|LċGxQC`%:'KektMF@,́\"8 (E 0C4BXpV"oR "̔D @@|MZ CٰZ4^ YRU8WӨaCD @9Pe@PetP?cF00XcFl+uJJ"VZL`F\au#@0FCmPT(ف $삩+ɤ8CCWO^ vCNcV` OW?COuA)$B00BPI@B<@PLpL*l1pA8!qt0 A@Ox Y5KP]"#h% bthY EIGIaufU C@i݆vm, VьfF4-Ckf >بhfUC͊ܖzD$tGޜ"&C?$Mj'C)CꯉY]VV"E:%L?Rj`2BS GL%Ԥs9Vx)GSAUʁ$*HoSSO&ո-vP_`e֦t`\mC^cAk4\̇P@3[TF8_o R1:+ǷvR Seљq EY.1yE,"$O 0d29yDJH'%/* %)W HzĘ+ʑ,I H7TOp-?laC#22'3$@qbZ,Q4J@5DЭ91+3993:sOHmTbȭ bFA$VdGx K%#=g@$ؘ@JA .O%D/1BI4@F e1D: IG.v?D3Pd ?XpMcþď G4A-4F-O1W#>(Ws:ER7L&W?" Ą$$`ARE,D1M|A2eYRfdb3Hc_TfC,萀CZ4@"Bc$Cӕ:2 IHO`CL=5Dn3쮋4CXDU@V$MnI/ºVd/mv0 =YxK?ЖS`RtDQ(u"]L_=CähZWC CD&W(@Hl+ڸ WN㫫?ko;XrȖa'Bf,-!g}$6 JNnvG3IF7}.gǰDȋk-2-&9HFEIڤIF?^o2qJ#C Jr6༝|4/@ WA!F8bEVqb"uHxdIt(_#~!% *hPX0S,aA/C>}aPLSia0R]DTu1|BLG+uhpFk␭0OF< 58C$_&ATߕ: hʯI޼X_Zx"z E C>T@L ?U0XͫX'(^5"&g1>IIV4C}ɝrX9)[]PCpϡ`eQGqgw9٠:_FwyP@꫱ZȊuRSDU4D!$!&4[%G! vO\[u\5쒚iMgyp="+ "1zhsflj BE2„aFty T`&"ѡL zF;ĝJaBL0q:ڜptҡ҇B!NW?8!2Hொ$3t%0$LWQ @Q 67"*bD7mDfÇ{IؠB#5p @?R=FU!{ ,;:^@4*'#DR=~ ḇX@:8ށSID7ˆ`9C B)cZ^@eqA`8$bP 8h^(C&uo@A_ "b|C'BQR8t@#  BFO/Vh^I2$¨s7 I:!f:e%*vˊ@RK BO&3@i!:pmV:6T0'yMr$."_Y1|f? ]K$ P #eiu]*T pVBˈSQJ;]6ATlV#:SI:EQ0q 0*H(g8a 2 W2UFIfrIH 77ӊ"B~#BJlAJ 8iQJ "kC^'69k9ub3J9<\>W3HT!J)2;a&Mt!9r;ACB$=H?kD4FOPrHXE[& ^hnjv1Pf |^2 r$CBKFn8 ?KwGqUvqgWuKp1 ZdD !}?\?vdBY3?0S}JX F`$9$Hц懎b+ȞTP"zH4 j )C3KIL@RX, 5A27*3# A2;藥Xr"0p?\+` :1%K=@6.6!.FVą`AIޡV^(8P:H2a5/;Vs^uG*fZ 8g܈H~ũ;!]{VMPbFPRDʦ do!@$Fv*Pb!I9f2h z)泟/hF۹ HO$,E&mȘٿQg2Gۓ,wbt\!;^Yr!wMWYv8Ђt%"A/KcE=)A{Um^o\_FjI0[܂M!FJ8;q:CdQXw8Ʃ=6MfRkr Kwa>*J!&0RZb: =My$DTCDA92H6">OcZ |F(VEMO  YpY2&D"V]y:grOI"m"he$@)g"M™X 0Be^ peìA| e^^!:^ pE\)e"l"A!!g쁾(6bd! `": WDf(9)$JmzBQDf"f8Bc42b؆,NP+q{Lq1-u}Ib.+4% AWb&"^ `G3h#Ibn1 (n!@ {6.a/ Vf""R!`MzB*G}!v*f\# pGja&Pe/p 6NR"m,"‚bR7&&-& lҔ`%H"`\}bMx󒂶c&$j Bni)4cH;!!wFDa), B fs jBd7$&!v6 $"s #%BdӮ ; B\bTc8m#Gl0•mk"0*: zxj!2#ʠQGT "憢j# B4&.*f@j+B-s2"<1b  #讧"`0#bA#JPE3\jY.F!k# 8K%)b8jj/ 3ЪR%TbBD,:*䎴Xm2$dq"* ,0M&x$!L`SQv*iOP acMSK"$l*"Q J!("a)ȋREAyB2"!"6B *dif9fFrS#;c) ׃>¡ H ?[ !d5Kd AA6"(HNRG}^`8"_LR҃; &qc3,K2@6MKZhldL\4c~YYBwl 563 bٲIR+⌖mјj`4)i#bО  "nh3v+*,4;-g5a ?F]"tj h1":m"h%0c'"\#+TU[-I\vYL捡BLW9jN"N0szNTix#qk(P5c>%! .nYX7#~d!AI"R "%4#̜-\""_34&Fo#'b&+crŮ$’""j'*B~mW!r cjw'zl٠| y\LVb"u/`*nChB! 3ؒYN A494:bb{bBO\z"\׃!"O\!B]h}!-!^D 8"6FaEb>J"4W4CdŔ$"85R&e\JbP&"!K,RV9J:or+(_8@|uR[6N2qn6\F`|$J@λ8byǍKT%CUPRހeE9D.r.X|ywYqﭘJ +4A8R@ ݡ\biRIYj`&jw8q Q30 !XUJAc\j!fBDg`Fi0c%1"z8cA2SUDALHjZ6hm7On lP pX!px!0ڨ:^|Zr0aB!; A[OU0tn,!!6Lđ(` z7u`>{uz'F"Qkdi ~yA!t "P"_C"Kb##Q!he"T^["0 oB&WZҀ6%$ $ BBɄ$kmHۚb Z )+_v5q~gQEbk !! (h< b ÅO(L.&a&ua0NpX4bIP ef! @!FC桥Œy!=a"" ^h$h# T@b8 |EG`"dU Q J lW7 bJcHeK ,t#h8ԝ KU5ruYHMl8jYw# 1BNE)R!n*C9 r@εBmT .8"$E,Qy[x P4WVWEԔY+" %#CUǖ$ƥ+ vqS Zb('Kw!*JFYi 3V0&!n",)L ,"x"J] _C,/i҃S^ "(Hȉ4.o īRB"\9cVZ&L6!,O?B|CJ _nD.tIl؉ B9k0C@H@4!DPȲ>%[Jlh{sV5g !#&mȁBp|jWw?l)VXWX*ֿHpaAFW1" !`DXaa!y𯉇!͋ 4Ћ =:@^sB.RZi$ Dp'B+Fp_ʳIr\QX61],.FN ' @@0b C8rDF\Yhg/ꅡQ?!Qǡk,5"c9CӴӆ;_|V9\M  ٩{?znUrړG@>zCFSxs"I4I2LB1uQ6??it@1Gi%!xp-DBDT1ET9*[G2NhEPSOu?Q0KʸtABY 'P;ձ*ݙJ.e1fTj2[&׺BV\u'OhXO;eU$͆bH8,Y-yAK*\.Nzũ}o;`.T<}OjO_B}4.;p;}B D? Od}p3>e‹H:AP=_/TCY#~ PFWF"H6XMiLu': ʷyPz^/Ll5uD2Ayc.9X$ %&yD@M?[v48:vf#x@*rݣ] HJ2H! ";\|0c ݩS"19 c CN:r2Ԣ>\1PȝG#FTe!EpnADL &W\6!SؕX)!ީ?HɃd$C C "Alž.=Q DE#x!B !X1+("*t٢ē]1#ǺLSpX4R pppeBiR@Pv ,h Y)襹:@k#YЇ/Bb^~I OբIHW3QTU>Ris|C)vXǷ`“)@Zm?w_FqWp"!.,E+ڤg[:/mDO'VO $T" Ba%rYJLgMr!np]Aiab98"0x-߂ ҄ $BKaBX0K^8z$03uySD$ EiP@0Lb(8"EP 0Oc [(] )CL4rĂ_ar3"/s9i77p/t37DF{0\ JE Ft8uwwyNa/qx 1`.\L1pW @sh5LS527Q&E%N\hU%pW2@vhk$c@s6F{TBk[Ɣ5fTew3@x.:69n)-|n5 ALlw&&A4 0w Bv>`@jQ AqW$ =ښ5ck?!)=Og'~!Z`yteq͡vFdʦmo N8r$;~{#ouR'EqPc~! $%^^wfUs|pCThAUq9@4O \"9HFy²}jF41S3nsKgg?+Qy =*M4WFZpHxѮ([|P cXEV36?\%= ڲ ɢ>5P? 87?.a$Ud0isS0F!F1roBO ;JGVtp) A^+ڱÜ,!0(*y"[@ xmP Aq<y1Efz pPvF J p, 04>Ԏ@!?k*9ajۺ=`RR4gcq?m )'6!&)@+`` 6º]Q;h 'PW[b="beˤRvJC;h9z:raً|phTrûbQ"a0G;F7ͱb3kjDAjL{<\j"DHI(|6m?AoL|4Q8ªrqkZ -S; EeYQ ;Yb2k pE/[4rAxC 9R/Q? X rEAU᳢H /h +_q$ {P Hv8>/p*s!Z̞*|*o,#J  +@,;dbwsBAf4>GEIN > . O|">,!bp_`]111c1^, 26F?S! mEogz&^ y@bA(< <!  uz@u1ҵ#"Uq)ȂU^т'`@gӢK+GGq\aq oq%qh!Աiùmكo=`ƝIvbQr;y c4), 77orR;;;sI欋R!Q ͈% bQ* -v9jF&)6o;Cqŏ3ak` =ll#4UI96QPꄴ!ͪ!UAϘ!5z 3al/B?di==KDh£ɾ30uddDSF]<ēlڎ8)̹C.n0l ?0 `  ,HH IAA ?p<1Yz;A Jsv7%kr5"\&1/3H0}@S3\}! vaZfT[|*l[z@ & 2J"NAj˽fVd-D?* l{Èv<<1!ڰz!Pq4qM0N6hnK>QB *@gSX0'jx5䆆 j8gg*abi DvhKTA6 -p6h;&<@dk.dK-Ξ1X2& x(ա-G %pcQz&:mp\# HlD ¼^1׼!Dkba3 /H;Үu$&R)F׆T<3JUϲ$$Ht1%z`&tO M1\z/ *;#lKJ(²ߘ $NTR*k($u@n2ʢ"r$ ;p#'k1teSC^Ր-|X5J@$ uɃ,;:?:8kk]KFĶaSAl4: sHD#LnL4b `@/70}#%fuaʲ*^Yu|5y7)*Aq?6A)EC70B% &vgf + /w}j&F$QyYL BPKDzҮKLԈ6˲aDu&^HfNzs^l'?ED©e@&2LPbLИ'Ռ2 |>2%ģ DBМV6k#?T&hm)H܈z ̓ !vqSI@YQ=~@X3iDH,f?2j!Ljb$#؃tQ0DY U̱le-{Yvp?L Yb-} NVemk]Ze\!p?tƶ,8 &\&Wensbpl2CĹ홏dW%oy&{yd,Hyy{_W~۲Ͳ"SFkH6j G|T<-$Y : ,tkxՎ@ !}Zh4!9 7$.aL̡pY:f $atŀ1.-Bn"?jdp:< fh`dfFCt,3$B&ХCC0@APQ-(-0ZWҔ+huڴ)#ZeJA-e5V=0k[CU@VE j FTrj?HD.P.DE`} mO7 b2E(rIba5,"w5-0rC`', 8l K~mϗKo-z@԰ZS< +X !ИGZjp5JVU-bkqItSr1|R0!LCB VV/:p!G>ً宮? '|KR 4R*Ex֏~~05w<r*Gܬbt| b&T~Yy%v{à6zy et{Kc*0 ťQGD﷥f!|4Gq}K'Cu2_tOS[5]PW-&GZӑ7$`D6 sy XTJ$ ؛hI1$0H,/#<@+F2!0B@qFJ01uԈթsG:xG{l.[#jSfIq0gjшgR+-gI!* p?: ۱l RC18$ Ę 0А!9a IɞJݨDJ`ݰ?|-LS 7IF` '!h˴I(G V\''#[1ڛ'Ԉ/ JĆ#FF@vZP!@GTuT$ZTٌEruiڡA q4tlLHXK`G|@)ʖЈ lŶ g ila'/'$ApI-7-a%I9„㲦Nu2 #8H.A0C;88t4L|QÆhCy1t;; C $E1\.r/\˃͘)4 ! i848N= 9+,  >KbdP$mKJ$R1;I¿9<d $B.,LD/. TbJ?.D]"s ,G-zAFTOTP>PѾЭX-Un-CPUYX=4 CS `Rȏ7Z-Vc=V /*\.BVjVkz`>}y}aD 2K$8( "- +1/hVrXJXI)4:0 ȂZر6!U!G3 'Qܑ0LξD ʱHш[1THXYVDZ(6 聊M Ѝ E 3 3}fE ɳE;Zx83H'{Z xMt4ax%tPxPUPXQ^إ~[p+x}O%\.Hg18`2$pAjBІM[HMPX8ȾF `x6ХO;f\ [ksR9Pf܆ _=dH hH,ȧ x#5K_@.$H3 P``H @2lfNf8`:Jx 8ݥA{ع}hk xQQy`۹ݗWt  H< P/P9=( ));{HP h ) X X `Jոt ɫ"QC@C J> 4 ,֌8e+  cIdрH>S ;4 !E6#Gp _VcSSƻI (Ш+֍X!z>hi>P71g5QJ=*y̴P<6<*؀X V"VLD#DβثJ赱 8Rj\/͒iq   N "CU>R]} 즛#8͗sb_j\ilq0.닯'DB(prJa;7ȅVI0B5U0\#YQ(-}kK'3Ykl6 *9$!i7|PJxЅlT#Æ`9@x 4kЇ(s`rйՈC|CDD QhzA}ɀ\ucDz`Y(E3zļWPJ0hEXXzK XwDL61O-1$(%BN$نpOܬL@ (e6-Y'3sR$o6  r5y[hTqGȁ\̧9R4BBm0pߋHX`Dr@גYѽʸHBT I)KH,ž 8PPT(nf+59 a ҎɪL$I*yt$uI)AP"\spuuut+CtR(Z%*FdhOD*$ t)Ӧ`,!!GwBtT=z*v,PFMv-ףK|tQ -_ p{m†#Nx1Ǝs'+QDČUa$7]?GE{4֮_Î-[cjPݭAѤ{xγ!vaPǟC.}: D[%O/Nށv~=C>~5|/Ł.R,`F1 D(CDCT ?9|X@R?"pA B8Q?">DG$$&O#!BT(4TKQCRDL!?N?a|"q"(1Jau,Ǥ @MԄ@D9PKDĜi0Tp&t @QJ:?S$JHFJtt)A5`7@ ! ܐPb7&Rz#U  PH9/$8wxq TMtə$dl#?#XAq AeLz Qg$0Kt|p@![6Dt 5wv?J*Y !*KT%x(T@ep!#Ѥo?6tAW@dAq(ISD@ShCٴ&t@qAaY=?Z+5gƁrHLٿ4( r^\乎X>A:=@xBջL; >Ӻ qָX5*t|˴%2bzA`x`AcU 4O@? TNh`P~I)ƃ )'." %%;aAQ"` I:@  k MB]!J0,M 识PA@@x O;%zQKDDƝ'Aȑ < RDT˂c;>f DӬ [ ! `D8d$1 NJdu l$1Zb%j)dii6}R*0h H* b"K/nd*[LDD0A9+P6?Zs6'׫Bp }6 9jsL@`D\a %o7 ĠQQ'oR jLo hO(Z*);D\6hA"z8Aܰx #\jXLJ 6qaYEg먁+ $7L\ (pK;z QBpͽ {NMDŮA8B_J$Rb%dEqFD,#p hB RvlJ0%+";U@aBDQb0 0c1(?W!IguGYV%& At ԂgAk啅B- ׋(;* 0A͝BA.}8|I l?d\"8 ?``Dצ ? hB-4U x,@?^$AUK̉DDɚDɍAXJDO0l(ħª 50$HC,62z#$cVމ0&lfY?C#@`@? P@$A x X4pPAxM~ F dADXAH#PH D`ĉ4WXx_  DD|  M.h?0?AXM @͹]Ax(@#P (@ 4@0M٘@XgY&Np J?t$ 砄dh}S  ^ C:0m$T\BTvQkb t @/@ L$KD@ " @@PBbD xhzOđ*[y9NcA   Uϰ(a^-] =QxHeUT Z5")!qx< |ЛX EC,-]E\BD*BXH!E}K))ĈBmA R`ħ !}`J0`i,TDNEǰCDSOPӛưu%aDdODOXNFjctsdH.$ACerH5!AL \X BJDrBXB$WH)gxEJEtFhgTb?(O p|,MHAP7=WXEgUlƢ"g}FaM@,J<"A¤| ]6V@V `"?Af6=D,gЭTOST+q\BSiTP#N܉HcF\.@c`EĔ^8cȉyX(l?$ĉmiuk! ݗ$A*c$H} <@nH2E¤ס< z}|WPEkx @Ľ.`\DXQDA#R5/R%2Ťf/, %L2_-A. CC0iEGѮAY[X@"&Z\D`Dab-D qriH!AD^DiA(@L #mMbǧI-R7D#Q%G? +kt پ]DX5?K@VXODP.zzqA2DnXx@$AQI)DuA8/Otħ1Bj.uA)PRKTo,AB/ n%$EKsD\CJByT$["E94!+FLTþlX |V "DZG-6l ]e]μWlWLJ @,D]PEaWEf?|^GDQUlUENUl9]DO^ Ag5`v-2S@-F;48(ӽy4U@ AC@t+GDS@@]şO+4%":{ĩ7(F9ZTᎱaG@ d=EƄҹf.T@QH ]SREW%Ζk6iRRO0_waSiWO )Ap9P-YL$C ?2#[HUDUt|75XvcLB0tԼ?4@8 \ DLG4(#o!S´AT$x)O&d\9AxA)CG {YN|m$8%C&ſB'3y 7'sMU?JO*&`$P`^itcM7먦J!ւ ?R#Lr#B*ȸ((=IC9MVXXkbp2`5|f]@"6 Ѐ ji` P ؒ&qk& @@*PA[hj)p7e0B9!iI +ګHFrP "0PT@|? SMqRDz,!d x*B ZZ yKX $M,,ު$%9N& %!CYD~ )\.ɉn,iYK[.yiICʲV0YLcTfY.τ4YMk^6Mo~8"`e q,&EiIÖFJ 0YwB N@P  遏B1 **8?p'HMg3D}xaX`fQ DnP$z AH &dC1,4; #)Jg HY T"eSBC!4`JHL #D",HN- _QQ% mHPX*-:@!F&4 |,bJa(v+ @=& 0 "y C&ŦQ uv֍@) E IA.(=H- 'lT'z^`B_þ3oR*;B6<'T SJ@e#B0$St}eL LR4oEhaޠR7.J^Т[X:OvapK A@IDE@^4f"gl$p62 6(TЉ1gmr5I`NJn* NaAjDώrlY%vעGe%Stnz2upV8I,aNS 0lY&[-/MGȑm*6~<, W4^ܔ;D%[b⏥9mjf{lg⊶$@@ǽl2#h,|IڦುB6E,4p/:\p)Ɉ3c6II̅"bݐU(5P4 LHԺ!*8 ]\J'Žm~lV\Ƥ)Xա Sv2h%-A㚂м3ygy#@9!l2"JR3t,Fw > a*VS8) E,BYD@* yB-2@A)V\eL|`u-TܞA٧pG hR!q9}m=ulllp>V, a4=AҔYTtT#= ՀRZ1X H @W41FUZPnBeTlFe+4(IB݂`DUO;zD4PLpЉZQƍ(0PN6fDP@P6ERPc*)F /:𒀩kF_)g`$l`b+`FfR#!F#ʏt$7J( *pj2)Pj`Db)0/)/d*O4̐ub))@j()ҊP$Q()&.ҋA N!TN#8v*''!B#D bz^a*.Gb>nW, N[ĽJGEN-q Έ`0.44qTd-bpZ#a ,*, eLEBh+[J,%`DE.d4pX mrD@͂%6G\eh@$AB4ENS>fK ^x')93*$4BIP(!*7; @ )4`=" P4?@0j:\(VEal4@b4stFB*"KR` β@B$jpf*@AKEk+-"d1Ppk@ r K/!%K j !As uRFudH#: Ld( "*@  Zk-³4.@",z\ c̴t1`4AZ 4A7' bʀ @!pB83""MK(>@_w_U_2!U; HG!,  0#l46PԊ%JrdV#L El"@-ҸȲlg %rQP1 Rf8ڢ M BaV6g@ce g2 bcAk'gbL[ hl8"c$eWL-kM&fg#6X^R!lҨO42@FO `¢Q :֖~2a*GD<7,N3n :cmgզI0H0Gckfۀ2+R<"D\,J҆Z E!.E)*R+7ܸZ0 3"rRt[ %(!H(0]}*)qG| M~4FR,&Jz"YC.+NB  *\15+. <'ȁ6U(F8o!!"~*Jy'b#()N ˁ/@t$Y쾒 &Xu@B1bVf0b04ң##4'!(xPdL`Yr@JjI\Bڠ"2=nS8(sV,47`bsF*2)jhd:B3(!+3!b!A+; ADl:A@>r&Ckk"`/9Bk "c6ǧ". D?4|o.d"$* ğ¬1c.,EE<"&㍊8LDY,[$ąDP 0 Wj$St pe-QZWxE_DabUf /ҁ.E|p4~Z+.ZPdPT%qVLTВ'\^RLR;j("$6>,"oAb9iy:r*XPH'hY(PDLIbMfc@`QOGy9 bְ) -GC b,4Q(I(HBת-{wM&5du\"E~-~1+'@yCKkIX4X &p[Z'#tтadHH+5bAsF9)FxTρNCjduIs㇡"g1)X/%qq8Xxbģg$z 1P"zwTN"~/H*wd=*1h$* :<R2(Ί( $H\L(G̜46N2G,(!2dK"tAj6UDlGEd@P=*Cf`DZ7m*?,`N%= %;C( =S(,FEQBvMf4!" >LL$ZF!$b H\ˎ4@N x])<B`I[ 1)t߫b+B~˂UW!`a"^T(|SaAĂaT*aSKPX^!..sd5'X TB`@@*ETo"pLa'2ʖ)F"dTq`HI3H$<412H(pi8LQPx')>J oFuPV\F"κ+?kCS r-Ԩǐ#K6 *d?E9!:D+BqJ7V`Wqx;. 0BDB\Y{ EAoPD L3Pa 7凜mndGkѭ݊F+谁GSh<@QʐX&Xzlf>`kR %P)AEv`?^.?21)@4mG^ʰ\#?׸B3?#Fv00lDU!DA$oJUEpR*=QBXt߶*H9&˯v {KxmT AHu?:?uoTXp#Vu)#d %lF `b;m+P/G#mRFXFAF5]J[Dmm,WWtI (g8 s)GʈhFu?ݧ,)HOhL:frMy,U$@ 9؈: 1 h=d(򴃃jtĀ=N@n@<P 8F lDrP/#sjLQyíІAGSDuzr=%XLMEqxqSIRo$\IZF,%0TXҁ6([RMU x6e@QP8HEjhB#(%)v2:t,?IѴSD9e&'FW\Uh:#?[etr&7JQsP8"ǰN2%G0=nC1#8U(b(cJ8hz#4 5q48!+%nD6 ,{yT0>A\)֧u@}469ob4>E\Շ(ҍLa Q|s#{yL= $84@ =/q#E "OTa?!r DTj@H @}1KT;t&^w|Q5TbTiVb<]ENШ,>.za0Ŵ1=MDƑleyYfY0%nH k|4(EK?ІP4$E G& L<)W5eGN.ջ$6`/3m k)JX!hu7ݧ]Y$ M8*VWJۈ'Ԣr@ @?%&h H 1[?Z:fNnCQ75[Us$3 @~$ C\|&?yND$2KL"+c*KN3"2̨' Lr4W<tA_YSLь~̞F;ѐԇ;ƙ7N{ӠA])PG rs WuQָεwkͦM"AfƉNf;Ў# rdU'lx9B: $X С%1G\H9(S`M7{u0G{ qBRpI9FJQxĊzI0 6΄!7aX#|# Ixt! ;@t`1?  x&e&}jGU"Ԛ[d~1l7 $:a.G\ ;k-SL̡%`v@,ܯhS QGuh %%imG!xP8nE¥R( )9/[ MyJJ/ ЇZ92;E`Ί%4D?`V5Įc bySƼb#o 1 c~`(W/EA}\@dp ``XO 705q1 )0I$a%TD'GH`{'QEGqV,%@ebA97zlJ@[ ,R7#jG`bJD"9=8B J;A" vE(Q-Eq-W"D!D"X&9QSLjyfH)6L1'$h.'i2%A0'%RQ ( "8TID"D:dRd"#yWyٱq (q"CRQ%]!_kpi2f68tdhґƱbb`ePhl',)e'od`vHA0'fGIf_gBwR+T9-Y)lP#}R/Hi .=2_ť.f|H( )(IFqRZĵfSZ_.~0 C j )qX`(0h8qg<5QH BcrA @ g-:'g{p*I/Y,9Q6cyT8h6qSt'X|WUysk_*dO:a܄: ;%`;ss!j:# ђO)NvIC崟P9>q 1QV @p'm0Im OcC4tsk?9@>&mtFAAJ:Ri.cu,eQ);AB9r7<&9 Mp>J1Tƃ<)$wuGL!CGtC;5D6t2F9lOgJ(!c~t{vGpfq':a`)hJ'g;a1)?)?%C4[E)EK!G"UKC1)#t klnJgJJ"FdɊ`f8Q_6KiAg/wtMqʗ&& b\J'j'TTu 869ʗi'-!L$LK[6T&1@PL){ՕO&;aC6 ^r&eq*EqE[b P&F4jB+ r\>5 y @"5U k`MF(!ГF($Q\(]FbZ+^FK;6Gifj<)0_LtJf!y-sfJ)clk:(k<,i'rPmwyw0>z7c4G@vp@&Rb'=Џg&=@<a&4Fn)qS7Pow,w"zGa f,2y\0}9! QfFXydi@o)P\{L{J!/2/ȧ3<ĄY>Jyi_mg\im(_mc~ x -P0 Pbxz3Ut淞qP,y,A})ca !`YÕaf(V#۰268HӤ]޲F@3H}<UQRV8]o7e ]R$ q) .eC-j1MmA!c~`$DQI9^#Z&B1H)^vx4Bz!=nuHᕁe؊!%r';G#9$Q❁1D~X`#ʪA䓬dVk֏llFEtx$ErHb;G$䐷L1&Ek |._=*t+`%cg'..|o`")-)s !␑iƓr=.r&^)K*W锘?W9J${'g~Qm1g\ҷu y4{bÝL9n^̃Ynq6tdP &:ZMU6ݸk|󞥠ʱ4c1>S -544R˔Y9DSYpB q`5FQcf96X{]LX\AX L#]k&c9*^3S[д3#T" [9_@9r'& E,:*PF9G$x3fC*Ot? 7@=%5 e MH_@IkVQ8MCSZ;gB|zFp9CzBW0W]< MȆdg^Gf雤u"E#$V"GDZ q7rOIDKhF#O##=IJjlI{>?;, 2Lux`@ń9Z0lj7JX1@ƈ'# Y>T`!}4hO)@zH8gc M&σTB}lװt ldŎxCT<2i`߿{~!A/ f%ZIYbHň+8G!1"BM7 =.b9,^KP&oٚ`r!߁IwN+v`6E^TN)B> oQCD$ A 2h*C#\P(#r<×!x0b ce6ȎAB=<( "CB )" Ң2h<1#"@6òت8J@ɄC*!>QA'>J F 'F@^i`SXI녂3T*H6 Ջ6hTKԈl% tHψ>I׷T 4IՉ؈4@ӄ4W,0Z | !VCX(W$ՌV(Us^-X Ou",m$ D_48-] a#b"y}@@!MH"ުȄJ&8eQC.Ie1[6%1eTe(d9W\ɡ}"r̠wF:i-:^,!^@;mfm߆Ea^9no5`؃$G-u>vڂ "g M(nԡdψf$+_ BA*4 H& Hl"P&R@,"#Td4E١Rd<@yh&с hBsa@$0P3 < H.QanK*|O#Di?.׷o$ƨG 0 w с>*C ąT8đ s$*3ep5> IGA= 8 &硣H8^9 .2""??&$T$UȀ @6N{,'p@"-%dZUB 2_AY n fS7T'Bd7V2/1 5Ny j4Er`@'rRh&Lɀ_L.`?`h xA (@R*%/vJUH ?QEulIlD #"h 0TvX̧![*t2u0 *U͔T`+H$V9P lZ aXԝ@RR@ƺH "0 h'?ؼd<]j( F3@z0%!jC"H McɺYv@@ge 4V؈!+@ '.9L: !F:P⧺R,D*K P"ڹZG* (VBxXvfC5*dZL0E BܟwBkH'"Edn4E^τ%E^[Ew.*qF ISDKZnnSCN)xCztFO~9IP%$8`HrQ)pL $A,0 #ECB=IfFJ#HH.I>yAxFsv⯺ɂK魄n$H%PJ0O'`bB $1TEё ?C 07(O=SpAT `*"GYFBqݸ'5y |FIùUn`B~m!?h$IN{@) 0x@A@ P& -`7`!Enn=bԘH40buZa>Crނ"݊H`"Xmb{2RZ@z^j<ǝcgId"WA,HK 읞h(мAT5ep>눁]z_c!iiDeQegM,w RUhzo,_n[x+#Hŕ$5 o2$ Rτfy}G⁇tK (@6+C @Aӛ~jBص/;Ȑ-P` 1⺁ݩ0 9$̗sI&࿂P 1 -ia ˰#Y( Z;X!4d.I'ÃpL D(B3@@"A$b0 z8u"@2)UDnp2 %/3PX39;˳ ?"H=)؃)I#+nɼD SJ^#Z$l8ɑXHx6##+6lkVp71kc@7ucx+y@q@1tSqH Ql1  b!kɂx1Ȁ$hkYϰIL= Q_<TTL^Ix1 1hzq@WY)Xl x5P1ʟˢ¸1' ˰a~y33JԱz пMWp8oupє٤̹DŽ\[GwLKDTđ@1 Aw"/\Tg_93 '#9pc8Ȉ񑋷Č$E?-Ԡ$;X#ْD$$ܞ %& 1e X::` ƄF8 ǰ+% ` tڟ$!@H])i 0P-8LX`LHj Z.Z_6H0YS`#w6># 7ĸn;D%` Pz7$U8Ae $ @j5O5A4Q)ۜ7 ]%UDVr%X%AHh;MX46@Bb0ͣ=JJ)8PEz&KLhт,({+ش˂r m&.,6`5  0: 4R<8Yd] xukiЇ|x~ |8x:k+=$ܛ @-⮅040+ H `*0K -,"hجr,ѠH, +Q @ *,ʬ:@Z+zё(h"ni:S<;Bc-^aS`,UjLTJ Ƶr V %h Vy hWɈliԛ,ʟ 3 ~̥ ట0P_"V!IpxHȈh` Hxxd8oBM84^+Pd+JhaM UsI8FS4C9Mav=x;84JS4t9aḷ&;$=A=58$ 8HC&d|c7 x0z=Xk$8 :YC&֎CTQI Vx{hm!%pЇ{T&yM &Z< T3RrPբ+a4KBЕQP !:K@ $nf Qu x& K`;uu:А +3G/b̈P M &X8,`Z ⊁P:KA&@B(ʱtl4}ц肦V>C%|q״ ;=E A;{_~s܈십@>nRp@z=c8C ŔÌHǖ n HryJ R+ h.0D/_1mw1;ۉȠf%(+A!m:{N-  ?~1Јho& 0ib( n șʧF"9c6 h"m5%rTjPj*:`6 @pJI Xᄰa8P;$abDa; n˹3VW \En\!hUa,Kb5ZX6ם6 d`>JG6H΅D Gux-Y~YsЇuUne#ehYPi  ˅( Ɉ) c!@I;uȺU_ ڕ|!W'UhRgu՗ʈ߯ۮMDіb° j .0h1(o/oaIR tѧ;LۓM{7k W_@ wxẅ́k+xFp/I_h,pydZ w (^9otnpю7+vKEL| zes-†z {hz59푰 @Fg{͕H %H0 wPz 7t:p+')X |#  卄WuOW)} OЁ w5 % h(OF`>hAߋ.*C'BD\|,i$ʔ)UKC8} 9kDH{sp(  ?,3, ɎZ*xʃ~娆X @р̜.rLlt ٻX8f:Ӄsy690glY,Y XEDd=AC]<(E- za)y_.y;-E'&\"4@2%7{& |yP0if@ A"VA֐ :ӏ OB ?4A ?25pPDq^"'= Y! VA@>PACVNI]mVRUTAVxPR1ҖDAW(S@KV%A?\iAw"@?W $xfVřU@z)d"(?`m AYiY^d(G$UUBP zQAa~VUv An^ZR(UdU@7?]I>#D u kRt&8ځ疪ThvPN} #K-AIJ{|{0JrD$U`Jp GjİM9EHGI`qEiE۲2JqsG4? v@!p$UHU8AG8fT"ʳ[Ek^^PK &AUrHzMԽZEss])9 ?| 2|8M N3.K Us+ &A?@} A ~(Ca#] Y.ȑpdGCމNbPYeӝJbq"$CD-:&Dq$GPTeh(%񉃒"t\Bw9(A7D d̍M;TNy`A*'2e@n$( !X ; # .˹"04A>p-) @P\WADZz \hD,:p $)%:j_c&p 1G;6`{o^.!ԤFaYq7M!ؼI)EYIn^Lb產bsM w0HS|.W$ ,;vb'WEI,мiJ0'!AD(lq9,ȁ $<8$Gl>ѭ@A%Ġ 9-5!bI[zqb8GGeZ# "TcUqZF@dX RI 'USVPc`$HLlD!21Ғ,l?5y͏z \H#`G޶:MI$ pXl#e_?b ު ,iWnJ:=%Sl Nen-Z{݃8"q|@"V8H8^G?^A׏m ԇ0) il󕕁 ™r!YU,ޒU`9, @ŸB2(*XXBK>liF.َOLWB~YD@X)ր^&2H K?V@`#Hb#ڐ) &@D(AR:P:cY@:MHOUd5yST%!}C OGPʰ]ng8sjn+$a"kA)+M"eS־6ML~k`9852 |GoAx&, Km$$ Hr {d>8zo؅\8KIBk#BdqickG\};BX2169s#T9G"pёx[izK#RA%PSV:ֳu'+Of0pcQNx:9h>f Lݹw@jm`ptסOq䃓4qLCfIlY'q:dP!p! +U86ؘ Y0dQ 80,?"#^-ӡh& J,->4IOA)PDBV“T k A @sX] r@GZ<υH÷evD-TDIb  r?*tg,<@@jpZdGL&&HiȠtNMV@|]"\A |eICGMxLtC2C HKX GAAe[EmLoH8 NQ˴@ ɣеJLf@ّLeQW@tJ ^ @ t/: ( T#Ac;J|#pDD^ ԯ|aS$VXxhW\JX#6T܄TpI|MŐIY@ .Lp|ehPf~AIG@ODT(,AAF?nOTD59(P7T 3GGAĝP a.ҿbJ%WyG`җM$"@*I\%*pVW Cܴ ChlBi*"x ^ɡ^ʖu%pDQ,Gai eRܘmC2ዂ]xI`Ij\?fzGnIj,Gnֆa ZoMFdmlF`4fA GO`~(݈w9T928`z{˞DC|rD}Wf[@V(PXʋrtČ*ܐ 9啝D!E۾Yˎ@9Q#LZY8RDuG2 3$WHKA[lVXLA1ՅH*n\.[T~\̑Zc1[0Qlln0w`q+AO=}#C DF:,>cr  N:l-2|x1'18ĕ1\A87œbN:`6CqI\Ih.A@8<,M"71qRFE9ΦH$ hS 7%]KnE<?2$G$O24CCǍ\ׄ!zDl @%NnΨЋI0DDBjX]J@( p}"tŴ,e_4DLk^<m|AX &$?DsV?D3=3]@SN_\}@2A,@|ƍO߾)A42C4Lt'D1 -5xG[ AuςI 4uVW_@4,+.1A <^2b/Rs&t{l<Ǖ0AAl?{a0f"(RAu<]Sn#^ &G,XPZsemXHsPW]4O4sf6c\O r`Z  \!0t(D @գd˱)R$EF)K?Y=c Q?tmWЍHv?LMT JI/*G Y#l C@{RT>QDK^E@@8)E>޸sE/:x`0  I([0TJ$.ʵ4Jl~0KHDKtEGJ|uƈQ@9%M>p|L|U $5ew 5ZC ebzXF:AX; 噣K^)kfy8FS>6Nn+/ΨO5ڈ $Q+X5Jʸ\-SLsjND@؁+I\]RA) ND)(_C|`?{FhHB01@*s:rlN;-Gci{CO)\0E`'$* 4XX(`oC&C'qԼVD㚜=HUtDԏɝCUM7L<~E >5"\(+(y)$u S)=Xi}3V%Cq5Q0pؓnA.s3dq?HH,t!AfI+/0ӯiۈ?L <}A} |æ#~+vA`l}`4]!a/TA9݊ᅣP)(0!G jI$ca:!7!<05 yČ:6QG* 9 2!2!2rEY.!5MqRT4Ok7vѧ@ͩPŢHMjԠR*NZ+^T2Uc$kYUmu[Wfu-WM䕯{+^#5֕Ib!z,V]c{V.,8~qYZ6ېdȔǎm5r%\䑐cJǞmc_nu[ B1VdtMkM惻wZ#/y{^Coz"3dx]wE~0xfM`/4׽V/P"\ cmpB> k(q ?x*fX_ yo^ x^JnXW+"I7\aTnH/'73,laYeJ8rVfs2 C1vnsL#ho|H/q_\ 'yO.m=@nɒ3<yo.qp tC(|N?A>0_"=C/σE|ɭ~[3O}}\=?{=K/}R Ӟ⯽ľcS?|-?߿a"P 0,#/G"ے% ! ,*d H (@"(@ p_0ހC:p8)S&4Ys`W`q J! &MV {0⟈9( Qc>]i8<=[#'S $r@&vڜO=@GkL+ETRm$PSpD@ jzӵ mh@XIF]Ay[PI4(j$ T?5,1ń?^U@WE@S2$ E/jp0A~+.tkT3"0S K$|AAPPR+RZPJ)ёyWA 3Osl=@-ٶ[ANg[܃EEuo/vTŒ~8ST $͍Nt-x|P蝷:ݨ* Ab;/k[ćhdY Pႂ'Tx@plHЈbAq@ZA@l @|v66ACMP' IoXuA޻Uu4$}(ȅrˀ@atJ Ї@L!C Adp mWWTP. X*%0_@@3 N` cFH RCA~!K ] 4  aVڐ.< %ȣQ [9nhGPPR1$ m6H0QBG1eDE:JAq25Oɓ,\Iő$S ܽvn$*r˹Y򉋜jכ g)]3A-]t\ FM:Ys [Ir0"EE#$~n7Zg-9Ǎ:0zi 994lשs_ %WVs}UcC%5:]l @ wXsn}uscMqxXj[%m F=pSӄ 0[݀G zM.jGBn@]w4ζh:P2e6AЍG9!4 2nj20 "b Da 4LHUlHqw6AnAIu#4Iv@ρ GR sX,Ad4YY4"+,AсmWp`MhEzkBU4^ C@0+B i Q D,҇K(E9y@Ѡ;{F,8i;{ B|Y$EYs4‘sMsk;ڬ]FU^s +*T9H\;|a[0r=q=# E@-"p 4#IKR`x @So!ob@0 ~(1 qR }1Z*2AP p 5"sulrb#t@CbKvp7x@P+ 4 p(w( C@sРY"|&ӠB64!`#5 6g!p"IJCHJ,+-Q'’>C6B1%Q"8C~ U78r19C1g/e@?/@62GȈL1Q3AB㏝8#c-6Jch#nbWR"46Qs/05EQ.[1c3'gaב7qWtMRa1c&pC -E9Sm 4%.)F݄ftfXa7Q* Jݥf@4'%Q'`P "w&s"1qwkwz x dc|-,@2$ |JT)E3etS-RQMX$XK! E-apLR)0K LA@ *p %uG !}}F:5twdQ=F /WOAi28\8,vgUN92OpS7s.&NZgB&2idCf!KqJ )@`,AƲv6oE!)ep=Q#1uYB ~3I@!i⁙0'{љ4@ay͑Ilw*:%1™e+З*@Cb@E_B։_ɝnb)/YY+yZ3 4c}3v9@n>qWcbsk J;Y7H&/>I/ U"%B)Vk0)Ҕ .`E$6/8 .+.5f2& c&SF1EAfPVI1e1Z_fb6. 1@CKC%!\2}xUa dv5A+s 6g*J::Q2F9P5S/D®vJ2<4| p d 4ka  a)1*@lg};Dr.j e5^ monc(* m @V\5XйsŬVBa_|Ȉj3٨WCrzz64Yɮ*.͚Ȣ<ʤ\ʦ|g7:LaQK+o (./BQA):đywAynW0a@z'簤z7xss|y'N0U1Ds<'acx!r1A!%vj''*OzzW4'{41{ ]zQWyzWϯ7>0"LMU 3" dvBgjB}:,p~?E(p$FA u "lkE5r#9 @T82y`Zci`(DKlC 12Tq.R'afu*\ CnPR8e` t6e@$ٕb#q9 ?(x@+gh0Z )w(GlVGX6'iq@ Pf 턼gPNHQ,gQq-7܊8%8Qؒc>=P6CpH刍0R+HΘf>Qqט3ۢ޲7i 40{A9SzNX S,+.,X0=18#6svsbG\41:#C>r:cmb: 1p;T>;7ar9M{xd]M{ ׷.ͮIYAe6B qYC% Y0͘1ڎasF>B5WЯ!ro"HuH04\E_Y5iv {ʟ"Zw  νDE/^Ao']P^!Gk x16[pQn!oDR4.jWBPjD\& .@ (Sÿ+E@0 ز@@Vܒ$QuJ^0у"2`D)D7𗶂$=DjC. z, G_45 ReWmr}Ss Z[P˷} @`]dAJ _&1OU{APi"r*D%m`"YWx,D j] vH,3  ?d|ȋ\J  RM+aw&:t[ f}q"d :f4do @AB@ 00  *BGq0=( #`J0""rK ^q" z^I*%*2,tw\I% 2I(8h,2Zx@!r-4q,DG-$QI'H 3BR2-hNRSQ}8UֱV-QUU+W_x:x k4` vbDejUYϠ5ف\sE7݁xc(r$sd^r$>!D~^(%}x,_Nyahs(.ȿazJ &dOS ze${H;rydMI]NZ.hf9蟯:kk. "(&W%] YP`l/< rhB9& 3aKFElaoM?^`="(B%1-:h.$3 0㦯0ЀT|>0*{1Q-F(g#jhȁwMW.9Df /lJ> d#@P$kT7#+q T -!1 ȗ8 S aP$jWhźabq{PMAԡ*f A-q @࡚s$c@9C@RXHEPWtq@6>0b < X4$Jh@³ yAT!9kHBPV >z" ˆ(rhsR|K"4  p.tb$@obP^LD-ۙY= H *Gx#BKRųr@BD2IV@@N\R plBP#(XH`*C ,")]9~.oz Q9Ov*@ f:IObNm`SgAZ#gA@R8Q:@Tڕ D@Se\sfW餷 s WGxx ]u.+kT2B!m.{XJ(fe)%W+(Q,Y%,% uȖSm8SA0[2!rNR5D0:T2H@Q{.?\uDcVuYA6%&iI RMl%cbX"[#%0vAlh #76SwvȞdkFkT lXIM`< @4|_qA:1H9"%1(s2 "h0$Ay[얘%,?7hpE(hDD#|قipDBi V_Igқ8^\t&Ev nX<fnTfѭ`[ S5uE?j7y5sT r,-%?ɫHEY@yf,X$qEAEq*AHZ ,|PJ"L3۵Q1s8g@Q6Š2ɦ`B[9f6 ( A#`BM XQYіH 43j9r-*M47 x 9h-ii GCH>À84dF$x !JD0dV1`5xD`wAÅQ| x!VEt[EN+"C`Ьy4j#(l8y!Lћ^4" [q$r4sD8(( 8!㦿ȃ"{G*= $p8Hp9p$EAPgH֡3iAIX R*p_x"c@>,DT) ᑓ H#a `ƙJ(kJTd28L@@b1,{ YIC dٳ,x36Ȼ"Q0_ȃ8c /ˁ$4wI2ɂ:!$A( QW#+)/b "yI:2"Yb{~ 0/z|N(J`! <r%$(2(7ʪ9b왌RI`G;xD8=^<  ʑOC" 1e 9zA*P4,'2'b22sɫa@:Q©䴰Bш :cƗ%' ؊ H4)Г ?t+ꨎ+©*9ݔ<-O#sBA&@ Ū( rry X* P}BpB36eBQDXD ?*8#u (5SLDȑ`®0?ʺ8ž1Gi>,+ ?!yIQ`>)Jx T xĨMЎ pLLpíX XU%0M,`d L]Q7+b7>b ƷCK),mV'*`r `#t QaP}jv;7|Bց`Ŕ!t,)+)5͍:\;֕9g]9֍]8+C-d2r (^9dІ r1 G¸8+=T&c};`2__n81Wx5"؄8Tc;:x $X?eؗ 5YɁhP{HYts>%]Ȣ>w# Cpnx !ЪN5( [4Lυb]Qjf2]U@?9ah2S䬣U*)K4)[QT1$ /1AET0 V)ܨ?.hA[a W_u t֚kjiGqE\Cy0aö46p),h0.A`Q&66^IHqpT0\M؆^DdL0M9D:mux#O|anپ;nrS}1KIm >0{zeX}6s{3Vl5-`'|LA}ǶȀ Rxpio3䈷]aRW9HI_ ʺ1\>Kc;51кlIe?!>$JuA0l|5\f(!r@th0dМK [' *#/0J)QriA pXAA3)uQ&c-( 9rYRWȴ`H HH`C ,8Z+$!ԡJ!f"2 xSb#p"+܄"1#2"Yg^~%~yz,BD `UPWJ%(. 0=8j`iL>v6k' PiŨ@Ԝ8=@]@Ox\oZQ0uZHBu /f {]FUa/`wJ,ht}+AQQFM' % ?)((b6 lk+u؈T S׳%)Q*HT(b-F}Qp%PH+!(rZ *b{ LycR **']qkS9Aj4o{: +ǂma?4|@ „ ! ÁRAŊ"dHǁh$ǐ'Wli&er/:(TEO ܉ ZL@2qbS*?>>d"^%t`T(a_ B;P@{no$A1_Ŀ7D CDxH368 ukE>L@R ЕAp?@t'$>Up&@gT #*O(B Lp $Prꗐ sI7,<Y 8RXW@dZؖ@`6V jߛ_Gה) `AbU$X0QV 2V5ؼؔ,%!nHY" <MbC6^Q!I]Vu# iE^ (\ 2,RI4k'Y<^T L0>95T@Bnc+_)Ya(J|99T`%hAH}`Ip˽`DH.4T@K2q ωu0MŃD@YQɀ~C9/𬢰&"@ $ h@?X0x.,_/эr4nGgюr dI"MR-y^8wr="L'N@ rL `?tTHe@313#)}_" 2%?3j%!P&"r8t@_(MX sD| r 4dMY֦4>hJ .ږ6iL*PIi -x OL;\|7 R@ l{@JbjGI8;-FWޤ@&+L*LY47A 4Ȁ“2u zࠁHS`C$ 1)Ca BMmmA_N@d0dv`C!dBLX!]#V2ܚqɀoJI 7P/Q^ƣUty@*gij@@}ume NIdRC$@k K} @aT#e C@jf]!I@93y~SDScy 28TB (K+J lK~Mٚw C'DK^?T`ڈ[gi4V#gW2Ȱ 7&DR#*wA`~)HWGn$ǐ3: yK 0>ť(M2K'!:s*"dlAJ>Y 탔atB)Ѐ755j@f*$ ZmKEqia9`1;Nsa Ttcļ\\ badGWBSe?ՁpZBް,) ڳO,!y:~$6 W؏) ,‘I#HXD"Hu@h? @$< yL|slS$ EI|^\IGPᔥP@ @\`%4tAd4<`EK BK) OTaʶFxK8DS ˴؋Ir=UH~FJh@Mp ґ=IӨ@!,ṮQ DH!;9L DA4@rPuHCSd?@| ]ElLˤxD /Œp؟D,8^Yy "xINbø` <B7RE8N i:DM hj A4E]800D$ -xv 6DKؑ= *E,dyIYvAFdb gGC=kDu(RmR ajD-))U!D#}_)!tƧ|g;;´ ` n<%Bt VxS]A Ċ A9 }V^h D= ULT0b`ai(E5b(֨ڦ٨ ?@MBx&x&iK&C" w@hC[)XYqE\CTHa-"D1qB (S4x2T\BaBx"\\D0d|0m@$OĖ2aY_w` kH0AdIp`CDñ!DE`A`́rCVJLah Zpxv!xԖO|"wFd;vLg ^0x@$F9`%Dj<zz@A+KY?bB甅cLB٤h T"|?AIGXCԇ9T`zD%@ЁsiXBnF&D,DG BXčA2AQ(oUD),Yi\Or8#<)ķ l@@#ͷdL }퍽Ohˮ˕[ip&A@ }`F) PWDedn5||dě<vAA` VH#ST)[SÉD[EpDa Bn#2F(DpG`>RADQygk#ي!\BVndQKFHQtppqd_!&0˹σ#NUi^4QDP$jF(TpOצNIXm*9N"ľ/BIA [VE$dVfhO؁+\8v9MB?䀳FȎh $kI'x@!"**q-R H R8U0=uы.v!hCxKDފoLqD.:KDoCJTnL%ˮ4f9M Y )M 4J/=%TI?g,>ר[K= Bt2Chȗx?y-SJ,Ju %TԁA Nmc4D%ɞT ]9OxǑV ֘M;O`W*[ 'D &AK2u cddee;HjDY$Dgd;ߙX^s8 uDP1lA@cAk^kbTOH%4uGͣ%ģQrr*B,XPS\&xOwOx/^1&O`^6](|w4~?W"|@^tW{C)YghFBllAX*~+,E/ۧ"Am?LgTA$ @Z)D@,hH!.KGhGSo۲BA#LBbOH^`MmH\@-ao?\zltپ-C?[ynEpڒA@ơYt>bF~FG]@<_?>p"<@B |$D Cx CH< .,̠!6 ͡6CBPKoBlH3%JVq2,jT~mH@_϶,K:n]w̻]. \pùXou.Ua]^B?zrԒ%_©Kb+ 쯼}k "k%YBukA7U/|sb[I !I@Iy馺e\{+ {<) S( '~d CAPi1& @^!WC *sI64RR12 X %X0M[o48Κ˱| 2` J&S8.6!ѯ$((B PP (N=ݴSD !SY!np(*Kʣ$T $5RF&Â:T˩N[zR%$T:"J@ - cV+: B Ґ&X."H\c"JzSQ*܂($!u AncҕS#7#]gM%{li! 6zH+zRjV@:m\jUC蹚$0 MnѦºhm'HpP{쩁;!h+L{/Ζ Lkp@_r ^x ZHܐ:TِOi run g~힫Нעz7*Ts! vNu(7viH,22EêB"m\Z \%Ȑ%!N%@C#0h_ypICè'Gk @&ITQ*MiS/*5R8UBGwyس B284"wrIjj9,e5YJVGs+xWyХª0GcBרG ׂ%`oroiuc!YhҶҁKmHf d *"u/&~zIdq[eE{\סÌ蠘\,(sI,UjMۿfr Rf "BP2W0?uxp UfDCB:p|.y߂CEH04rBA+NLHDf&V0١+b:'#b4 (WmА39LJ%$2DžL[W tK!ap`j{;vt,*P+iKK M#.E@@+ jY߁GA JMhy$T8bkTŰ#bXcBx ORHC6`]rl0AX@dlpnAʿ0[XN`Q`)VI ݃{T`E @cA*$8ŀq"UqcM/HD70G(ȐT).n[n2+!mR`2&!_YJș|_H4 ZGŗd42)ARB@#iSKT`@ >܇$ɈTvz+dԴ?BMmMэ]>*Օ; H%aʲ}.$8PxNܤ4660]]hv}%!LX i6bd>əc)]2uV%GoR6W,؆>H?.A$7 / p0)|?* #^%F**5XsӱCeTcBG DlRCCRG%۲C|: $ ^ bD&PbD& yt}!UOuB`Ba^Ea10$(a^ϩ >+rH` @g ||'6bɌ$TeB쾐Cax\B& !y  ( S`$A40e `n'(T}n Rʦz),+F``OjSJ,S  Kt ! XxJ.d*/F. \ʔ↢.gcT8ܤqDgmfsn %S N$@b!f( Ȑp!Go,p"<+6b'xojq#jM`m_F#fF7VfvxV.+X@ P r/$p! "YD%Ёx'5E 'Z2\X0aV2$ D$ +depJZ^v+$+4 8hFCBy*a#$Ld.Oμn&+BHxBBDD/s[BbjDALM"ig%R6D0\B #FV0PBy21+ E%(@~2%I7B6 b+Ԧ J81I/rd%d@%8`:)!|.ė.%RkcXC1 @&5?vbwJL`kѡ3mp !/> ?N?A?oQtBc0Cj.aCD+BtC+:oT@1ԁBe7T xf4Gb^6a. 4+eH.~$L%A2FuJbJݰK$(e": L'/†7¢ eNpOO5P"b R*!¢Y 6Ġ!( Bw:`  B! heBbCaZx܁qej.:0*]P0*';@UР.HS;C΀pt;bZg 2$rL5\Pg^nZ!D nD,4Bl%b@G٬Xe0#R(Q[+@ b : Hz!`F$R A|ANDm 1 b4 1"BD Bx bB `KQ{ *ܔmSg m+2Rl.+n5ienCFШ &u@%TC9|m!(S1!{.&f\a'CN(c.FQRbi2nV7i &XU|"! $, !(<@)f_,\#d\,:@ ŽF7#!>FZH63xbx Bȃ,Z7hW%j m!OѮ'hK~)2/(S"q€+".KTi'*B} B%:|]!<sPU]B D:!™Cv"KrS+j'|% m:^B&`$%AA8R$"]B|%CB X\B'qF ~^B 6A.@1Jhd'Ƙ aGT'u"e9.BJAJW%3NTS8n!s$?5ȑi<(AMnLhV "q`ô b' £†&'KHb.)ԪFuSQmZc Sn9<7] vȮejen9>^}xX ª/e.%y$aҝ%%WX x}C;+"ZPu u:$*%J^\2F3xDZXb4@Bţdsd5F!<@Z#M$v %(0"@XiET3bĊX{e@Ŗ:bDKy\n+X7}71<%3ϸQ:<-3S(>E×VXH8>#6"N;.x.b;5θH|Or8PZRojZIx"$Cr{vq".7t:2Lɥn/(w/̉+E_6"jCB+v7J4$ D DxNFO:Tj"zJMx[jFDEb荸^t p$WZm]&lH?`lZr $/I%Bɐ oBGF&?,Qt?fdL>4|W@]K]EÄ&JC4 5YSQE38LpP1©م6`\ͬ5[]* =]p2\olA>fD?E} IksSD.LFED",=(тqg*4MB"م<5nP4nUYQN@ՎHt)Wou@E/vzuՁV9_{q@Yx[gyޟ7\nDjǎD8jh#k[_0v<kXc QD =$ _p ƦQXIDx51GN)Fdg9ӴY,! aC&$Q{!j P@JqxD"aŹ C$/VpkB瘍 LJ!&' q G^솔#/@Bq$0EJf"G6L$&6 NrO =@!xTEsݦQ,թ[.ȧ$&R%DToHċ>UM `XGJe""&"=H#-xKU'q!ͺn"~HِFF*"o .t À#M Zә6fZ%bfMDq\vqPGr:35tEG9E!oDX#T-HTDD#nW"5bu `i׾ !?AN|₿@MgcTG(&*,x`z8J$«i d @xAh򖳼-7˄v&# uHp _L@Dƺ!p|B*U3!E802L`i 8{,&<5m{"4X$ 4$L0g<hlx12d @t24DߎqCX%$#E4y 0 I!J'wB x$DPD3&/o zçF4d!9#|NSyb=XDVPMiHnj9Û1 ah/i 0""2dA(;RN9 ЂܸFz}-piRl *K/QFR w!.T zG8lQKDñ(7]GEC$1)Ix(5i*Nxt5j'1KY(HzJ7$HMBԫ&G8L%Eإ}2ij Q:qq!vqa>b\`grebABaU7Q <1Qy!Pd2X!sq0Rc#Ev(r$&Wh@9HEI7"BJcQ2wGV х%s.U`E V/V>J o0?2qL($j%%& o&hRL#;Bo#AR A.h&bpw")/>[Ԉ51 s %& 550A480s\9uM0/ gDoB%3ѐ 1C5b#jyS#_0?@I1 tM0RYLyq7ijEdQy`7*;pW (Hqd)q N$pM3'D4)d2wBŐ8Sm=ksWF!hXWϣ?!M?;w:WXU>{3ba@ #[HXaq<#;#;;yY郫}ԙ5J!;:1c?9V=cO{%che M}q_1AK @bGvn ,B!QIPGcGHI!"Y3II#Ҕb0CFcM c#&s ᗁ#N*pb aBy22;ScJUBI2Szч r%{*TId..Qp' OaORΰ($LP*o\1 P @ &]UEc s]ѷ] r52GN)~k 1]ʟ +q ~k5^"g{3lQ@yrc%r%jR1Trl ÍGw AUxEpTHL͌wlluA%A\Ш`bo p `r `_pg.`n(rwyωw!w!@9jx!*A/E!61SQ@[=F:(ӿiGy8h!q@{a~h6GQ|yQ=_1V%%#3CBٮFJ@xi8?@67kPɅjgt"PN!IDs(3(kSS4_7Jd#ሠB'#ز#p u<P 2w` U 'I- (q@z s\fPP C|Vjt@ mBZL]ĕԼouTrs^Bؠ `(e/k0I$/2%0T{M+R  @|8HD6VQP0@ mŒ'  [[p5q3,2!+9P5!6P0{0<-<ʻ>+E& \j'?<7%E@@ sr27 6k`/ԣÛy;:yٝ,:i0a;}Ǜ)ٜݴ;(d!.WI;kBmYﺑAÞI)/V#y:x/-q/^d???/#DN/SZRr2;)p)SAa*  T'}D^S!R~ATG= A-H'/ 1y,{-cݼuL ,ttxNUBּ`- Hsh͍~ ~?` L%ќ G ^Syr `.]3)aNڄ#ʭ"%_;+%W*xٚ`OSHT3 # >PA%*<("ˆXD!E$YɃT r”H㇇ c>wj\PĿ`ɔ?eA˒aŊ u"ʠ'ǮeKZ!$9W]̺U gJ6dslcƐ%k8bcv qɡ DX}^ UHnAK]mܹuK]P8ϼ[9cu,hWZ.xKCGX_1^BkD{N`̚ ͛Zp?{SHI1/ ڛo,(56԰2"0[HȰ0K\iE*6# ) ,Ц$"%uH҅t$4^9 "rR@J0C-XQ!UbՓ/#Ub!Dq*Q:r*AK!(pp + ‚!jkl* , JL+:s !%*%H8I,$%\R"HрFUa4h!gU KiyD(mY"R<6T z]z\Lh1aH[leҎG"hFJ6@X%CUoQE G@KI@Q40.HgqL@bV=$ 0'RW=Hiz V6;!+`Xp ,x0`⼂%~U;9U!BD%"r}A= F]V) (2Ql/ JI *Y) U&ts" \ $HP )ٔĒZ|%N=$tNbf?5(+q;C*$:^a6-g$@bjS{W$ha2* iR"x4@" R$#aRBGDJbћAz0龶,$iHN~d7j>u{@#<`&u=$bSEq](^٠l|{|AXqԎ0/$9\;yiDAXCHH):A“_G]S:ԬAGkɥ=vY\ ?@V]kg{|.$EVm{wχIAEGL)x*)Hx>@!!DBB2TpI]XQ.`Q h @%:i @FO s  |1i$24N"h~ŕ*̀Eq8Y:@," ?~h-ch@h=14./j@gÛp)rsn@Yqv>82L *6:xVӈUfUYUWL k}LH2/ .(  -؂TţmURރt +%R; =݇@X[PuPP|uX*0[x ̜U @8P.\ Iw#ݙءS "C kC(Q7|}p%+&7 n97h`ـ R՜{Xܴ}Î7JKȸg P *Xֽh6 [Zkb'~)BƽPl#n ^b1c2 x\‡(8p{N1;nI!.c>3m) K/"9b?ndG~dF730mӰ-4)JL!OG)kY[=J'*@&gQ6D`#E,ų#>˚-x0ý;MDQң[|se gĒ/ SdQ}dT-$\h1@U>s:)ln ;Ľ D<9]@ R9C@cN+ .8 j*)P 4y׌X8B(IЄlؿ+}#H,{=ЀQT팚.3q 0]p @x5!E-T= ;xp$|~jЂ"Oi'&D#&@Eid隶\ D]WYB2=&ݶSy!lpxr@siFag p]pƸ@  : "4JڅxNI11@G|;|-pc 념'MpEopա߂HsT Ϟ- /9(JGS@0V%GǕ ɑV T Ahr .|2 j(T؃,%:ˁ(-Iы&j\s( <7LSB= Pp\4H_0$oL$"=,&PtUH̿ C'HgKsIOS }(q3B~ι 4g X9$\2qq8iPdk,x~jj NИyR EP8Pmw' Ђ,ֵЛUIfΤHx w]$ ^xw(̡<ȓaq - '׊.5,&VǍ*&ʂ3U45i)Z.h, #(K &Mt @ 8,+L>nIy ?)N )hB-Dҩ K)J)|nSX4&({ @ @mxxQh\ )YV)%VͥSoU`8X a㚐l`5{- ͠C mo͉(Ub9;zv-;&AT`R%( Rzݧ ' cS;~ڤD7δ8<&W=Qމjj+2)hhnپL4QD!&5QeB!XAq><&@00^> / YV)"4`~I<T dDRVAJqBU9 Bk!|ŗBh?@}?G݅D]D*bяAZG;>wDxGL$p'va$O QIΖ&DIH@P5"l$u `B5BhSшm4bMQR_'BO"% L9PQsUTHT:0)HPfG|G `6)?z 6ie%,&Rk)54BhVvG+A2v/]Q |pWo :2Ѿ!׿^rGִ'+ܲ/CAV$ x5yy09 K3ݴOCc SOOQytRw E kݶo"LըͤDb/]7DŽttKf"}\ `2D$@ t44 kUԢ`qDC,pCc8Q W̡-"A^A$k4T_h4B>tGCxOK{", [h/LȀ`Y-07` "A}kH˃Kr`vMaD ?$, Ԙ?Tc|$XD*F/9`GF ^ VJp3   J`CAp jDam^GHCZfU>EHu..m)f#A @4A|C)70B!EԐQ)E>8I!Ƹp8\ \ʰ!%bኁAyR1 (Cpܐ`# z,3/BM^6?h2eW!2\._N}RR(9 >[B˫H::t wTcŚƦ;5 ?86Z** XxɩCֲԥZ !5MjC@`-pMfg10["u䫕BVG5V"Q 9zUw[mXBD<bv@t2YD*9>'rlhR>Lj)VATi-TV*Y"U@C6ec_m*0#ouz!Kx'Z#.2NLNRTH+IY6#/H9L"*ț\Drp8p@++;0= ր,$h*AR*eYsP.Cc8#x="G@MQ"H$ȕܑ"GȌL)!"KbMBK|9xsOcAǘhzN_>y6m&NG{$"Hd(B <͔_jmG" h6ԑcMcIC)^snIr湸lvG1 pITЙk#,Pcs ]v':@l#$1 : Mbm H 𺅳e*sЀ 2QJ /<ocFlH,ru[i>A<@ د1vi ZJL/։1D9(SQ!$&%*]di/Gn4gA:/o$6[OL+Y) ojc2v$|ݾ7i1|)S:w jnɀj4uȓ^i?%2{\0w8oci,fҗ܇U{"Al~m+I /|,K:L-l<򻐿jbBɞYܟ<MWX eUUGϢG\FvD )IyD!AH HUHD͕ÉiIX1K̘@| YeotMCyD- ǖDBNRKr셁4‡qGА:%H C FeUZUbe?@׈GGDXvD 0ģPdVe\et U^1D%q`faaF  B dO&_$CR0AIA-LE-$@#HT$^+ >kHJ :7Gki<^jcZ׈?⡘iΓ?HDgC!Ahm\ qM~MT?p?\@LtA AX.)8D0ӝb:rZtL%FTOZS|@TTD0#tX"tx`(" 9jѡ"R$   V`#AIt!OE Bٴҥ'" 4C~rGsdh|@|Lu/TvlٳB_D9 2%8컥wcTHb7$$؜c/w {lܥs7}zH0-IӷgW$("261 ڜ*XvOpNYEnء/>؃V%h݅V-ދUBD4,`SO}(pn7`6 ɺYҎ1N#2$<۳;RH7< m)'q#>/f7N'7E<(9i[vڵ/U ř'vBxOoȣ=T b֡6В :+.(Az-%F1 3aTPL ~z໠V0R+D0%BFȡDS(iE!~ ;F?w Bz` #B"ìN^AdH`|<"7 P6a_!MGR¤!9c(Q* AĆRy(E4 X_f6 #srzs,|[BD Amw[A>)R  "rv 1\J}h KKWHU@p; $Q } @M&\DWkHC(R/! p2h̼[}PMs(ˆVML([@,F1i) E2d(`}E= '8AIƲvfwD9hH՟@wDpRt<@)7I" aTPrK5{>$CP&(b9D t:3AGA`Ry9RPG<CIgP/ёC/L?il<.(zQؘtSSeeds `+d,'+s^(!*4`^ `P±`vD 9ʀ 菲@LB,`. 6k b*al!t $8<"|FSfO(w*6<#ӅX_,@:y2>jnIch㛻BJ +pS"RE@EtV"*A.Bpt:( : 81h)hMOGLKu.9> dtUAF!V3u3?^'2$M. *!@5 vm= Oz b/{hO*#@M>us+rdJ7!~ '|sK2z?gEh `cy),FbWM ڒ="X"Ac]VzPBېf䰣>eF.D'.F ʢB/(wc\47"r!R4fI%||q"qIy+;X tF f4GɈ?62I@=tR|w6tw=06\&0ʆ8.'5 S'RL / jg7J4Kwtִ?*ka$bQßʹʟ bEaA8 H*\ȰÇ#JHŅ(C:6s 8qK# >dxP 2 H?pgA4@{[,H: ag??I}˜mdIPN>$K!#H9RfK9|.UG&"d#dGA(N c":-쑫"ξ>+\9$|SS($c*d.tM%/hAc|& OΟ+*X'AF?jIEm<"#a<mkV$#Be$|B }<fX{Ubfv/d& $IX"hlndC 2d_#ve7Xo`F{\~qDqd>J6a  ĄTXVxX8a,"0C H'+!Tjl؆ncS}a&ZQ&Go|؇~8'1/ sVAz`YP qSS@uLFbks\LaH[{xX8!geC- !I‘$%(f0 }p \vf2b/27̲N0. 8"#qVAbV!,WTlg!+w!C$<8p!yaLP1E!Hd A1!r##12#!.!Z1 #"(B"&Ro"1 1 IR"0/ )@ow;'KD2H@(p- C$qPdKAB2R q9hYn-hٖbGI%"wJP3E!4h6"z - @ 6+)"@ :xa43S4x0P -as`-ۢVt;00)lESDPDgr'R 0(\`G?v`@x92;4rD#oP 2&S02921c3 Wwpq4{T4$!6 !Q8p7ys9lcK=O88i(&5 ?cA}9A@;;09:H:7 dAC=s9䥶`kS3:1AR2O%ԓ?zI]Zbb#!?PC7fJA#ZDD#a7SP6]qRAYaZ&6%tazSVX&$j0%ߣ$581aV[;6 M^KS0Rr7]*eqi[nVӱH]+1SgL5\Sb%e<0 E!VQUJ$ZWhMT\X>opeq[4oVqi_u]]U_e^_%Wtn8W#R<DheJV!;^by+fVD ,XDgfP<@C$`;wDfB1h^lĈef<Đ4K̺1H-qfփ7a%b~ctJ>]KSQeSe&&Zrhag6ar[; =>"lWiսy RsVRsuw\VBh& \g&l(0GT G~$h&8l&\<\LFץo&_-|n_P!06!%*cI{ [$rHuUyVIF0 ()t;+{rMQw!dKyG(Q^B;e/0 8;;[==9)x iմlAPJT8ĵϲO[Ld'UP*`'jArEd0"Gp3dEsG)xE x7x;1J@j\,@t|Ib&BFEa-Wi.i#{ͨBBǺ*WWDJFdg[ْDG;<G<:7Sv3D5S@ep>\6q{ߍgȅWFږzB2h` | "}L ohvCya2^fh5* N Y ">$^=1*=e\vm02> D74<>@',/NsqxX)@6fQN@"&ȴ_0lvm^>nӉeN$6E$%$I`k\/~@AaPdRIoюTiߛ&vY-Ca4.]!*(J~i72’#R"xqtFW$n0z)۴$){  [lR"@1R q"Q{J~;vC@ , ` àS=B׮34PK1.q`G\G9ME[$!$$~1!=_N28RYhm}RH0$@SR )YJ2Q E 2 D d*p. a nR 4p=gI/>e4Zds?;D9?q~ÿ>a8ܣ5{8z6l!? F?*:?9`#u$a:z8?燡DP_> LX0ĘQF=~A!0>ȁ!;`RL5e`*0͔-(G'?6Ґ3F]*2GTcɃ- -5ǁ(GŻ1?AhLJ)E J |PqUk"8"6kj17 \V}vkMEl?[Z}=o7K- |Yu%ϞE@I? 㢌 a"T( 4)jと"I Ң" 0ࢃ@"è8v=Q;\'TJ$Tᅲ yc Lᮼ( !G `a҆12 XĨкDKP3`*H41ҠE1Q #4$C&#((Đ4#0@[/:d7= >;h`H!uW#2t ;7_J+1:>d}#TUJ'`7N -Zx @\| PnG0i`bt,&>X&F  d*IQ#3j)L@2ć#Y l$3}PP4*v;!a"n:PDeSH<`$HV(1:d`Ĭ 0?j@ĺEarȑCc3w }( "2NMڴ芵hL! qK9Tp2R9#)0D;t6>q\%.ȏq2|2<4@01FX"PL ڹP.F`AZT;[*V ĊU[P"?~O:r, I?.'C9bs`zpG 6(8`x4\̹=hyTi12< :_90g.4&I'aT Q !77\ aԟ R QA4dzA"(] ZWq܉@ ӁU0?%e`Rg*sg*b#@Q~i#$A3=]#E @'7r'P.Yz&h3pz@Nom ~ Aw hIE NQdi4P5S<X,ɉE2঎ ,ᶛ P:u3( 7 گW҈vP&  m㉝(mAA@ |%*!44A0p7Фƒ;jP \h AۈQC&Ks43I;X*3T 1D 8 DbLB>b?ʝθ fIBC&#`:SvۈMSTBqlC(RC%LX{pc*1z`[+iXK}|{{~o,p臃~HᓘT1\3Ahqă0zʃӕKsčqAa{L14L$\!z8Tq Z 9b7i03ʏ]iZi{81 oI؟:,+Ў841z : h>ٌZ :؄1 аHV u917yDM7";B2"PT r(Qcr(s܁ a `Nɂˌ@w܈-hΌh쓢p;08P@Oj :4+3PP{4Pȝ ⎺ܙD*18#Jʃ0 و,.0 Ɉ{!7P K+RmP#!;#K̽<̃ ip"#U5B8#$%-43/PpÌ#.D0܈"HGRKZ<2p"/ /уȴ ""E@S$P JJū*11Z PT|pŌqW3Y樸p;'SƏÇ`hl{j0|9nFpe5F} stdra tz:0z -0!fҀЈ4@'혛e\(;9;(*@tf?8:UXAESU&\<%&0+uS :{;v}͚ڦ4 @y*L;L  "Zl)с[ºЯ%¥PVcc9 -iP!Pkpk9s+bԼZ05Eԍx\bT+{kHiÆeݖֶR O5EÂl 1 ~,޹Xtx_貃`> 3@R :w:3_]с>-ga.||/ˆr p(,.Ɂ+)@ 4%n֕ÈIȁ=@޺-P0X093P838]hg(3B(pH33m\(0c5C3tX0[Ǵ";hVT`8&9I؅=FE@/)8hD @:@0=D8<2( T 9ĵ oLp8(25ꌃ6woQen^$PZ[pq/^{6St 8uňшh6Ȑ{H4fk PMeH[5<`p}]T` H^d`i{hP:Qև*ANPst,~N6Ǩs"%ÿ#ˈ0 ;kQN|y3\( ÈXس, l)pQksQb<@k =`kS li";nqh Ջ<+=}͏p*&ã.Cᧂ 0 ٛmۯӀغKۼ!FhiH0#U쁀No;#<(ͪa \M  0A DX@l*us  _ X @ҀV&1Q -Ob kvr*QcH$%I‰0T0ՃJ$Ѓ UB.B;[ gVhl(ID`Č@eӺ< A 8($D$J^6ج&N):m-HKbLăTC(&7W@g%)QZ_espqP0n~u_g(άJXIȱ\  :h 6!pm+H m@HPV0XZxu H!ּźCNIwퟨ @ϑI x#zш0]a4AꂠHQ$@Ƞ(ʨAhT(p8e2k22r2([2_HR ݓZM|NaB O` N̳ĹޙѲo,`ہۧAܞ^lƹڍLʛa<2ZkOA)rXIvjLj B2 I}aR9 8P4 K CH7ӈ]Cϓ,Q R}АpQHw*̖d!b4P-CIHp+4+:$0)⟤h*R~i#nq:{SG_5SRRj*Uu`#„eu@9P%H?~ &T.-"`%X0p;"6r`Ȥ˚BP0ߪSs* H ㍗+t[ARe6GW!  - j=M rsl ,OsQ(OD1?BJH!Ahc!R &@(<@.ZqC@!HUDNe$UYT!P$A PuA A` ԗTdJ]8GRUONuZUsR Q@TTR%g89 i"I7&?0&t@F@P tP,*,2ʁ V6v"AJ%lYe~T֩cc|Zlk&,fU@-xՎRBv=uMn[̢/ֆh Ëi <0|0 #@0/Cԇ:/)Ӹc45@"s!&`5hxu[2Hxr tr>wj6q=7uqD $,t0;չ.J=9[~9kS[U< L9`M@?CRDa@G֬T8RZ| @xYN <@v>D8a$  sF!>\"¨NTR<2h?"he JXm?`-CbL)>Ѐ70%e !=.5/"_PW^BE9L8b$ P`x &'HP@3ԝQ@>IQ悬_2|!Ol@AIC?!3Y_@CiK l AP@(N2_F)568+Z(1h-OdI.DHNؔ $ {.E `$F@C H/ C!!c Xd=2&RCj:$(DAFu0SJ $eԖhD@*@@O)Mӥ!o M@uhTSB- ]2J:Pҩ[)&5(a֊>@7]UxjTA+4TnHvd)6XЪMSQt# h@8`h z?:QY%D)"@6-x<%D7(@F&Tdfqi 5 |Gxh \}ID0E4J$hqTQ1AB(s]^P^X@c|i(hV9̹o "jR@ ?ieu; uJ"U5qR$* KBc #'فy KH9lgNXEl$GGQT0FdC 7%$[@ '$ۿ?x}iWȵW!L+Jy@pcݖElHJnw98Abo±C .LP"ݘ" S`b65?ʡ t(OD6Ib7Fuur]\bfe S:X4yôXzK}Md{z$Y6X'j$-*sKlR`L~|b|svxC]BVލ .0rW\8֯KUm, Hckth#{B2d ]rm \*޶V/'&؜m nolǛ_ZSUd"Si9AV %0GL5@pH<(; Ax@L @0@LZ;̃T >DA$LQ#T_,?iQ6B8B7L?^@ñe\QE0qU|mdޏ&iiJIވxK|KMMYŹ1N0i&ooN&s4;3Cu\E,@%h<?^) 4К] d dT$ At&LA[ {XT?8tBx# `-zAH@$Ԃ ]&DNΈBt:(=5('9t ~hSI ADCa`Z0CH V.)0âDP< 0!Xܠ?412C44ëBC9ib`@ȓ@;C7ǘeBPv⦝QUTC!!t;@?AHTT(P)MjTT%9DmlNR4Yh5@4@"fDERDF9eՒ A@T#0hAC@ T0xAiQ 5I1&XQ`Au42tT"AEh8Q,<(&иEWH\nɍVTM@hYՕCD@kRh *QBFI0_)p݈m!lUAdJS㹖TD }BTkEN;V]m\]^>l=Vʎ*g0VEnâENTT@mwa݈d\P@u5m P88lUuE "EK6\HuOd f\B,6zIqʤpBnަܹYx͈ 2Ã=TJ)8zcLFI&\S^r&P?@QEjXDu] Jt]B9P/Q,APDQ51ENHNNL(Q1D.nˆ1^K{4t0*ޚ^޷[&W]8DpgAYJIȣ)RKlGAKڑ]h @$@ّKBD^E5dE_ EITFҠ!Oxr`?eMbl5A~҆H P[HC Tw2Ap`ar$o٣~jUD$u|AT_n@Cr  $ i2cʠilȠ 6L@P@KJ9D>o7+TT|"jNwaUopci$Bi1NƕFhT# G#90šMq odAU+xzXv`AGBPC@T@$9NV?DW@ #?N\p| H]4 @l$؅HZ,98[h3pR NZ?:䵜 n9O<_K,R]8KW\*1'V:OPRDDYC,ʂj1.I8o;D d$JDZ8OEPqmh܎dy;绾O K(@rzv Ec(Apt'n@.X݈HxxV[Vg "NKO@@}ÁX?e0Fܨ;DЎ6-(",@L_@p 8% B M@zhwlO `O #lA9 la$a-pA( u@#DlQ]^.C. K4Ey AeŊcݴh1Yq$#Iby!BޥZфwi)-,Vwf;"+B2HהRJ% Ҹ&SI<@@s6m'x PqF-x!M[ 7a(w~/#h:0@+:1C&ïu4 Hst($5h#/FCGQ+?&;dwD?Kn+>0#y). 6'@%xH\8dYSC=^g NIa!JO.[ YX-j"^JC8-PiJsJfLƘ ŋ$Ab4n?bE c^qD*uiqYJuVdWEUѴ_ڪBR00lR?"F4DAI|:# i\HSd}BYו(@s 4v%(atP6W(yl\ B$l\Ԇ mT6*$y+M ոM.#8D.\MBpsJZ'ɦm)XY z"$I*"C 8 B 85 n'Z%y$[M!!CLLGrTk %KkԫJ'3jfYܪ=Ն IĚЗ$]fSY-xu[L$f.̘*<4X9^$IYÒ.W8)B~V<:w!hE/эv4ЇW h'I:tR Afa֕pPA/h#w$Ad+k]׽],NwQJ-ѭ#Al# =imuOP5IۻFM1ku.swV<%S.y:=7H[np/ w!qO1qoAr%7Qr-wasϜ5qs=ρtE7ёt/MwӡuOUձuo]ve7ўvmwwϝuw}x7x/w!yO1yoAzя7Qzկwa{Ϟq{|7|/w}Oշ}o~7џ~wϟ0p 0p!0%p)-105p9=+cDPAFYPQOPWIIwS_P  }N y L 0Pa~ p$:0 n s_NA .P  Kp0^ Ggd/)E12~faQccNEiqPwOwM 1Q?OeNyqQq1QQQqٯ.oAn  ȑ# o("-"PQ!QlNB!ҳDQ"Ib$`R&cr&mIH#Q%Տ%9|9Xo(.(-2a))r*1222[#+/+qv`2ϒ,aѬ$S-O,m*-'.7 0S s0 0s0321 2s'7̱>:*Z*!5W5U5+!Y6EN//R^4R.M! 8s9o!F24n+//#:S8O s 7u;s s37.=׏/3Lq2*-. ! ,+f H 6zbh),01{ PG',_ n /^I@&" .ab߈%g`0ǁ(ńy 4fAPFbѐW[Bkr[ijY@\Ր0AJp\/C)0ÿ+<DfS@Lv°_ZCYЙ89 C:MW:'gx$46,S($Z 0'aS8r9 dJQLԅI&j5ĺ:6SCW,Cҳ H< +ق\,tJmzķ mD+a=@,#GQh8Q ?QpL?2H Nc$ d-&ڋn`#@ xwLɟ_ `_'|~+ܶ\WQlG \ 6$b/iթfkjI,`ce+9ʄ.#%h?JP(Fڀ U4v&6`o-D, uMBUN"gЌ|*%EgreLym˜3E d/y3d̎Otlu $x2MBЏGBM|HQBEZRHWԸtM9`GAVյ2 2 uRj]| >r |Ha1^0O@D~DǍ1nLm\Y$ F1"=vů3{η~ CpaE$5ZhQ ( ]'QCPA,A@TN1bpn@ ->BNLaء@.!3,s?|ށ48 5ɹ%S?6y?b!@\W.5d*\aQSI8H p 3ʑ9 hC<" [Cad!/$tXh -S $# D sG9HATa) F; a q_ɻdY ROp+D;N =dA?/t|A7':MI~`ޙ("aBK#AD@x?&!h$FAI;c"fZ$&!G#a\ wcB2av2Ba`'NQ\ PEA   w\0&DLx˕ ~v&PVƅ` /-G2q--gs+vb_۲QВM5O3kKL3E3TQ˴,a+`B*p#b11P5Q+Zg2:OF-luG/.K4PȀh="4`n #4@#`)36FH@CQ) !P>/g& bhXlIC,up&s)l3+!hDf_Q$`gje4O8/18ad%Ppf-d}\G1?^Vf  060U1<*FJKGC$'H `")7`7o @ !@  ]a!kMqP0KZRY&$+ksCx?G[Ed(]2 #Mx@Qh p!v*S(Uw@iQig} j0#vZ6J0S+66T)U} @`M84i)<S110A!G[Mps`ssptq]I]  bĵ-3!mCFFFu /ѠԡI0IZV x `$V 0#&5$%'!艹*,J pep @AO  %Q\Q.4vfd֟cH,0_sF,0 c0pefmsQx6dNOF- Qjm y*o6$˒Ԓ=\#/ 0;ז7D3u#gR]4EcjqGH13ՠv[p~Z6DWv!z%JIU5)1Z+C`ex3(I4%U2!  S")ĥpE&]P 9q`![RJ#m*e%s2Q0~[SP  > >ҕvHʑQcaOh"Z{2!{@ߵ(\сəYUP gY13SI Yµyrj\at)] \pNypȐW~W @ 1ajDD6/(^8BdLpq2Sk&I-A!3sLxgә6.@aɱ+,ua_B5 E++a8 :8CٓdF^F%d&#ĿG 918 vg5ʼ4QYS8Z-Z&LAQB?*6}x rъU.j;HM2T`}XD4y2Ld2IdOٓ0P8 i~[16Qb˅!˜l4bLB[9e3t8 a|cD!5I pLJ6`EJ 1 9ȁ^Q)Szup CRsGF:,zXU0k/_CGGp# g9 J!t u!v BM[V6mʺ--PDP1QDA@bZRH# aɉP|tK͇I!wN$(b`,)MrH&8_MjX/h%Ǣ⑁ |A*(ÿ. ~8*ӁOCLR TҰǩb=x1I~/D'hYpGU8ɿV!ndB7oZ N ĉ M'Ny2uOsӯםсkƞBR` *t+ctpA8 (p7Q `3k Z@^Q{&LѐpI^FCl-JT,!ۼK:@v{! ,!JT!LxtN|K8B˄l!,6!A`4,I K5!C5`> GUL@URrT)@+C%ʪ.mTU+7X#:KeGRhYg6Zs ZAl᪲VH݁dKwå^{W*ҡ|&X{_nft(EWMI:`?9dgˆ^" !ϽVoEYBs!e "Es暏F:.JXm+C9fkRm4Uk:mfm߆!TAݍG! !1(Mv`!*bn!&Pʉ- CáQb#XhBsY`a]"#}z&٣vYL zw1. H:7zuTj䙁fzl?C< ǜV] qX BR$m8z{| !x%C{Hl_.NQdUC4pHBxF$h?x pxPR1#U#X|H) Rlq (@G;rF8@<455h4!aoDۈ͘X!|7!]*0dCP,mL'hH(E@26S!$DT(0 @F#@C P{>@ 0 hYIQ$DD@)s !M#MJS"99@Ih"UT&>\DՔ~{@h&|+RT: )KZJ+>`g1T @q"(`I S" Ĩ#TA))"u6%\t#YF/>0"^p{Y3 ~ fx'[(2N25`@inSćAphA`DL p!|x>99!QEV!ODqa}<&pD49fq@xW'C?x9r(a-r#0z2ň"?dzo"dp!!JY/ x"X D5DgƉVx`݁6Z_D2@-Dg0 mF:0L2db$1I3JD рCJ#0^tF* D9Lh^DP4Pe }1VSƲ1";ͬ$aN3Uaj<AgGVs `l j]C; 'z+Ku(}o|w|hͅ-'4Y8pq$ xL^Ղ$C窋ZEVAD ?Ob4~q^3+f,_wZL%hunZ>pAJ. \7mVN_we$cL["^wZc]Kd!5_h־)G/xw+ώD3R<6 _,t\H,r~ D(o(?< 0FH%d0Dr-&<:Izb/H1Dl8zT/Fy1 ‹E?D/D8s{H8%KKsl!1V!a-I 0ג!v*v@P.*[>]i<0: H.0kO @ABV!A@RI>h1>7A.Ӯ1"/ϊu-wٷۀr74 :9 ,跏i5@<(|704CW.2#AQؘy4Ĉ[D@JLDNDXnYG؅1Dذ(_tD9yᨲ9x9E#. G@ F+2Yp.|(1.@LJrvXLJ0Z {. ĐÀ|(ₙ{GqtȇȈȉZ,( h1("s4࿁3pH(}Y /S @1iǁ!@͹qKaH-G`R|T;arJiŀA `0?ӈ&r-` iA3E8?ZdMP/;`h4@38@$* ١ĘD z!P .>Xh)sǰ&Cz?*qx1ih{hILe&}g0g0o€CQE`" Q@L2 E. $@2 +(.82A+" IЎ^8 h;l|mH(`Bj+ik HA)G/|/7}E `|/t89L@/7ps!3(Qo8Brj>DQ10cXr0{L{1ŒOϑP2F8ȅо]К?MP{<3&) _cP?F ܕLC)Zȩh4( |  5BO &8889 ȁsI P` KЈ̥6XÝIJI6趇InS+ @^c TІށ8HL_ъ&x 4!+ <@u6L-u-3"z9pkp,֪ʣzY"G~9; jYߩ u?8fⅥcCr%ǃk)b ǐ889mATT@D)GCk|ȥT$jȇX0cԈ}m Ǜ qmC(ő5LTBCJvM8rV֍-(Ɂ:HK0߁`#f(ejмPPMod@(-Yx^u&fۣ qqs=ryρڊUr{}2ڃ.smQ$#UI Z }Ma!W0]xJ]d0%'7Y5[j`P@x2 X3X $T| 9EG?GAR;}  xbWu7`*BU=@Mr' + MMF}٩,hihy'ziЀw '{"9U7脐+ hTy,JcJ),0k s7xUwڒ-foFup|]}`7oX`܄֪.kIh Yd (E\ X"H(ZpA:ep>Mtx2TA „ $X_Q{Bڻh⽉_HArU/ѿ,x?@\&zYNUXЃX)M"EԃW@4Ik?=X|u\_A^.J!0؉'Sl2GC3 ":℈$"W +pU8 82Gi// (OIS"(Tt@NJG8ϓ8 #_B6?~|Xw]"u%!gJOaGPEPGPCBg4$?34EPEA,Ag"XL$p##oUeXae0oP"A9&i8ei&Etn&'WnyQXz T=P(Uk)*(BRp'u-Pw)egD*tCA9Ԧ^*ZyТuhUI e~UVN)+eagV(l2qz-;nÝ 吐[j]"E7A$P{Mz}7?S~w$A_^ Q0uÉuתs j4 P\ڂV3Bo9KБ }r\I%A'TKUze[DZ5S\VX@Z" 1Yd/ P"{#wCH<HPF&}t(DA4XO?e)A ?FAAE7\$Y2R^p\Ry_4aM2JV!(YK?=YPe!hMjfBvAC" /C<ް%A%L`}JAUׂE7΋pRWqe-ǜnub+NEYB" -Q֔6LHkL(!z"( Iޔ%Ь*ٔ,%ZE Ǭ/f<2RӃ>h(ӛE$Q?0q8aYLo;NdhB&`NpA$gڑWI kdPD)ChE `*"К ɮD z3GvE(er (@PZ vc8<R2QpCq4Pu \yW-QE޴Uc6wBKp@A{Zy`BLb?^y#:A%(0]0|ZpJpA(".uH@po%"e) p2!uq8[hO~rҊ8B᠖cJ*C$D; qxt8䱙U ,a $G|ߗF4pѤM K8DI(D thD&DH (őxYd@< եILśʟDʚF\ѮVϝ툝Y]F \K]0a0 hp aʮaP6JXKtɵpyˣ< \e !fѮ|1^%M a^߶!D,tBGi"E_`eACy`BK{TȌ,BLL,<0w` Cp%} =bB^VT}KB\*? žNpC{ ÃE d A`9EIHApyQ%/V%&qy$ՃUE AXNC #ҙA,fgB h$ Ih g 9H?&((:( N )I: ]B)D&*D`^oHAcq &,VU(<SGA`N T4pENș&!MNMRyi(#AV])W }l`@ioL DDϐ`A8T SD78@PP Ap:ZˀdpTXAp% @ǚp&6}F#APA%DVHTSV"I+&؄\pL?NzAs4`0/xZAXNh~tCw~C0fx<9@v?+?܁~ݎvA˹e0 ә$"oploNM "Ks #3B sB$@3:ɽPG1NI0b30Ktܩ׎H3C3BAh[QB0#inH/ &YZCt}M(D#A7,9FW,ABB]#]Le1c7;vo/]BO? 7Op{W$cx ?BeB9N2EAU~$Ao^Iѭx@REH5e⏅J\WAO8lx,PZ%KU`PgJfJgmR@*T1pe pKJp46Ja-\4faՔUd=yɁ?_.9BlMPJ' *;ĜOqfޚLH(x&  M(,zB8:B@z~6:?yǂwRFFc4 .iGu:׺:[v^llDVOAČ+7CET]8(?\U)"dDjL0C4L@/C 01CtCJ/lwI?;*SBoZ*AĀh+Ē"[e&P|oBM Dgą; &<ϐj|UúȪC\R$OD8 GJACw=\<zW{-*{C)]?6G PPe%TD@HùkB2D@HCy x s"{,rʗ.%0~ߍQTB0-(_j7L SI[XƓ9J(/@Ex!AAXU)R(bDXD@PBpفDД7`3[ H%2Bl #Tx=zy3!p;h"Q(i?ߙWm(}D NfQIJW(*" d3lP2BBnI> sÈDDOD]PC#jC.$#;h\VJZ8DBs@a %D*)2 ~R#@DI0ޠIY=@3@ !- !,X ,@*0&~P^F',.8PB[GŀhJ=sG^"uU{-8,@G`R8-,Z=HY Uvf4&Xaو=Hx`X( AH:,C5&d,S14Gو:8R)&eIg*SqRWdì/:X@0\KeEY M] %B(@&&cUد "E{AⒼR. 15?[Bԁ cÉz+<"B 1$ԧ~[t0$8R/]p\"}Q5H=;^Ag Ev0!Vi/!У9$|h h`UL>*ĉrI+b`DJF!ł?Ab B,AfLAT)EE|mM4X{rc@Q+Dm1H 7P85RZ!H?px=P HAGA@&$Q%YATai݃JV@Ĩ%.y Lb"S8&@Mtj䦈 u:H<-d$ q,vpZ aIEdj 88$= _Dra@(|p/%fs28n>|GzwL|nN8@r&Xi2ѦHd;؀ iFWhs6!(/%ӱFWeiIf!JRLjsyLuM`ԡh0gu5ѻ>% !!p,cR,Gȴ7OT59 8haip u5p@R;ְc" mu[W6hDB!cU)TP a^iRF2VIWH,1*͕gAQ 7<`šp2B< P"xDˆoԐ5dlϕ-Dq[ &4S@XRd!Ffa?,u_PA$p]A5x=#Hu)7 EthuBL8"$CăA$G0?\4*#"2f`^sq1>R6=HH.fiĚ)Éh( WC) 'U /4@Ce%=1( U@l$GdFHUhǕ*k$4ܑ%;a%x! .`A cIY! sIfY2x P!!l* F:{6B|F! @`)3Y&AH:ybanHLgc !cn8D]8U CxEy-@Zb™D&C  Z!Xep2 KԗXV>\0@y~*kL6˾7 jTSk?HdGU;B-KB$ZwOmY"=?|RVHc6j0w̸\i|n5磢hQ Oo<!` Dɦ YXA,'O{C) F %1B9JǠ0M1Gqq8kMdD`rDR& ">O}+Eh@5`.֋HŽe0F q40`ƍjVp5vg`fh#@u.-CDA"b:`b `R c Wcf#K "b_",M.Vƚ|Y†#\"BY,7؍ykP[ $dcL fH }H bNj>0dqܐ'^>zf4wv1J)"#*!"Cܐ7c!-gqi5"tJZ"zѥq5d#'~vdnG8H|kLq  jL$8@ #"{VR +"Xl +."\CeB ,ၢL&Bd0"֐?" R!M4, g܎KBN]hs!K`1'Aj >EV% (aL'F lB,ȡI MhLޤ0! s58JPBA@r-7)2"P0"V!҂L#jʜV*Ck@`'7$IJq;d:iZ'J5J/ C>lZ ;C6d*ABHgD6,íBU>-'yڣi;ܪ1|scA\q1BBC-AtjAE>4DIt8^"Aj@ dSas8MBLJ4IRI48vta5X:(Vm  BHHs54ЃLcH4OoI4t pL NKs zwn)t dk8Nkq:`O5k=7SA5TEuTC >blKR鐅 h``q04:"E$TU "0`-`P"$7>KJ LXM#"$`5RLR3e b b @\ <$I @(YZVsƮ' B@"@`%B' `|bGd!q~): K*0 R?{),r\)"x\<8oXB3s&2u$$p`ovJ Sˎz~v`- b%~b?w$@bdD99+q8,'Q8#8H7C-O 8rn CEb񉟸a18>vHxxd*95+4J8c#:^x2K0tl3FN @v$Bv%&4쨋d3Msm' STF(S v\ !XfV5x-Z|V~ͣ"+jJ R; M 7y<$H䏸xks ANT WCYH1 c@ n -E2IY0~D@0A *dfr߰60D*RN cCe!>dBҢ $ X \`b?"~JF d k<bkdc % "|2d Z&0yx"k+AKD1XڤQCgk@݃ot紜r7B8%äc=K(q:DW22 @ 4{BfKes>0j! CVd c |,pڡ"9pDgCdEOWVC$^Z I= nbpT۪\@@SZ w*F3h C1""Bi"’)"!hsODo{IbH Abm3,ܠZG{nM!藃&ĵ#ۭr(*Y JAyYJrMPL7%J87r53!t% l": e6D@;s)=A@ag6dTgkYퟤCP*YO1q AsJgyhѥuB8 uToL tI_'}?rs4lt ML[Ϫ$`NQdB# K]OOzχݣH EDMar6AIT }VC K:9 |_Ę B @,? #МB`!5Tu #`m_a |(?* BH (c GZСB:D@DDwqDYzǎr[oYZC * 9@iDjUH{9n efYYC7C! x"_}ft "`bޱP`4$Zc4D|vaV^eHpi}ِ qeFeP p2DS&4g]t:tFx>|HڤRA'YaFs *Z9IE#@1z! F&#im8>DI{Z5h!', 04әdkc eK:*, YvP @<ʃ"Pix4wDA F(\E>h|L)>tHBU5$Dp L8E{WCbF Aj$V[4ǐJ.rd=dאil dE\r%.C2>^|YTy\I9"p$"Fvu$UYUJpHAନN1CVdiB*P풖dr*0xJXr_X90&50WoC@4%b3/,6B[% §C%ULϠ WC{,5_&"aE(P־ӇVɃl:S34:5'? [ kL6NmBZࡺ2DMmVeSZ+u sѤ6l( 5m![di5 PzvPfr6Idc͸ EPՒc9w.P]wn4b0x]Jb,ew /budžDf#Q6BP uj:q9dlX#f(@Z*2A +qEp%cd < bI|hLb  &VF̎;a/yE3>Vp-WW4!:EQ G[bҲ0"}_#U}0 )C1P_'+OJ>v~-B2j$CPJ26T! B(U"L??<tz@3X (BW@3:GY, Ʊ!!fpZ  QP^"L"at8EKre]EbF#&Ao$'AV@"FAV2qi1e6k tmeqatbO1[f#[KWBE)d!,2[hvG#7wŀ0Vx>AEq 5*2 aĦ9!#%TLa6wrX!p{_|!&6^f!.!'X6(s^&fL! qZe!aEfm)]RыU8_oh|w`- M5+(,zk1pxP {?$2f150gjw0 "d 5bC@ .X'U,iy#$Iu$X9ϳtR|,fqMFpLd uT?D:% Qݷ!A'Ua+w`9 Q Q !.V/ a6hQ NH19eEienB+uzbhǙ Y,p Lwe9 i:dDdßNR ь\}NXKĒO'O 6\*ea0NPQdbG  ّzzs Os98OR,Yh0D`|`pT }`Aڷ ~ P )Ђ%sX(I!"C#%X!T@1Z`7ҳ1Bn}?X&{EO94xDG2"?eK;˳w=eDDQ!#[`ouGouክHj&;Blr.2f41K?j*+*pJ_]thD}Ⱥ1q~JSk%!!XFQ5} غ\_aa `}a;%*A3K4UPYV`"+Rph -K3z RRI 'va qW]6/w5`AlV@?qD>#K2j3)^M&A%D)6 O_C@!AFSZZ Q” h3 mK'rA |ho\%Y|+Yc2%3#K>mr[c8EJ;jhA!901ǐ굻%l#)Vu'K@G(}(La]`rѭke %p o!5 ѿoP l{@9:0?=H/`9P2 9ve1J|"uqNyλI^%133N~ߴыY20mP!ѭűp !҅Y^!"]>Ula+e,$,0NȐp G#^y  ?#|yn{ 1ns$1Z#0W;j6oٜ>2Ŷ8@|يEA=L!0ڢ*,^qp2ǥ Av뵙J0 ]  0);t T@pb*K A $Cfe[' OoDDh&~:dB#*C'!Be$/]r+x_D6v mm[PĪ'C ;L *Jkqާ^]*G@]$SD`Oŧeupr>J}~ `N atA&d_Y uO^o 1 Gb !@"@E ">/dpd0+~q}MG曑"*9ni=2v7^R*jpnoѻ Nfޯl[o^ aU^|Y&3? \РpPoÊ? /TȰaF#O\YZ<i+Ё &  1!9Q%/LQ]JKZ+EViծe۶-FCXA?݋B TbLbF2"t("Bs ܹz&]tѢQ zQB({Fiv+7oj)reѹ{\u=W ŏ'_y}x>ѓ^o^ GweFzͤ{TpAlˑcj @RHHTn9#q0*EcѼ L"@ܛG rH"4r8BqPA4جk09 ̸ G@,#20tDRX L:@j<"A|B"&CTъ }HXҨn0͈2&/\q@ѤT.]nx`aA XdsPviU Zim@REC;,U(@4#TPLh4HFBcC, oHg&%W `\`Lҵ_Ԝ&aqaH($I((c(C݃8:hf:iy8!e%cP+A$] -j.""Q!2.GJ!-`%#x# CJ$@s3GF/@0|:)HP`g5+>J7F_~u ÄK{H) T+*@ =GP+@K!_ 3H[xu@-P><@Ԣd鋀 ptP"@zPdAO9~U:|KHDRYZh@* ɒj*)A(D:B`bD [tFh,[HKptEIUC)@L$Cx`@eAkZ|"p9|d1$**O)STqB`q!ZPC]2"J7؊R-$*Gh&9,J Q|1@'bPqPB&p-|`>ht"V$J((PTT D`j<59gU,I,0gBzUb"> n'oed l0# _HW<"Ѩ"rpnB5)q`|W*Q mڪ݈w5z-Ys (iP:'4jj"ָjGKwDF9|GMGKc=0EכVFxՒVQ갣D DLΪ&9!24ފ &!KFNnkHe4,\tP\ҵ}Pڊ`emlf9Ȉ+ "G@HfvU$Ƽ񂵜9"J<az]bH| $3#bB0T? ^xbH:F&!fY2G"I6c$-ALT-<:,HyzaJOZR.6zXR.N11]jԄ UO2Brъ-+zMIr\RH&rՆK*؀ Ԋ|%U|#uWXxE""앟buy!EK!9HQsP%&XqNp,"wb`G힉e,9S@jsF5@J.ɪy{ x'h* 9o7!>|=CF4lԃIPDis=04#n}'>C@,"}|G_7O\\hh-pB{ !OrD.s]CH|)^kp׺RJ~Y-11X(?*Xc#j H?a1X#R::-` _Jaɔ-nIM!B@ "$ B& !%'PA q<+AH#xAg r4dax3-HS%sYx@?hA@V Fȩ BtY8B(IGцXB0O"Ȃ$tQҊ<6YD̓C~1j0 *(: Qp R͹UX|25!XܚP)V !iǨ@ǘp.|)蝐`^\ }0|H;hPrSd"]]ɓ0I;z)Nш@$RҀ 졠SP7(1ɐIaI8y!`Jd<%0*1:*J-{ +6  #y𢺔K8 ; P 4#!.t6!H !A!'qBq@K,MJ؀Z 3 BM0A 1:ͅB\ZD$+m*w IjY 6h[pT ĈN슙Lȵ Q4A(7♊4SIPRϚXC ȑȂKfL)hʏ"0ˑ"@Et$(Q@GJy{]6W*`( UT9恇(`mR+ܨ9Łs* 1T S9" =w!&: j[/'9A3 (E!T@N1똀A-[iiT0h-V TɊ,(:D hRxѴBXz: s̎x, [XǐKp;KTD1xTܻXbc#K]5 +X [j J״8 c W4/{״&;YFJZ 0XS⊗S X/5@{/6ΌU%S+KW1 M$7#Մ`>$8?X&% bMambg.O |J#4tIE H0܃hJ!h4쪎-U JͱPșϚ M|*T]i5XݼCRHA1?\P  oP!)e\VMiC@7$O޾P^6(`O#E/Uwa"EAsH3M0@8{K:`JZ8빊@\QHN2I9] 9|STఠ S A9I#Aũȏ0X a%JƘ z8W x_0;,VBġ%@$$;jqp0#! @00Ic2u7;7 L:RJ *W! )294 5 %:ՊFl% LgSr螰iܴT`Ҙ * HT6t ?j f( a#So /8HdZx P0Pq=to'`K$ƣpxj-L`@$u5渦Wބ3ɹ""fjҮ}yփOG%߃)!ک?QMOQhBRmjQe  @{Pr/(rҾHn$9:e*L@feЊxTJ] 9$YxS>b" 7)s?,,LU1sD}y<9x-ٌ"!i7 ST@S * T  ,+S*U h|Z`1UsR{M%Hbx j WT 11;}͏2{Xc#ov P,k/0EGUORftg|Ub50[*0 I_=8 u``Ab̨q#ǎ58+$$28a$-f tad@!?IH0(:>\*hF@ K7!^H2 @ej[Cٴ(A<7 LԟpfA"p_L-ѻ'dGV>HFGYpFa$G G!1@2a4`t KQ@17: 9t`cA؄=R tԀG  =rE q4O?n&vA0*,("n:K$ &wH)hcC`F:R@ H/zx #wpFF12"ϒ  ChVr")5NghT M`"XD j TĪhj P|r( u9HYT T+l C)׺,JD@@ MXW|H 0:,0j)H^jnlB,+(b e0ٹdWRQe]Z6d0]ku06I,N֝yT$+ $$lUE2WkSbLSx ƬxW'T$(>5ekň~C L|[#Q xl#-FQ>ڃyDCZb, ȆW]!`N,##vWxsl@ "odxO1@B8oΔK}Y_}M32Eݱk g^lzc^gq~AZ!,pVM bA"VF Ne#cGٿ-7`qhKFW\@@ F,`ddY15PTEBh]ӄ$ٹA]C]yQ]MDXLCC\?jRIp0oeĉXCte @40G\@H_Fن?G1BM5ēEDHEaC.* BuSkTSgIFFR:Zb]".`q!FH:aȦA'U5pXZp1 89T1D?TI8ð吤DYQ lCX<<@tǨEl Ī *JFDDt@`DG= FXJkԉ9 @4mQ zi`>Dyklx {ĺģAB mK4FQKST܉ \DI?2 <GKC$ FQ5ΌKM]LM L:JwۜA^5$۸RXUZUbe$@%ꄏ/A+@ U 5eVe[[#B%mMSeF0%\f` PDlaΞ`2fc:cB&ִ$A1CH,HØ?@]\`?IDD]jE(bB@ElcC@ _Ѐ-] ? )QDY4gx,AX8H8AJELFyPbƠFE\EHΑRCADVF]\Qudg&HjW(XCԏݩQ$T'R 0A=?8@Y-^\ӄ5?(BӄNgAg*Ƶ-_@[쥒ƺiM&GmEBECL!u;bDz>١Q/Y.@ B(/$DiX>L~BBB9?BL6OqDyD TYDRmprHj=#CCp4Fl*F(GCjWA HQGCeŒjr$+(q&\|AUjTC4\DM(D@kYEFVQ:vax^n:$DlE^ Uv]Y4]5N^eqU\TER VoNTKnΕVQ-oV_ZPVA@ܕ]viA]QK>-5 |p,U4ՑxJ oUbV S<`c QCcLTy%$)ͬt1I9BLB( m*mmLvD|U ЧۼE .@pC29Š&Xdʎh`NgrUgp?6n-P[ aaZka"D!M$04 3+??0 4 oU/QPңEA?Yz^aeMLF*m`N0KQ?CDCmm } I*h .H@!KP%…L@5A$XiG-mZFD_0\ ]uQ?|A M@ ]ADإY gt'D @VD!\Cp\%HDed-Uth6FAԥ_K, Z\[`JtGԆaM^2@A%I5_Y^BJx@魬y$BT=_HNfcº𩝱LKa" rEcBT].ݟ؋0sʮC5_&%u2-W 3?sHX 9/}V3ן  a <,0 F ! 6j6 <*ъKbg_ Z1qqۑE5J]&؎*b<!Iބ2MKB{rԤDbPSe%k'/\P2 T(J8,UL 8#|C}H.`a)ybDgTGb꾝 6FwXFuC$úe@"ݵv2(AKoƗC8U6M|0BH/ $yoNrCj۝DX#G*p.Lj5hC|j(rBjYG/pCDF>e0D)"?LIb؄@.aG5dDD>@ 5Bfg0@!Clw{Pd -_<$A x Ch HJt'7D^{  KDJmط6Zc]EKF;E^K3=jyimܖǬ vg7ai\$ekaSL$F]Vy͓o L+敷3jdry{ӔWOUŗĸrȂ8Jys[jSy9-PrbiJ@~biA hT`N?Wƥkҵ@ V:td4UDMHL'CP` Fz|jD%^  ApLX\Pz96ԏu8RFB@A-@|LP\|o\+im ))MoA(QȒXMX!,$@=x DAi[cYtC`lbYgHC HǪQ0‡+%eCTzP'Ru-THuӄ.MFArS\4R2MeA3ІջFw0)pKnC>?T= :A0H3S)Q.00`fEguԀ4 P! p΂lXb':_A$a@#D<Z&wR@0H=R%?R"& ˂X\ aOo`"; V^ LWϿխ.0USA@XڃV TJЈ0d1ZPjA-xZ.BԒt*q8BR8>P@< لh;p.!C&%`gA"$p g`jDHȯ~,H6" $ Ĥ:DBpI:nI,ۢy:\1" #|i0S΁x&07pD2rH<{daC$x`'KT 4N'9m#AJPB|C#@$n 2v9}i4 \El[ ˚ ׂ`ڊƃRP7`; `Ӗʕ+B !~EڏKxJi*}aW"1=Q6fέ2+U\N.:{9(Y]l. < 41"i0H3xMHH6 zہu/ʖ'0u %7!B$bMimbmEhGXIqe%W$BJǻZH0/Y=XulrJˏvz-6 tÙ6|M)(t}Ap3M+ b聪 6,Ѻޠ/lo+^9 ݻPR %d{Z x%!!p E qL,*%:@RUSO>x+% `⸹]fMKb *eCTH5 a"&$>Rã&9IO~(%Gg)#HJW,Ǜő.yK_0YAr> V *""DBV&z)D@p81px >k#N#"D( qF ЅbD$..y`&,dLr?hJ_i'LP} tb@( HK <5PҊa =Y@sEA "d> 7V?KDxBD\y-i?!74 pdmɌGlZ` :ЀQ1X$4PY!(W*Z(B7  tmIHaЈ)gًlxV`V0 Rcc E\J<kE\"O)sAk.v-:('qe ~;S6E.5 !\9#]El:@d~B\ad @J`MS> (kKJ縑8!?v:m!ir^P* ^O q^!"RD@VФt;yu$U63 NӁD)K4P!`(hJ G>B  x9!wq<ăKJ6#s@% n;셏 ၫL4Q*a$!Hg"&B).H* XiY :5?AVr N 12PooAF D.Śh~#Xo~& Xr_fb fd,D?M,įfbi&&+et0+<^#(p4"4 c aJF+ 1"4mj'_Z4DT0 dΊZĥ XBcr^> ``Hh"@B("]JƉ _EZ kp'p%Z%l<\-!8S&C$vg©"/oi6`EZ n Oa ª(BOR/b`o]fJb )o "&!gkd xVp "YѕVcN+H iEh 4 2j‚R'C^C*ʁ$vNxAx 0 nrx株^.Bg[2gIJXh"p`"ot6&xp)xa ,0/&!B$HJf& R6.%"(-$jX%J-=L`&< 橼`/Czn:!94Ɠ k8gꂕL`VD'<*P` "aagg 6B.z"<67dXEDRc9V&_C$dO(7/x6,z'c(O$2*J ~<*H@z KM3)!>!\nd@@VfH >b!Ef^YT8R!X c "a $"O@ `!\Nbze 2 Q`zvQ~1.`H P`&&vJr%D Kx䙜bO$u B$H$cpVQ/z=zDo'@X"t;U͎13@4b:qajeZr0wu:+0"`ڳ!dcH욄 #&plCZe6m3uOH c:[Er\4 WPcnV!"W>"A B bwBSv4"r@p`B  SA+Q"8a/` . !d:Ld1O/R ܏O[n%kg{[~PaI ,]C;Ҧl 8X䣃bkDlgW:Gۥ#׼zn`Rm*!vC]Pϋn   jgÅ'0)I7" !.aT@8"/292Xrc JX$`v%b?e!BAzP r!'Bf'Y 5Yn30e9|Qd~P7sHP SceIxq|Q G\ˁ @>RAq@"-bgM`?()q #!U' BbM"` bn'P rq('Ψ(;.I*Ј&)*B!{Be|qOJO@$4C100)]V!.#&9&U'V$gJf&1!(Řdw*yFCRcRl#R8^C’T1`[Mojei&~c1-+6-R+guJi2dChj$,c!/aV/"0q/w!x  yR92$#E%c&E{qy|Q'+eV愒?9in}|1;"y94001P^)ihj^13Yb;bƱ::b{Q8_W\a9bi[-[QgX,\YpRŀT:IaQ  3 vHu^nVLC9 +JYKR9 `m4O2lP״Rzmʫe,)qh0ٖ u&OytjL1xpZlo4F!w2NVYoPR,e'CJM@`[)?gBz0TdA{+@GpA)`SmRaI&w*jo 4+rU, zdq|&lږQPTEAHQ9dQ=WuJҳZGIYH%YS:X@vIag~"5_eS%[#)3dTe|[;\dᙸ|Q mg^HI|q;qb(:HOa+~\+wXAJE_`C7)#3HP3~(adHrb@(*&<J;pb zbAfRi\&`g*pdO6R,2I͢cYk˲ 7 q8 qfO"jP-2e2Y,7rp!m.f_Hii\F/6fr,%g1:,0U"f2Vq֊-lNcR,Vkh֖Ų'ƢS\hƵdprHƷ|g}7 ~1 )P P / 㐝&w15P3-+l_x{3(@@-ꙀlBki+UL]V#D,_AxP mw~svvaQ3y:32豜 rU26?noh1QI_V1ZR1=Ѕ"-KDmY 4 PܑIἠ!q񛻀aq# @8]4 05!4R = 0]oV'B*8*s)0bŐC1r "B!8(ز$J& gآ0!B$fzRb%8yk*ɀҍg(<" >@!C1 "m2i'8F Nd"Ⱥ IQ1+r-02*r%rcC!MܸREz]#r-Py$ψy W-^qif֑1kpT,,F#%|L⮭r~ *;.-,js02-p-rac2 ,޵6h.;݇&ZMH+bPb}{gޙ cݑ!6 6sVG:iv#y9R8UXGѾ+UөnWO{UQ~~ӑ(+ 9_})vx옡v::2as ]aq )@נQ`^B"% Ps3-Ԧz  q6@!~)*?N 0Ȗ+1'J2' FA c \aGӳW^gCsQ=s I4jn&å@{J6+惦y+j2ZJWw:GsPTHtH"]c QB Q )00f74>m.ДM O:Pf!8j$̪1i2GKSqOL fOMii<ẪO@ h? <\Q!|FrE/h@7B Q1N=}Rw=:xb)&j(p=$cKןo|-'!|JNEÃ|@E*pˇvڄl W`YUv NT1/TLP-Fc.#s%H6  xE.^G%"P6$IcZh\ DLОb$m)2W$\J',\#B&'ЏzcHA?hˆ + HI>J*#AB"=>L2O #p!܉- *8 (l#bC0>V RB`Q$0䕃 FDx9(gTyd!kΉlikyAQ}!(dBX\)6Ȃ"=p'h\~'[r H`?מ \Z*YYh=S!:hW]^$[BkÐp^ )Yht \%#(BU X,*F7c2 <ݍcO>odWfeps_ީi+CJ*iVoy:h&h~y>x1oqgUq1;l&p>KuR@jcꩡnoCHBB@.@W: DBN8XI+ '*F౲ ~+iwiKYVN , r!A @@&H +(@l@iKjQz%É;ԚW/\. B4 ׁ  !:7$0CXIV ]lZ;$*Ѐ 5H2ȆSb*[vGjr`hR  @EBX@L@ p1@8qr1*<ՊLe7NkVA.]_-"dȑ; ,:*q!dU&JE8тlLܱqY D5H;XFzADȺfx\LAUv MZ$̔2hD@srmi#Z-GTH($F'b"5#__BM.";UO\q)iؓ R&Pm9t2>i"MAIQF|CE4^Ev, fç42yRGTEC0@SZU }7'$@ \nbk %gRJX0mD҄5Ag0;$.@𑠋$"\0 ! &/V_IlE1q`(- r)e 2du$#:L3"!$!{ AsC_8ؒÂ4tkBI H[ ?1#Jg1=_0t&/HSPr1J8eW$>⬛k!7e\fZǐe Hį@yԟ+E Etuyon4k31  :8/*H-hK- Κ3CϪp?HK˄sY;?v9P &.2 ?r…HB?@ 44B3RC ZA C̳$!gi"阢[ؽ+B=@-bF<#.{r69a37`eq85`$V $ !2YdS\iKžкK:k :8S$ wy f=$;:狱# mDip6ƺ`CAh?hm\-xf)9H#(P UXWT 28~`1 ̊Ϣ[ɖhi?*cPQM3EkMLR P6Pk*dMͦ&vM".þe@ll \>   D[IjS[d]_BŸ`D EJ>`}w 7 TbAX5`t! P? PwPvdP{,@"F^w"bǢh]hWw(#ZDP~ybGjߑ89} d W n O[d8We$oYAs9݆(}!C,u[-$hywdTGb5ARy)^ɰb `)z~ z૗*FY`rՔWV$,kn^ l2[[u0kEꕷF- l2#r 9QĖONq\NE‘ZԴ\F%<JCEu-u@!㼀UU6bzQyU.UT!uz5mCY\UFgElŜW-8~D]ųYfGCǶC1 bo$0%Ks*9nSPr TdOҨwE}Oh :n2NM yPI4$ 'viE?8%9FPGIE-LCWQCN!GyU}C9pk^I>QF I@?Ic5W

@Px y k м #3IBpAl u`I"?Jؕ.+FhRQ aJs"(]9TpUd 0!B@AHLMaW #Ŋ7Oǭ"AQaBfuE, iaY݊>_ZP%6!h$%* :GAjZU ;B KO}>"Eaj#R!J4r\:Oq 20i@+-Qԣ=5Ŝ`HBȆhYVES׾5bY [q !4z!s`I$8` A"%H! EPF`A[]P]Ca|K@1vO*M0+Rʆ--iߥ~h HMr*A H AЌĀ*P A(pj2H6&,  # , Ђ9?ސQXJ*h_\.PZ@r|[3ʑ6$*`t  \e6lCh^ХA`0A y?O 9 A2[5 {Hu!UǾ`B~9(s(SxGD=k7$q4D&􀖎< ND Vt`Oh!=ʎJ/p`^$,$0FA)l,@d<e0($cuVnB^l?9$N7TjFW=$<̔ho2 $lP=rxn@mUYlx٨+!K,g{TXŊ|a+)Nzz}EhIwHCɕ@@GIG HȈ퇇RVDZ (E@@TM&ǂ[("ZUHUwd!y"Q g,PDdrv=T@ux5D t&uXtPD}\bHTbP t 쒟[9z؇7yb̮Ltځ̊>Z>ʈգ8&M?`1UZ/~ 0[2HhZ4І = ?$yV\e!V]V mN& YO4P IE9S$Ph 5ȉ M@DR1#mDI&G>) &z$ z]!Q]1=9ڊe絰sNGc$\˘YQ`5PĊǔ"'QIi$1'PhnHTCBn6?fN65DatV疤? ϡSC \CnWj Sjw' jE‘B&Q '9P4I>yE*e@aЄ AA4"XJ~(4P(FW`sDEdبkeC0TDmj$CJlPfC=%1hd S$ jP^QvqUʨWDM%hIRrx*JTZBͪd_"%`iLF] ֑,Vcbaa:JC0fE$dVYsdspfCPnm49F" 즈RXIYv1XEp%zl sp]~1~P.y\ͣ yE~P4 vׄ[~Nl=..oבP$vKY DӁul ǂ5Dt \Kp!JmoWAQ*iĊ[bS/!)8B!eoF֨d=T%LzsvNJ0KY6WXb7Y;UY;4Ē@QĒ]٤:Ppï8BH.R& 0c BTRPtCZn̆?#?`C=-9D<|mEmJa\Č1۪l˪0Dʆ"o%ia @&nK 1!!2"WY)E UV0TCPSÑG -C 6!  G̎"  DA͜T(-'VbAl!]0cE-́T41,47D3?FA0]EJ`h4980/1;sEij1OF37W0L~sCpsNH6k("<I?P?FʶEׁQ]CC:?x *cC(?0A?p% F$N=NCA0Lb8gAM1?) P\ Mϵz"LA(kfDA$PPTW卞Le@A'_Q^F_ ,D4 2NZB{`Cd(e dcG eB=O^Vvxxn,c&}#L@ x72Y\c} @ְ9<|wcDHo~ˮp)VAc)'dC$q3Ũzt$oeU@d)xVBpYZ܏:Z1GFFA`CETkPA.4 Nn3XDAxPA4ɤgXwxVU0V^~Gb\D$#@r7LUFKS,wC|`/7L#6Lv) cA),I~i3/C;4O 0"\ɣL4_Z0?3P4b3?IB~a5%Kk(QIb6@E Y JDiC@t/҇$8bE1!=@K#Q"ˉ)%B!cM7[hA>' R L*%uB}ʗ@KLڴȓ'U& $D$JՉ`Zlȑ)z,)p]'n(ډ:VDL[1KL1`%^"p bDaǖy9&lA}XAdH97<#3NSuƇJГP>:apOD{׫!I. FHbQbd%¤R'$I„6\hhBAD4fl&k&R!!y#9;4 "jJٲA'#-!2⨠/#d$'JM7 FF3 & =d8u$£4%R5scFҔӁ. )<4Q4QT΄| & D9*gGcr "nFǁG{^H9՞Ygف@u U`%J8 (9(Z,K" -w"r656 )Հ{9(Bg.^5nɂ٩ Ll f8{˂Ghe7#$X0;Wz@z:$/^/b: SBh̢[owa1"|phLK5NȬW bI-gv ^QI/Q?Xb'v%Za"tPwBOz(m}%P驯%A':FxVqge}H!gI%:Tx]H{@sa("*_1iɂ@h#Y: `a<%4 QB-t aCΐ5 qC=D!E4D%.MtE)NUE-n]F1e4јF5mtG9ΑuG=}HA4!HE.t#!IIN%1IMn'AJQ4)QJUt+aKYΒ-qK]/La41Le.t3MiNմ5Mmn7Nq49љNut;OyΓ=O}?Pb2!B[AYفX(8(QYF+(X$hVB$0B:'Xި RT$:xGH*SXdvtJiPK {(RjUUn]WVe5YњVmu[WΕu]W}_X5aX.uc NdHnfYnhhK{ӊ6f\g[DplcEmiKnC4zmgq[ WW\.gKn-gq\jerkBE?ʫ7U"_Η*3[ސ6E`M oxM pL`#&K<2,х1B X71sWDq179qmxdPK"?(nP`,,g6Xơhey"d.fm˙Qs d Otp|&-'\D@DWi+]^.LsXgnаvB8f 7tc;6v=r[Ius7f]|sޮȴ<_&^x>=6;8YҽeSns(ɥ_7<9M^S\a~7еI/Mݜ[DRz՝~u3Hیna ٹ^O]kzuy]fݫgx#y+w(i;ئ +wv3;/oD>x֊?s1^|Fݞ9X|'t~=}i=鑏o_0C{QPd`KA_ TW{/@aϿ~jK<`0BHL %#0D~1ATTT~MIT " baR+iaO Te O:qSHFFq!Y{DT H }OAДJ2 qDXj%K^hD%Mq`Y])2$Wap+pG0*HDΰt>w+l.]sOO -DzR}B½BK |'e}`@]UMp@Dhx?"x: x"G :&G襓OecK!uL:Rr+KAN,)2@UIx!!@dæa AG?K+Jr@ͺdX|J)A}l$S\Ah@3PR+æj[gI*@ҵ8tc&RYo-cCjKX:t`0et1xX8W5y'&^Y fZ(6Ԗ3}rw,Kg{?T&R]2:l PŃ PX UB ¶QEd4%_)O$KH-Q*=̙T{JC;KKġY)lj&cE7Ňxo0 t+ !Bc9M?gƅ =ɀqS0Lp$\"XFdBjf,׀Ppu8 @@x `D! ;̀] n& )~1J&hh7OXؒ-&3 ! Vb*b4@N\Iۜ)2?hd$獛Q В1(n! 4(oD8Xr臃* )d 1g!H Ё$--巘JXʡ0W2˖j&VHM .C [cSSYL +ʹZ6ushd̪X-1M PQ[DE"2$lMK`0ؠB>U-NZȒ_F7*#n@W26JDVfF%khI<)-~jy[ WuY!ʡP2DVcC 8#Cl,!ȥfr\n+ UtpYa(eQ7(yI!'$y=Jf%lxK*a5r*npHJև )}G0 p ,?dt#1D " @ hY\CC3Y }M˘<6@Ik&T%V9sj% tW/)|!60܈@&Ɂ,\S P5d .$op @,52Kj&)/!amkL"Ȫ#AR.Ba3'@p|%^?Lvɺ'?XDž6p3'$ +nLn!Dg:VkVsANЋF;G^Q@,kVvaKg2i9v`_ R4!MRԨNG!LffPV4!)dvɔM%ƣMV x )#b4.^l8/$ ?dE䧱MA,`X9MRbMfA Mx[I@ rp kA{H J9R,1ƽ\' HC"EmB&gT1da(8D4ZTC%5^pBHGP XY%|G"CIx#rFCRQ)RJ La%.41D?~V@ba lDDԥ":YfF>BD> o(E0Qp+:@@ He:@Q#.tPXDH 1" وЊ$p/;}&*qQgMAb{0R<z0WXdOȑk`Np(}Z'?Р3a@@ `*UYQc5r ÀW؂FB+1X1V12\)& 2q5͓,Kr,?3g GcRW0D1-1/9x"r"/X$QMAH$!R2+3=t@ P-N-L& r+Q.]xh8,@@2:a Q<{\ "$2y"$+ +҄CS2aUs7"='F63j5t5Ї+a]%-`\7!PP7d1>A^?K6ô^&L`$lrYf0uw2$kb*`]¥Q`7_.(q az'3M= Svjw~gx;.gxCV!v |qR $2 +%ub $$ @$ I6=JV<a 5.R p1a"=U>@W3`9cS}?І$!d@{`\,pQ q `؀aZM;g̀5@P )P`sAB"$?O[5CCp#plЛK35+0tR"7pkM_ !G0Im"]P25'lyOșPx(a$uF-QЩFzE^AP0A@+A͂JI&Q!1I!U)]P a* [Ats 0 1#@,  T2A`it"0"c #Q*,*.&PkaKkfTN\Jb3Ay(Oh!⌡Mb$6sAxOLZcM,1D/mJO7NB/~OPX 4SOWp IDPSh!!++RrsLBmbylI!.iMT؟.A+1ɤlX,qٶSupG!!dMtAId@-wtVI c !JQJq]`P6* j=DJA ji9i@)ёۇڒv4 MyV FP {W4zѕ9AXz0<> #u%uiyZ*ї`  ?{9 w`rc?|xT#!/K[鱪t+0c*_K@$1EbqSP=9ex"]x$h^_j\R0%`%6`ѼR!@KUKXC+a۾b6lvjb٤-x `Z/aL|Mf +pK R!ovH,}pL&GyWl!gd- |CUe0Q~W4"u) 1c'1h+q+7!^)'z @9Po $'q({v@ay1c$jn:g<tWժ}w \g 'p!=و*!7!\]{v0& mcX+Kc+"\9,ؿ1N|2! WK¢@-BiG SSW;0Kwv@*KS1o*GMH|HKQ*{RUl3e%210O4H" N+3}/31ic295(@)XĢA= 4'߭/ЄUe GS66b72A^5b8Oڍ&]bHLZ.6xKu{3UW[5$1  QE " &å"(#e|P @[ۘ^h,jΐ-Q+A'J- y*y'A"c+xbg:x{x^y57)nggP+]k2?`qفi{IҢ멓1 Q#$@Hp1ݲXoFO&"6v$=ωE_!$jX$BTtt"EH5qX[a|mE  `axG ZF⹝5ѭL'4a庀d$@ sTF 103> +1SS1y%5456+OhDȤ,kLNbr iʧ8b2+M6\aJM! OO7A/²4NR&0.5܇V7V(TH*AQ+a,!0 8-iϭ#@o h'CJ$3 ,b+O?4 X/!"W^``$.t( U^KE?I&Qj;/ $*@$kI00ʠAwI 4'b(V]~/ְ]dU =(G|JXx1w@r*#n6} ޔz(vèxNf =; ڥ+0A@VqoŽXX{Bz#wnu-oR!.-.|VP$>@[O &6P 8pO z&nJoF?DL ܃F * ܠi[HX !1ӎtQ6DC2B1IJK n% `=4PANdQ#DSK.sЦ 5tB%PG/ST-D.%*LOE5UUWjxINW!A-WQaN[u[M(Y%U0'V_>Av!uR_b7ouٮyRq%\wzF!NպjRA`^1A.68~ Ra#XDC2B:R 3,I%b_9fge<  %&Z%g\ā$xt r!Fꇆj:&"X&4rb֎+ D"bM7augۧ!$dqI{sZD4B:B8V=+ɲ{ a*f%3^wr:@F`B27#яD j`)6@l,j!,\-J"` ¿aCOU0PxD<")wrK-* C#YB?CiPB4xrA(>Tj(HYpAUy}@#@^򝡐!) IRaJ,=&pHplz""aJ!(\H8P e?O @Ja 臭@@w# pnGØэƄ5ˁ(5!@"@.=P#R|e2 EQP%՝!-VN i $k4|J *E+@=d66a!J ]7&T* I”!a )Afwf"p!:p/d(O<,C nF(ܙńD.>0BLjy!G?܅9&Bz'$z!. ^!q Pv#PdCLb8Xr[4rWH0mR±+f9mcKѶNtdhvg3THWZ"d#@ 0<5p!_2Q3tk༲ؕ T% j}0s )z"j1 Z"2"^`̇d 22/ڑ> 0i6^p&s(!96aQM 9H %&KEb2 0(~ʥ&ay"X$m hQib 5Hjl'&1A*^ ":PaɽaPP x`*X$(E(x9]X%8R^'ŧRFڏ)= !1+ hB=vA8Pj QRzVr38)*r|+lgŊWE <kb!"Z+J$T3WJJkugxׇh+Sˇ؞FQgcp{r R; Ȃ,-LQ5<r gkFAFN̸J=JZ ApÞ 0 C1^QiU(G 30?#Y̵1[W99]U.]•ݤ XPЄd`|(l?{X@ۊ!.҆Gj˩Pp%Rlk=EA@h gbҵ#CuaDBU8UYssnªO2]YӀJyEpK! 5DUWJ`tDN u!\]̀\`񻐈X1h] ( ۹{>ؽmr>DB=X!CsގE\%H pM&>WWQDp@XzhدT-{ZTد =C@0Yd pK=@(R`Yt WS)lǃ^riVk=cf>?RQ?пђ>(T)7hA\'1966>*cV:G=Q,Fz**)h+^V9ljVjiv- GLaV7G;UUP2_eEWdX^9Z!0 AԔ>1. ï̚cbB 6b;skei \.X40桮rZ&lLJ`q Çi^hpR5$I8ζV߃#.gv$.VfmxᚇE^ VA]T=711:HFȞڂ3!mnG1_pOtlb帉tygB(8na}eXFEU,ptm(ӡ@7dK\'~.Lp(FvjD+dQ *`JqJ/se)˷W h$˸" {LLO[Rp臓}0:\h٭,f;͜"r ^+  NF΁ 9"x֟,a= [*૰#O8 <؏\*] HB!P)2 D$oe\ 9b>AmaLu @-]Ҷ+%⏸&K뀂Β PH#us7'w(mNR.fVlV -%0A]>рeyzko5u L 9^;ӍޓeI'@mb9Hya gІQ(*v>wtp\U#7X,y8d8nʬռ1L~8s}#5fX6-@MuV88X|}h}[=Ο >X0Y>`X w\Fz2xKStb[[B}5P8.#ةа,9>r#mR\[L@qh  @ [B 8pA"8 `ߕ/>Ƀ 1HƒboU:0u`seF"! Ո*ed: E p E`l!SN YƍR!-"H . bVBhã +D: ,kD?Je \*5(Ɍ./_|a3'A N[8`c))o4Pq9g#@b*pu7t @B`$S`?=[4U:ΐ7r6T="PTD ImW iqdd$z5 ˾,$AhU~*^id}鐥 VZ(p?LT? ̭}E .}RF-o%c t}حJ íUпCֵ KSJ #AX1R-x_dַX˺s#@3P U12xvqB1F|+ q?F\9477NH  Q'AW 7(!`@ ȟ:3.uj~Z'r NA?GLY1$v4@$|`ѲVb# *QǤ<L#A^Ƞ!ABL`KrUȪ'j,KB(vj+j_FO)<<]& ji!uj\ 03I?0b$ [HD$C$T2r`I&2IDHE1R%` <%)X h4o"0NrD(/uD3Ģ?Hd$I㉀/ ` [F0An,PN@iIxꂓk+ФBrPH=T@r"AkL6pe+9ab@#O1n^]"ٕHs׌a }`& l̗5fnU{i`si*3'9NPHA:F*L24A-_3 hD#q$}JPC FĜ 8px^]*%A$]|@FtAlQ׉yHN @ZTALBp(+J;4CD:NZ\Ȟa 9\2t҉I"Ü8v<ښDԅLZɎ(EsIQn%Fm9NEqFFIJг!F-X V$gz4BX@UcDE2Ü(z\ lLD K* t@ -. /*fbobPCi<PH+=#RXU]@@ʀc+Ip fTMa~ݎ@\8)C6Š8YK6S>^tZЭLBΑ I}*KlTZ%[[Yۨ5FAE^ M_FB\ v 4?8,{"HEBxB.TB4T3dH4gfhfjg)QVDeAPL8YX4O4ddRUt?dBO`sVA4@ &F .D| d* eS^` ZvHA)$WEm|E|^x W P*TK|>-Fx]V6AYI$?9ę0$iT0΀y}DUKZOL[HtJ]Ut~`I ؋[&g$WH|䁊њ@ŋA6>[0ZW @|As3Ǘ dNzէf;ccdbt c?MXqdTդp#ipX\lgSNMME%_1FU dyD\ﱥ /{(f B0JH$XoD211B$Q@DkLrƎȌ1IjRTTgzVg+lxbCp : /fE 2t-)YP&hĠCxC(]n. 9 zBȉLaGBd J?LJ]{LBZhy. ,bKud P.2NA+Ri2joP .eT؈HB?P?jD$7̫ AH21hL_ba, O2Ό Sb6YPA/Dh˲M]cW-Ӗʂl\7P\ZF4vFdqZϷeF#7ȆАd@evdݎB*%v/DC? ,mEx;*DyDȎgD茎Ce|SZ(AN>BOBCL$c(tax/778CRrpcym8#ZE8}: ^/Č ΋Ѐ- !A(DD\W,mC#?D9P;?T9CXdH:@@ CQ~DQU ][r*'s 8; xAaJjCA]@ //VYDfY(t@Ħa8*zۚyYzNb/2ĘM,B):BX?9H;ΡAJ @e?@ZEAIAATZ@AGۘJ2e ʺ LMļU5JjhA{1{N8{ČBMQw wYXW D*;ʫ^DmWΤ; VR^ N*\L/!E'Bk!GFh1JYJt~Ffn0LgšH7)=nHAI8 A@7?|AP@{" Cf_"x2"T )5ͿJ"̚cPPKFYPP1cSOF:@A'V-#XOcPp@&- s#€@7"ց8 ߎ| ؛w@ 1 hvGнriegkqMq`M}-"?mh{8D?m+\IC/;P@cdfo4u<@:K#|>6}Ej9(XA1jolA'0ABOAH O{ ĩvA`NlEJl:8Dv)Fd4$z$2kG:$$ Qȡdѩ z@*rF$h|7s;u2" 9HAWlʃ!2+TO& X+"C  $8unXNDT r) [5"h=5^jJa@XkBVԳj DRG֩/&Lba ^AEB}P]hQoB&t 1O𐷠DH^kjVHHsKU',x ~rU"Ȟ!h"ۚ$4V*vvF HJj)zfZ!Iv(!hk!h-e} *hz, []hw@j A,+A'd[j!K,{(!t++oV<8(Lx &eo^c?0Wyϓm@};,{_D>Ա\,SH~H|*MBS L2^zoc*_ozr%BX(( >0| G zj/xT f̫AB$Q‚ A!Ac2`Jl+P>h屌 [F2BbMT 9ͣ҃"3$SlDdEI¤M uʈĩucxG=}QxBXjɈ d!@B  4G 11A"@&DSڂX9E !a)~:9Y.f@Sx 2LL=r[Bt#mrdo{L&"1Sd}@WWE8B ` ٌaa% ˢ"&GB3 M$O  9 {8h@ $G7j x(lġ[Mq P*;h#6Hsru'Us@񏚔aSNPr`/oyk\ $fDI ez8Vx!%"҆E@`)9nb4. 9N-*t V4 V'_t}B'#&de%]*Т$|TY@ RhrޓxPP HNSl N nQrdݝ YLbvIxÀƴ,@ى."XP'2YllLg3%+@; q[RVĒ@y WrS*Iuu`ӊ@9Dlguԥ}`cFi" sFS P+GDW,4r +ȽғB-3#q  ! (6S eG)RTcZjAրZ~$nPs_Ok]8 )B}+"kQ1dA ،+zPCj($rnB2#_0@2p-Ĵ@t \N>)P9UfNF4aA2\ L5d6NAFԀ6Gȃ3S٦ tkb3l 8?lᲚc0anڊodS'Pauh>aJAQa'"IN~ɝ-mGxoݏ=HEV?D*3Xh)H$b|ݍⱐ[T\r } "d4$Hb-XwH@<"\?p9H$s\|^e`A - P1f)2M\0U!ޘABq9@@Z2Xs]sn ( 2ybbP @ :/*= +C; XG\gW&$'G85;c;-"9ԧ6bRAK2#\dIO8 Cċ&Ll^ l l LL$mTW W%*G>Tk"LEV½ND ?W @JVb'sK B7"Ά: lL"gH21`$t.=6gD>$";B藊 aXj")T b-^ɊCs(AV B.,CAm ~,Qw~zwA 1 .ǭ V`CVq #l D@ %@: F [d6U fa٠`b@a̖#. "l{eB@B`v4[ 5axsT"FG3e؞#Bba#3mEv ȡP Vdb#B:'Y;c G"N:%jzE!R[ f$ /@ @t .\ǰ`Ӄ&qD- B/ "t WhR$AR@`F<`<:V)EFxh  Bp XGĤ"KFˏO#t0p z -M¼DbJDL&@̤ݽr^{|zZOZNR@lӦ F7$%4$`*Bݪ "΁ f"bs*(Zm !d֪u&zllc;V$"9"r.d#ZzI "h)-(`AaF)&ՃOBи_|i02 vSqPÎ0ቡ T>`@f߃ͬ9q˿>㋆G&" 0=qdGc㘃J[Хi RҿmJB$$ꊠ iF:;q?T],c R+ A9ti@c~ӆf v #F)/w˹`  N@1$A 6aC_6=xi$4۶AS}{k%AZnuSM&^MUgXЁa8Y-h ZfYn_]Gd7cry=8}_ jc)vi@cN֘Q>%hbHZU*}i!倦A9x\$ rVYf^{{KHo?L^@ƔA` fa4٨ }"i z8iފkGȵ?$s9ݗ]7O@RQH/͏K=@ؗcktI JF4[BDBL2CcP"?5AGqF1\k><sAsu3F}w1yzMHr?EܛkB($MGaZ:O\Bt -QD9hj$9="pDwG!! ` ɉ &A&& |VRād`p!ܤ8mCx X]D<~ Bn;l3H4o"X!Mx+ Piir)db 8kN1;+HIŸk i@UA$@PB$kH x}5Xo"$LTx0Fp<3BؼaGgs =bC 1E {\ "@Ga)Z $(@ $U;ǠuCSQ#E*q#D̊@PEB@@ji@5!\"$s#PIAB+l+_ Xr$A00P@+?>9_(H+̡&Wb\.Nlya<".dKw/q_pǵQT$Hx93ز X\gh)N@[`gvgah(j 2&sHN& Б^yXtNG 8k'R PcT8CZs4mCYf·QD: l|G^ܡrXzo vH"xA@"Z39xNe` W#*%Ib'xP!ࢫH0`l)83.vvP !` xqcGru"rl\ Y4|xE0p3aUkrA v oB*[W` eDIAN0 7A}# z+IdpEHA4! t+Y@8<6H0M%6!*칀k7cUq?sA§;CHcHc<݇o)l8H W8aC 2"gkwD溡vI5)pD8c/8CE82H>=x 7+Q挑aq=j,D'1GîH>E$ CQAb!zPٰEcMoZ="2uUN`&8#z+ܑu vARxӬW1+:U+q[X#Adp 6kHG\!="Qjt x4A@AփCRͷ#\J. 0pt1cX W1XŴM=QaUd:1Ŵf[Rq+ap51J8_!Q"Ob0D5E_A,@CP G,GD'9'%h%\Q@-P!0# B#d+js:r B&%bJq9s<1#%-l% &`%iFSF:!%ަbsr;ns9뉚$~`Ymc,p`%`Ppg츆b?qX@'##u@%[>6cihpv(|'1 64UA:tB4ZC8g +0v)(j<*r8SJd `@!!Bh @DFUtǐ2wQ%` 0VN:G}<4AWcF@E>z* I4rHxb2# ꩟+"яr^2I^?ʪꪯ a`T@NuPD LQLۂU4M@p0PN jG5e@ ``*"jU2q9F9H3y ,,Pw 9'#A $yTt ~7@TQ Ut0N0{"MRêUWuM!sע E'Po yeXXkXޒ/ ^$9/ ZS\u5g\nk ` IA\P -` p.w5@8ı&]@1aWH0p0,3qP?Z|.hfO*e#U5V`&`0DO UbA p\HP @Sp-{ Ż\KS@0`V۵}7Z> dAѾ# 79ۉWWhv}EePfkп!#8H7Q5;ѡPkZg#o`mfeT}r. "Hf 5\fm AS:>::dOWB,*/f#Qvbf'PËe#XXֿ"d.o$fe:h¿-ȖnFiK -@Q,ys9>b;'J{-7 $-  q1Ĩq': k;; !96e[^ąXGP XW^;\e-EǨcB!Hp w p `U]MBcb. S7.E">RTEPTHɽ|- b{ Qt#A͗`!)OkX3!q q{G4s+}`zA GӸc R&}Fu,0<+p@P=՘+*JN29pP(#9P 1А8bmA0qI uW *}b8]Ds$;Zh ? k G4/f:,h"%qة@"ЫF$*ПI+z+ d:!R,\ʒحS|2PȌgz@>" p "J q q a3pX[ 22Qʝ 8zMWmB!ʠ)1LQZGѺȓa]u8cY\̌5?`JlO1 l @l.%VEZV3.l%JK˜{f"@}qt^R[Y@ D+31$ iYy':`&, ɥce&f)wc9BU'\7٭ }U;",wƑH>"Ѡ3 Չ?@J}>oϞڱ~>%޺֠FO ʢ,W  =<=p ِ;OH-L YzSBJ*2]F"%Ė!/#tNҼ=W[QjZMnff`Mu @ X28T117p1U[T57xOS`'t3LZ$`2=07V"̈́Pͧe0\\ ϗk]P 5/е}us[YVou(U5P)}2  axĂ\zBGLߑr A{Դ3R,4 #X@^0υ ~`x8 2D¥?$S>9 JLI2I6q,%1GPo}h@P`* fly/‹?\BbXpu`iA B\ X~ d #з DH?`n-lD&3 >@ȯ C93  !xЩ@j ?h)ͺ*o)E>:` >{ѷ Trɂ`(BFBp 7 -DsL2 `4[. +s91,3TSISD BrF JMRg rn4o$@^).^고cUx $~IG~Ath$');|b/Eإİ8*Ph41;8ԯ JK={ʅKQd͠>/_#X頂>5>2ȎHX\dGˊ s {ֲ]ldΩ3 ZóB$: x ƒ$ͯ+$:5ъ@= M D`FlրGS b@mU5.&M-;7]FiYar o 4 9<Р n@M | p:+ڹ- ; RfK)` w\s0uG7/ѝ!  rGo{S~G5f Nwt5HF</L;(9G~߹L'z& ٘#|a\-lH Ks`ĆPH-G?n3'enMbAvgL M2x.k悹7Ku]KCfڥ 1FAtGh%udȊ!Oih(jI1H2NcT8 j4*T`FQ/ "seCj4g)f`ksU1dkL~w1+˕T Wif6A K\A"0Ȋu +RH-RjS LPmH&`BA&ɨ!b&E(QCT1X Iso"ʠ")E$PZ j0%RA%t.B5Jc (ft"A A3 &PTǬ$|:V%5q/Ek[&ObM$&Υ ^VV%la {Lꨁs^XH2Ƀlg=YІVL4"]CqGچH -H Umo}۶7q^X(*y#ӥnu{]Q !̮!1,bGePC0 /R&Jxq?M9Г [6#,&ox%NSX xp$r1C8NHDsaZ:6N(?t36rܒ@̏L~AF D1ZԈgx>h #a栈x.(RHD(u "gؔAɳA!N> yEo C' `e {D& 0 5-@Dwl0It=IXDXr(d3\P.jw&HBbP \\H#3E'&AO=DK~)M>D#ߌ(?;C*?pAC uq}C,Aҍ/w)2-#F2HӜ!2 RY cK4[ML"Y{@<@#B"AP_$@BgH(4) Rs !)y0˙UJ*$3'i"7A W;pEsgͼe~#w= <4ǜ0 >4-IA x=Lwqea9:I2H)K/ $edZ1nbIJ,fp?*iDjA:50YA%I91jS.R~AFHI B۔:xc;>4 ,Q :`p_H3(xj:xa) ,@(EѠ=R`-͂*(v <iBХ{)p -D:yۃB1BB"A A DQ gP!r d8Xx q8Z-d DCL2XXHQ::n+/™$$!'hK=0уM( /(6ೂPDHRlѠH9`L4ˊ{!8!bh#%(' #*2#)$--hwl"ћz-hhE({UIHX@hT(PYX>YL! {R>-Ҹ1p5Ѻ6J+鹠L >x !2aእ+J\!& h (boBKp"&᰹@ H$x!l#jS!5iG9!ɜgK; Skc8Q82{DylQtG41X͂pјK!Pah1pH@jX AT <h$N `Iҳ>C{P }7X@P7{LA0,hA8;< ԰1,lOl/T9cԗ0,0*Ќ 2iO p0P0m$Q/P0@ C@CBCO*>L B$Z-UP?Y !+jz:%N; (z Pr$eY%A R:R Y)I[RC3?‚ p;(5(Ga0u8eT5ilEђ*!`l 0gIϑ Zxҵʛu±%)J`>Z.UBŸbɔlI_,UkVlV+kPGSH$5t)9mMWu]Wv]sUGP!xeɆv Xl 90/9Ux$XXX2`2>+ ;0ij- 7۔;iHl=hHA5|155pZ<+ͪ4Yk0U+ӂ [Z:H 23Zx+// '[yt2#'sZzd2(N@m3҈5%b)B r8PJ HCdS:akZp6(5XI؊H[ \(%,P{ϑ`08` l`=M"$׹2jƫm3-yloΤ3s[X('`= A @-A7[K47A80fR@ HQ0y wxphRab @=;9'`]!Ѐ)6=3 ػ #a3I0T^p\T,LԈ:< +䟻 5)6mAc t ȓ<h/JƐaY搜KcfC=  ɽhݏI._TVbM>3 B h(9IS3at V% SkE/Ѿhh{?0@ "!\(Y-i`sBO`Ĺ([TĐt8Tb*d$_o"Ђ"8̂AеE\8,$Lφ`3D;(䜋,p%7  ŗ 5V py_(eop3IEw#'~ K!`p5hkpHgj cP,D ur@ȣy"7Ym)PN_-6ȃG q  ^,/(#؜J<#Qq8! uٙg1cMx VfFiH//ȳT-Q"0 j (itDIHVjyW $\ Z0cp+)[t9cmr ;ڟEmT) XR1'EU7rc+ qa+8jrx`r2q"τDz)@DkARq! A+qEmCPkX Q'#UO|dR󂻲@@\0WH@Wţ+!AO-r)}tA'ݓ][K0TDЛr15` SeZ yM!yj'y~ϐ:$BՌtu}PNꩁ:sqDB3߼mśe YHg m]̇AtC؉z\_:G9Dу C!xD<Q ftP , sH>Vi8x(HPI'LZI"UC g@$xDB4#9$VJBYN:aV}F钡'ECFB'gI `*?h**ОV}8ulFpGzi|7j?"FC@nt)!LJ>: "ƻc)WB:9VAt>C %kCB&pAf@q)? Di sDSДK|t ȦSE-D* ][#UTX:Rp {US#%}~Ӌ_"3@qZ(m*Q/"T!Nt@f.mE:8 DwQ'D 6AQ N}#(TaZ"kTJösĉk.GA)0E %3\]BWBWB0!:^#|8^YCurD4[W D'x"E3e@y ]A|_sh̘]< F<ٜlQ)Τ&UdLj@ȋƄtdU`=ehjS7n!̎KWKĹKp, ՟ ۟Kadj1eF֩rdDLakMDȃfa}t׆6?DB:6އ>D%j;hj%qNP+yDSHijUΑZF"RO\%l TpAi͗ʝͩiN Ҍt%ȳe [@h&?BtkHɰ%8! +\IK0 <:C:8B)C83BlH#XC؊qWPų$"#jy.X/@ddALaSmT[CYآSq@5$BR\J 2B<@:k yNY@. De#=NH"3E,G$cMg ]*Je H\ iSPޠ V 2f^4 JYèTF̤$G@jTUKEĶ K)djW N鰷{IW#G`lB̤ƶs@ T@d~dѤDJm@p6(j}]O,jo,]FY>p¹naϤ|;~k3-p[4$@ @ Xt!'CGx?4BF\??GD' AX y G"(Ut4rčZxL:Q:B.,<]ISH.n[aOgAjri*lN[ bD(hB6ԀYjϪNNY [.tE ]oL(c$y=̄ML:DBa/ `7)@pEr:AhG2HDHGG =BdԋeTP U=uCȬ5=?@]B3C{-}@BF~N?_@|(|6)/h rEujگfk;D2*[KO /2KWyrf^z+a HÑG~ +hK}r9s\bףA4y0yD ;`FCD3'2{`rA-8E\CTE )glvChE&qݮ <|MxFx:D{)T_ZCCD.-Cl\K,CB<\DN -;;_3 (\J$Zb-R iȶ5&A!$]MnxEJ3̿ ÛQ%V*^ U𠈃 zX0M ܅P( w8$ 8 0DFS@ IL 9@֬MԺaJ ДT+<`R:5UB.ٍ!X V-A4r-x a"2VgMZ"M ĭ+-YԤ>. P!r "325(Pb :!0+c8f F6Ha+" 2!if̝W}J2.0"c0A煃|F4z;i$A:&Dxi z (4h`El4 4-F;4 DՋdA\2Bc8}|=Ȣج HjLp*o;-WQj@:#^ C~i[^ɀ>h`Q>h7-p 8Zg 44lC~ htˁOe4p@ux_c @.Sb%fS2&K   ny04`~ 7 di BEh Ud@X) p|!8Wf PL `a~D)TtH5q*1O j$1IQ2sPS&dF zZ ]6 $ڨQbEC"cFrhJ(Dˮm ai (s> qALeI @4q9@BQA^4@w$Y|CifЩh8d-q H! 0A$cR XX 5DDx%m7%s@dA* 16,SIi1ɩMMilyAbĢؤm F !Tnj_ʗ ^?A 8 R. bQ @K5!u)Pbv#˟PeשP+e [ٻSS r/5!)l2 Xe.qDL 1Q, In˕'7[Z tA+4e5,apE4A L@"Ai0c] 2߃Gha/ "(u(HZgmP.-2H\|$yڧ"R4RhIAdJ́=Vl6ReƼ%tL^!GřWa̕ f4SPMOQ /7nc$$VZG\`n OPu AHDMa ~,gVs#Q U4Ɣ`TLqH܅06r$F栓J3H %![@ݔ}abJ@mU3$olN#8(?(\F'Tf.TT`&}Y(vMrgfɮ TQjO#gd?q$? >DApXWKz$I?H#d!> )ڤu* jI!:"2W$^jBZ#YŊ'hb>Y RXځ pYh,2D4t4@PNAl=R|a:'| Tr0OR| `Q͈kY%yP!u\ʟȒejS%KldjfW)QΔҸ @|?1O se+} "~WO!l0[x]>**`AgBz@Lhpb h2[v $bn "^W +J  4!04@iRa~ PR,% dBJJB HPRJ܂r!~PP ZhhZ#Z ׼r2 ؊" \e:-1&_`^ ^0T0s{eS0[$d"d"M`$ph*60+@q 8_1bt އ NS $du2 R&n CtaTP` wFAN+ :4m j@j'!fDFw aFE."R @ ^ 2 $Xz^@2&',rs$g7&6a!uJJz ȠS A'(lwtgz/xlGm0JyfD4 )AF! 0 f̈ e~DN0p( :D@. B/ (|Ч+g000W(;6B7 "M:<s`$| #9Χ e.#0@c^2rL)HȏNp`< :݃+bd25:ĕ~B񸣗C2..p+hd?89 0Xb-f)=LAMCd *H= z)8iX<*ے)+ !@E{E'E\=9 !PG2PJ2V$%C ~b"a ~64'+$  l#B-Gb>+Þ~*!'k؈5"'vC(!ު7Ll tt B4M`©HqzA"n. hLF綊Jj'4 bR& ZrejʮBNzFtMmd mbB.7k.4 hKrWjп @ҭ ,!*n.  (kD 2A(ʽ& 2A\0@筮 Z-YlJM(f#-7:!e*PM! [W@J@mZl #l5 TM´[=b>ɠ싐v1jAioB zE,4;hl+6t Rh+B̶,R.SR>4:LGiI#zlY37ļrH6b{<ɞ1-6#qA/3V) uclU"MA ts3 0LV( '5u),!b@~_Ab[ )E) 5m KSl&^J)A$_ bƤLv 'nE0jQ@­ؒ_ZvpV]hc "U˦"''+>3X1BCD.*؃V:3Ws;r32B:29Z-0.1< 5d6Te4e?7zs14S#P<GNh1̑.fc<@6&O2.B낚%j@!C{?: HV@pq Xc7,JbЌYa6l0|bsO 6 e:m{/v:.}.z+ڣO?oLZWZJ) )FVV^&#[I2pƠV)V}bK!px# (| "'@00`A`Cb3$Fe DTC.n!J2 #2m ,f`b PYzJ  /NVW (;@Bb;h>b " T! (^ʷTD2qVz TP`1a [ y#$?@A  ;4pQ,G T?.' R0~!9.MD%be! ׂ0! {{FBx*T!  k& xtfL^!I^0$j`R "!K>NgvpmG).8r!T` )$݆ @v0R & `wjf^!Rkx/R"Azm4Bx~)*x-Ę`hi/ -l"/ÎɜOLZW6+4ZDr@\Xs<, MÐ@ 1>F Li;#;:*3'Tr|4tb!FRW0/-aDбxt$TI|qXu:f p$B+@Hi@p^J)ySP* E`ϰ"p6KQ?R Ja!ހUR؍i$| T)"j#~2S7_|dQ%)Q.$D+krտVdvxh%dv>LYk܆ \ R@⟦"H]u@hbB |DX LŁU:H@(bH,Aʄ(xL8 g aˆ6"LVM񫆣M zshj=~G$^@K =QńV^,>1.-ڧL&TMQX#@4_]zQBݮڵǣc \u+IӤ+@vCtM¯,<ؗQR)zJm֛Qjd-h nՙ6 l5}Q4S$rE"ÉH[,G3RWzWGIAyAZєhZiE_ݘI3jeEfN*xSY HTL%hr 6YEQPO~yW%H]vIABtN"V2ZТDf ) & T>TW)VELyQoGFb_q!Btkd)e? OH, xE dL=Q3 >):? \E]ҔP;%?uLBh59O#lR`UZT!_ @I z$B }p`SkkxK| ?rc"R?8*(팔 5VO]5T׀b"OOq%kk[A)>`z#HrV0?TVo&īQāCp2<Ё )}]0rk%Z-nT-ߢ=P SD-U.7X&nEaxQ"Iy9_񭵇/7pnٯ:G?:'H#[B> >) ShB0 !(" hfp(D}>LkQ(7!H:P.z`\b6G$pQ @8ðC#%ZG&f/rtBqZ8,DCad\$s&x3WMd㎅0, ЏqQwZ@ 豌Í"Ee $B*&.[o0 @!HB0 Q SMB- 2\AJBPL1 pfMJ?"ĦUWhI[jSlp,FDgPrȇ@NJE4$?5p5!U. lMB8WހLBq#;,!=[InT9d \0"#dUIj B n_QT{8 qX5ut]H aPvQb e?=e[64i) ˋƀ VC5J.O5HXQF( 0aC.VD>=IP YP@8qB"p7:Io2C CO=BAq* ܈ƺ) PnIXgyR8Z!(D@Q @# :5V< 0s}| :$>xLi/r$!3=R0 &e^96~i@@*A5uܬ;$z`I?An8E҂{ H44ī mD2f!-n5;]BL \$ЎDpr88Њ`yr,,ڃw<ݴ+B;":vp?B8 W@ pG  bK. Fo0?5>!\w,A1yc.B,Q8\P )B3# @kBa 2ٽMkI{ؠDTTجvCZr2'&u-]5`~.ki0 /-4)F7nI Oe" _z'MH!P 'E4,A|) A7MC׷C !( GoN;`3ahC69G[g_v }qq%a1$()ᅌ 7"  v ?#mX$ $~:Ex؇3A?VY@w1-C cd>QNA凳,(j @R &1 }1K's#_Qk$#8m)2~fA!~'[;RFH~zS #.RuLR‹H !&Q<04@2x)Bq~DΈf AO"uiY[b((%'YPjPҐ|qą 'Gr<G7Ѣ")Csrԗ;r-8a(r 9,e1.ctK  u5@u3vWw G RuPSY'v"f7G;vrg1xw7GvT@Krw8EC_zYzg{Q5R3{w#{ PRSBo0=343TcoI 6+I3k;y>1;  2%an֑pS>}n+87C\B!3ߙx|ݦAc9v1;"9Bam$T1@/@d1aF0*Cx? Do;zB:DZFZ@*07HNGyB ",`&܄&C4p}*9wtNtsNCLN:FO*^`KacEQ`Qj /@ $ P% pGZRR π )?02"^)01*XUusCNv3sCS5VTeUVl%Uy],b]XxȂq p5)P0\/&Gq+ 3[ ^ %63u,{V]KU8H0LKB!#\  [G1rCEi@&UdV% S:s"@Rw 0"% Ӡv Hw/iiAt/Fw P+qfe%ÖC#kCe"7d83&fFak&b^<#,21Qvsj Qcvc `e#h fghuc*q,kV3Vbg"lƽQ6:vB"nvxiՈ"&ы e;k=Fa<6V{Frh&¨e g{hBcfFa4&'n!-plv$>2a:v&ڗDfT7E<"$)EYr\vKmt<$hh"+7qo m>]2  Ay-]|I X2k7ֵa*6:+%/sCqy|]{pɧCW IE\-PR!@ 3ez9=[-rXxVxw WxzwH@37 Os@zav aw#E7_6b㚹g00 r/e/q p5`>M!_P xz{t"NG!~Lq!0\r!A0=I~d}k{"HDL z "І %0!Gi_Iw1kdS8LC#6x3 AEq-M1k{~8G؍ܪRXR(SF4 04PPۅݮ )*jtz~vE9J?3!d(9ӽR !ܑ*ǡ?JSri-c$¢3!V!6oWO&&lP"PC#fg%G$!-!z-B 9헨Đ{".?R_!(.ƽp!"o8'|BC0k7 ox0(}i(хP)S ڠJ0J]2M20|Ӣ*{ M!q0T\鋪ҕC-/a7=3tGPIe+nrѓt[_Q,ioEJAl["04<3Ӆ<閗ZI*$-"E` Y4vGt mXӠ0>25Ѿ-RRI=lo&8f 78ct =ll_{;8CÜ("BzB@y㠰app6oq>Ѓ=&9zlޚ3ӜC&`p%@*M\5J26JbzLC D$AM\=4-- \fG}^r?LDDt yo6 @ ݀飑tnS!n1C?AdJ+ p#€Ah!Gu:8˧!bILK1C'ڌ\'BzPP R )sZGHL6l̒C `}0C}ԫی=0X CYP Xo}sq@ϿJ/H|"( ʜ, ",Х.GŜ)¥2qygYBB:kſ Y4dVt-dTXwt4OlB(O2b/XgAr98+N0vt˂ R/9*qb`OdJ.t6r崓[pցǞca{*zb8m?քac[?B,`&VI *&!H@g'yBl"` jH׼US, .%kv2`3cEس:AE aWrO eNbA 쌵/*XYeDHA" Hp:8 } UyB S^̻Z^b&K?;l$k*hX³ HxѲKVwάͫZl}, QL [

Cx/Bv;τ&LO,9GQ'!@q,WLsz2D'6tC?<;{l)k3j@tK7Y ;0Y88p!^NҬgNZ8g)"=$Ʒ PCNb9/$  =I TĢa F2F+DDCP1:@Eg\1$"9X@9%C(="{LbSCk2 44 /]&a `i&o2i-8S>l=S0@JdMp|&p-i_xL@ Ǯ9,/We2T#7 qfaR &D0d!AV-`OC/X4 @Pw^ H(G"҄!0bXXTшH*bP(Eǒ0*H@*hTUxi*Ðv_Ka$ȵ|Լo=X ڧ.x`OتA S+{; zrj/cֻɚkd` Fg}J4-ᩪzd9.dt8X[q\N*ՐJ$hH cyr N,\rt: c94]5Mr5ףYڙ7k]>6p,sJTb /F9*|4#IQDTĥS\t*H9zlͱ򝳀" 04a`R9iBiæ #l > -aX 0 b e7BH'D(JQwpF ph<!F4VcG cd)Lc?~XҜ `ꨁ ; Kk_|e?{7GK7©sUxza~ ~W^s"-}wZgVyt~h`fS8?Lr,,a \'ȁ.ȒNQ 2*h =1C 1H$b@H4TT@hA +#h\"  ³jy#,B+2H-) 'pp9p0EKhTӿпp (Iy0 P sݹ`^ 7xQ0 ǀ 29 P Ѓ3NS?pq ȨA؍qH\0#!?8 R ftpX)(XD)Rdh"#N!*@ 'V ; L3P.ͺTLKjt*83XIr ȴ+Pܛ8BPa'0SRMà̱,NH d #% .u <0(*9G R$Hw <E7ȅQH@ [q kxh#  x#bsG  O@ 8Pi_r'5ӱ_t{.NJ:&ZinzXXLZqվU9ɥ!4.rj1 6pB*c!x&]9ʐ4<9+9Ը@@@M*(TD18h1d<),OS.OB2LQ;1`r@yt@RYTW *k;!󲼛e;SÊRZ bA*Jr\WKP33\ճ`/p9Wy_۸3} MbQe% f]P۰۫|k8B 2/ "{zuZz+ȁ± -X㨣XX$)UF =ʏŏ "3PQ5 К Q0 p x,:sQ!a"-q kU`F $Re%IR`8 +&Z0M`-%uTSU8Ӈѝ:PW@u B5T^@9cTI5r I GxT}Ã015; S%Lb$kԻ %POw^+v *)&`8 +Z y p[(Ryq9픗*,i UmûY9.Z ˫;7YM !bS:;>xWNU[\>{@n‹ZwxL;#SX[yhՄ . t^hh:ؚ|XZW  (/ѽ ju|{A|\iNڋn<@嫪UA!V[Z  Z`oݔ W,.NZ2< 'D5E8,%8 Q>x/aȀ?L^DD4`ThP^H(;&kpɘP{`Iđ닼7YQՠ=av+F74-mÂMQ ЈŊx3^[TmRTt) S4 @?+N ΠuBӄ` Hn(]S>pH,n..鰃E <CHQ&r(r B}^@HtMIQv{ZpX6h#lh@ Yɓ!meI|ؐ^*F9r4 b闍IKYYXʱ+ߏ(HYü|Kǜ< htL zrʺ\uzڧQ4t [qJhCY@>5.(4aۺ4b@w :陟ul@ 4q8j׎c `g)Oh;+ѩ.Y~uyy 5IxcȝB^%@eܪ23\)~?sSh!,'3"if ~w_HZA`H3  *:ojGЁ6444_x7enxx;`ƃERc7`yq"vpn& t=#6<Ut~wU@Flzhx/RZ`bj!WB('uױ(~]ZŦpֱw-8 3W^V4jW<ݸ*תjW}۱³ XH#0#ب/8$@XIX $؎h* X(shɤJۧ컦0-a^L Z-jZ`zNe[SOl %oeRڤ ASt],r<-H إKm!J@(%M0@ڤ;5QLDN?d1X* ՖģE"4/2ýA!W#`=eJWR!Ot Ё Hr51TJ dD!|YxYHXFPE fUf$X*D W p5 ؕ+_:Ӗ*ǀd@U79#Ct@tJ ϝ,=Sgm_ mPA`TBKdHb]_ͷY xHШX)C(Y~)}EUJRkA\9)aPŒZnQTjUlZaW"dHR-!$16 = $b?!+TvE2\E;nJ'[컧z1o 1 5rJ |rU(JK ij-@t  L c  x*+4M;4QKP}r@G?G[+Cݼ_iǠQ76)@#0jhS7 >8 8˨ JP7a1t h#Td9؃fMcCyLKU΄>;̴žBtNp!@%{R>=[=KN d0)] },@H,u}?CC%?BF%V  *!cȨh0`A9|-d {@ Ex 1!Bp +l/ r& !T8AwMBQND|X-2,?臿8a q/P$ 80+,b f@@0  w>\'3$ P<$-H4;er DC';N1%BF p`@H# ,d0$9B\<``qBH3LFzG ̢DEn$AHC5)賚9t CA!=ys>%:Ёġ&C;Th &HBDB%4$"%Kt : &2BN(")<, P(0) (@1*\$`$3k_!`t 6ah#;p"ڐ8>] b+ 61>%KV$9-Dx$@,rp()aq!(Ҭ܍k_r-%Y([+wb*,@[ut0@ś/.@8AB!X9 $>NOzL^\ZD[*,l mX2؝,4Rˊ eKT[N$h_&B!EC$AT (@2а 2,T%B;J"(D.0-v)HJ:UC6CK$X@ j"mD)Na۾? F@?0X|E&9`G؂]*XkH„wgiGH )dT =C̈#5{ B22E,cw+ !MWKa!q< Hg&D W@?p ! JO \<,M d=D:zqFdd=;HF '8%m?&ʃ-4Œ6."Qve #ÓMikP|x_߅ߕEiپȢd&eV5rL _"˙{b2?<фB E9 &@(AtAz "Eԁ=ٍ TЏM h\0DYJݿxԭ JC`]t@|]-Zӑݖ BXF y СCtƑF -ţ?T^N\DF& zl@pNdU?$u C7t3YAXZPL!Đ\ @5%é hA< Ad}5˵D"·PY=Yd;UT|C@!,D-ijIi@! D?xfdɨE$DW9IJ@C +[uJp?aa%CĽ@pECXʴ<!VmIVK]cDqD!D?"ĘʬO$"7-D ]^0EJJE6IKL P= Ā!tMJP@?R™}Q\Š]% Iqq%VFMǠJTΐ ||[| B Y^%_/^.^D7LCu8ݼA h: C]da_vg~&,B&J#g(=AZMJ<ȦUx:>f~D$h'q'\XtKDN _4O(ZXqn'wvw~'Ad&@FPSLA;Őh8d_H@\BSܠ 'R?x~ AEARP̐ |??H؅%pODQC%S {Di$ P$@!,|Nx( ē~h1H D(IhKEA!yE 61,A@/M$ua#<B')&F(ōPO.8(4%U_Y@åR =PGd$6DF.,," A HS*%mӕxOY/LS[>P 0D(R}@[,U4$DESKMkk~R%B+Ruk4B:C&#@@0@ w+?R;IVlQwAQ=R(h@Z=h +CFV, @܁35t*57X݁\54lC7D*(Ił@ εIx@@YEܺ}+;@FC (Q@AcG@G[X2(-I"%ñ-1gUCvu0?G&`S=ABΌALFlUx"tHh @Ldx"Dfl h}tsh7A$@D?@FSFg4A h@t7C{?<,h,$ԁ l]Q Dh\T@ghOP%%]DCQD!BA!Eg]DD IхN^OHB4@P^ў$05PϑY'5ײlTu͌YStclD)DWag/(\:~>ꧾ>뷾>Ǿ>׾>>>??'/?7??GO?W_?go?w?????ǿ?׿???@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;lYgѦUm[oƕ;n]wջo_xqǑ'WysϡG>zuױg׾{w?|yѧW}{Ǘ?~}׿ P ,LPl!P ) 1P 9A QI,5PLQ02aLEbq1uQx!F"!3E$['|R(S`1 ń|.e1523,D ̆:p*z:3:l3"%ϥ@C=|%uKzK bQhOA -zS)MBuӄT]U^]X_[q5L#Z]U[zTŋUa=X]YakZm Z^"[g/]\_c]%/yMnٷ_~ 867zm`mhͷ1w m0X㍷@bfHs`"; y!-!W3"!9" zkAҩog;V1ml^夜κE$z1?+oDeg_U~;o[HnרWKb! ,+f H JJ(/ DcʇN<'B$8Ƈ[vo&˚W|9ă A m8EFs Y#7DXV`Ǚ>e1$D'_;뿏u&A%JXU`$R(ptXM R+s+R X щ(h`1KF^,m@j(Ц;E9_"?J}|=_f,Lr6Q$E^ /v`?XЁ^M8IP4!ƒWZAaTqA{4ȑFe̱Q{$G:}GAF&#A_,?`"NDBA``~VnHeЙT!'Fx́?Pp9 w Dlt32@8)PmAO0=ApP ށ0$`척T@N|@PKE+TFI@AA@fFdDfДSYXP)A}҃DpQk]YPLZm/:\!K+A= <,?QbQKfYuQ # ,rf}uPrh0Y|J+ѺizPi o9Bcr=x|ra#?9dd8PT~hp ABs87 x3D(luJli Hm(0@p l9]% @Z!h&F ҁ1ZHcIqR+%&"Ie@`2%}'=5O4*gi"HA.0HjA$KBsY0 RfXc$ DăDo-Hl$ >`%e].%`eWMk]X\w9hN6 "I/K#nxM'ضVy T Py(g 2@^j|k<`IIj'BQD;:z&n'cu.>4žmvgPy&d( aHmgMZ@A!x !"@vҏe^ k[3Zn{۶ֶ@AccQ p |`paC+%8 ~8)KDK7̑NhfUwY GN>@j "A C("H b`1ZYy 2`A%A'N ⓌC P$s\t AZ %Y[*+ A|D"QL1K(?.$.<)L(d GKWC%A]NSC9@z)ؤ kq \drcRlyA2w߀pί0UMXKDـf]MVx4Zo]5 #1]A<%Q&4͢-d@4DB:NA"MZNU!#4b#8@CPP  ;u }0b$*#'8 >b?q$sPP ) "zpU%?%Y-"r#3#<}!Po *w j  u $[С) w Q 0_T"!6"_"x%B̠L   Xe+a1+#zO'8dTiBT +ς{֊xKVأ>ks&Pa^3!Y/TP#x/,&cTsj+1+ۂ` @PU^aaQ6'ay!Qr/!=#hIH`7!/@*yA_6'y)R3A/*3N7R#+˂-k7uQ4Q5![Xkc!fiE mc7zsXD((86f9&@IAIA~{a`vrfGY5fWhJFa.@FacP`&`@SnTXqb#};ܖ;%Re~0H.hՕ9@r@|P 0&p[@2)$;dIyr|!;"C =)fF[&CGxpP:3 oҚ_4bAt7!aACr 7a5 *i %5}F2 xkdFZ1LK2b&(p&r]"Pxe-ttڡzGl%W[J>vOc qI 37DNcr Hp >:H,GsfQ OFHc edyDd_~4}$H_ @a`vQބJ p%L K{s%0ZCLM  M6ZM1t: Mt X1P?"MI@EN xbDUf1=ibr;qFRq?zQh>٫ի Q05ȓP լ<S5!=4174ҊX!Ҫ%gO8PQ0ݺRzc&SkBugKu@1pWAr.u;WCd-Z1d&:JDqS2A9ZaWZ'{ZQ6%`GSdKWK1$5K YZju5D!K L!(mLE/0^q@%ɐ\E;Fg:'X@΀ a1;(p %jiY4#ʅ]8x]  WA))9)B0))yXA0)!B@aa"%YoqdJn BP%ŶhS4Z)7>#9 adLYXhIhQey2~feP% CQ lEvOF8!cCDu)kV2y65g;y2OS ^368b0OËB 9+km;8U63fP6{8FO/jORP%d\f9v?k "Vl&-)jlc g<Ȅ\a R G ʠ` 0w*!D 0ʢ<&wuT:!UyKqWROUyĤ|˸˺˚QҰqatBt̅1LKt! X Z~]VGG+j''sRcJT:>vQvvwu7''`0 BoQa)-+m`v7XTt+qQMekM4]~qX tMD{ToaXXKX2.R]@S@x> @gh(=n xXa n8_P ְ#| se[P@vF1A1M`p$}0X1'`h:Ak( A2@` P)=t]k1` $1[AF";=H8;3kaِW,hzd9F1յP&,P 2e1Rp"t*|jR3 32sK$5 q-+!ќ3x)!43؏fTc-*,x&Qh)3S+8Q>1zҍk0od444S.HtKȁ-N&Q]6&䳂8\BYDY_I\d,1vΡ^3N{q1yv]ס7^k ^Gmcb(:IKG'W8X J 5*t $LvQ%ÀM-KTV]W TtNm&G0z1Z3W 4_:UÄuAJ$54ձ ᨏ 㐨7T tZ(i-M*{#| dmUtt޶,TzĶ >811h.yjWPUj?uST!E> n?! "Аp#":@L(t%AS5JUVB N +]͞EU״V[ſc4ݎIHp O".*Ƙ c2AȒ1K֪Ob8P$|#G?\-Ȅ@0[g@8 [խZ2XU @D <ۼ{.Jqdd/eG\Jv`ޭiۡBhDɤqBa\WfeU?×0-ZoYy%VYf٥p{vhB>:jjWNiƕzfl\OiAkqɶ5M;i{!ACpuG5"?p0=6Đ#Ҩ:R O“h) B1s.MFoVR;ẗ`L㝽:PAeY^0|)3 SBK AAhɎ~R1)+\IB$A#L\FSpdAX\ ˜; 0xl xAtP ^Dm؄ lEPD*XI#0;B8 \ćEV@"^;"H7"Cc AXyZ 6C! nR?EC"8?6 0qF`7ևG{2sODJsxc)E# OB4 P^O䔰F0ћX B0q[C.+ɮ`H$ ~. R!DG9 p 8pK BQ*:p0Fu4T$>* g^t >IE,$cZ/qAC( ?6v@ŮY9Vb H < 5A DQh$BdcFgAow 3 2%N`V0Gu"jG<ܟA v &$:A.r\"X3|37ï"гǡ ]- ,@ڗJ  ja @;?Af>lو;/ @e,ÁL,{.oٖ񘬠d /BڕB։C Cŧ1: zB)! ia4CFp"l̕krIPh(2yb Byu,cpHƎdz8HsdȆ4 Q Q11gi'5+z$G' tțL1E9銞1\j +0n:ٙF*W ǜʭʮʯɊ_49 3<`p<( P9+ ѿ<ꈱDдHp/В+xHb *J!K ^,g1" ac@M);*B 9- #(B˔{$& 袂&Ȁ 8MX2" z/g҈D |lsjh$ '8)Y& <,X 8,ArZ/CD"qI&)"$8 $A)@= <"u$!K!H!(긇yZ[%v|QXМ\† tP?'q}Ԉ -]MI \  O2*2`>;)0S08(9*3#. ;#J9x 7xQ,҂"!xTFCRBshɍg\ 񈲘ߛy7] HANHWȫ@Ϣ*H@+ 2ɫB"&,Ax q ʭ 1+A :EUV@9 2d(L(GKU,J9{_M-P_E ̟rgɊ\ɔY+5٭TUY|٘;K(َfPծ QʲY1;0HEQOC5IW6HO/H>MۂH0[(",6BI;H QBԂp$K4 {ۑ\ɰ1܁&©KR01\j !`Q,;`h3K 0TA:M8+Lr-o ՗P(M ԁS [鈼 heH[HK|.,4ě8h)MВ+# p#85_5_{ Z^ 8ڬM (b ص6@HS 8m:ր^`؂= K ?[& ؋=HJ(4#%H `5D=(" XH:婏`lج:)W JI{;H䩞| nԈc:ؘ Ǝ˚qSYQ iC;ƩK< ɒ戱ด8=3@'wTV- j)f"HuЦBo&g ڀOM!0PpgN ,H80:ނ ܱ{NQ*.Msh܂`\/FCVO'1R(O @,SH%ڍ'Ј u.%;;iC^X^*ђ;js"AT䣹ՈKb፠ P?:ppZ(=DV$E@ + H܈(!` 90Xnݡ;,P&WvJ.CĔI bI AA n n=+5tAf({[abb. Yռ+P}X )ڈbHV6n Ɯ- ,h ‚ZHC@XD4Ntx@6v$DT2ŃCt Y˒B `͋表Å5P!rN:&ҬZ;~b&%A,DPjHp,p"x%G,(DTA@r (nsXQ{?!^_{AG2j9} 0!qҨ A@ɿ T {-A*kRq!fl &;X%K׹T;x!X'tM@+J- AlcY+؅4't@0D`XD".Q?X OX$pHJԱkM7A0}1@6.cF1he$@Oee &|pc9d@LVL f]TE4d?1 >䍑4KcAi85\X +M@t?$Aв]Q=b9A zrAXuSF품DYKлm,rNU @ZQ5B@hQ*VF,-KoQPenTz;w^WQrUvTq;(4%C5Q 6IPi"4FAAR]W4SЛ %'dj(A np4 AT?cVau;]?U7tjMRPs)D.? wCmd{j tMEf^}E~N¡c(ct*[G^=A-?@t45Ok@3?OcOoثRPqr0E`F:R%C"{؇eGJ7

4V(]# %#ORg9@'%A;ݼ(rBF*vsy c9. Tr9AP!lے){ve2|f (͘cJ́ PAD0> daafx$BI2}\&8 R qd "o `@9wMPt BNQh^?Sҕg‚)y$ґDPS5X; 2h)('Ax1n^Q:+t)Vխr^*Xq8`9A yC$ q)"Am&iCdVY 1Knd+yQX$)H6@BQ Hț  VCP4$,S*hR Fa@0c0 ($$l-HFL$VɜU5DcJ ܁w [P="DE0(8J`VԽ HAR@zB `Cj&h(|5VA  qx a5-1Дw x?ul46n"HP>=^>uĭ @a zB18l)^ )b2],$K $(?AAfcJD-vAs5W+  " D*bHAA[@2Z[ 7eѯ%'PF]D%БPgP+V*R.,%RA4@@lDf tRr%T>@)o+Vw&9. S>݈tFmW2]<Q [1yrcp, y&_ hY Z$z^g/d)(Z4Bc^:sfAǢ bB=#DSWc2&M;-r2 \) ):BDGL=p>fR\3x&ah?|x t/ l+X@#qcm j>D G(£39fj {A˞ zLD?8S3Db o?GсšB9.K{ #0Ap!a_PCBsN  EY)MYAA}hZEЈA L܍,hAh4t@I@8 8(N9x H4IzNA,@ < :ÙD@D @ á?(?`$ %4?@A\d `e8\L?XpGK$ϥ!D<B4Kzq a @MĮRԅO@NWxyl 0CͭZBT"lT͒}@Q` ,} Nb˜bE4¨Č L*ZS 'j=T% ˅\I@cHW?AM&\BH( ,^PCX}@hC@D NcV  CXA(dAMVk9 BD$ %GT͎0BXH6DPy?AAaJ_XA^9^B@DA IC=LF>OaYBtb HD$? Zڞ[@? @?A0 _(0E`DIB e?WŧG5P25?8I dW@HFHl"YNH @ VhHkv |-FREID]&='ϔ@&HXL|a@TLRNh APqDکһ<@&EQEԧÝ#:^ȝ]DyG\R%I#H~V^(Ӹ $ +D=?tC6%I E$gb(ƨR@z;9hVtH- Rİ1h(6P\A !E\T7_jcԓ))iGpZATNNPSA|! ӊ9DQA) 4x$@^JE?R<jC#8DAY@k $cDʥlqDdD <@zlW Kq!@$QdA2 HdA-x&$RD*D0Ey&xU6eݹPiP Ei@C|{,0w@"<e`pxpt0O7x ,l `ŒϐͬWB @<0l,F BEw ADLH0 $*VA TB (B &PcHB-Μ=HDZx(ZtMv()_RڋrxD/!Bb$n/&H[q"2D8Mt hPě)/d rۼU卸}cItDmDEMbhܻ\,ۻU*^St@}\\dHcFD@T!Ԍ&i*߂ҵ7fLígD,,ieE{yN ͡~iI$phQ$X)DTs* wF0Y? $VPl(e A`&ǙJ`f…AT`_#HAt/h@@^A^f%y^]z ^6Dǃ&A'UlW!Ds%@2AȭAϾH XcL)̟tB.bdD., |YXFDBR@@mri3 7W8ۄ@DůFΤ \ ab *t Ld :GhI# tAp5 !e84ia QI`Y!$I@AFAPPčKL-k@DZ"Ld DA-:D$Dx00A|Ҹ4S|ʁ2zD$LIp"U&V3ETAEJ#2Ҭ|0Jx 1>$,U()̀/+ѬDYwKNLY?OxU0U[ d.#jˈViHs$@3AA&d؄C6dD2$A@wp No[2dtNxegDpM. XU4UQ dTCS'F, S74E&?Pyq3}KiKglMo^=AgdبIZJlMqXaIPHZp|@m)rI*B Z+@L@%r~D$Avs n1%#43PBXtx2w `I؄s7 QEH yOrLFy!y D{H{.{^dAH>-v!A&'^LP2q; T.r0b2d2Qh׺2eiAAzQ:hA-'iHIď"As@D:/)LOT鰇Did;ǻ;AjE#kpa)`EUu_nAhHo-?HtMI1ЛI:83DCC/:6F%xMEpmM@pSjϠD0Az&Y A&xʹSJ`"GyWUY2"Ϋ?tvd#A4?^ 'rܽfQF,A K,J'GpH4)EFEpX\,c?HZ?> ?lh?Amn^S(o]mւ Ki+=A=At @BBu`A0~DzEmPMdh_&pwB-Tbn)@Dx_"P@CB*$$ B=H"*:|@" <"4 N !CL.4W&L"2$ B/V@(9%SAmӭV4Y",a-#)jQ" P 2^]F8R۟D`#xN.+rNgpg (Ts]WysϡGj ӧOUEeNu}r.mTVS}P=60.B0:<`!n.r!iB /2"Jʴ,,2/(79አ{j ȞȄ0/^'~$( J$"' r&܀ A"@u̘Mȅȭ!-!UVA(a-& R f&fDMHVG62X6'kyV۳Qk * iЮ c%h [e;^ѵznco/z8BNal{I$x9&k^s0':h"D1AP#8!="ȏ$]L!hs29#Dt*KU!r |#"؅Gt6qqI 1żNA8Ѐ!BA*" 23֑z g/j$B"@:O3C6H(- Kz|AwPd 7d`+2H #RA "T-8b&8ccUN i.35ɿp+A7oIXa4àc;9nx9!QR8&d MJJt|PgP. m DZÒ QGN恰Ԁp 24!8H/_K1O:8m:5D%$6M]hAQтF=:eHQLQxҍE]DrYѺ5'J[kOM-]we`vx >UEE&e.wpucSNe1Y#MCMЯ%zU$#1D"9(D܀[P9% "bDDxqt0ш\&g$\Th"8@!$B칍=}Ɵ漪2BNFwmO}^d1I<;!_H(FDv%EY@*ub=<FB2rch dTGNG?js4:SG l@ 52%T9DP n x 0訒Q9oTr1x9:6,:LDh` OKAQ(I4@7p(>%E `4 (  / -!ބ'V"d4is^GX `Iy l.?a ` N~T}[A>h,)B-K"< ["OЄ4dy@RҴ.NH}jn*ZhrȱP-~QmjNr!K4O%Iy[ܠeM-ZGpwGiIBC1'dWӻ֛twhz$h5z@ds4+DӀw`B a["BBR!<~U烢4S!"d8,"B2!w\1!A`|nC-ԦY!:0">% )EQ" 14[v^1V"H郃L@ r_F ?&D,`CVց c*f( ';RO·@+$@ j2?τ& "mzfbnk8b` ^E؅1)p*~ПPf`ф) &Gqd 1MlBJkzB "@4#`J>f Fku~0#?b索P"}cP$c'i"s h@($.b"(}VfU\ʡ<¥+c P'jJ4+BR\b0!bkD1I.†NCM$BqH * <#bH'6BbM.b^!!ɲh(ʢRo?BM$b "!d'a"@= Tm!""$ŒHDwI $$Iϸ j<4q K"DE.bG, >Z$"eIpFZ'"iP F$fmh@t"E\/ ~P[@E<h .rv- " B؄m< dFj~ܪ a], ID2 !WEWu l2 ~jʠl5 "BS5F%bD( @"An^ŵ $"N&|L-# m5X"T2@bX 408+**^jNÆmcl Ա(aS`B3@6@#b$$<0wPnM1B/v%No*""? @,@2%1?32WH`+b:)b+NC>`/~8'pV+mmsrn`tbV7b +P'/<2gB"I"i;<ю $bB`0 " N5<a7M ra &a"D| !"hS), dDK#KA(.2B<ܴ[ޫsCO/!Uh : !@P&-B V""uz"^^aJVʈ6SU^PceTh^.//GA@kFAubr4%\\QǠ-G?0a@P P"8!x8.# ^B Cb5bG,=@" Z" X.iZW"&$ y NS=T"`cBlĆޢbl+SyXBL/(^*Fv6^&ss ZpNp& #~ڎItY"%k-K'vc:W얝絔bHn # َoF&r>pyYH/‰eS" Q"H"! ..A(87 q^F DϮ7S"A;a"A1b.2Y"P 80>ygz#X?#Bp"GbIX4. @te W"8e$,!w#!ܗ!7&aZoB A sc$ay}Hҋ0E`AZ8PdH1!"v%ر%t{Hh-N"!)W( (.' c'8Rr2:`()K6(B2!Λꮲya'✢+㺭f)N ;(^tANC-ˢi1cj98 $!r9"r!~#*"*%C 8"P["~^jZT"dp ~V,pae~'1.,lBr5!zk %&G-KfuIC"N@ցL2z^." z8!Џ~T\ A`aIR='DIN0n$!UaN%-"| b "F D^ ZƐLS= Ԅ&L>o.cdHUIA̶,h U_`L̉ % W6bbH`lQ%1Z.!s eNҋb} @ t]2! t*Qʓ eE*qEAx﹁+4>rx!"/? ^ʕERX/ -Y@ ?UV%Bw=8n"R)#`"y qaHdHvHG) HU + %@R UÖS@FD`8$ #@=$@Y"S%̓ T@ǨY?cTRdBZdamwB([{Yl PEX"aqHE\7B`P3P]Y0-`D%r$I90`8),;8`L4u?hU,&Dz$4UE\MXJnBAL@-?$wJtELp%-,[5c4 Bf 4$%%`N 2_dvJ.̒i <]oBSG5}<+ҥH<6'`J?;H:%Ay`@u&4D wMíH,䠦H"PG-L,$!3;@]vB2t_9cjоEU-[e) dVe !֝W7D9ٵha#6yy n j^0&Y9Hc>+n:뵭nedh r}x=kFZ?)c4 ."!U 1[]cnm'R4b ښiVr }Zjw>\#vQ8ƶ2h]^o$U^Ee@{m\sH,ARE.S bCx!(ƭ@h8zd.4%`3H)3p-I/JH꣒2D9òA!`!$;cV!!nА _"F7du'&om 4H*IiOH(H[R"Q=PKGRj_" A!"Hx>8ۤ@MPCbFjt zЀ zx@PWY׍ zA8 \PIIA"Ƒ1H= k%ll,Eہ<&n  =%m0r@̂YNDQhDpQqYI5I <⚋@qMGF}id\_`4+bjJ97mjH'R_.Lr%XL^jX{2Eʄ+:@K21p78aKqL~ @^bKLc'Nw|END+<ڱ.54#a&p $C R`JC!Y⡃USNqDNPT-p%cP%Z !&%5(6ҦN#-x0*m'$@\&BVQ&P'dH^g,Q $+1, nuBb0 }8#52{)7vr`v)(!ЇжU4Y`or^y%NV pY(ٰ\-\TgSvBv8xP 9ч]|` ' P]`tVbGYw0@ PA23 1k8cb.M"=f9.aC0O@ c+t̂ /,$0B)5P7) ,0t"a$#3)iI3B?dբ.[@fp. fPɐn Y3hf-6t";DZsbz@)h 6(59s9ђq!s9s9.996  Pp/`+6 5VdV^z3gCUS;s9A>CC9RYƒcS]ՃSe%P/ 9[=<Ma^ ֬kwwWBZaeZd3A!5q6 wI˓)lcK E Y05 Ūd]e$[& V+bu+w 1' re+Pwh]Z`q^SB1`GcN9K 15^&&\b˻k% T 1 4fPc1QVa! 4g7"d+dok1*Ef0 ٫/2G9A.m7A,wC 9"@ :8@15w,y6 Lrvu @fjMS>v,7s€o+fCbf "s7 F?rE!onu'Rt!pP #' fa!`X*'lь!l".&aFVr kA"^']|gdu LAt!Va`rq"AK4'`KT A)'YpFu8qd0wu9Aw*s'[mx"msgfPm3n2Ū xoxi8F+1:bKߗN|QQo$y2Ƒ)3MQ8Sڡ#yҀbrM""E% O0`Z$`Xr0 x w芈Xт ;dpuRe2 5'9c`qG‘i=كSVB[;VE8F;>{QCN\8w 1.NC>e':+Ѽ+b+8d!h:4&e7nʾ:6с3>쒻>Q>(?\>W+;_c,K!!֭W=#a%KB k*n(Gϡq' ?R60HT#1$ -0ەMŴGp6HU+qϡ#A3'WLBuI{DZ;ݜ -]ߍ͝r Eյ$BJo]sN)FSQ3K&!)q59+U^faaREH`ֱ:D,KrMaSʰ%EKuQVQU%da]%.X FW.WV:ܱq/:+ֿ]M5JR+QKo0U#*X  Wă .dÅXx5n1a"z$YI)UdKY:!58pgςjQ `$t#҅PNZUY=Rl!ׂK&!b"X):L[qΥ[n[3^@ BBoBG~ߕ .h> f:"]y(x韀$@^bBA Nx lɐ1 N茉vGxHvcrȱ&9"x,P/x{ NY_o\g~g?n[?D ' [+Z tj z'ihF";D gA`!<"' >@q`:h@E!x!X kDpa<8`qhC(cc& M8j3P))MT b.)~ӣ@,!rEm(Ih8 J̼`u(4e*"#$$#);;d)C -v ۂH$ g)t!ikylaKwCH 3H)z +@\0Z *pO p7! c B-A. <8 @ j#:„*5!"G!yX @qzDΰnhd$5ޒ@9~S FVE|$'r9!''C8<+d7$ͬ+N0/*Hijciݡ8 w(Rao+nHw쳊~z,UzPB@EĆg|ǘ2j! !dƢ!KB`oyYiI ͓LbMʑ&B|+1d8r3BIe`D$S+ *9|)P y["(#qC$щ/T9B`4 {r"PZ{A(.4J F9IC4x$@j>P{ 9b](=K,2 5IUA&<@K'juk.1t%tQ eOĻ @@CV )+vk*DY0i@M84P5do:o$f;N !10k2 ś[8 bqN 7? -+6jU~FYZCSCB 1JB5D4Tb d^4 h]:qD*ۢF2M)QG85{k`i0Q(RTӫNT(3`/,֋ Z75)Z5ԡ.lU:W k* "X`1(৚a Tb- ["E, ;/KbVDŽ$BlagԒ.uO ![-s2F4j(CԠ'MT  ovviWB68UA| Ń*S.R`HT(/¦;5f8ž$Lw+Nxl1qطE\c& ,qX?)P(| [dA΁ބ@ö $qHhtPSp BWd-nd #`d͐Gd!!V s_G3D#ΘFtĵw# :)6fGCOca*9ʮX۔ e&N⨵iF&FB@fP,t,;g?[.J>TU9"bj&w}n }q u`9`BZx%|_o\S ZA`z?9 Tpbyz7]f\x;[;J!0)5n&ϸ]r\632)H.Q)\|!aGQ\0Y?RӤ+|l :DЃ1ɛN >% }!MN xoJ4[JJP0J.djAARy] I p[:`a&}@s'ĵo;0{/2Gc9r#H cEo>BC-wJ4#!I)Aλ!8I_tyCBް~?RxtxHqطhUK8ЀyY@# 1q `?K\*qL JG"Ȃh9]L6Hhǒ@pWxT"#ɰғE\H3i(DJBd:I&K*$ {WZx*j-S#' ,PR7x/p d dPCцtJ.(o@I&!JДm'Чk14נ҄X%IU2j)8  HThRS:5o9UP m@ UwId*XxTwȠFM. P*uA@W1X}՟:[U+aզS Sz6HP֡*,_ȾÂpeY֟4 BUXsEJ耀u-*Y իȬLVш$HXz':|͂!P/s.Wn"-hWu'JtҼ-b8]QYYYW\DȀBІ8Ti %XahfTXڦTvq$딴YhKX? K*͌uKcb1"Ѓ [Ɣ,XW $L;ۂa!=)ɑ> C#[\PMF|u 4Ka ΄ !LQ01UpݶŎđLxAl!9!dI{dOdτBV)lX9I27KF+_(F hͅ M hAVc X[;kJ3 ` ]ް9QS ImR|\ XxI;58IH_`G;65(9E,ᙬ)haa s1bVaִ*p9#Vf;⇀*Uulc n0 kRb/b0w a\/0cH c:c;N7k88 ;.dC>伊&֌@B$bDdIdJ֊v ˀJm=(h#&RhӪMfz:I6 XMK].1k" ( ȽX(b- ȁ`@.k5YQ& #q h9 xC )k=[#Q}֓ 1 A *F>-QYǺڊy,Q Q)G ¢cJe >p9 ((ٰV`dܤڂ8< l CsA*I,~),ђY0*+CQ T1-蕹ypwHjC 0N弐2An?3μV2l /l_\%@`!NyD .g@+`Ow d-I]AHMt@skdwQHr*֗ez$F2id Y޲~¡YS @cV _!f߁V.ɠmIoip=G F ~TH_Hve ~+~D)QEȫ$Qƍ+ BJOSͽ_v4Շ0G 23l'J)ayʧL %as6N BGMXQ/Hsh@I܈ ijELh4X߻L 08L(/%󠄸C\ A\J\[Y볮tC'dߊo\ NgEpFh c/h)NeN %q= Q@@|ͰLIЄ9O17mk! PK:u%ȁX8L &ͭtI hHW1 g$.vWbz DZR-.xm͡SFޛh)u}E0?h&&3P`8 u9==s3U>5X`*#YXpTiЄڨX TG-F%wETr?nvriS6B+VX6) 0 s`  dd*Ob6w#d4ZVb`]l;.WUyK{@Uptbbq/)FpR?{w6t/ JۂH / Rto~.r(gY؆t`mGGMx/L(D7&@%%R<% l )r$ɒTPT;42a  \*88cH&IH8 H h#bQZ0LhD ?l,Eߤ]y3ѿu)F"sC3!xU`h""VW`cSa 0vbxBat6?gHF~Y+ \vߺNIX'G ڕblU98]a !l`b!*^S ab 7~G?O DP])]Ux\]-vӁ 9 U`BLS[!m(Mi4Hcc~4u%O-^{@&tPhQW!P]Y$Afk٦~>bوmY]֙G홧盃BZc4BBRZbZ)u$X&0g%!vڪZ1)yq!yZrj* d gh~hBR[J6-?I)O9T@0H|:8@ 9 ?b( 4 30%L6*yc>L !HW/ tP2@r yUFsh h<@O,n^:oDH<}͓,`vNt}_CAN| ]ІS=ButD1, I,2 (4 qI yA]H.@=*4:N@ Q?FU#{A6N^q `Dd@:!z  |C9ZcA SE)71 @NA"L"_Bv i{0 D] @ x~8E1;Hl RT9D6 sϬn\Y8(=NGI 5٭ `a@R>Xp3Pg/%^c$D KpBO Pm*4h4O@&CȗTs/)HӴ@B@W4xl4E=Bh caPϗ%1Fddeޤ $YhVi43JZSfB)Ni9DNdHM@cB^OHb*aNFBkdNy!@YH1G*]V $I"1R 2R'|1 OQVHDDH 4 B1OCBX G܀"((!EId:>d:$KS2x((;S޳V^-6 H w@+PKP Y(t_HQcO{aZ~s肰aBHr)#@xi) {qi׾Dg7, B)9Tm}"<bIRBf͒I@N4MQӛfD?wjA䬛DIS4spGe4?%ɖx2q`mԁ39]!=q|@e4+[UPIYS^{Sz!c+~|^)?aٰhkG&*kk$(Goܯb\ %!yV7rN!El*C\6tll8rR~p7 Dg/XXģ P]͊LmL9bp 145Z% M$z.^7c88b H8BhB 4C@D #s8a2*HP2@ uʔ? A??XFAȍ?M`Bɼ9SDLBdB9?\AF< OtL*NFٍ Xՙ͋B u4@<:$@<8b9eܡQRH@%$C8ʘX\Adx@t? l9&\\ F:?D?`s4 !hO^iHX2\O=((@AWRG LOU,Ax kEPWM0d/K8Am%^=?'`dQpHg9@!H#Ә=PT@jh0/?,@D iHAoB|[!]nͲ*\zqp"rLQ bb9%IR}\5\qRݱ\\"~GWLiʵ2}*Uo٩* E/Bx?vťI%>b& z|}GǛIIcPL~Tz$պ YH [YBHeUE$\jAY9IFe@YʠP,^AT&E^`^㉋o t^A&%թqU*%&C3JY Yi--NJzZ b7kIDP GK-g*ĸ^P-:# @+ZERDG.DMZ RQ`ldm&a Աm˒^rA@ \4Fd.{mDlp@ ʡ3 ,PaCU'-Hb՚1PmIH|HL=t~`bFj՛~EFBS𻉄PBF4C$W+ g⊅$|G1[QkR? RWGA;4.k-FUGJ.6)R Tc1FBWu@WbSZ5[;Zu\\d`O D;B@BW&0֦+HiH86 + yDt;#Xה%X zl] h`NB$hQ,LAZbXw5ԼOuܥqDVPN`&Fwfa(@&xA$` JtH$[S?pwYɄ4p m!P?\@:v* U &P~9wvw?F`AWY%Uhe D%PC $@:z!D,'1 0$ysԃqL;M,,Lq؁8}NOdF?J DNiuj朇&DPŞB (WgB_$vd`nD"@ \ʔTMxClKj܂)JRGmNψK$^fxSWT)@uTXh}Og#LB茽1D@D8Lt-mN:0Aާa%/(')*؄L7_UhG:Gv`GBL`.{C}!{{W3Pˑ \4W&VŁH?@@@J Dx R[sI|p8 9Y? MG_4`?&Sw _n_(jENi_ ^: Hk  @Xd@8CŷCn40$Ɔ^Wd6 =w|"_iAlDEO@(G8*@ߧŇƆ}mXXB^%Hr*D/YM?ˉ倷&?Pu|))DEwwfYBHCT(.I '5dBtHD:EVtH(*PFxO:"- L\1ݝOȪ 4xaB=8?%pŠpDž#IF' d x`"*0`+'^*p Lwi"BA<yI[I>yB~5̝J'*6C[Q j0Aha9.B^'f<B K2ڬ vXy۹g;A zO\y@Dڱ*$>jԌ.:#2_l^1D%#y 0PZ+TH DX)j҄<H:.V2b6Ԋ6D[oāh⻒:D艙B$Á"6W\RBg(c &;@YiH )P^xnh, L/ Dh$P/2\>FǙC9zdY Q"4!dCy7R+-x +6CCH,qN`" XUR 6*q; Q^e<(`T @1\>hU 郶SqlQ@P`9I *8'5wlZ Z +ڠaǂm)^ FF(58B#-&&NH|e6PS!f .8x'!8Z`-!,캎[ЦS=@ݲ6m+{$ƛEҁ'!%J!cV=EIU_]e݉%hKl'#1fZ+"<BQa]Mh!;bn!)V:M1H )sIw BNG| o )z$KB/l B $` Y^o 8 pp 0fAZAg )M@cd:pZ2EVA<@!D3"J{8A$cCSpTZ+~B 5eYH3J$V8(-oxSZ? H$ Y0 `=<F? #/cE.aX| u |ОbVIʭ `\Y K#c_NHzS< Z/e&++@ /pɨR f=j PvjmDN@“T/@&+fE.aj.%R|AtZAHP&deHTV>[@]LvyAH5* BT3VJEnU[75O 4 b%x*Ȑp ef9/WkTO\迁؉d _j"L~W \%: QMl@h6,Ip>$`CFt@-.hA)TpBV-s…"d0>vć'EAE^ 9A0.Q |NB|L'!?* ?=|`D`<h PU""<.VPC ,0 |-(A@A`!B J` (aȈ귬:!Fra i4ȁI[Se#`tm|qȶ >UŴBTHA08$HSTH I?” E{S:V-y}m' v2=E`#gxkK7+ 8vɫW0F3 jN> U6"ͦqc-D.'A@>ҤElHGnjVsg lp G6 ,3Bl#d<红H{6B@@,BP-\:ԖBa+dF8nΠ攻y;!^ $_f殮pC6(YpA`yv]cB`Nw.W F\{ 6Ȕpv :(a VU9j$doaȊ6/!ŬnDLL=zO @lavA,\a!#E+!QxblR$*o-0,b΂5*``ùD!2`@ "HƩ,`cר$,?&¹&Fdfa<'kԣ _<66` jIL 0(&6ev`X®J#4FbsFE{.cnumL[1Rpo;Nk,Z6t{oe1QvJ!!A V)o _F8Ge81qN@,.&!Ā|"` <<0DC ZB D`ܨ4̯o <0R?f,`|h|'+c !  H߮J BJBj':j Bg0+%yj$:.ǩzFUe"CUT^A-@ @!qd<A;h ,@@V4TEdD s ɩI"@ x̙IJÚ.6ԩ  z ( >A,)i=L%Fk HiF=)x\AlPU"7N:g* 39 1N>Q>"c4! !l7M$8`*Ա  k"K"lFb %&TKLJK<D+*믂 '.>vJz$άN".1JX# M0N4/ T2DH(,*-QlzU#*$ 3- `6r$rP^k)mg ٰlQ0!f̺*I W%4$p1Z]k>!G 5rĠ!>K@΋s$ha3W" , I TlI7&a0h> !+ą'Sb S`8!TJ> s!GGb BHN&” j I6B" Lm!&zk@^g&%Q̾l zM:F BqIkITaLGo ?ۺkIxH6zx %2"A#(Jb -")rAR/`, ,Zb&@zR!KZ BD@v$+b:pP'jDE8B*~L"wQ7w b< P("w)!جcb!᪂'(䭬*A '+Fx*(";$27&WcCo189C 9q_B-Fb^T4w#0QibRA(/1:qLBNfVQ5n!v4TI3bx0Lw+FpXvB-tA ́W$@"fWvђo Pz\R*:سpv =AR'!A$!|%c gB#6(u @o^z "@ jφXmG RhBڵld`9!Zma1Lg@bK`Mf37Q 4TdadU RaBZfB;jigR~6h}6! i A¢6.|/!Vpx s&bB= "n!YoKoEQW W0q[ z4e}z쫳$̶LFk^0Me#zbb+()^0 a  iِ6]ڢ5'd@4ݲBpd@0:h<qR¯"iI>d0\]f o޶utfx)d)oOX2iBn * cW[-&ff'X!$0RUnjt"Zw,Vk8uu¹[Y~!j6:!j@-1 bpDpV+ۻqa&l!m{;Q!lXysaXEn؋\'+Ib$%XL|jyY 21X@ 0-Ib?vB|R!Rh#-7ӗ}`;Po>" fH% 7I'P!2A $$y%r*daB*jĂZz tP <@%~l#,w`Კ}Y'lB\/ɣ|v 31sr<,?xtF'3oh^5 ՛B}Z G"!yJ6 b77KUd#%> @2i :T:4 p 9 +&: xB&9i @ѡeg4g88"QYz00ǐ1g1Cؙ2gR t?KvauxRs ŰD$tlC+F`FM?%P :~5>YzM cFVHEđ_<0IQG } )QsI$@!?Co"Лh> pP"V TP?@RCH-zC4v#G 8oC0IOeA\V+k3-l_B?}Wcl$WRor&~hѫehQp+D+?9xQ!:kUU 5hL[F'WD$0tr$q)d,Cɐ00mnQsDu[$axI R@&4,B"l-VaogHݚٹv ) T~ZRL4ph~~QYLFDpf"Aae qC0F91 bL 2rd*XDrf\4Қ`"#pU!'yprB;]: 4 h% XDp"Q~0pWt,?"c@VBYhLaB8P C.j q]´U A VU1;s,K$>$7z<`z9T$%-A$Hp Kl@ Dd~i@⋈ßz Ky$K%aRR!l%,g"N@+H:bι!055`~QADF~tn@yvh ̧O4ͨF745?^ }&GWҕ?^  礼M5SԍyiҢHI#Q@ԪZ5, p.]!/kF8U]hM?;"uvS\z|+^׾ ,Icg7h!r@+Mnn _nZ + rE~1"|bK1VB+Y-Bva?|08!JJ£4'x4X1 CPp@e&'1n'\{9\7 a0 ](BK0de!{5#b%fȉSX$.T$@(WrHm i0a€8 x4 =y:+~be92| \^!2gpI.HAr/?Q@F !(`]& a1Qahi,i (D @B+ @d\aE  =IHwR pDx YS`+ bȰkZ#Dq ȫc\ȧ.4i8Y @"xa!`dP `H1Rp |w $H4Qy+FP@C@^ʂ1WVTfo6| F6[|)lGυ!͠&TKG3Qu@ N=멙 + L@t)@ٯzLFNdTuG9wIsdjd4 kR,3uU'H&=2b *T +i8[p]0 eq d-_SV@UJ3dg2{ !{}41z7-Aj}6B `$oL01.4:@}!rae @z0DS^Ϧ3 1&(9aAkqyjX=1!2Gl7l6 @@  0 0 y/PS[ Z Xme+1 `/N #B:[0as~rs~h(@+%8 : VB:sRz?r`]PO7e2\8`V":U8+Q2%cceS+(FR~Q&;_ 1A#A1bl%FRufp Vaec\X->Jp ,sB=F’+,ڂ 1 ڐPҐ&$p W5wZ!cW1GPPQi0#s*43WQ97O2ve3e91WL.4r2A7`Vs04W4;#1:lPR:k#~`SձЉwW8r~Z!A,#`C/[b^-9,ɠòC';r]X]X)I"6h909p^pUQhEf~`ǖY8e3== =I-@4)& J T3& QH@t6fENv9@bJIlAA~6E0p!]d| p H AAУaHķ>SDSs6TDEVEM5nV |Q }( BprFt!q舐(q TGȡPTy70r@'}~i Άs#A)1q)Q;Y!KԪG0Gne!$HȔm/Sթ8r$7 $ZNyN D5OW!QSW)X4q# 32'W*Gy;[S0uMBF3S Sk>!/ |t۱UUVURU q K/a02;:6.'# 4۳>@"m!*Qa pw@8yZ!739%[1^^]ҘZGhdbP! pCt鷻I%1[#FNJYqwl~.:`woqr\tQoGMNYIM1zS1T_ q2"М΂4ɹ/Wj˧aH,Gn*}G%Õ3HE~Kz<KrԢH`!I/8;  q?Stb@mS'Xɴus&!#B5)PP "@d@4G$A>b?5R7<ҊL4~cv!\q>E\DoAGk 0h5P~p/  P zTx&I`pX9: <=g !?+"L9!W i!n?#P  >Po&_VJXSÊ#<θș%'Y_*!#ѸS)M]H85ب Qyž/c@=P@;) %#0 $9ς p9J0 h,Q}/79K ,n*4E/yag5:j4SW3*;!Tt uΧ22s6nW*3YQWKW!ȑPpDP u5c9j%)^ӕ9 c)3aH$ȩ../>&Fb6?M7F"he<< S3,s'.*p*0 :I'fc`9-D,@~QZJ.xbLkªBB6^*6d2cE2$=r>0@:٥5:CoMREv@$Uke=QRՖmU>Vv_Q]EhB`9jo:F! }b /@ +*/[U{٪ ۷3~!CC94u]*C2~`}4ԛ:~#7@? D H!.4_?t,X8($`2FM@0$;@l?}vТ2dgCVrz ]~VXe͞EV,H!TlW\uϾuۂ|_4B|0M+P2dʕ-_ƜYfΝ Jܩ݋ }x>h` %?\pō7.s"k 9WKx#G^z > !v/@$@D *dD$:b0,"h@ Q r39*5Zt-<$Q(x"Ifa  a ZL膂Z|1n*H r@:ȋra! VB' Fsd:( ('" a LJ/Y-D89X C0"L D3cQ )`-HgRX|&ymkzg} jĨ/Nws!<$hS(>cj``88. 0IA0A$!h+ 2n,ɸ ZgjC~G.DBC^ " ȜҢBm6HlACXe * Є4'yB)UQ9(cCb( V7vq(x 0H%`U"nPc]ԙxmQev렀: D"WF{ =tFDA?U0!+ ~QAt~A@J1S2$@~Bd@?y@ !R%U~ HD$@bPx Xu"181{b (*`(j(`L*9IܓHp$)bEs\VIv^A6 /'"4o:i«d7!ZSL! . d&ILMFy?80= Y VVyLQp4AIB|yDy}D2YWGy<H," 44q#"Ѐ," eBq=;S;?t!n!?̩% *<35f2Z [P͂5;uj |AQ`pI[n@{PATXDW``$D>JTq 8 W2Ay1\bUvA8Wֳj(k[%WD(v,B>sx˰=hM,u"Knmȭs<"`HttL3Jl+fV&L<ێ$. @M `"-eP,/Kq3H34$O@f:ಟ@X?4D, ((< h9`4 T]8'9',s`‚XBb84☥ 01LT=Ï@H P pȉ$;<= 3mp9 'u*Xy8!8Fİ2H,Q&!(*L 4R|xLh@81A15;!4<{X[HȽ:!!ܣE`(*i)'Вp[ɕE3,d+\+o>*)hth`n{ h䁇ǁkp`,F GA9˂##D)} r=H!8xY)# ~19Bx9~zɜ'4* GPh#hX!*K*8(Cl$@8J,ʐPr҈+^`ȝc+r+ xp1huxhh|$ +'p3*3 P(8z&ڐ4&.N­j<Ÿр++YP h!h!x"pP#*3bܓ!:` !gg.P% (&x 7 NS pOGS&IP#i# 5 شq!pARx$ TRiJ=qk 05`91|>C 6cz8M ^paH e ˺HI:lletKr H,hF\SkC=9S( () W':\ѪLU J 4,b֐̄*(,L=Y3LE+-Ȃ(#E0LL\J *;C`ؐa+ghRH!'+@L-t!l[eLAъ8Th1I0-Z-ŃXCx7j!kQ14bRrR5ځ=XJU Z[,(ȃ^CElڧU ʁ-S-ٲvVaDW!#+0~PK %@tL8S[ h-@1XmO 9Pi 2MC)s nE8ʓKTT(k&s@{2׽$ݯx4B:O_j/4Cݰ]""@,8j3^3Lhh {18D=\ =ǡ!&Xp!ك5S>5ѥŵEX󊵭m) @Hx26t.=B)ꊁ-f+ EWzK[ #KDRë3oIӫ+1mIUAH%I>K8uu;:3Q;rrAqEjX'.'DX*}X$L@] D ?ej%>/n1f HϛsCd:ޫ](#}f 䝄9=aگ@>gc_h1p)d;-LI> >g"@؀㲞h ӭh҉8kYXUgp l!^:ܽ@>'Si %n@- Ndݒ.9>]i&.IG6݃PbX4]FG$4k)B:?6@Г$u yIC83 k<+A 8]7:VfCabQ , )u];F>bT_,M/rlX9GqV. Z_!Tx[-45)5!f8Y{ntT`GwIy'00 ˉ+W_`!b `k1%|Drh)D HqH'jH~X1U!W(F{@ WZ9(›iӃDDV<)Ղ؂J#B@6t"ț:h, ('̞t}9Rbv]ЀwQZeT*M:༸,HEKӸT2N$2%%'+ LЀ˙ZN9"[+k-=g$ev -4!+!gr:8ݡh3,CѾw8ŗ|PNݫ|'9 H.CYشؙ: +" d(RBV2x  =Фѓk6%j6 y]$y8VhG&1}BufM:r鼶xmY[;eFFj’͖T'vĮd5\ڃ8q3z!g,P:MȆ:0n畫NIr_y*iFWPs 8Hj,W|M.9n,9{}'YIN]3)1WcUWg!pԁQPv AGv͇Ee|ymqihWfad[5Aa L,?8nHCF_tD LeUnu O, XWʁj`C[9f`O1pLBcN^ vapxإqxP$覅E@AhM E6E?DEūVĪ6 hppjPDD QmCU!lCp0Ӳa:LzHQeD 1QORwP"*ǰ›02,&A D?-?L =Q4=]ñP @PA WF!nFKGAYӧ37PvYl5)fnidAaKKov#3yDB8a4AXPi7Ah "j4]Á əbA{bOnV%j 5C;ܗK-ujg.e~;;>@.=k={oci_,m 3M-7tL ?(vo$39fԬ!si , Cu@pA4&!(!ч]70Ap!M 4#^aFCBǴa)SA67h`LdFȀ]P1/zA@?;S8K  B 4HXAhd(4!=IP>/>0| aT$\ kB/JLZG0/Qt0B, 5he-Nl?pAÙ F c@r`I!XAd E23!]h4$Ay,pn˒\CXP%_Jȅ̄F"%,uAZjT)ANa'!HAᔁ ."0"8] j$Vj`:*ԝƁCհa\2hFh(7Eo0Q<w  5-e(/0<9hxv G?,}' ̓aH׆*q^@~[4e=Fz5z+΂G$8l')v@tcRI"a45oɓ`-$A g) yJ@XE#PHWTt,^@~axA 5pS@Db}G/ t9PDP!\)'g z[u?Er+D́sP;Q_AqD tPr_v zi g̍v A[~ xsii !I~xG, IqQC)ELQ$J IE.!!ADl?P S0T 5LWπDY<47=APCCUA^C5 rOA@D|E+Žd٬~$۲uA?Ã34}:SC5ЦA>u&qq8. mZC&1C f95ʀ֞W؈#Pg^|1T'@vEq'|'yFl&qGa)Ch4GFLN8``^<|6>(F?-, DPC䀭DM^!DkL0O@}|]KE $ (yD++AոF TERRTZ FD)I ,<]):APTHZ=iL&EVHAC4-TXɜC$?if\AiA?@ь?BA%0̈́1N3T:al٘0L10D1ȢI)F >A܏"(8 AOh? Pe@BEV>HDH J KDCHx{n L4n!U~dLlB"UiT4JXKCKk9R Ntv8EPJ2xDHeTGAxC@U^TaCDj6ԅ|$Z7ĀәT0H "[ӡ ~x~uW -%n3Jbf+F7DŁQO2lAs"#srH2`!.p_+(C:CI?0@+ۮ.7Dħ]4L:#-< 0T(Dc`LlZB΄*#c4b6pah$3E?A  L:;_<1D9$ΨGv0`PdwDEk<@\ ueeXD<h 49A7‹$UnMQ/5S|GTvTGYΘ0}yAY#upF<FIO,Nv mGe&:F^]POD7GcC]\`veѨԷl hcfA $@OgBa='>(;dE=膂rF+o>w>臾>闾>ꧾ>뷾>Ǿ>׾>>>??'/?7??GO?W_?go?w?????ǿ?׿???@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;lYgѦUm[oƕ;n]wջo_xqǑ'WysϡG>zuױg׾{w?|yѧW}{Ǘ?~}׿ P ,LPl!P ) 1P 9A QI,QLQYlaIӘFŨQ.  #L̄lG"TRJ#tI( r* KV93LnBM5d1)TR hOo&PM-@OlhH3xR(0z9SN=eԤx\R UUgVkQ9|OISz*]9gaa5v M/R\lV"^{Jڌ4m`Q"^)sm)Yb7[ֿk1 VY8._܍W .ƌ⊿J/_E٠-dB9d=td+C y25!K'|1>z4-i)czZej z 9n'p܆a_8e {Z:(Yނxo^.z1r-ޱE6"Q7țs&ijGy}|~wۥB>"_*Oyx I"y|?F z_wr$::':\k}KG?)x Bu|9#we~#Ɓ(uȂ'a %AS$x"Npt0(t?:DM8b d1$Bbф!MGlts6&yɗh[:dL YjE߄6b:<02ʃOk/L"GvocG}$T0"5z60Z^lOfbv |18"ˏWPC$U .@` {+Pi _r@BO5TR 3c`ATCa1KMp$QX@ N!fx4XXGFe!T| xUP]Ae7hY1Y5zSC$1cBy8f)iN+5j0@01O @? \cf{f"28p=B:A` ?B8 n#yzF8^w(xV{gAPrE@A[#X4KGwKDuM`KFЯE'AXi@wv@8hXNT br @&U@>ۨ@f.c5"]5UKL-,}1YK"0<A(Az4Tl bu Bвk7,e4nD7]@Hm,lߝ-`N֍DxpE ?TAw4} mzikDYdAo=A݋fO O-r':f)IAƟ!rAT=YkTpAO#*H5A"Q(8-#6 bHFX"VAq aARSa =˙ Є4|sXdrW*&:hôa0EfH:"vBI"@TxAqq U={A A>Gaa 98B2cP^1=ZK9@!`ӸA{ԡ2 " ] A`#`M>3a51(&5 ),p@b d F@P+4yKP X@)6} {J6Ed%4v]JB&H&?Pd#JxNTt`$ATc(h)~uwѳ?Zp>e "f B% GlƂ I*`R bĽ"Q*? AyO xR$]8󏮑>ݐ8ڳgam"R4,dX0UV*q!9 Waa5 A#01RQ8NBWڭ095iuê|7R Muc=CJ(b.#0uRzQI*2g135.0!\o) 0b.5l$>L`f6%="1K0Θ3: M~1l2'a53z4+ɶzؼnc|D,Ӯ^E&I?^ֲ#Q #eBC~ѲNyHz]Aeª;$ޔ%ax=RhA]A*Hq>U9s gy j?@lF,A= V!!!0o;?zA/q PHJa*,"ufYnJU f(іww'PV FA+lЇ:D$O]PA!:p; Ɓ 1àTQQEgR z@ "pir%U-ӒZ_` y5J5Ū*1 `b$;1aR/K,rpTv]vϻnE4&K ^?!|СC28xXH` T8ЃG<ƍ X…ND!DhǞ'Q Tt":S" Şc|0СˬRqӦD=kIryӞNśeW@'ɂ(p4՛Xb3ꜰbC(A8 xAU͔)Y5D,ƺ`{T3gD`&i$iJv"9+y ɗO%?2x \j@hR tZ לR6f︽S,k?(K>)thC)Wd/=IaТ b2PRn$`#al_p&NLa ]!R ,Dui@xkTGb&SR\80xlC3 .c cUL h`9*"~ @BU%xÍ9H r1z81p^`='sbG_%`)"Ȥ ̼-GrQ8Hn~AkG"AUJ]*eL =|UW%* #v&?bŜl^qP @U(-HIdQx,IiC SU Qwɋ:AtA/[H !A [-|;qbM "g8@4Eu*;箙wMl#7@KM= z9eCݨe׍'9@4ޚxv&"qM\8, SV%}.h*% }GC%7{$m{֑%Hf @G I1]`h`)!e}IX)yt2Xl$I4ě/rtNoDh@ nXpT6S,d e?!9rEi OK 2sy(>/r;+Qڣ,H 9i%S&K ()*32bY 6Yz*Zq74j偓[Q[|*:Bг\3hx3w;K) 4]c (JRxQ#cO / (Q `531@ H.# p/ .qi GdoY%X2L a  И (-10d$h9`̗YE×x{;0Ƴ2AP FϱP1[ `EC jLkL V S@ ITt="*S+@ȼ: Yܻ0HYD@ Ȅ K-{u-l|Hɘd Q( ܑuH<*@Kґ 8_\НʩJahQ8=yGj ģJЌ0w`8?IDČ0P/h ȤSY1T̏x KSdtׄ\ #̂8Fj&Љ̂p[@R JH6J-8hD"KX>s F`ظ b!rH)E 38)8 N`Ц8PiF2$$.XO QE@$Hpʙfс%TWj%%X* h+8QcJ&a&j ȑ1fQ8822(XNyȕcH#.!`5-6}'pYly 2~ӡ84؅8W8'I= ;h.+h2QDꩂ&C#E $p 2x$ AC-`)`_x!i:2`g ĉ)̔,Qz*(" 4=֫Ž @9T.Nߪ,E; 8Hh=  p;nՉS @pH;q WL`QV9Z֓ @  1 .s p/ HhqJf$ʤiZLxp10X`8$q0YB#0z`vV!V0{(}3M e) ˂X](T;"y8x ա%P܂Ȃ_Ճ^tT)0N ]@O؅"=Ȱ ^CӜYm~>p$98 R @Pa "8!>\9N69#89<(:h π8!K P #OpaڝX

7J6ٙhbhпBa ( @H@Dl'\RQ+Rr2#AM1-\HB8'{8"̩j0N3{xZV Ջ ހ.N䒦.3q2^ J +-YV%_j)ߔ(l U9}R߼8Q"hZe]8@iښEN DkiN<r t MWE|Bb)^t-Eј<̴FQatϢǁGsr,V0oqqy :jqFY04T)Ljm"N$ZKd<2Ιɻ[fK0 '3DO˹ g̝fP<Ӊ`y wHg*tU }Fx,.&9KK.ȹyl8dʂ nM9dr,D's ɐ;|"@sgW4n!>]e ;~j9A STs+9Q$+i}{"+>pXYꊐa,zj.'n/98~,-ZҴ @#;!Y{y]W62~j#J7ז-h]$B0p,`R@Cz O"xA :wg 9!5yŜ P)Ԧ:#܀Q AU` qXWM|8v'ظ? :.]$*OE$|0Gi)j1oY\H2 tQV`9;d.=EFXek3[#Y>|&X(63xmsMT/\?!vO;4{֡L|d .й%?AUNe0AinbDNtEA @ C4d% TJ1ćB.8Q5V?8ER?385N'p!O(QS%?<|攙9+4?9tO!TG$W\c$*~À@x0uQ ÐO*_J,qiOe I4ps@ѼN4@ħ@@'u>l4qԜ;9A0(V^(?4Bc 9͛][@?O|[P"*t CJ *m|5e=Ё]1Aj/EWNkPPU6[`2DP2,C&3QDG}D M)BpQ`62A݌aAc\VXÌ]|)ES 3g]%dVhK P>8Q 9d D )P)Q@(My1.@hN K5 y1Ò@ $(yRB=1GE,W,[<& " @ $isl>7БBnrb=|`DHr - /@9С|<~w {<ю@hBD H b?2< c  .Aڲ#Ha9(z DS2chV#$ iq (@$|bV٢ x$8 Ac r+9I @Ie?6` <!Y idIFI8` p ?IDviܭFbW8~ oғ!@$9 ݈I&O͎3kZr2U&![N²$kX?ҳ1)6$곟'@X۟1!\G=!w`} u ,5@2Ku.'ȨNh.})Lc*ӜC1JZ͸P ⫱LC5p5aihԀ| f稁ZxP*;?gֵn%1/v};g&yDpUf)bR е.q[#+R,f3Y[*ۛ v!'EDPr+dyp?$)@" "xB-*0MP42Dviis҈03BaU ?H1"K|n&"\!,Ȟ"r6>Bdr&DW`!|=H"A'CgK+N儫|11'@OO_f@.M!ނ`Qs"E2`@+XA쇿` P0A /`'z쎩/XSF3n`A5B''F E `?-rpFHxaB b Aۈbl \VՆ',SlGpG')ױ(87TR^aA1d!`xˌ9&-lR"m@8Xa dA*@%*'##8ThV~  h c@lD%NNB> !kmXIF o$ !: @$#K`b#2,HXE=А U_У8M] @HјS_L޽߀O@9eܞ7M4p?@hANLk?X! d!4T?doJ8X uNNʸiNS !"D%OiKH*D2 rO<}NOWO@O$ZfEW(((U@@8gDA7TD;t@ ə@`O=CCIPv@vZП}@D} B-@6H<]ZPNX hAK-[ADOB g(@qJN-~ X Dllls \[R ,[?UE*q""9N|dR)e(:@,%!ćBDF V >@MXvngEB襨OD`v4dqI:] uLWTd]Ƞ[\ ]\GVd()O@ NXI՘N@YWt)j[GDD?iLT섯P Dv՞] ~*VS;PܼA0bhKEi*֪**Y@M Mel ѱE4@\ D́ViBXC X]tίkHy (|mv%DF?8h% @ RcK^D XN$l((ha-j@@<*ǒ!A r@l } DXpĪώTEȖJqM~֦Xʮ PIA,p0Dq40@I`s$u@GfYNڶ߯=ZQ!Qךy /DW"~B@PnD^ɭ pn<mMZ84HdDPAE`@aq5@i. @ZNl]AIi^TDΉC`iȆET@ EΜ2DlCDa)AJ TBMxEŇL okddV0ӗ0tR} ʕU\ $LISFDG`DL܄7_͗DED!_=AJD WtDD0V gHDȠ]SbD9\%gLQO D -BPbH uRXM#]]d6j˪ f@X*B=QldćIDૢSKDu? ?Q  ` E p INHiO@T E"٥萀@A)Ezd"!iQ:c%| AiujglLNy qK0={Q %> Ȇ̉A7 ;R@@[HtGr@tϜ N,`\ "bPD ?D^p $"MaDP*̸Ƚ|!n2$LUb<PY3|{2Ԁ (ЯqֽV i N! !^C}d3ػg9ĭ'(kǻ?]+QpX3  \nˌ?,cX B<ͣJ*cŁTG]R H^gx )h rYg i%9f;)^0!<=T* J:pZM0 ;)GiZ0ȑ ;9trCo F\d\QFj!#\H `*hV!ρX`T ! 1v pk+P$a(He cb*g@),wqԦ(pEH D:ݚ x`P`HNU;)`s ]]TuHh=\aV=@X4ȓԅ| v4t.5gPG4N b8.%E{t;ꌳL"{hnL@C4B:ROꮯj΄˳JLIѣfs{; za؋]Wd-Jb⋇\6'Z-f-s JKvy$S(MMai C+u #gg~_,^O"& PԷ>P@ EN(=|;*ZZTAFֳQt4sljiQA(P^[WvqH !G A,i ?1? N*ER@6% VMgo39ȯINu~ AT0M14>d'or- f 8-f ')=^lŏ+9M"=Xbt+*`TL-SNA&"i@)"H;b&Șn6 W:5e5Yњִ!'7h2Đ!IbA򴬒MpME|0G`@03garԈ~T9Jֲ LrJ@:g7n*ZM0?;e!z!©_UɎOC]0OoE8%yq5Tڼ\a4T@!<ƀd&&2-U XXN"Q*9IiA ң7 nXX(`;WĕF c d<~/EդDK iܾ)Q>5ͲScl !bgNXk֕|-Ⱥ7m%4 $G]EH1o58ަ0h^V@ !e!V*y\"'bATG,r(%r$B(x5Ԉ"d pXX0*`@t`p?Nxb ![ƅn)OE&E\@Kn P!a^aCw^bH#1(D@D34!0'"cd]p69jAB YX@ n#j<t @3 P! ?"`@ !X&,Lhv!'.RR0r>,0r !a AE8!HHİZ[}3 mIK!&!LA@h ]8k!_ai%t  \ $3 T5@buA!LJXaG< >R`ha`oI82 /[g/ ^EA6& "R'*6.ʍ*4N!` k"ڰE$z"jڶM$b3'Zx~)M'L  #\p,D6<475D@"#&\G(} "q %pҍB6,M0p6'&*D%-b(!v涘?XN7wsRC!H rig3癤.ꄦ oDv?,w+6&(,4w$'<~(v) Jd&k!q!YlLA!WGA &a~^A+xF"Y dGĉJr$IƪhS ln8@^)v 0 ^C e!{^`PzjL#R6p[ g a /ri5(Qn! da PŐ' N` ieS06[DXЁ @ 2ÉAxM(N\e PA3w :bYb fc0N`rf@Qsb""fV`DVYʂ` ax &i[ [vsJv|'t"wzU )O0LHb؄$v2ڒA5E`Q"cQ^e%P(9#Cӫ)hUboXl<"GlPmN cX iGBHy+F89%mɒvTLs'Gg-'Ip!XE DD }"a"-zjaq.Y u*l h658)Lc+Ϲ(;,"*B1=KR$G3Sq@{o.9c%.23UC1Nf+Ls3^iH82Q;δŞ:x:Q D\ѥh5d;<<7 b9s93g!rX32B;ğB=@ϡ;;_b ;&}bu#5)N@֢f2T7А's o5؝=ڥ}کjp[$EuKThz%15 Htw:@! " !$ bZJ"<#ъL7<5LB..APFSzP##Y*G7&qG~= ш z&fPT@1b "A AG j4>`4@е:3C[XA!MTebB 2$ b:S5F!J^.Υa-5і"fõ BRǪ!dĒlJe %4S;>2>aa~ @<4e ca#B  4|D| "xN] ,BʡfaD`~Pbx EYu\^@0… :| ) t=@_ @HF(%5HF35" HRB@+ c!TcBVPDG(~I0!& JkD+U@iPAv3df1vp%".TYhh9Tt@B›njJؘ2@V+[r񃇕'bd*6A?~47xTPsx!>@z '4l{hͶB%zRA4`u AŐgfV!^G zJȢg}pSSQPD?q(PE@{<#4AHGo$tH0$%Oe0v?( =jBC5D$DB/<?uB9{]a̜S [/2TwSDQ!D˵*x.FPE. iA А.d9B4/ :w [zI řBˆ?Gp@! C6Ba.8AEGG$tEH] /HHt00A@]SD׊pA5,@еP?&X9Lf7?] ""4H 3A\?cԡAu@ B(ohє~ Bf0C+ЅC8tl"\B, Mí uzHS3P*tdB 5(@"x^H+@ 5cyiAk)%tRH*qt{JtuBie}Gd}+9TknxhWmڟpH҉o^9l{F(JFG&F~Rit>%7"P^xy \ Zl>_xPҧA _C[Lvp 3KCdŽWP f4%gQ/,OPfb0AQL,V A s# [⁲H`p4*j&$`F5r3U: & S?pKbr p֛{ԠpJ@'xh?R>r!mD[ U,Z d#'Q(E#A*9(/AdZBpV )!յ ZA&!FBtr^*! (f)bZz2Gc-@Zt!!(]AGsH   (z."Մ1?@P@2H CZ%<ڐ t$w\G*JpPԲ!Q\ljDxDd_B &DU!L<-:7^6dC4#ё/skCrǿI,cDux]fV>l'ub!Rv /(+tb>ބւ-Rx* yyJ !]3ԑ*whQ+琮Bԁ҅Z!=Ry\ ݆XW!$w w\t!X귩~^W Dr Gz YKMHG kx? @/!۴$$&u( BV&[8JԲy WXB̀!JQ݂Vf8" #2g QA(̼)d dw/[4`,>sXHLn,0!(&P@I2R (c; 4 7BAOo!#MFޜ$#WC`E<@f:Tfx5^RG~dʐ89!(БG 4 FLxa-R: uR/@ ! E` C83ȰJ$P$!DECnq?`Jڷk!P8"j(D4^Snf!RP1L*A$@Zqk)X@!P++VP?8l;HCGi~+%DDH! p`E`ﴌl50b*P9di9=Ō|2$ +Jccw'kS.Vq UR|L_C$U;*ƽUns, cC@'H+pTY R*o B:]=k(ַ&U1EPG]===Way6z 7)$zT"' L>GCP$Qe\ѡs:x+r.f:t%XbX +!}uуU\T\0 P'd`Grs5V Vw0%1 \& pNPKt&p![ %*HopnreIcL$0"vqeAFbW%zPt + 10ZW Aq)?eGI{p(|o0 w $"'∆xJVMِ ڀCܲ0A:Eą S j qfP2R7-p2 CN V&3 U Q m( 83 jt33,4`/fqH[)ciPSc5Fk0P.2)w0g*"Q`Q ޤe.M ~xAuyENs uPB51 \eY=YG~Xt=BXAXϓSt<`YXghkP=X:Ȕ#X;5 ƄHgs:a>>9a` -6Kb 4A4)tF;d2&-BC(FHlD& I)"74s_bu{ {>)97f[p9b;a 6 s&tITs>3 La_,`JR7PV7L;rU q pzHHBOI-p qRf p DP0O:mhr ъH"Am2V!ͨ0 ixX u$P Bpe`v፮1ΐ xbze13D1< 8T+'5p/ QnO8Q_)Quc6;%`sOTSA0TBǺ*[A  yV8; ;`4q㷨aR`cQa R*S5!_Å$Q<_$kUX;>Yab4Q5;a5,Ʉ"!ۡpbءKИ15ea^ IX_ L4 q\\` | (\p2\@2  q5/c"^ S^Q,K_?qà K."I˻\Z<2ӑ k("frNbOF>cp'+3`JP 0 ebŠFQd[t=08б4&>j8+!ʼnZD9MFkAyk`fNvt6cBg6`[D|BDS7LQ \s'g_ n m$@M>Fo 1 $qj91q   $I1sj _`J (DN-$Hr$rfdp/g EP0X/UuLwp0}!b ^##=)@QU.HfX4rbx 6E˝!w`Ċ{qqn1 Qu AAooBcq?DKq`QX/?APZ(l !N`'$Wjyk'y  )afY2X2Ym Ta*fڹрxD眯2|s!}+y)Z6)W8_5 *IaS jW1l )7"8k>Avq\&M0 TcMþuIՋc|ceQn#?1%U2 ā8" p Pxg{[:| +ZY aLэ L _:m:jp3ZG ZFMܘvȍ $ Apߨ|P $ڃ' 'y"ʥ_\փۓ砕IdH)Wb p0 CqA/P@+I18 X!  dNg19+/Mi t158c^35a2D& 1<@6zE7}.xY@I jy.@Ij t){%M))ZdNJ2)0( \q"15oWUXC;US=+W> P@Icy1!P= <=+X?tաR{ٹei=S}U?s=C>Ѳ~cLb!r54 . ;JB &Z4;>FXqrA{ tE,pZQ 1DaEr 4 9v&ڴ|WZ5lR  \⦏Ž}n a !څ `%A T`_OKt'UQl۸-[Vۿ- @ŎMz]Lm@J+ٽޤհ{!ND$K: 1 rpQ QrmaHNp灰KPRV!֗`K%O5 *!aH@yq Aɡ_ UkUʮäS4"*w ab\Y"yU:|}98GWݬD#XA .dCFX"f$A>$KD g2t$ZQI.ejqEuB$iӆ ^Ŋr]KxAW- ]Υ[ݖfyǑ!/ vXpAx\I$5J&aǙ5o)F #DhptiϩUfkرz%a >(䜁]av@B lyP( c wOMXg`1\B(pt}àz(hKh!4&b '(/2:bb3"1Y,'@ q$#AG:,yF),INx9, 0Ri 2d$Yd( 08.~ , `\IG),盪HqN<<49*8H8x  *!Yq5b# IĆg,(8(D U2s$UZx39IxûR uuG7udxْ/6H^xس>4;PCҠ# HHV蠑%#U- #$#o{ic]I&7NB<(t.&. &J(ꠁW֡ ?^lm;LJ&p]6O9-! Zc-CMВ c@@i h;D2XB䕆9LAA1@FГ B#x@0݁AB>SH`C`]^ЂPD"@3|s>h/}l$_@@B =0җC ^RlG I|gK>%H 2BVSw_`W$<Ki0rA$i@5q]BD2Oi.+ +B(M9Sבl;E̫]d٥E qh?WĴ ,AH+V#7y2@2_d\3b AyUDO!57$H7K7M=`!ryAzjW <[Oݽy ?V@T.HrQ =Ii" f}Vd;N4HtJ-dWaj Rs )=j`IU؅cٚ'Fh=ɨ# I-dAтpX q@xADp+p!#ߢa3ɷ+, v#BȓNqC!r?obIc|D ;GDlP$2<4Ҡ&É{Ҭy ORϵ bCZ ~ I6@q+1g$<ՀnG&~X%"uSսAPD 7IPIF9h"!, ͍Ϣ9OъOH>?1`br7Lj,8H9<A h?) *s> 'hPHCn10,x;"8ԛu!\)@ DX2Yٔ)4C2"xfQB@Y" =$()9}!"#pU8rZ9T:*"j x$# S$ K$Ys@R@psbr3BH91a̹V *]Cw AC,p0]EE.^ٜ žp2!ĀCN=$#t\$]"чG,k* E %B8̈́$:<@0R91.\,BCCCà  12wC3E4e4y#ˀ$On`N( $jj(\[+x ڇ 7y ފͬi\<ެy[07 ] XX" iS,t^蕷 h=b4 9p5f#x4I_Hr9 U+ᤸ͸8! ڸ+ȰV`aaxHQؕʽVb".bp!\[:&Њc` 6b-b-.,b_u b3>c4NckNP"<ݥw\bE '`И|ڄM@=ֈ!s0pQ xKЉ㈒ A(#dQ q(P;>H[(2H~sax=؍ ڹCQ -EKb dmKKAYYqۃH,} ٥0\(mb8VB̊,2Dp@퓚м Z}z*)*ߊ܄Žh#220i499$49,܁(9LK8 Ow9C>TIxWQᓡ`]g^DN#d" lkXL?q#mxA 7ӀɈd̈t< k䙢)) ̰)}l9`Z9 !2@Ț$Q0ӝ yst̍j$Gyrm>_lE!ZV.E+c&/1`&9kNa`ὩظP5߯,i &w,V ~ ~pN'ŀ: ]Ya_X\\0p\ =G Sjl5!,(ׂ"H,1=r f$GC 2xK8U*Op`^=8!$; N vbXH5j0؂4|``}ho։QT M=Y=9f|vHPT ՛WZ*@NOz1ۆAH~nF'f"war;rM"2p*$@ cР <Â:@(cH& ɢ,$h? !S R;y4 F*èIF0MZy2;2,4|"XTQAdRk?g5G <`w/_-pa!aCMVPݼ(|Yfjt:Ղ 2.hBI|;hcN@!T`jȮ{f pꄽ#ޛO 7Ұɿ[toIN2uOE]Ve-]&r8]`jء."(=P^-6XT(cx+!p֣CYG"K u[&d_ shh_cY"aeAiǼ%epɠu♧{g7X}%Y"2:f\2%ft XS>l14QĂ4@AՁ&O$t# 9% 8H%P8Z A'?1ot:QYu1@8DH,9-Rm϶Xt;O("BI"LNI|S'Pn_%?}ᐻI,]8Olq8SA*l2S,&4p"H~W%^m{gw2KNǻ&M?[At7K@ hL Gx &s$EE4.54rj^{$G$BbP BA$A.%bLVso#L@ X8 #4 4;#93Bx,SSAeRA;"1Hݒ @v@ b*x~((@, s'A$|K@yD  tN%g-zͩ ,6J@" l%h85M k)A0Bd-"Y2F&ON) :@  P1ZD-0E@#JrDۀѐ)B8I1B85$hDo9$2@*GXQ)OiOZxșLr$b QK֥j4HK1NC$̰C?$@$<  1/mb"nP21m@抩9;qoJ'Q8ۓ~3&hAZe񆞨b^JD8@1r` 8)">8?I ^LZ?^`'PX)"mۚ7 nq1\=8z@?S-LAA$JZsx h+ 8h@( %M`or`kAl!Jp5 $0BD*D &LbH 6 @$@%T,OhB{ȓ B *jnlؤl- 9B\U0!{+S+fL) ^ɔVM=HCC2' upAPb6QYR0EĹߞÙ!hbF 97ͪqIT3~NY=YCl=U&`@A(Cэx8T(o}!1"Ӈ \B @{,t Gy$ưXX ؄jhSK$T1M<@acGh8lMJQ̆yl2 Z`v0xSiGNDH e 9͇& xt`p`as]@N`V g䄴 @ Ь|IF E[`p a|QC*vt}I8*<":#E#Ep]AI81$z''Aq1I\"(+b,b--b.a,B THCu͍PKO i-̗=E A `I~!I A^X% A L? V66ZQ/ DĤ?8AwuI 썣2D9#B~W K DBbl+@ A$@ 4O, ># XlřП!Ć]*Ql*pĆK m5F9+tTxPIlpזY`aVSpA$Aɜ/@ޖMd NV $PD&HA^,\K_0sOpK+A!Kĝ@.!E<`kNE)/\ؐƯލd%ITAbI@+OE_(ĵJ^<!o /l@`.dS^DQhxvx Rd mדGf0xE loO@c$ (0[m!ri{(!G#D 0[o`3Z=4)g$ 2590 hI8S- }?As2IBበ@_ni&oCbND[tFWtG7 /P*/lGtIIOtKK"Q-LtMMtN"$LM@ܸW-hUGDDDePqID dI|A?|oZLq?0Tt"H=I00j AХ@tK1]BH$WOd(T_L.Tc3c/dR F9i6^|;~NP/ ];h׉N@J X!h (%X`PB ,^AЀIxJ !͙0 7]i܀rDp Dd`R԰?Ԙ~V`"w`;jMn3@趄ÁC,vdM$A>%=9mS، $Rd8DEF ]70͸oq @8@T5)rDBǁAƥ56bm;BCItyI<`P@%D<{f 6M]ЉLdtD/=Lwjb逮yX|PST9ꢆoPr(@z†QHQ\lL)'A<k6^ T𹙀_GIr_ . 234"<{A-i.ɮ DsߧAXnJUprWK@`%wЀ I:;MPwNX@`AA^ <8QHM DWA2ȅ;MEzA4$\5 5q'@dDiI}_@<7$މħr`BmLF0 1$AVks|s9LS@cAx GMdĶ?䁳.HI @( hl5ac{I4=٪ÕI,޼V?tE@ΈD_TL ՎWGK=A+M. P1'V{%˨KlO׈w j DLHچ@eŶR.9ڑ۽-\ ,NHrݯyĿ 4h !(dXP D<Xq! `ŃB 2Š(&tQɅ.a|Ě!ԙNo^y݉ӡ>6ZD)3BlXOҼQ@Ada،.lwh!"`zP/j RNA% ;Lx?1TrB/X^CWIsjԚon BI3`"Rxe.%\znCt'VKzp1 Ĕ9b b^82Р(FXy( zP Uăi;qc[q+"=>(**2"4:ЄZ" !*#Iv0"#ӻh!Qvܴ(yHL2_*J- J \ȄDŽQa`457*T&(3" >eh4gM6,R@C VYZV0C">b] qyT5գz h/} ҝ+y}j{E*^ >a{ G| N-cD3,ΘM>U^Eąffuޙ}68߀G袍>餕^馝 µ-:kx$#: >HzkGbܛb! iB!u8IpXZl(Vlf3( A B5@R 9]`gk܅p^ ڈ;+9D3MXD!3) e 2m|"B.QhWy2o. O&B22PAB }'J X(^ - \uT![!?@ sA\!"qa>pAH? jC)@q=5p4ԑ B\Fq!< 54 4#Yp0A- %Gr #R>ְ K`ÅpI _B& 0h (B;zGIi,͓F4&b&"Pֆ@rRY1He ؎z |&%Gt܌D&~ TY QvIK0"Qh Kd#)Dԁp:D)^NB/ +r TB7@^IH ˔%E(EKoD -)HF#Ѫd!GՁiSb,#BmFeV@Y, )܈B' $APzleX+jN.䩫 Nb;j &]Rb*[29rGK+ 'N5iv̖$ŮW6%OzX$1E3@$'$?0"t e?&HAX< I  D"CCA24DlAQInFa 0#!Fq (A䈁˚[yoL$Aڬ0. R@B!FlTgFqS!@!@:@%[%<Mx|"j &8^" 4H#ѿ85feX8 | a!AS-`!&Vq%`4pA2sl&v,"hXP8)1[@ >7Ia]fǒ@+ޅ Z"ֆPd@,>p@X(q>+Q<OMj$\}EU *`|`ݶe¾P2%ť"(a'k$v5le!6@]8@\xDBc6*Z#8p'ԅU ,x!(&fX &­#e$8" `EmVt I% 'ki bԫ&>K- _2-7-4&.^ĭΰd(iN/!aPbHP 7g!!POfc^+Rx-ȋcf g6⻄⃒l~rZK K!@L eڣa3(E PDEZ:sD8ɒA $ (/¥, :8<@>_'2bpѠ13#F+*~vTc.dɛ*!f3Ìm-Θ9rÆb;d.#L7R%nU r4fAVB+>)'BXXՄZK-YB& }O xf`BdT\S0]^c@l,_sј2bV @*bb#lMG>6E4rqD3aˏxE!r %)0 Dfp"*-"g "',\cT1E%aW2MHEH*n2Rp!@~1x$7كLeop:+ /uhh)T `]~ V@4yK>@3 Y$;T)6X)ְWR!y(;UG4#q@!1 Vp[V(,ny\q ‹X`HMl6"=ݰ -r.`t"@ a03$=6$D(:s͚HH̙H:׌9/sHFmMK@g f]^1b('mb )"`\h9Jt#`FcBj`E *Ye [I(:*tF$T!\M BO0E b3@\Z`"츑j! +˚?ߍAU\ ~c ǔfTStC-T4MMA*H`.e OkRWcY&Y vJ07RH!Q&r<"OKX%zke@B>L@5#wuM bH pn Ҥ(&|7 S UbuJ غ&ED# rLbKZ 0$P$AFT[ $]'2(6h/xZAH l2)j`a<)p Z B@ `%:3 i""Ć5!P)"fGUaD `sɳ |Bg":tT= ?pE:,Bh ɿs4 k\H7WJPH0&׉cF p 2 |ю( 퓜&sd(roGF" D"C"E&O )* Fn A#j`W5r7DƆP_E$V!uc`\, 4D`0EHև Z#&Faw!9s>"\(ZD4rDh)W$76AxEe #ZSu?=zI_+N@3eJ9ECb9egNp"TWJ`eW=j]$$~ Q;yXS;eW9 S0nN>t !_`w ZMEa.6XI&-ݵ 6d ŵ*Ng!rUЬNrNE,mW?ZJ4-a(pc?sQM d;  O.XRCTBGA3V  )P-"XN@h A<)35` J5Ԗt  J T4/CMKBw3Lv?@g!l+/)k*LVF9UUsZUFDU_=J)DCR G5r * 亇.2dN?X:}"<7 0bTo|D¼9Yڹe/mm }hq0tC&@ښ4H gh 0A_td0d*_Ľ?y) |.@YhB˟xXmIBL`p;ߔ# ȓ)RXb'Bj|V @1kUؼ0J ӑk4ƝHB>} `t ꏈL"2>8E /^^+|IRsC`6Ce].'XDɆy9FEp#Edf:scIje4'-vњ 8IrT|0[zHPZ|)t0O>N- Q$^aGW9q@xZ!s{qt!aP2L> M1ɜG''͉GA|e.r5'HpŽR8]tĂ!D0=B0MI @ܠ A`N(-QNQAU"deKL !rB/CUCa!yk$B 8DpLDC HE1BW*%ҀK+s@a HB!1Bp}ApƑ-'VXYC2g ZC愸 ?.7#0MPXZPM D_"8RN t! xCDu *ELDQ2as.Leo"dX[\0ʆEc,*̌#C7 SR C݉a`.ܛ _^ލՠz`ԎV2|c#GC4֨A^`?0 `"( 6.N3+@gX+틥MYRAR4 Lw g= J!P)!9dl FtD*ڂ̳N-% TWHΉpADdDv[UM@ø@`ԋJd \D|(>z @W !m"ppUvKLs$!g4f鏩Tx]THd('dcD ,sXrhS2g8L3n`8WkA*߆ d(~G|4rmhղhq!q#a(7VjD-NDz.PMb"Jch3*j ScRF}h'<Ѐk#$8 0 u@نK̥. EP% d!RdSvt9P M SPo\w Q]%wJ pr}&d@V_A gJMa4ST<38 M@'Z1"\sQ!*I IV "P!0 " ux81]' )PX ` ЁJp "#EPD%A(`rA:фݔMhmX$:A=D#BzT#TY(8bFM"9]'tDvBMMX%"%T{wU8`r{`B%H)H)8&((B)є}& vg1G$})KQ&SU*WwGl]Q=OV9!,#%J`"? -%yB"fiS:Ra9Ac9qY9u-e,u 11R-XW0{0C0@_ 0X 0RH'Ks]Wvc`iGsz4{p#͒Ҥ^y0w:],R:d "yU7g5 }7T+b_YbU_):':nH:Z:SkyA|5|* z !1<3;R<[<Q;0 `~8~~)i6ǐTAXR)C$%"sr>+#l "M`rdwmaQEAB99tC1t9A?aUd7B"BRB=RGE@>-BL54??@m2CRR;2TtpaD8=Wc8%?CQTt|􋲴F FDJHC#FR:T:N$ITL啥:ѥ q5DIYlڦnDKK\QFLxzaB'LLk 0E0h'Eʨک)dCG/uMPvPiiP`S޵s`!g N u#,RQhGPP>0S"+G0!?SDZqSY,Au?B90>8Aݴ*,tt*YEN1`\0VSfh5]a WSqYWW0Wvx*  8 TjePP !  R0sF ^`_0|a2{uc`)c/P0T=K3e+6r]ҥ&!C\~U@CyTqW9r)P kdy>y'&;f%:[ 6W& 89a:p2_p@'v.r[d!hQSQ< ?@EG dm ŗչ~ Q 1 a~!qP  ѽ)A6q Qk&52/Mg(m6i%@nM2fj; Q]Bu=ƅ(wB"`B:vl5Bߓg|fAhggC5'ht#L"l2B=2m.Sgwl&E%n&=Snc"F& D>%"bg 1"!jmSQj@#Dդ(Lvl]**F";*793+<2E>!?o*"?WO&?>W,X [)Lǧ 5x]6G'4uwwxwyvh_q̼,r Y!1c4Sw!+7K^]Ֆ3"Yp`91agG #{bvh7\Cv ӱ_Uu\ hMѧ.aLB},K~ɛ;/ 0@Ä65Na!3:`mO@*B'@:7"8b!3H*>3+>Jp"rwM3LGC\%aFG)vC=Y4@tW!9H"C .JlSyMB" % X=`1b[CNeXA[0"Zᑵ!@cm ;l.93h9q(CP 1 B8ln8 x<WDpw"9f4>)UjI3piL|bGp #C"ļ{v |B48"\. Z):Pi:AZP)Ϧ99a` ةBBI'Ϟ,23?G2^B$)Os ꃠtV[puB.a.L)ADH'P!&jQCJ" %`9?$`/A3dϫ]HHjJߊ}ddEVGtu@uuox$oMWjt; a{ Ht$F?Le.̠tR5 tq ?zBFgT(??3`(?EP:aahSp'=0P $SqRIӓ_o$|8PC+}lY_J%U0Ic4 yiϡJJh ,>2Q! *KG;pda #a9,̥fF $t$dOX+,6/y`4ˡMF?d>\m?^tBAwD"qma)£)' )gݿ vqׂ$mոm>3:r;Ƞq.!+ : : !K&j"AMJ@iD8`)d ;Rp@A:PFpEz㕂E&Eq;a ^p{ U.G5֬͐S(xT` )T*mD ʚx *;( 8/ BW XsT[3жF ^nHEVk6K 52(x@MYUv έ )Buk* >MT!Uw$MhՃV l@@l J] 06J\ R .@p1, Y+Ⱥ:p?`I+ՠm-}ך=HxmzPH^*]B@h k:5ͩj*mH- : A"[)ഽaλ&ܳ@v1LL0s?_ʙ:[kȘY\^EF S.~#I!xY&eMh8?@rj`qR`"`> $(_w_AASա } LBZt P)-Q`Ԋ%G-P58dc|H@@<*hoRoXsPM0D0W&*gJjZCU$CjMB$>4Glαhc]ކt ?n1 0$kM`G1M%J2h @."308&DYH?,R 4$$K6lAkv\;I8x]94 IPD+;3쒍@ Q B$aI@(Q%8D D   q]DIՋG0>D#n$qq.4D NȌrL-SZoEYB.kU 5Q@1@ LY*ӒnW;LMSцR%a+m.Q}RʀA;U21T]h8ZpaI)CRxNXzmBՔ;m G!t\A NSXy$GFWeβ(eV9Օi@tErKS$Y?~K0l P$ @l NT0殔)bqa8W$MHu! uE1b(DuhJ2ש|hR&wGQ2p2#T;-ҽp)RϨ {H 4q0x Qm? X# ё4 (}LT~P$PDMV a8 Ac ?aYD\&1 `c,q@lE  9[ 'DrhZQ[P6]mpV?/U"@r K2i2 ^P[RD+ 2{X\! @,@9 &@l(@&,urQ H&JJ9D812;vђ|WFJ&vwmۄnw3GFʜ,[5VGxp7@^!i4@1fR'):R`612~r/y̗G#)/'jy!btLJet7O'({R9')UQw_{>vt"18 2i\ E|.GHĀPr,4~?| "9A`+ ,ؘP(9Iϻ1E?ŒjOsn A>1dt 4ȁlF),M!:l" n> CA@ppS|WNBR $/D_ 0hB 誉bg5㲡 !r 8~Psd8h(瘄X2A`2xA#8x(.1 ႆ昏 x(IPfʆ`Lh` 9 a,<#0C~  9 #a7e <iȈUЊ*(I*C >4l$@? 栎(+-/0y1)3I5 ^6Kp?c*pR-3G Fla1Їs:Y速 Bb(>HwRyo8 SI b Ysi/Y(nHIB{Y)@ An<6)I|{93hf;ccDŽhK9i|:\ӨE Ww-C4LpıX4jKIa۳k臁0g[#jP3 4#ЃpQ<4$ #7'+r#3yΡ  <Û,*OCKj8Ht $PO  + -IErZ2gx J1"" 'J0,-+*"Ʀ %LLojحa&$,¤,(Lj僤=_RZ#֋9; +=>!3 68 (}R 4u >`=,#$eQDЀ-i&k¦Kn2Th@'u.j9#P%ˠ pbHQ5Q {C8M(c+|˺q |aI)S,_e}H$7# **77ּ/hj٤zY%ҔuHr%{itW-++@ '".,oU Q 1LS",l@h҄Ŕ ,=XmU4 M8%~yS)٣2 ;"57j& KD@檤 .'ƀΨ@2Tm;`l@ Rڴbɛ{܃iM ɠ  Mu 4 52盿L(_f! ?s7W̐bƜK0`ͱT0̻~9}c `W0RoO(OX˞܄ƏD9u[@ЈX]T^ِAL˾v=hd$P >(.PW@jPY1B1r@Ё0偸33^F J،魢,CBa 4crHPu& #p=z~ -P-6{K#;MߚRf,ҚRP$3Kd )#)́TaM`ف;=6jzң؄5`X-#x';DfB@:+Q&?Q6׽x."pT8J BQQ# #S w2_A wEXofvR@Ouy%`AumԹAFA, @n@G&@D'[ Rv?HZ@ւK&d@B6j T*G +FXڰ*,:,!{Z+ll`e[׶i-Ƃ%miT-qځK^k#C//;;@$ZP YC2P 5O_:BiӪW E|2)" A$L(9L1NÇ:П)٪@-BO?Kꑌ TLE ЄF@G/9!Ϳ_ H1@$A1R q]XDr b!( fيa!_i3jBC")tj (r}L9 kjxAa wA7`- >d8H\02 P dX(Bʈ1@ |i+Pe2c+CtH)vPQqP}l##iH(C'9@( . qdpܐ!G/r!r@38<@ <0"7PXNZD47J&D% xA@ <# 0P=+c5xG8HlC<9 ~g 4A5$Bf+{@DT@+ARH M|@kU\k"yl:#$7<$d.N8f"S(i5\)_>LHS4-v 8JLJ^'tM9&@@H6|Q́U\ h (hECT@H@@pkNȆD8?\AN$h*5G,BZ B?`wJJ\hf(Z.nhiA.$ eP eQ|&,??,hxhMFoPJh=Hh=mb &?B93|!DS0p !D@%((@,B ďL@ (v0*Aea # D-94 tEA J ? 1-RU6? l,l0?}89 kb0j Sb@P?.S;A:|+@8B0JkB%a?+k  .@@*R8TX(F"عeAQD Y`T,2C8@8 ,C 44tCS`,"9FF8 u*7@n|UTg!VD@@U,VV"USv94nBH@\)gR ӲU\;l<-b@؉Um\T]1hޢGU*BhR@UJUlۼO%)m%eREJ 䎇ymfUo.)]jiltIiش|Sޮy9 (#-tYTmT@!-GU EG%p_{@e ʇEvX&XJKhDTbKp)$8~װ4@ vu՞M AX0l \;ƲBz$ZcCY f\L^I`?AtGWPS`56(F.ha99)+0P0/hD/ b0J0F'A?9#t8B.Z"b7q{}ӭ(BZx1| ?q?QQօy@HAM\?>|E"`8@_*#"S`^ :j)1&`a @R `(ůmzXΊAP@ٲ@52LN@$ϩʯ!ԁިF2 D&EݐQ@`+W3_3<,tO!B$AHMJ3 @@RI(3%%TRD2ģC7C[R]z+,0_utBtK9%۲!PȅՁ34p@dUbhl8ʑx[T"S.ߵT3Xf<@䱵Dx5m&THFMب8H_^G؀KoʌlQ" T Q[(`ќr'-mC$ğl` C4sAܜ$ Ҝ5c@ D )8sKv3<Â=vP(3oAkwuwAv/O@4[ $Ѡ(T4XHELX,I, IcS(E }K FAH|P F@ =,81Z019@jD'X`f A@IcXbҧ@4p!)0"@tr}0y*@PI>13B b(:ZI7"GbX@G{ 0XU'G$⊜aħ>uC; r^>!ȫ1_:r#/@L*XSsS)#0ñf/ex]$@/=Hc_k" VEFYa@a` Ho- ~eCʅᯓ)F\Ȯocd Q FQ2j6ޟO.O.DJXP6ElX /Xլu)D6,ed"Z <>'/>7?>GO>W_>go>w>臾>闾>ꧾ>뷾>Ǿ>׾>>>??'/?7??GO?W_?go?w?????ǿ?׿???@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;lYgѦUm[oƕ;n]wջo_xqǑ'WysϡG>zuױg׾{w?|yѧW}{Ǘ?~}׿ P ̏LPAz nlp;!P810L 7CC184;T:t:#| qQemQ{GzxmԄ'\r k+,ҡ.a;yD4/,,"s 8M٦Op~O?F=9SH L&RHkOA (Ҿ2uT5@W?s#I} ѽXeV!*u?_S ѩMqiW3~fuYghhZ])Yem q-|>\mo^{Mnٷ_~ 8uwF\͎b=XVv\bR=F vdMNY\QW4d2v9U ;V!1z9î|&hꭟ@#8ňh6-jYn֮;L}lcSq~7))g~S%GnrN/3#'3Sht+3ufm byݱ'\xҔOCC>! ! ,*f H (韈u៙K̩ 7uɃ,@PF, 0@l# =rLhZ !@Cz` n}GLQz7^sl~9@lJ184jAbFOkAx6wnWE4jP<LB`9ȸ@f">Pv?6h K1ߋ":bE˜ӫ_=EG'w}9@dEhe%P 4@ B6|O  !a.`ak`GVDP?-D"YE@TU Q?V!PIrp GP?8eDrA ?8Z'?Ҩp*FfP)IY9P1@)?0/sGAݧ!Ë@C?#8- A[15%E.ԾSqSAL&Y E^ ,>o?TOeĖ(DO0׾fGh4g$$8Щ+H6$CAXW,P~p52cC8JcR7hA@8B(C,\k FNl .@H.qea3aȐ0bcrD.rWX%FnDQ@U=j~YY#h# @?Fȱ>˸Ǩ8G n8A2V ۅl$S% FQ'd|3$`B9$10 3ڳ$z q5 f@t7"8D'`%A$!dc1X$ i%|P1>Z D*?Y'(%MgDB6P ¥(8!iR 0J0D')h,.,h$QT'G1#h Kb!HFuZ!?LO,DJY*9]Dt+y';YDIlcU-HX .?1+a"l2GZ &^111М1_T5MZl@P2Uɲy&@ Jvwl7p%"et'UpkW iMR A9ʲ>7Y0̂@[fAI lAjQTxf%b RI4QP$-nuKW (Gpʛ`I8FR2!-΁{:o|D > _(q2>FTQ5I2 Oo8m"~E@D ZEE@=X ,P =\O/$ \lqH%{xH}:Y19K@(ݨ8Gmȑ1<| q@T?#bBg96N> 1Or5@ow]WP@5$ dXG meRYvCp 1=X0u,yA=94QxϺp`ar=@8=%L@$m{S2(e뜰O]ڟ `B*teKr|嫿 $s/on-7VmnqnhPpsLĉM:x~'ԧN[s:Ȩ2_c:j@Y:eGJuBTIih~vuϻDd"Z|DaDWew9jcAHDpt\+қ7ykc&=;] PƄl%6y@\OwOOOȟ-:Ƥ hʬD'S\ .LQS >M*yb5 7hͦJE IKP1}!':W·c>~J'"baS0j1Mh 2Q  , =10 D, r `uP.B ّ ALu?d %Q$ %\ V0C>fj@q9a!RT};G"1#5?8RlF^0AlQ4d`PP 'w 6 Ix3$#:&1%R3`}C(,r*BDP(A($H$qHr-_8jl GAqh/`+s4)g qF!0o`+ rӢ-o 0 0 `s4 ڐwˇ&!O^c@9cN0,r0V,9YbR8g24C22"3OU>+S5E491\/䦓sHxHHyѐ'\C(!3n00V)zc27#7/:I/1Uuelx!3Ŷ6Qّ1-u# p962e:s:@U #Y,GjPo,w6yK;F:I:NՋynT;*tr/tqE't<雠a<@@k;#of)% ?Re ^7u @. |m+b@icj)A$y8&HHBY4>s@Չ   CqwmCV\ D%X)&H* opw`i[ף1@jxHXPE, 0* 'p` }gBe!gxMS9`(4%TJd1Ked4^LE&]UdTt1Jp aQmBQA Z$P OfhP7r8AMpPJljMS Pᨃ!W Tک[00oQJB(w)  R RUB% ₦t{a:6rxuEDu+!=Xmi%Hzbu2SYtSEw514X ZsR5aV&U0|bYvYG3aU zuk. 'c,a_PY|р}O~J\Z]S@jupua*Q[[Q'1mW2!wu!a*qҵQRARaSeF0_sɺ[s ?날q};p vznci|6! =c@4dY" H@B,6P[{iǠKi>g+QrGA(1A"JlaBj bzip9:dg?ڸr*[wgq+xBGi 0P`B[9je5aa B<ɕwQ )p2/PP cW!2Qt<˴\\ `(˵\w1gyqql1Z"`y;;E'ý|؜+lr3GRx(W7irP@sņxfrB02 ,:ڍ8f#(P)JQ~hQ HrtGA (8U7<-\,Ҁ,|V'$0<6A.P8u i]>0MZTQ49 "@D2 ]+:o}RTK9ZQ*9:[s*KuSNpbD 8a `$"ȁ1d s4$򥣒KAP; s`4:HD x&`Ҏ?D(5?W>H9(|A@m\mIhv륄[ 2]ءh Ti9.JV? chN"h,Ј 61@K@GdAi!8Q *(7 43*j"`!:js'7/[Z>BwuL;4`vAQ>B00*OB"]4h`>"#(>8  ZS (r H A!((` Hh̅ %j)@trB46 ΎJS4-2A˂tɭ^N  J,ȳ9Z5! H]HP3T 2A(<س$(%([xrH zUH)}X@Kmۤ p4IJRpzm݂D$4K7Meʗ~_$ 2XԎ-]:*z]\/8c kT`IucMV6!Ɲ혱$fԹ;ye&h!L BIBaZ!3:뤪Na{VHfmVhn !XDG!X⦭ՅnjB` !!ZHAM?Mq@i@aQ(8<؅OqjdYQ&EA!U'dDKh ؎yv[0w mD"*!B`@ ɈdFBsr2NQ8Zml@8Q<qp$!0?08` ~p&cI_%{rc6#C\s+ezP<5>9|T<4ȅ^:kFD졩u2"c R`z }U]T1l e7֝+B7AG8"ejo2xJ+`;Ȉ7Ѐ0/6'9E YfM >A CW9~1+j) h n[ %D܀:Oas ((,$*4"76{x B/xTkX2QuhpSc9S2@(?Ppcp00RI@"UUtYbE2[D ^dWDjāRSL$_@ 8JDDTdXj(y>%?vndrL&,9tl(q*{yz{G1`!(О@ H;8)8ip !0Q@BHa7"i`TɁĄI t ⁈")8āx9 :3 x Ih2ʅH d J 1P8 ,SB)pGd8IK ʓ*!_Y*;@ 8=##ڃ1jEj` ^@^hn 4J*ڢ%T"H$(}}+%"㙢OP"%`ڊ89)́ 5YL;.H$(a#H*"\2|>2 1BX6i@ lH1ACpɔ (q&dVx(n=;pq M_>8W‘/溻# IpY0$6x/ f(焈+/IBQ?aBPA9:p*M!R<=@@ RٿDEZ{fDypȿgO!h(+C0I?Ѐ"1NPf~ T5V2.cc5XQFx>,U1`2ac 2\5`9`8߭6ђuxTS8LjsHLK.Ty^VӑPDT^HN44)VyaƜ:w칳H$8%Hs_0:lt`TVs q?8JPN[.|2_* ADK7YvaXX`EH0D|_wr*ׁ Hu57YȎ Sk\w/$Yx#Y|j0SIOtEW!"<Bg }<*ڷqhR?i0}J AAu@ tat<99%^8Ї u].BAt B$HёAu ^"0A~`BSS!?1ʇ%NOD]&DHyY$DYrх+JfOMOfAW<)P+&>ITFD(B: b4NfVNoeTWHc赐`eS4Iz P9a2?ѶguBX] RmA[]5Ge@f?-1HBMD{ yzD,"k`NMmJ 8 &*I/.CUE~.GNeT|]"@182 q'-,j#'tO 1D,?*3L ,4#BIΨ| DO C ҡDoN SCmNYT@1802@Fw0]8g7YZBy7␖q]F&0ӎuPVD;S?PYWL™"Kj %t;|&x@Pt;N8 1-~CGAdB!yNkM%]9Zl&m{9O5?hO | ߦ@()ғ*h =L'a’%h P0  (;O&E6nrL <~X06:Axz-r^bąT#8 N du#vlCQB M8h8#([kubҡJm|%,c)YҲe,W RնD`8xADZȢ"!I Y(dDzD(iFaȫ` 0"hF )4ʌdǞ!n2 TW V̀Ar9h+ [qVlA[9h@1+C44L2C< BL%!ZH t (zHpʉP13+  1,+:"8䊤!v y5RBHZ8j㎍ j jC'!hDv}cl7'f,S{ȹ= $ |#4!6:hây2>o5;C nFSc 1_b(^U_OȆrDw2 y]AD gN0`3+fDDK2Dy;ri&Hq1Qd(ۻ(8з} -NV?$oC\b Mx#CtD`aEbM&e&HEv-@@#" :K*Q9ALhDGXF07;b0D0W3@;=-8 5*#h ? A GrIkeP0JxZ'%1e'^70E֣"28$A]B2u} xܬ 2/:WFv:f8Ҫ#nڙ"ʜ| Ԕ`NMXdɠ@DXeɒ ZR() LIwȌdJCHS bDP<@Ltڝ]SAǡ0T\ȴȶ|~Ъ96H@E+Iډ@}AD]F] E%U20SFqS!E?HBT0 eޫ`aA'tF'j&VH^`M9t~'xx'y'@ 4O,w&A@ PZF,}lp DQF1t3,D4 E%TFA,?L t@0YAGs DTPTBEABĈRV '2 Sh( ?1B-DqXȫZt|@XAN@A"EABD8nUN0 9DLV=:\iF\֠2Bdt 3/8ˆĄn)DٜGLqA*D"!8BADp-,ӊb B x`A( TEȌY|fRw9>"Jĉ.Dg=@Vj:WMK|YdI)ҺeD@uٷvTGq$Ǝeh$INI"ȩUn`DUw(}Rȓ+gH"E(tElE@0I"E@&$yOMulRNşeI%=*.[']dziR<,Y2[,(cN"=,̏~CfCtH*NFG. 0BԴ(Bd: ',M9~LA8*Ȝ8zrJMbBFF.mDxDɑA䓙?d&f= @C\>P՗" NKI9ءMM}X)"AAP?-T~Yt&.Zt]4 {*K-iĂE4<SN4^N0^EHaTbH*bvĞ<%ǒU'RVh@NpFnDDzУXu?\Ab v vH zJasB.f} ЄW%?N aiVА@If$RԡHG 2yPP!kG =2'# kbN!VH}MlYI`,dD &U,s%,T@R8Ya@D@iY*x0ORq;xhKOqFI D+uZ 8O9NuLuZOwNOO0} -$34=;P+BD񦟔-eij)dz؜" i1E9j?BHMf8zc5D@4dD,Z?`ŦbVP󮚀<9uk\"STJFnNPM"CPothiEK3A If?G6nBs{C E-p,D6t:80E;/;@J/U0$P-8ߐ I7Ch@˕ ](ʼn%A2)$;'C8ؔjUW (2oB)4o"T(LCZw$Ԧ_F`*K~[>(/x)ߟR3FKq~]i+Ĝ@A',j/-,0ƏSL~?W0ragz:B9]g\{{^!Y!o +jjXI#~d%%x  bv{RငHtydʮɮnq^ۮu)+a!JLQ2!JYoa]io"d`,:6Iq] &I"!i#+@j bEkY; kX@넶OKHNV) $[SҔJHaPpw/OaB`)U# O$#0l c.fW )dӁ B 2$ !hE 4 [WJÕ#rGPa8Fyc 6y0 чb)xax!P-  FF&' Ht@O & < 2T)BpP,G\X dHTHA*x3 t2y,$ ༟At.Y@**aLB⹙ Pٲ>R<@!(eIbIT@ \B"P7R1,d< ?> b, k{4PdKqJ"-$©ˮZUAqLj%t26sJ; (~aRD w(bf4& xɎH B,`WIgX!Z?ثHYb UXIʺ:@#rb6gJ",U2F3$SKAI"Q!@T(ĉE%$`M.(c \*A£"G?xlM/@BK%'h4-The~ZGIK foo6!bH!ҴG7htNBEV2 %X@‘MXp@oO/BE$[IJBtRDC Bl'i A@aJFƓba _NF2;h)fJ5MZeu* S sQLI$un.Xlܡe,qVBvdA4|uhYMI4H­e"DÑ 1C A F4!g& Eۋ2bl(SB4j.vCλ/ :nfn) `h@ZU= " 0 "␺de'Uc*uV50t x.H+h'\|BhClA2!Ґ!M;ā<!|U*Md sIiXrREdىŐX#O2_ bMNN+Y4GaؼJR!X%Ǻ+-$/OytJȺJ򮗨3Z~ړ!:Ddm87 ]"C6keP!l-cZz,7#Sx"0)#h<"l+C 8c*#T[rllo崣J!`1/:*/b 4@H\ܣJ2FG/lB:/0)`@Bȏ'FWsX~1+# %EPo'Q܎ HwŽ!PI 3@3iPbmD$vA\F(p/ޫ b'X+hD [ $'@rV+ s,4 /dB!s^bJ"O$DD)imz,!b Ͷ D2V`Ǝ- ! %@*>"!J$.Jc@`,DB,"R˾_B˚lR#ʡ*hT.$ƌ,!˔@_+U. -](6#f)T?FB$HB&V$` H Zj!8$6>,r! ^7)ڬ)lRQhnڰ8$c =&<&0ڞ5eB565\B2b*l50^T*:*fɒ͒bo3sZضc;^$6nC/l*<2Frc%IficBFcG: s"Ě"Mw ] eD$!8)C-pm"%'^y p$P*>cbide?vVgch'[)m_#iJiӂC4ɠM#ª""Ņ!dk1N1T@BOQYBIK.$ !΃'j()9Sb"5RDT!68b{p%t!&r$m@Z"X #$[σ7YQ .BTkT`܊5@rٴ5/]`Bӌ5^b5l8H%'®:&HCpT%Dd*ew$&L*"WPAW3ftW"IeӀA"դOFfA ^6e%掏K_jWfqmEFXkJJMwiǏ"0 ecA"_#eC#b**TSS4޸$T=TVG<:G>ݖA=>+#>ZJBBḗ=yKo 3&%;JuF#C?4n\DL|bT:p[V9ETeTmϮ> f**0o/GM=+CJcיx­^" cof݁QAjL ^+AE'¨$x+!TE[E\.!DKH"eYwa$FW%Kj!RDekPkܦO?t<;^ZXl!D@gVeu,D2tIr!Vnn K7RqAAWP'lB6$W0E@pZo[J\@]{˩^#,7C^w_vj&`$xS"zlΧA zc<"A]} k >"00B (|1ĉ89O?.!"B"ǿkUxaά9P&Nb-"IBT$$@ O?C` 6,D -Ba~mBzBdމ%k$Vi;\d9xo^ 3h[T`MW d|u- cEv샋X-^֯[- 8!aW#y=^t,1@ۦggPŵ#Dwbzs{H;)P1l<6~uv LB3/XO dx,5^dT&ѤO*08i%P* p@ 䟅|ل#\W t 4CW?_ Plu^B6 !J9|Ple! dM=GP=z0HqQC*;B( : tYK+eEu=AB @Q]w:1TЀ(?n@`?zȀ@B4 P#?NdB5O D" , S??Մ3ṡ"OeU)"n D-+:1hBUdp`W(<ZK ]ȱHDsĨ+6UC&kߡ mCE|u,@=f8&8lͦ %p]`-CJceC e R`B&dA ` &`EsBkq=hu!8zl?$ 1rA@-OВ $YPEu5cIhxAP?\;B2i_1Rע`{yx3x6Wo{<ڃXZvZ}ffc "&L_ yWD9-M% oЈtO 1A pI#h@aa$0!z!DݨGT8@bDr ,Bpa\*ؘu);gd2@t -aQ0g .^|5}i,!]0xZG HJr%//L7s=d E怐PGB#9|0UKD& tTS+D@A~z29fXBJ` svP:Ě=BIP$dzd0IJqR @QAG>0D@mZ [6PmNȦ5!*'|2_ȍQO"q&2Vp qYWoXMXapT ƒ R'^1`TI<Áˤ5*hYqPOyu ~$HeЀZAYr׬BV`"Sd/1jP9%zI08H, +QD`H?%/&u PR"ڨkrG*9Ne؃]1~ sQh5zwga (2U>$U08ۄřcHH`j吚@rs@T&@Jlj~Hq&y1A- B3YvI@(@Z*.,eS`T|̀/<H$<cSBI(l%YĖ lLk(oi7E3։hFHQ6LVh b "n9$j fRxO2T:"LϒD+i Ќh1sֆE06h/̤GM߲پqD p!B$A{^ﲗ1B(:L´՝$abZ]IĻ:@ [Nȁj\v-MBjbw$ 8BH0=\B Κ 9 #S &%PڒPdBF0$CC--)1@9GD nM $%: BP6I!A1>s=f2%Vhr!1[fM(K1zkHgdN1 mTve7xRU{adhPVQ}Vy;ivfj51(aS&ѵ @"QDЯk .ci#60:L+_a(;s>ѕlME̶aS"kX 8O5U#w ,0* A q $ @0. !$ (*6T.ZO5'7ZPmUP+UYczE'n/u BSZu<]uqU|^"a0n% 5.aS+m7i *@s7Ip pD#?11AB0BGs h# "@{$@ W0Gw,u;&\%A(XR}t Q}lOշp*Y:xO{{DPuɦ΃Q>{#V|Jܽ+ᔹkHG}ּ́󁱩<[RXHKfBF3bl<##4+d_1sc;<Ӳ4F=Amp(u -l@5q#Z-%#q֘wy2!\cSlsQ=™9Flė{H)%\<ٵw:s|P}H X + o86x .<"v&)2lC,@ -%|š 5qѐ5\==OàD@|U;k'l oyž1Yw QS+v<.48*)+ XHݚ8|!)ms;۔U-XlH՗)S:X[6e1m7ӛfAAcSil]II4HG'ᲱoJ;Qs&6־X$.Ãf<ze=d^=pBtzޱ>=6xIy>-S@&?pu=mD EҟBI*-`#M䍞QfB* 駎D>Ɛ+)F >=m뻎y{cgͼnǎɎmhQ:m:☃Ob">IiJ/ W=ޢ ɎzDbm.~@0OC_qxj{:J4鎋qQdINI:S$oa8q C$" '8Fy5.}miU 7%u gUr. ,_eZ -t-v^`TEuj:WnY]U3bS#F`Z[' T`z Wܳkk Zwꅫ/" PoPpR |Ū+. Z q_qVD"$( e#ֹbХ'dqdm{LaYa&f!Eg"q`=!18~dy˜?A9vbyBd5 P@~@A80@!x# )F:lK $P`&B(*c T8Xp G81bҁF~ F U0|0bC d΁uLOnG&= $)&\8MÉ/n Č%[0aRE3J9!(ti 3kr8q#ꊶq ݼ"¢L#/hq Ӹ $ȫ]{_Da?w@bMn A(rp&c0' )ܪ"b ҀУRl9k Q=QE4")c#hA(7{RrR!((;rK)e,GNZ(RdINsj81HHHមC$ bQ 6; A$"T b}%[kg$Cs 'a:b&ghhZmq  "D(&ugpS³@H"X]1khf?HsA-$ T Q7`|#Ppxb -L]x&^}+%kQ!k!TЁTa易譙v>Dh{`߄HcbÌ6n lgDh#f[:6MӮ\E2, i 3Fфsmꨩ;nxMVj4O5%aL133,+>KYw^ x ! !+ ˾Ne+HJ *!%35!,uFԘ!z5y`Q|aDUH b愠wRĩ!8FEX("h(^5`?TD@12GȆT&"Nm?Y^@2b-"H #!nR0(! 8@%;QulQIRCL!G` !<>!$ XL*1'Q-US8;Hf@g󰆨 NI"K @9!2Q0 o9KіyQ0$@*_8<àRTA%&ʈh '\# JJI "΁A /*H  q4LSy%ODȔ#ExTP:YimXaaGI a9 @A0TB‰JəpF?̉z .T ‚Aiǂ) Kx 3P14M( "+Kc !*@竈Mh;))&Pㄈl/!kA:#8j(Ccts| 0S: kc 'I%p ¸Ot5G0&O]%U lB 8 ^[&rQ ;q¤m&P'Y mۀ΁ҿHI(P #j;+p#+l V8lLX#P+0 *ȂAЁneMIÌ ҃p#(ᔃ80'QxXx˖E1:2P+Pp0P((sCō̔8'K3#ہ[ yp3U[1S+6+(Qk2iZ[]ۃ@uX6bH[`x? 1g*k،j{m1` 1h6 ۸|K Xȁ}㛟9G :7X87ќ>}C h sH`:1JC1 وw;CE yӹaS8 6.K:)Xb_ zS ѐ`W+6HЀk$v nr:QHHQle ;4SFQѻ3cJ&5V RмC&z 廎U~+kML2Mс-*LM Y8Ӭ8G(YU\W[($6QhPr @SL$S$sբЁ,`NtYT[akV@ T@ ,-Y] gpbxPDpQ8_ Rg(=ѿY8=)AbPÁ4&GDB=؄K@Pz Bɢ9۠iIGԟKizoZWa+ZP?lE=ajaj@ ɉZ蘁8CXġd M$&4vuuDl” ЄXB@ PK Dжi$터A jǼd,9԰kb*w,rsR(M9S:xm\h ȩh'm$ȈF3 S8  i ojn=^FKd6fJXP6/t[IC,\h ڡa XEɅJJp˱Ƞ>DL9 />FKPdd N'{7 ͆>ĨflUl&RnS g0g Ҽя bN`X]=P-*a`-2E-(@t-M9ggDNNNP]uhHB]VhvhnEcX`=W0*& y>' prCh$zdg Lcw,z r?|&Ww Bљ(8?& zHԈbWp%O yYr HFPaЊX(FCj v~zr~qGrRh BHy)vcfCmcn*e<;(Ҿ#F#G J]zo^AngC: q$c)@+zT:z̨Լ{ T{{X~O%zIJo:X@H qM!,_j(8Ru=W1 5D`ԓfR0<:L8' jdY ŖiMٯ5 = c.R'Xp w0BJؗfGHXB۹B>bP HpV/!H`&`qb h_ E@P`ItLiQF(\TANF$q*JPQ)F@(rp ʵ`k#-x8"9;-PΞo\$r/A3+ȓZn@',z@VgϿUR {&` #P꿇xY4\!ӷ)p/QυcϮ} Gu5 kʔJv]JLwE{ UG?)4Fdq]E rAImńH.J KKSDA TؖG 0~Z\lQ9 @d `FSWJr*#4cVFiUQ Y)Meљ͋P׵mqUJb-Ulf/)vT[v7EPE ?QIo"<*T*Pb nj 4I%["d(4*4 )$@ c>+9v@@] "Q] ~@A>8Oz$#+RD]tݿWLU$ A DF ~ !?|[@"7> C|` "z9P@o7 2*,rA-q$H{t!>YQF2= zհ֭& )/=W4",3=ָ! @R㫼Y5QjDGL`BoEyk4n(G^L#CBD:±,IDJqY&-2 11@ ֡# =m%#ɗEڐ>+NClco {RFgA.ѴHIKXF{2R>/y˵^QRKQ(r?|'|aOԧR,lEq#I@jRn?P<*+j9[0O*ӱ"H4Ȼ5*zId$ B (H`a"I*a>#)`@0JI,BYY:N* EB"IL{L‡R (pIdB'o4o+Z ֋@avrA Qgy}5%TbfؿY4hgByRHeP`Ϸ t!JKM*SP)(I6 BO-6 u}TLD_vB ARTmO7[@WgUJ.Iq3@F wdttg ,BJRR cX&7,LuTpF8@D2>*1klc~Ʌ|+{cBrWr$G&2Gt,ɪ;_3L29j^37xA$n$:  G?2;hVZC?l ^)8U]p_?%o04a GCJ@B=S.{ޜ) *HrP' H/|\R9YN.H,BT ԃGm\`y X(hz3vX f8}P 0g<h8T@2Es2bi Ep"`LXoW ",+A#A@ :z!6 `Z4%VFStCyE A?&\D:cЈb x0[%L/K_A/{ͼ<L\p6F>@|^Bd#5Rp=@,qFF@ Ne$ET ) ‹m@"zD A@ZU1B$D!q`A-8wfI=wL k~Em^Zį@*lKcl)bGJ4k)?$+ήJRJ$ͧ5f~f& apFlbXlglXYfl>ˮZԋXTi$"m*^Xeab E-RmZ2brmzׂm؊%ACijM@ &X?f-v@` Ax:ZH@lZDЅLį=L̆ZQbv\ǯDGvlNNn䀜x" =H8@nGҮ́GTEԁ@HT!LJdFdJi[2#Eh]^A1@HC BID@X~eA2pA@8n {nVILCSAXuX8))Bp0\3\$Y+< ?P]N1?4^Lt@eyD GHE(\(tTQ?6E^?K,2 U86x u@!'5)FH@HxpGAd&IG{7Y$P7"AA L8$hW;(K sٶΦo'bA s6^ݻy-0ㄸL})РA/ Aֱ+9z?dz5Wx 0,92B *`E n?$4( 4(2+*V s#0{3h* "+$гpL ` "Ϡb:- t y.S!>+7L-"-D$1JB,~ySh؛-~Y!kEdh>jQELJ "siXbxAfAM$ u5.,Rm%g p)CNٱ-8_:ȎvnrDxt3K)zydRsqf$2c>o~SCF s Xj 4lFh R!eAbd(6l RP\N/=Y%*.1SuX ,zS`VE@ (HFB N08`h ٠ڄAקH""c!!4 8hht8*SIy@ρ>\DN sRn*fD;)$gA8ud3&CT9P"q T3dL) jIY-8D/F,"(EʊIR@di }Z`ĄXBdl6î65^D)1×1 dlyX+bSS)$07?\: "1H h)@ d!-0 Dd! NHB@|@^I6IhΜCMNG5<@zӣ |C >^64+&/ct`9aGÁ0ZrgD:.TTGJ(Ч(y39f* ¡)ZLC@|D AkMίTP#>>v/I`H /"$:,bp #?*Dnc<LL&D 5XA`D@/xND̀/.*"HH H"pmBx""tfV bIdTPW „RSb;X$l,y "D N ⒁C0.e,"c" j:F 6Jb$h$ln1`bD&^1&kx4vDdb4l&jdbc"">D W B BU>`(bx1O6J B!"!1f`c8G!{ -"& P`ԧ p 7pQrcz:I!p7g$bn>G2Gt "m(y4 &Хn pPXA  B4a! (w)L'e)ve`rو @q\$#""bn|MR z*-y(D("vI(h`) JZwbbN p4K,/*)L.@90S|ōR"VȈhʶ-9M("\ H˘S?+C=$;sKDĞ=.B"玼⇠hSh12(mn*ptnx.Bh#n^@v!U ]i^9v"@!pO`@xpt1|R  BFBBlX`Xg4 b (b o 0#+613N!LH `bb'&Vr*\]aydA`0=->4K\6 GD%>g~.JV$-`>4@c IoBo<9 @B`D9podžf p#%@ ;  XV(Ovཥ۵)F>s7߃RN:kKhwk(<"6tK4A^ SD;U zVN(R-ceE ]6=l] `a- b5b6L^=qczbvJJJ*1'9RJR!s|]T!i1+ԩVY5aM CY?vN Be̞))"{nUn'9*"e;ZhiR;"&"JT9h."n3ԦB"u+$\A $A_\_ (RAċ3j8M(0Fp@P* \01ŅFKŅ >zD4K?Uz*4b"3 'rNU[Hj" Sj!'nU3Hl߿>Oʌ0Vq@!/T3acԼ 3#V/sF !jdq07;[(A,?-4$yC *<=sw^0尝*: 9tD;TPaB!H|kBmQ)5GBpA&PAh?gFSAA'∝]G(#+4S==rnrvanh$m䑅Fs&N׎4.gEY2`)^Bdl.KZpYYCBFtQ^Yg*蠂JOc(6(ܹLeXzAyniDjJ ҍꪪZd"z]zW++hsd:AY8Ql9i~?G\dQ2-%!Lsu &وp@!YH1J˟}a5?1Gama &@A9ĂF8,/F\WE0_?^E9wЄJstku@t"Rfb!y?xxg^Hdp3SEc$UuU\k*-R8P?g\q'1RTN?k nк? `PqIc~j.H~ʈsf*bk]mTɺĴ1$SH7HK(|ԧ=y$"w*`D5Q*QD5x*ꮈpt#E2KbǤ" I,c9 1 l0B_Ye3Ո0Xǣ0AiBRF&t@se>(I& *q,  ^ N37="oH\Vx  3.@H/:Bt,P#H$FG1U  xm?J+ġ1"r"'Z$D:hW˸ Ѐ (y(B]$ƅJI0# l!8k1XBRW`'shGR@"{c0,Q Y`i?0" YY` P B&]MXTx 4&F1##⺈4Gd^U,B/$?$ XH(XcB.a1,$ T>Ef/ 9 :@``&?5eX(6OM_H?P ^ 3C $"CL vDaQH0U1X*q H"IE1:lEV*H"jZV -IL͝^+ n%*Ȏg?Vm"'tۏ{IS:!HGf"oM:7mHl&gW)ZXzja ܊$>oiKs\^r_t^CVb#G\<0qW/\,Ϙ?m hB@r2X%) jq^t³ˌ1EXE`xf4 #CNp)\dȏpNJèeN믹fuX7  NVp6O/;Gϝ %p8hUdeXv3G}pҋ,4|`/TX:"-|bXHj €#.Rpp20k R$:sS0 7?9|Aqw ]/_5El%p^h!_va_݇__% $%+=-! 4g.9UGV.&t[qY:H@3H"NA. Up/$4S{ޡ&02I8U!Sm11 /Q# "%UȇR凌'zH TSHoEs=H"C'h(ֆ2HnᡉaC7pFr$n؊1} 3tUR$/q]&Xx(B*8=)/E) ƀ@GŒ8X&~qTHm1$cx8X( 0OGf/f/A4Bah$90[4Jv4 .!/.{. 1z ! u/ cQ F*2A(p,0 CcR!a2bAbd6$,a1Ic`PiC5`T6Zys^yA6 0i!J${`PliH15PU:q;H/S1AMqM9; :R!#HC:ur~q;W":q/𚮄߰u 8 ڑ w<&~us:AM;bYzux)-q(Ԕ>BFp{QS:3x +.42DC=т C]D 0 ׀ya `РSAD y:'('FbcKM2\21g $NuJˡFJaF3/lTQ$00AoB 3OAäG bU?cM/ W:K?b5LHtׄ"Se4BKq^e@I 9JKME/!QOp+DRGQNE'0%qcC,8RNQGq@RFb=BV5-RoD{l! //(&LuS/2?řh RT>Je!Rts  yZzՙź6Q$LJWHW\*!C/3wWPWւoU0'Қ%Y" QB'>ۇ~Cw!\{V\\D:{UqAAA-|7Y7 C ]74~^5%-DpA`؀Pp\a`^^㭩Ha?S ' S' mFCt|6fdvSЃ9ILAa pbAf| $𷵠0d.G`f`N91n F4"32Q#2ƻ3`ygh+Mwv;c!L;44uPgJp A&jij@&1jVk V )00{kj |FU x1CV%&qńrypnQoƦ1W'ɱSMgs7nEV-<´Km7upK">) aR,2:33=cHnAy!Y`đ 1gi&iyU1cc1JqWpx]B5$5`y6 a) y ]]51ٖL&!&= ~bX~}i?RS7N9 ;;Lk-i-sٙ{ 9/a,~Z^s?3s>:aĺQA=W([D1@iůn%Aj4A%!Aaz@ ) "/ t@ O &b)~  0 ȃX]/CRC a!D9EG)!= ^ܴQA_y*JbPj @S}iuI~$HAIIAԣE!:+ O|d+[Lb,2JZHqLJjJ!KpdL0$X84LK=T$q-:N^i-zNŧ$Tmnzߖ$`@"Fq|*"/@ٳ*+<>p#qM̭o"=" آ)rY"> zJ"GtPe$/!_rQ u!$ ߂>\='?A``É"TŅ#4G Р-L=/Vpa < '!Og\" "`&0@#$A$<' p0W¼$cˢ!͠ H0 x$(蒄 %9 D"!.a  H1b1h<gJ"T!&XRHԠЋ]1LY+I(`J I!^m(U J:T nP(X z+5V5uT^ zYi2\ vB ¹eKͶCJ(Tn{WUQ5܃|E#T_wwi(1c K5H<*+p dV!7&&+<CTlYg&@Q!=e: *@hn}r9޼ %`h̯8 =$M;M N :4C1U"O3X^Ygg\!Ě #h`墂:Pئ*!< = ڣxvIƑ!|2bj#hz?QYAkdK"'hSZKl*( C hQ `<0 gc!T 2"7ȉ A 6 Ax9)϶l u WX@? A52vL`Dg:չNvӝ@x"BAsw aAjJP6ԡcN~E,΁|?ψ/iLe:SuZBSa$tiP:TըG(&A_aK\C>R=AY%R 0 cN%rL Arтp Hz4 l!`/ v$AX1Q e RX$ j,Rp8X{*9hHaX6!7+A׵?9q9h$ $0] $3!PX# ,:N>*Qi6Ԝe2pzUQLљ7&&L a eKxU0yWJhP*e  k@dJT>$ ((+pa.6pD`X4fƟʂx1|K!iCi}!H,,{.=O%h, -80FЃ|4_11U=!x`DE$ Gsw4B,6i Þ#I 8Ph 4`4AaXj05 H¡,/ > |E(Y2ҏ.BрeQ @쓟ȱ4 TaOxeрP򃀁|;PwN&Y%?'Y"V!IǾ-o[Hz@ŠlG蔢;@^É+%~\QfAP >Ge@cxa1?݁ D٬@|8)Jae9~ZJf,;RvMt栨t q(*\e/3T!/ v**Lj?SRD @0VZyhU@o 򂼑 8aQ8(= 3".9jq-*u-^v)Q͢Z3n> Cga_yC):3Q|2?6әψ4f h s 8* " }" ("qx*xPᏔ"Ha-`29Z (k8x͊!,xHXB\*U$!B堀Zؤ X(/$lB&I +,؎HA1A3A.=I(TD.)X8@BNLJۘ(qXpV a*+h8멋ITx>,,,Cb3jd',ܒ1=L- I #4YNѓX)Y񚜸Aŧl3l(Z8p =QK1K`0н|`s#4j&4U,5(k4"SΪX 0# >A;ǐӿW J 8 i@@/Ҁ P , $ |*)$ 4N#^C H>z H"!s;#2<"\jp;u<"; X#BҡdqO8 (rOt/ #2!'#!<"I|ypQ%Q na!ۀWJ %Q:iC  h›Nj\uĤS=v:F`r|cdRer҅BgD[\Ѧ).ud<2E4Uwyz}Ȁ u@!P8ec(i035&cP5UEeTw2JThJ )xyiz(UeoC80mVuWU'\̡+ ؖm`a%bEFf(d3*@p/8 a+8`!@8QQO[S*z*-`8HH{WB|B`"(J,X1xm47< -ںܺ:+.₊-yYA:3(D/֘.EJdJT[wrpJdMPVx -)|*Nd({p q'k!HY X2`3,$H,h8q,B >[Ɣ#ڸZeɈh1p%ڇQ\{ℾ\+-N $(s#Hݾ{mݪ2sL75k3+2H ÃE< Y P` L[J 4@k"Q#P005TeN u h(Kupd{rh)7i6ȣA9k16k97%̅8 ʹ pYKx` &v1b(cj36~BR98RYK8)#࢘n~p6^E9 8 8(u\@ɿEuQHG;@B"[܌5`D/Κi֠! mN`hQ""'"<H!1x07OhO<^Zg|oֶK>4eT.JOHϖ68"\ -P e]$~Ȧp Z"%QS!Ij%XP"o)=/XV'w՛dGg\@t_"ROA,{n쎶^wtyx ԆPic*^`֚)i;iS w <}ӆ>xsTcDjzMLG|Uȃ|h}Jv8Y{}YRPOl}PוWtv:) I m)V܂J!BVpU!nI)`pڱ{L B!~AaŅ $ȐEn ࠈAL+?M៓ aǏ t2/)/̗I)R'¿+Fv9zpDD#̀$Fgײml,ܵy&ُw-F-1Ȓb$sV㠵*PWFsX=E}Z)\[qD$o[cr#YDE"V`-V-893'AxaϿ O}=4`yeT0$&ڐxEhQk SkAE`5_7`X4(0.?MtP_O2xٔTf݈G<^?|:rϙ?0DN  c 9/}ŋ)ZT@o5)IDSUd ADA~V G͹l}Pi+C&J\}4jđJ\O" @S?9*zD)CJH (H? VZ{cغBzzlo?9j+N B&āR]5DGv޷>9dȠ= 9Sgc#4'"p8h[TS>VEf Y}D} |1HqA|Q}@ O q_ADA ZH[8=KFBQT#'β#cH_$J=Zps|hh8T#/9H]lvrSB4^C-H)@DlA-2Y bEI E !9'AsbЄ1dA8YtCViF4 >F rLcFgIΕG&r CxW܂))H45'$J9J̣ Z  PBm ZDEAĢ1c"R1c}yAqIDb#$g: dd%%QQQH"I6_HMf 5{壵RFRQ^-%GXҙ"Vddʖ^$K!SEdlD7?U*DԱ?-GU>o?{ 88e Lh%Ӆk6zKLe^JiAZTQh(sI_xKМ5Ҫ6ː>lٚNβ#\bAkО gat62Zٵֳ[Xmb|Ęk!&y"+ @MDfQJX[b#U}!3Cˬ v2}V%bVA+wk~K8D2pZJђ rC>ȗ bTګ%0`vhR_hM.N<8dqRk-gՆ籯ثRȝ:X-ƣu ryc>o fxa 44Β8EOkZ us|PXL[9Ӯ<0{q SgH5R ȨCY@k)W-bԾm,^3sމgI6dӱRW6+ }c/Ӿ= zBbV2X$  R1+2/gH0> W_/qd* Vta?IK,1AA xGxl_Lhh4FԁS_9.TV i ~EX*q2ѵ:80aP<Fb(0Fx] A0EqF=4Z?f IAXAē[|e~C`4?P ?`ߤntvlAp#(|F4SLc8"m0H0H/$?Ab(J'BD) $B;:IɓADD?`A+D"23Ej`bq?:Ŝ ,-2("CT 59VP5WwW]kܵ1YG|&#Z^5auVǴW5^\$ռHnVa6b'b/6c7c?6dGdO6eWe_6fgfo6gwg6hh6ii6jj6kk6lǶl6m׶m6nn6oo6pp7qq7r'r/7s7s?7tGtO7uWu_7vgvo7www7xx7yy7zz7{{7|Ƿ|7}׷}7~~7788'/87?8GO8W_8go8w88888Ǹ8׸8縎8899'/97?9GO9W_9go9w99999ǹ9׹9繞99::'/:7?:GO:V_VW:lwӶ::a5c6غ&ܺ纭zOܬzl;zЃ<Ѓ';+{'{_M{98lC/lk{oCK8G;Ys{{/3;;{LNW6G0?8|J;;;x|g?p|ǯ];<ɗɷW O\¯ƃ|ˎw˼{d<=GC(=ہg=[3hk<}~ K;[}{}$<ۡӷ}ӽ==~ԫy3C=}?S|W>K>w߂&{~觀T>8ö>냾~W>sLxvȾBlEKDgR}:?DF@Q#*7K Ta~17T3DT>CM㟔}@8`|&TaC!F8bE1_ƌ,@dI'QTeK ]"1fM74C(I:hQ%UiCj2iSWfպ}_ojѲ"vUm[oY P@ջo_i#;paÇJ1?t+f,N]ė1gּ烹s-s=tiў?lukׯ vm۷ n[n\xp'WmcvVLױgw߽ܶ/tѧWߴuݓ]?~}?s 0!k@VPlP 0" % ! ,*] H JbAb)(x.A-oH&;C—0B(Kကߊ? d M)(d%Ő=c8$7d딎G;2gf/C$f^! mn4[-Z""HXr*aDp}iq 'v)"Fſ_g`:Km3ׄ6YK0{!y gk?h^׬ GP"7 :%HE "4P_ B&?@W?!$M KUd@"ѡz`q7[e,"5^P"EWALfhP @rpFS@VpQ   ^"V9L:LA@ڕJ2R M`ɮR7 \JP <@Nʱ$g @@A*mn }JθDa*A0*8%`Ar)QK" :<JphK_cVw>Ǧ1U9zlEρ:tjAv*:$MXN*Lz]̝f]A*&D_Lun6]L dZB@eVWĘ>728bzlPG&DY$v !W s%W Az١`bE!2Md Bf6C$%Zsm;h{F%;i"X&qtDڰ,CЫ@Q$wcjT|%ZY* !I?T67; B6FC?VC@UCEH'5̯' @\ c+4*Q@0> %.( ˆdʼnZdW(Cx$)ơat(!kFD<@h:?kl@Ⰷx嬱 x#w`@8zc;%V1T9trpwjUK7t-& u 29rY 6sOQcBįaA6UF_)6`'4<@ V$M5uO8Ђ`x-mtڼF/̵Z#irJWF2rp[L#>ٸ.ᳫ)>|JӪŧ遍K xK[N8 \DV1F=i̦AjTHw.x X 05d~{`vdAcQ%} @ !Yn0 uok_rfl]쪫+;)׮p%A<‹u_μbǛOW_ H)/e#lqʂ6jt HHNvғ b@rH*!.}pE dۖEL 8(&6 +\VJ_P,9}"#9`|Q0Q 1MY"0{~|B;d/  !Qtg* J]( `(1 "{?3%qZ@|fQN"R)@p|X (!EZp3A ) U"*,K?82B$ӣU h @""8_[Aad;rAV)B +aL$CFa!8y/dsx0o lPl'_Q(ְC2u0gXcEި<*q6ha*|"} ߁ R7r-J,D!7C8Co<7VVѐ5h0GP,BS/(jC# s1wB3h%B-0z0pC31?zG,k?)!Bɐncl,A-l- Y S;1`D]Uo1k4][UUi /Qai Ic(1v:iWr/wp2Fz:d3r.u7!pQkF8UsDMƚmzcu~#;јIuw r2o ` K{@6HZR,=2w`B?A>=(3(T:v'  G []f'r Id3ES9I1bzP. :q=@_r@1(Mq EX߱ *LQhFegP2nCHf!Gu'` 1/pwy`QR0KL(!#(*u#$NmtI+\Z8)A[qtH$'4)+'uZNyŨ1{傩u:]MU-L[ vXjHJp j$} 0f$@ Q!U:QQ5(['Pqw_)m8#!Yk+3UJE9p3rS!p:,wU^UpN\tH"Vp[îwHulNWZ4e Yp,.ylubA0Q!qZY2K8#1J.1'~:abAaU^_H=ڨ 3  ]e _S?$"0JΪs`gC ٜ0 aY%WIa=?dD(@w! RfG ƟeBP(`O&Z!JX;KS}G Ib(*v6-J0Ag MԼ+@jF8 epG"( x]'o5k*,{z.iVBl49v`V; IAᚖs7AvۙG30被Z9S@ycƗpn$IqnF6Pn"P'vc8qO!ԁA`sH>&W?`Q->+SQŁb{,.eu|'Ο@ n)&=^B&d3=玲C!XDq$@gA;>f]! 4* K;*`G4hC;IB K&@@! *'CM E/(ޫѠ Qka4j""_:EV- V\uiF@A*H}0!7YAo79 2 X_ ;M4g"Cx u˚1GƼ<`}Ǜkv7Ɯ hyB4'B4mVhJ8mpɥKޚ?&?|´N <,D0AL粏&L6"| =BOTķ\[.Þd E1GwGjjȦ,D"1J)QsR E*3L1yLcSʄ5TM% "(:&lqL?yds-byZ͢OG4RȺ =0L4FI?1Ӵ DJUUW_5VYgVHkܰ;qP86HTaa,CbaY'r(ȇ)z"!0D;R<K)3䚗ݣεq|iX爘6/@-f3ؠ9?A* I񪠎*F}B"q# Z] abRܤP &1sX@./K@ /B*V@b2X:DDZ +2+l1HӲ&uz! jo5)jW6q:+HN^ܥCJؑ b$a Cd2A^^jWEFz\ =9|uJrD&.sN tW lgMD$iҠK/*nvii#] gF _0 P!4p ~E3 T$AAp;Y"/ @pjaB,BX:h*$`ib@eAtІU! Lu \hcBʰA[$:P.%YEnLQ>B UXȞ=Zp 'ZB8 N~"E]fvxR`67B8 DMZ ##:(91e g!ndhB 0\@gh+hrC(q36&]@%+cCOPq1e0H1((&+b*!A*ad5f X_ xB1 _4kfJ{PA`@݂e_(̡?_`5P(װ3| j%9diy[V(AS1A ѨFz :ZrS. N ,aAGF,l$"~!8BQQ<@ǽ8hqዙLFE x7ぱS,ߜ?Js;8UDbpbЭSE ʪFmP\i^T R TV l*b9l yYJ萬z0#-ye2!VHĊ\}l43#ˣ Z&lrn I>lf'FN(Y' ^̌*AaJ@2\EN̾?"$;iD& ɶAdh&pJA3Qb+FV! JZFx`Dl:T ь["zDs_q4\UПRA,Pd lP% #30(tphlM{~stTF!@k<'hJ \NԃK/c1 rLHGRX: @[}W)N83rg.r7i%O h7Z qxF6M"}P_0iDbeɠǪIL:yGt7&FJj| eV}񂷅aw9*$6nӕcݜ7W(Me \U}#t:]ɿ5ٜDgQJ)";q3J<&( ۔д$4DA*IJ XU3]9dk2h/@a.8gh8<+>uac@.,Th3@;1"yD9@Bx&ɁQA ("X8 x&0! ( *9L0y&@#J?%HW,:Ќg ?b"hik &&ɂ٦&@(IQ g!)F*G)0r ~ ~`ðP{0e{XÂH-(q1`p " I . # qC)c4X*Ȧ=;l LX3Y)WFx؋E(h,qGqxJ ;!ГSYp ʁ `ٸʢ/#/Z "-$4z"؋$!R(!K<Lꏾ@Cˣ ^$ɋ䰔à˄L 9 KhsA9"8Ƚ XJ ִR P)h%&INoQBS9ѻ |29(&ԑXr> T\$(ە&&<S[ 20i"USkꎄhئB8¸Fyö(yJ>>Q(cC6~Ta;Lq 4يs`(}'OS Gxn+~rkøpáꨂ؅-FKPS,L0=ȫl S)ƫ FH;p2BB?=ʅxFxWqCHZ<; @@  JG% ji+*mX 4aM94(By mtB2|ҢVH28,DCAÔhTQk!gz@jׂ/-(,L9g*XZ0aˑ$, Kk"AE#J 1[;OܰYҸJ< cف=12%0X mة< \pk-5.9Z!ahvx8?.LP{ae+y8U_`}9evfivvxQBb!J LzʂFV>A[L ȂZ(" p4Ya1yc*imA„hj;$RCEt)+\ aPw fB¾g(BPÝhrNvyI*t[}yqÂx%嚂nj-݂XA/لHP`.Ibâx8OXd d^Pgh%DŘ""_T( ɬ Ӑ=y 9`FoF dhk0yPkxxrh{_Hr0kv^l4 XHp@$IH~M$ hH*,`mc^.@)譝)€Q&0߁ (%h XM| kϚu V% ٍؤ9 @͈\) A!J@c & 5 Ћ=Z$q8p#7x/-N2K/ ڲ(R̮ o,9 Y|C# "@SZLz͛bn%4\{.X(:.AB *@2QXr]c8Mh$JOh=H8ώ`]+{)/58PoxPEM\xg(*8љ'v,c=zw l2-y{(dh7#M}Q fCQwV(Fyҟh$`-DUAs(F76. 6 ׀0& Ɂw)pF(G4\?p aYHԌڠq`(O邘>4CVՋ0FSWVMzg+: V.?r0m.)tb8nE}ݖTg",RsMr)5wPL?LX.!h^.Uꭵ.-@-zp.Mnvf@;vʬՍp/Jְ#B 2< C:$Łzءÿ!txG>HI@sΌ/5 B y_фB0Ӧ F:Ӭ4ŽZ2[Цʔf<*ٸr玥Db"/P  ;!+G 2lD(Rk #DV3^:bLkUŁfGa*r 3ID/)DT_ìc# ɣ4C";4ⓋV7<6~-"k(:AD( C. BD?̄l!E#D??BX $C`S6##C<$L?542!@$B*XCDWf?09*0?ռBB(.sRPLZEPk7A _j8S2$GAY:#gD`ХddU|*tA0P`aD:SbE@Um{F#!!L'=L=1ւWm Xl[yI`Zct$?U&PD 2nG&TLeBWpm]hV S_`QE/ĝ2dA i@%p-U \`AJY0|UNkBM%IL_33 L=-S[}5Y']5LN1hUFTS\ϤLX7cU_k7 >8%7BTC7C=#9 6%0<`2?QBĜSUunVf^Gn8;s2  5S.nlK Epʷ={=?>lc3 }$3c7Ytl.B@A: -  {hC Bq 8!Xn&Ũ?:BcVk.,Y Ӆ9$ s@Y}lr$0ڂM)<t[@ 2MŐD. "*L`L_)7d&_!ʮ܁NjtQSD@=m&XIBD B(D)J 22='H^4<0: QF@B}x;qH8?jy !jBF7B 3!Ig@ (,ˆb-!@ =ʊ`@1UȂU"mDҔ а|.hI xl2Gd!C7IU{` DKVE˴Vd)({HS xib ӾdB?8& vF"u܊I,LH!T )u2T`%GAꪥ)"*+"DHr@@  S"G C ƙ+U:#bSBlaQ @ı2i0Lr[uǬ|NN fb!S"qel^?caUf;X/Ec~5wm,x +Btfh40?" v&(`L!_\s1&1HLZYNJjyHeʏdxp3d" ÜS ꈐ |KZW&XւPy8i MNARD8K  {&G| p#̑{Cq D#6RXEiPG1g EZBJQ7d"|6sl1Ul(]zΧ0 OQr!@R׌d%BrBM[ p$'A刑IIDGԔ(  YE ZP˛dA(da XAdnM0D < sJ]~°WOTED]!XĢD<vwyZ.ŶwRd! eThaA`z ŏWDDWP@} NX@( ?`<|]~EM#W D%\ZQ<@OxibT"#"*"> ?$ʅ"B̏PdCF0Y@@ |}BAf0D 2 )SIMP0 ?GѾE@FMhC0_1 P.X!DC TPl eQO tW\V -"GnU `!;!cD cDPL^g!EѸ@ d>ECXf[?>9D 8=#8@;I-Y ? 3&1E9i:iL gC0.@fhX6!eAXH:dLdc`HnefCX! ŽA2D@8'CEuz(c`B-{0J!+l3ZHf4[ؑɤc+Ɩ&OT@QՄք!QcYL`<@(^4H AD8L Q%cDcQLB2XVXѴ ʥ)^ɖ\RjnIpWp@)PK;p%W1S_l#& =@WB7^jWx8GJD1 'Ce =NWPLtlY`S7I.`G"M1F& =W Hߴ"2̬md J`8 ?dSTD9Mhag. D老{$Û`~8ŘeEj=YA>,, 3 ?` 42ĘD ?~B(w.< 6 XfBo (JƞIMT O^ Thgo!O1erv$]ZDIW"x_c8^I saGL W캄SHjimqD-K%AiٹSyEUo kcI@D΢ZԔ](+$Y{II:Y_^d6h6$]DžYꭷcqD+cY m8c0ȓFj#,M9?{X_E``E'2=S$=2D"JJ1FUΣ 8شS,c,SH5<?̏vxǣ-{WLi77. =b)sA3O )p*z 9Ÿ\:gb"P1v2OiqQ b1 b-&Iae9` c^p-RK^/`.!15cO+7]";S=? 4#\@l 1(;h\zԽ! m(un'k9⋉ b̏1)rNK$(5&]:!I $@NBẢ :HtJB2;!4܆ !AީY.1E",L@ ؤѩ(AtpOڐGNJ +c)<)WB  zQ^hњpjHRǜ CrI{F:ir d{ǝ0q矐^4e E;HDsr3ژ@oĈ$D/??ȁro2`4nHI1d2)1馺E^^|[ `Oc@ ʩ ~?(!@> 4I࠱D<8ԇ,oQa2d94 EЀSd`,rڞwzEP+Ƚ,u'0U@=,8p CY@mJH UHt-SBG,v)",6d`f)cxEFG `L d1#th'zqI s 졍 *dACjU2 7 #* #$cNĀu [IG5(!$$*] (%F!=Zҳ B^-b)*8@ ;.nť!b2$nS_E5SMK̒I <{]1aCֹ!CЃ61>0k H"$Xcap*?1KJcS"-=FϘ.ɩ=g4b/4Ӂ"@d ɣ=Jw@(`-U38aVgUadOt%}{RՐAo@ *aDW`{sC z}dDC:o=]+0<%Y< ׌I@ В+2bDyP %g~Y{WAA fcÆMqf[렽V*蹌} ڂrjN.aM_`" %|A@˓HRY-RywaWF4 5A&{`خDJCjG8 :(:YHd1Bz!!^C 36R6y*]#uA,h6F,/I!:dı"kAThcdƓcBv(9K  (HB=AסקWY16@GILfe*k߄e-2MshZҁ84%(c"|31 ?93@k )pu qbaSui`Өm;V,- ;{WCT RmluTpSA`c<)enD!$~OGĸ31>8 &qI ҝmfw9urϜ5yXR [x`@ƃ_[R*U?Ve.P p !|߃hrL !2H9yOdh}&IuI*vR9 lM6С\"szY+ úH c%C-@ B NC.R8KA 0#d##W9y7M&q䂡Lr`Lh6(D yDҲ)Ŕ?t *hQ7^T4hHҦ (-%Mgzj i\ D!`j Em.~>%0 DLwHra&#s t#ItFͪ%t4A:@0nAD`(@R. `)>%Evf&恘xxˈ0ڇ ꐅ r;"?p-ˆxGN#** H} }=~#V C sd4B`FNC1cFQ8/# >%CNh@&+ăx -ˆD @<8(34beBg⁒BxHn@0hz>].Bb(OB!L.b;jM&bïX-&h$  h]- 唋=$9,cP **>H#V !&I 4 !v` (^)!of" %d I l j @ T$~a'z 'F.!i-J(hZ%HG S:@H 2c f 0&A0.#`T@<0ax\\" "+ @ J&&Yr Y|m@Z0S!*o4 J*P4J@PGJRdʦ.=eF&z%Rpab< `kyh3vL"~@mԠn+ʫpcx`8WS f!(A;/$ b;@[(M.G?L|k0#" >NlM/ꤷ4* 1njc öL#Kwڀ #QOI3 oH#r%5rt1~='n@ !dx7+ j&p!0^JgN:*S&FĎ*+ !X bpfD Ndp F 1 *tx.Dc z2 hC !*dBhCb%gBV8rDnM=D!?EBdx%H8؃ExI-YfT&9#*S.:! A*!n/.7fH8ayG hjg r4T3FեvFP`ü@0KRb&< 6F<`ĥn+,%\ :mn `Kg9`4NbBo _Fe *!`@% JGaF"w_aK`!j~ pRe`2ЗbRoO/,O*1(Rܖ"v~B(j@ &f! $!2+lDp b! BYfm`Gh 1>f/P&jpF!71h]iI&,! \t&\ 8`\"S(">][|؃ y `\HG, ƢƷ|&vx(@lyjeSjx,`}aS!l01һI]@ Ԝ`l2n%ex0\*Զ_](` M-/~?4b`Gπ^E@g7S vAut c'_"&b8=@K$ڄbf]f&)vlI"M=NN<3dx .A2KQg$pP)& fb 1~Y: ԐN?=R7xD—kdK0x`dUh.=y['^ &bsL  M_Nl ?iMVDKfy(ފk4g ? &>"Ԛk^MXn75]"YXkʞnY_Rynދo@uD ?_ 0>[`AW6f]Y%hP4P#Eg&"!R]QG` װA7L5l @DM0{[ m)<@`(m*/x`IZ =LCy,iB/%gke,c0r*TV,Il2Mi4 g3]V @"E@5vvD<1$Pjy(m@D \&UӑSLG" /kcLv@,( (:HeU0uIE!wi|lx#4?Z[E"RiEc524KjDŽD\bV}# <)EHPk95 #v p 0 ^ θ?+%d!y@R,DfqZAa~0Y?{ĺ dw¼b&1\/װšP(]" 8jJj $pVI9Fbq`H*FC{FpH18K {۔T{#ra;3E,NZ蓙AYt-*ƟV2:jX ckIwVl frK 4-'8lC,:Êx $.*GRaXmjaKK\$@ 4sԡ ?L<4<U|4 ip4EtFJFp- K)`a`>ÞtRȇ ĨZ=bIS8BA8^W+SJl,`򁬼"]EhfK1E08MUd/у!h:'v+Ck%N7DB5o8EآTK^c 60Tpql"'&7HC<V!3nlrZnuj=n  MW@It2XGăa>j }II, o F,/R@ i#8 ?b' VZ{pg)0.S9Agwr"aq /!R*1&-8Rp!q"MaM"M 7Q/3!!"@&A%}/K-p[25CE @aoV~|qȕf52-,6V$4$a.xR1q{x.)"9Bx$vQ@R(3ޖu8v'5*&# +3r:(/0 ;4 BU: ̅. F¶z/Q Rs) `r*ᨈqfuaSoxV:APuN+l}Q!ֶ," ɐ  `#Rh9-WcjTBye|R&i:Ts2@#C\8@(@q+@q, WvNp4K3 @tO>$J7VFB89_W0;#3#_<38@9Ea90a T]s0 B;Q&Y030k.d/k47y\b3W{ ̣ Q"d*Y=š5Q=g9@o~&SlZ@!7,.~{>ߑٜŅ_AJg6*.%^Y\`e@3P+Wo<Yo-AØ.XZ3w()F[l_p9wSckuy7,5c >e+7 p=6u 2s~w @c8]WS0`WrYe5Q08 g1/)iVw4ʚ+xlfqgo&q׹wcVfc93+)Vj0 b;:ӕq וDDRVGǾ2䂉Ao76T q h#$%+!-6DuT"N2$+xmVItܦƇE"Pn*,"$ J,TX5R 7-+\""ZFuu)V x|;05 19'ĄX~{2&=C[8q( ']Oa/q)rv1xb")"}{',JXBR{Gg].:2ܪ-[/yraFQ !Te?4qZ`l' +'Ƣf8 h7[vN4BQp-{ɽ2 0?Q ͘#SΞ,|@ć p` T V`=92u.5AE \ bur]S-Z|TMAкU&DɛÅ5${A wz A ihJx[h݁dV$`%Z"`="#exb:@04“ "4j;Nj9s g)/zWPiYlI@9^'ܴM!9ي?2d3;"nߛ)`Y7F -3kRӢ;DA?5!_3<ŃBexpA"<-;R\U⭾{@`\A*O!CWԞ__Rz 3 4 [H 5 Z  G}WiN?2?m,'m4q\l+JSDQ`H4n`[AHun-.%/+1QBTͤ>Cध\xQCOԇר`wʣtĚtMSA4Mz 1N3IdH:H.COUBKX9+㥼}2Z Ps(!" \ӉqBRS۱b5!:<^%baC+p5q kWQD,/?H TN+`BX`_` / ׽6PyWaqM5튮s?V튱B\7a{@20l8B ՗D]z`2d!`0 M!`ʳ@ŗ>pBq$5q{ZL~!GaX*BIFd` ^4d0)5OsUJp aq)U6P- `sg+g9%vjn L,Aֈ uO7A `HÎ!E$8  ҤA<dJDd+c0OKp$A N;%@@%$$Im)K@;_rI;__ LIp s x`aVf `D" ݸpЏ1^0猀] `yg Ԍ;k!Bۼyr|Pk_9Yq`ȏ 6|vGPapC&~sJp#$r竹zh4:*DP" D%||@Xr68)/⼐@NEI(Dc觬pJ/C Q˔:od /9"lnbJːDد!H/F^SRGrQP  D`$gD%(J^h,ccF NMUPmN̔ I9[>"sXS ʠY(7;Fy]*vCE`[9-7JA qYS @PBᔓ 8ߚT5 ~NIS8' iaLb2h L(lV=`?BxVf2: $JYڃ`~f=h;T8o 38gmU~N簁,:[&(4{ٗ5X*J pifiWbǃ|3r*'> y%/ s:06w׆}vkvܣvGz9Isډ&wW~yw~׌']{q=a>M}w}\閈o@/4B$( ?" E+YN6MUI :CN UEfkU#y\4;j`` Y@P=0T"p[Y[̏1`с ȹ(+ .pkp#Fht؃~j k }0KĈD4WH%](Tr79?2-SF'Zj'ERY '%cu4DFlɤ2,R L0Z`;ASJY%}, ʀ0it`zXHE|ɁeM(L- (1U 0&!uFBh6oLcBMHJa EDE1JQ!CA$\b3AJA8#A(MJ2.#msG ;yfI4ے`ٌ; xP7  H+ò3)>82suxr<Í>;$d0-jX=Y4ر,% ؖQkW)O#Ëڅopm7Cv BPY[{5lB_`` BKq4 0ݣ qKx  k ahH1@\Ӝ#B4M8(mȔI K^xJ68h0`){Z19C8I(ꔄqx \0R&)ȀZ";Ё}:f{ZTZ%AW[*P΄IT)r!¦1.X:,qH ΁88Qi&lA( TR s4"q"Zݻ C)%A:;R?P[}Q\LӴT )!2qʋŴOW[ӱ0w`fpA@A 6[k7Y\{$Iˉ TpRu ́>ß-d ^8 p Y'hͰ9PK "D?%#>±,"`& ;K!z`9p &ʡ<960 Pl9&Sp{ Ѓ)<MH7_@;^LIs֣;83؛`M)^0ě œp3;}TUȀ];9 !c;kLHS`ڮgHӫ5PʻKcHcaӾ(h^>ud%%d# X0K>V6J~XJ=9#"'8?S?WNQpЄdȔci6 #vZZ08x`B&D@r \\ŮL(PCpI3 ۃp4S]? !Љgx&}0,U=[Bgٓuٖ B7i4 )fC͚t9LC0酀]٥]]zd;6Eݕ@F*cheSEDAZ dx&`F9^QYyiFڋa_XˆaE]ͦ鹑 M̰lԚFP!>sܰNܖ;;)JIڮAɭ;^ɩ|nnݠc⽝b VnNIhԖn^,JsYooFG0S3l 0`ayi4mxlK9! (LNܾ-!'X̝X7iNtlQ+V' ;J`&9I!+rg)͓k?d'fxX(XH \` .?9F>ꬔzr%O&VBl0pCZ~[mƁʃ- H9 !hrPrT 83N5rG X9ہrHt8:PQu&' APB@1JKÓg@3(vZ <{( o_popFp|aMyp>q,lۛ56uxtI\N+ BEV , :h`,F=+AB\л -X:} F܌!-/+Qz%MH<Sw_ JV9Thg+C9xEqYj-B։W,#Σŵ_ig5ՎෞjMԔ1 E ` wuLϗXUumc ƙ\<Isehsْn Rɳƽ' OpJeFA:tn$VBg Fp5O*ڃ8`gg'XL(`_59qtDo C ߞ# dqÁ(( @d5J{w@A<0Ohr"S,}0`0O$OmOIĀ v@tn}?r fx@A?\,aibX [MYc]u@_tGVh=_M$[ }DP:|)9CM95Ym[JژmJY`F@yBk|Vf$٨6D[mV)Ac:hZКrfɑ6WGBԪ%Dw WG_h +Dh[5YD1TE[Ux ԛ[sUENmIE2$9б1VP1:QDB/AA$@tfb8HPs7VW%D%%a+HYGBZ9{/NTp D(p.%"@ @=3sA 0@xTTP|SCU5x|#MǷH'k $ h[BAD~`90 )`yEQ T .VA]d}^UmZ tLLt[ ޖݾ`-CtCan`QAB0(5cgB͛ Nc5?|BdZؿ\Acz)}{H8L _ƤawHҔLE_ [(#TU?hI(l auzЃ)!D4F0|{=""1'B.B.+b1Z"8z1b#h3" ! #< Cn+MQn b_$$AdΪJ8#ȣ d3*HSx0(B$!Ȫ*[ "+p$2*H$^$" B&&,D5`H-duhB~f:[(@IwF:42 'Aps69?5͢KPI/Q)7h Bq:Hk蠳䑔9)e"?@0 P")&jGr#C8 6Q\}z";$Ms@h<Bp P Q3BGbjy ד HANPBrWj)Im0Pa #iZ$H,:jatښq`d [2n20#|dK[ $uIm bق|!P%U@  PF.%6. L u zr)[VE!HgHD"@67H2' !0`IJB@|R@uD1 ҮX8>L`Ty0-I@x/wg -Rȅ H1c s!(/oajF0*8r'0^EJPYl*+zȮ{>/[acQ>^3,@m{Ȭ4c;^FHрG`,tPA҂ .0_At0СK(.ʚ,)WYBy\$$8]Fc 5h5*9lyePG A!h/G0Гot!EUIQ"ZAOV劃 Bq] -_Xb)P<6(Uq#~Jdm\2Ah""( ,:Z\,pfV1-uӫ,L Xf$#) <2 xh3ZMkp) WV]k.LqX@EX&@pBlA D4% Ty˄W2/#20heM(`AL70@ ,"L=&S |\NBk-1l_J>ԧ`8-i"4%%B W^jC 'VxTq*LmJ PݑDښC}`D@ ltʙuHA)l,]cl{mT @U Dx׵`AH6 Q OA?޹ ^xpƽHG罋>}!DKYAX9AA$y^,"J1Q| pDgL, AI0ve$՘#u]M [ !āL5'L?ŸQM5ڈg:ŻV|Y)l@(j<,mUATh\DD5>Q`OtcAt"p[}@\Ue)B@ @D$H5]DmsqF Yd穘qJe At <|I(Dd\$| $DI~PPм^ %^p@= R)yD @БdHeYZZe%VQ`J]Z_fB4%a"fb*ba2cBfdJbdZebffjfrfg ԑ7I?TB- l)]FiT@$[eH@ YAD PrVyhbAp@M\ؐc…KYKhR:! ڎ RgA's,@ %I=u((=YBO&.Ekڙ˘`IDAɔh@kVaAGKADIL@%PC * D|(,FNE8BI<0-U,Do%Ֆ@TAܴUJY_Eh@dlbAHT㉗e$@GU&jALq) `LSRݕ0 kUPXT#pQ}t/UWEHgucG)IU^QA՗ZVTz'G{@A%OYz4 !hovs~P+{q1ƹJDד엡O~GXWY?Hr PtU|.XE@xa *I,rŁCTFHY|j_ǐU!t;$M|\ϹbK!|+ ow!+PޛPH`6U@u\w)7|<w A> َӥ V~C@ԕr0w(VێDL/" dA  5Z< 6[E·4DjJFRBQFĭ#e@)#̜ jFWMÅ8x,c!@cJ+YL9_s\HD!%4PH,zĩEPX@c/d@cHBJDi9|^xF G %(|l•nA?{(G-W* \Fskrj9Pλs)-!24c%ۣ\$;ZЩx9=tw_^E偧%[ "h!_aRp5%q!t&} ?>PvڈJ,$ 9A(6"IXB)T YB" JD#E f 8ʈe{Q1ĉ)Mn`Ãla)>X3'l GXH+%]R\b/  \ 3FbO{\2h#hظ#3$5X5 TbDG5%ֈV#&!PĹ iBJ|0#E `ya^H2'bp.~0hi|a.$ A. A" _ tPBŞ4z.Z/iqlzh [IFuhQ7O cƱ%ChY-& *`OyvfVmpDꔴx`͐T3ea塩|;r\kZ8+Ha( //B@a1 X~N~c ;՘N@J`k@^jK%gybz&$E)8:v`3*S vPlb,u @-GCϾVK(o)4|(/Vw' D7> 8b&Zo )* pa)=8R}`&(m!62=X7):#&o?#@TmV!bf RNT&nA"bA! -;ʇ!- b`a¤b\*9jM#^M=H  [4B@M.6*P+HB7kf,x:d1_rQEN"Rj4Ɔr"Mcmdy&ztVCm* 4P$%bq,% M=F=8@f`@ spD!LFPBZ.,n^rDL4" j:'$9h`I!L)Ki\VC ZoLD\F,(GPBg.#q${#H/03r%I#=HC22=g{ 5S30<.s}Ch0y4c  2q4&VD1 ﮈ:̨q#(&F)!$)BP" H!b!#"H1"#*uiD@B FB2zf!Xga(騖i&i# JAK;`t{gdʠJGR#|n\SQ@R[RSBpCkJ-NqiZV 8A!T @J AAJtPJxx68!OG %E'PWAdn!@˒o.f p'% "SlUX)Va)nUa `"YK|" A8!B,BV;2Yg,V6"+,b&4أ@ 4"Pb4Ux kŦd#$ fk l hg":$\ Bh@DlvE!~f$lHFI*#I*W#TC?.Lh5"M @XBO(3Vk-#^ VAV3:$C ,BEH7ww1+,TQ=2^*DZckR-C3G15~r'V<`% )&E,̉`w$LBO+ .Pl,b!T$EB5"|&@AXR-93'DVs}KwyArdCr$@'wbd =D֣ERk2ZJK4Ϣ56HuDܪ (6sQByK<Hx}Q84RY5Bu<85j>}cE@5E#OO -TUf#JeM1S" z3GPDZ;柣4LVbH#aQ0!Xei!N,Oj:pZ1zO~s3bwrX%,x%֑`$DRbz[d4K=``_8dDjNbj]pYՇpV#$nbfXaf¨2|Q#6T:#-n@WC؝\b>m!,314*$P-2sD\j$y !ttHmܑ?` M*.ymSܓ?ۀ gD2CD%("iQ [ jb&<{/]6*isLb)3B7(0+mjB GbIB3cHx(}!2 n"o6 n{bjro%&e&}.)fi(]&!0+3176E\dύ=$F$yTd]z$oc9~xcj ;#\"-6pi5e–ž!4PA7/}۬CFJ5BT= *Aӈ!~Dm¦g0 .a˶(A!K# f6"ȯB'G$=fZbIWk#4l"Ȩ(s04LGN)Ml^T0 OAd4*Wbȍ* (tQqVB 7 j!(6/!85"dA翎 b! E)Bs/aB! {q D<`=`ÕPJtB<*,G9$"bJšY4o, ]˪0Vz0#YS%LieO2s èAoxI3 ;r`up,)K?r/ZZSFn*Zsh$Lӧ> #~B+IP%+#t@C(P~e;}~)A.wC%z4'sdS LJT0CdD0 t)!H?DVivY"'V){  V5g`P1]AT@BAx  f' '%?$ UTJA2RvBmR5YX Sh}haܑ.I`֑WC7VcBB¡ʪ ӪףuĒũB,}ƍJxC* *L0gqkVPNq4٤?Y䒰ŝjJeL{) \-y4J[dt#fn?C?bm'I=4#UF #4~OE Z!JD,RJ|9ƺ"+ %Lp iʻSvm?C$ݭPlP E'2MưżE#C5D_0VkY[j%+!pK?P.{ ծ?yJzP?"LP $P+u2.duBtV7, EOO_0ʂK>lM@t<'lHwC FG C, [J$btux a<HA$L@PAR lЈKX!P D Hӂ(1qSzب -2UQ9Z@X~DGz7R"($}JH*-gbVn*1 (dCek&I<Ԋ$vD |@,DI_sI6|\Au:wnGtIKb ӘZ _V4 H!uГ.=QGX+:[%tL'_W=%|!,'O*|H7c1aD}Hj2Mp!8Th(LYta) :&ḇD+$~)D_AVZG!E+7CPKDyvQ eX> 4`1mʟiezo*.N9]fw:#uWBwE@B'GU T .x>2.r1J@&Ui$MF`q^E@`ns|'>'s$xCN$h\*2JVF.{`s^%tI2PovIW w< D7^@4πMB%8Fxjll%`?q;Җ^I5cԃ ƞL=CԨNW8EhG.<7 ,ָεw^C 0iY!S:c!D?bЄ$,$Kpp SȷMnKǂivq'r?0T0,TYі:D sPJ2H-[NvY; #gq~> ~;¸K4?}A(&x9x3 #|A/.OH+WXB@0 K74F3hn)xD}.c>.>ВWx8 eI\ڣc lw;W KZD 8$р@z 97{#6+C;Z CAT`f-AY Z1뉬2x!Ja`衍SI@زB-d$@~Ҽ{ [ЋP3D{Om4# p.|00b4Df2  Mhэ|o!x{# eZ+7&#H 1V+q 01$MRTWq"%#+DP*:/a{" " &R5ѡOȁ+\/Pa")rFG |DޑGDE恂`/&$ 3؂4(1^&cH"z a&*!dQЁ'b^b)uϱL aV:Grk\c)^7*.3a*3-&PeQ"Ʊ'#1aG';3aεڈX$7X} QQxxD!Py q0R|.! 1v,-[ 0 -/ZHk:B:.MTE:XmY#ڨ  0:y-!6" egj[}(#nY[QPJՔ5UZ2NZ&W `=; $@>XOUK_1w_ @9Yn2r; n+b-98p璘6^A@[S#"cmY;"@+:8`@(:9Úi2yݓI@0P N P 0 A*qDe'",(9#vߤBC$ulvG9&I~>S,kHq##I )DT:sELHxpT-2/Ktq9G^YriF\aarEe)l9Dj4 H*a"?DHZ$R6hafB-trLȱpH+!!дRzӴ_++Gh8t(h.Cp(_ZoYsp(ƉϩSEѐ.$S;dQU( dh! s{ ,<ܨqEZBETJ5]x}B3˜RG8}5Џ*vŒ.(WN Qy,J,!\S uA*o}+q\w 7 9y fYc678JIQVB 0!^iZB(z"[0A9)x1 ""$+a_.++_^;S8+ ª")*j1H)=FFP2.. (FkV!r$F7% ="'F8̘G)daD[ER_vHlhlJrgz|ed hlf 1r-r&d`}[{}hY.!jX.QidۺFE)+xG{x ۻMWs@G s PK¦pG> 9٦0mRp%(2 <ɠ)oP p70w>Y}%+4WH ʡg!:.0>L@* pD*.@M#ns`'Ys7l0asBǧ֧H0vKB2lZġ*f e B@+1ΡW0{ !u7.p\ gxWxeT{gIzyqW7/  Uy 1{^;0+_H Sgѧ}w$XqC8_Apl sB $q /`4aCH P 2 ̯ v~A sİQfZmsṬe& vVKԑ 3O;&rq( 8.7Ș0h8H+K2 ‚5ECq??r11 HB#W$ z/ B!!IGa!GY&@=(E-\򫵤!KXςH(!(x",oB0Z-M/af"bsr%$׺F+Gk*ض?QD$<(ct  ^)rGW+ʠGbkr]*9*B rdG@cEMO+mTb }D 2h!G5H>)$jm!F*0I,WdHhJd8-_c140^@K* "v.eTeOIhogqq%j- @)pZU^*@.Gǘ%(rv]Q'25| $ ybL @%\.5&TBAÎ! T`?b1Cp@7"[M [3@?0q)=? $&s gHw1Hb]"NJq{$^`WޝKBHl' [J&Bo_/&}D<\:H! c O1 W,G$@KRM}!l VTeF+J4PJ]c,FJ%YcGEEpT4Y^ &a `X$ ,R#-&KI*nwԆ0ldAyFS$z&ZqYƾtA f0Bӓd3|D: FJ.UdU "L 'DŽ!55MpY.YNr8F;]6~Jg A4ڠDJ5h 6Cj)A@o}=G 81bhhqwg h%&k'CBnO (}|^Xna mLîUaV >G0FACri\O'{"Bu]6X$T: y{(s ~7~? h WHsh>ʧ}XΚ ?%pH`@P*9,?S#+],$ 4 +`2b*0׿ '@@^)$ q)ʲ,@.òX(ר -4= +L(q*ʍXA%(YTc {/P/TbP9 K@ m(!U±h"Q!r\۶PX0ŨQ[ c˟ 8[ 3%pD*} H1j5Z6רE3 ^( %e; S}DQʼn6pg{'z'6o#sPn_5(ab!@V acg!d ` "(8ډἠ+ ,PF м`(&/ )e蹸[8f朝HFj 8㥠k9ڀePgxWi0:۫{o#x VH#dCdh[- pjh賨u>튎n} ۂM;5 ݓ>P^xSVjɣ<`} p+ i sxcIJ^vkؽRZ5+|>}x(8PfP5跆V> Y ?1?>&Vj3I 8_CYY8aR D=6mH,DHA8<x9/h/Hn׵&6麗/Nё=t9ov] f5oAx+PxXP78?Y;Jβ, t c( Xf(G<,bp!I+m;HF^H\' W G\I9hh,(-hO(g )ՐZhŨ0%bhpm.n"&?q@<HsVs8?ȡsa9дE(h"@-Lx(CG 00E71f^&fyWhQxqHjy_N`I@k $ 7|$h|"Kf/ٶ=gb'`  NxXLx4E$wٴٝ-Ί; Kl h:ڱo >} 9x1HI! Ѭl<t#_WМMM9N:[ sQ `T H,8S Z $yOɱ`xV8E\a~ / s>׺_:ՈX2|?^ˀn]`Aþ ;Ȉ.x|yr8{]FФǂKWx&.Ё6e)-44? I}s=#D0&~~DQI-;7'06tTxw~?&jR~ib~Ag(7B)!(7>*`q_"`f\b᱅ k\ȉKdDע&Р5_8}/-w=,<<2_#G_.e+4 -ܸrJ_,JV(O|peᄅ0۠-IwA乚7&Oɏ] 4ƚݻm>PZM"Vc$I9)8)PO9$6-c1.tW^(_cϑexaZ YYyeu4e]@fBVePXILfaYr@矀Vff&R@!ڧLx@BnBBʤ5@I|a,[JB΍LN餓5 ? Ub12#8˜^8?KY}%w!2eeux, p'$J5eY(.4(Lp/JgDifj*cPD{$BuT/9"?t$`eˤsLt/CT0|2L }@-̿x\Y29YNU͇7qնp?bBZTTSN!eO Uz+Bƕl& ? ZJ*d  j!!ȌZ|0(# GeD' E9m-P60:tT.(F)#L$Z)a'e%x" 2؜ 565t[jh1q!Ȁ$t&A<6z`\p#Flh\TMj8)Qbr$4JK"̈[r5TReP@yJ+ui@Id.a<&2S <ʗ(T1!L/@Qw90ҍ  /W S| sGsK'>}ҙ2 PYb ('P\~!= w`!PXzeBaTOXIN'JSҕSpa'C5+` #  /wF=*Rԥ25.KHv@Bq a1 * TH .Vf"0t>#`.'@,jr&d9Ȉrs`artaZ[̀q21(bY"[cSf3d A>T\ETGdE-?$ogY -l屯-$ (|3K4S%s;&v^iٔMv!XHc#$l![dNMAGϨ4 A}nA˜cü4tM4m*M!BR( DsŅ8 SH693@2WPy#v<Z`$PrH‹'/'H}ve(7Y{JƤ(LA` !8A /[ ;;D-㚲Z*hBC^˝Mz?3F|)C9gP8ޫ{PDSD,I [/ oI _-xETU@l"ВFV DT&M&JUp}x2WS 5f NP'0T^l8|.>{O%XAMRcӐ#Q`/qD.mlF-,d}/8LWU(Be {`.pnyfĤ{8Z$pkDQb!)\Pmo3~X@BvuD._j\Dd5[4`Gԙ@G_jvD0d,ԟMU1M۔3P=T R2CZ _Hڬ PpELԌ DXNt-xB, ,?LshP@g ZS\@ |LKP\}8^0$@d`^Hih{,F@b,Ƒ&B @!LHtG!@Htᑸh.I|."+*MH+EbyKKl[\dGy{(?HB4 ML5@B^/c8>ɷhHY@bBAF  Ԇp0Gv\A@^EӉ p ͝R%[. &PFF2HDMIUHʡx$J$+F6Gd(EʭH,J4D|+ 9 .$dSSbP:,S{<dK5Piž^l PL\ZNH,%H0I%RVPe$̑ȥi0dH)*bIAd ].DʰőL{=q9[ aW˸cleպD4M5fh: H 9/Ml x{<-FN-V^-fn-v~-؆؎-ٖٞ-ڦڮ-۶۾-ƭ-֭---..&..6>.FN.V^.fn.v~.膮.閮.ꦮ.붮.Ʈ.֮...//&./6>/FN/V^/fn/v~/////Ư/֯///00'/07?0GpAPW0_0J0v@;|0qp ) kۗ X0B ϰ. װ JPl11P01 'qނ&[q_a.K1z@q+0=̰qqsP1#Cq!sk"3t1!1Cr$ Ѓ'2(2=T?)X)g*&U+rE,2+'.r1'200K/03=..32#233.GNP(5,6r1o)399320r3B26g7$O3̳7?343?u20Ss-=DAA#t?s6848D?CK6HPu3P(&+쥄H#'(4=4*FrMAGoBHߴ4:xBNt%tMR>IKt=i'J)0USVt>muX5Yu>YXZZuִuZ5]uuYuu5Y5`P X3t6ab 6`uv5^ncadOV6c_n6dgEioh5h` 5jq6[hsi['vjBԶ6n,k;oDAnvd qufsnrWp7wj?LW5*ϴ88|wxx mucβX Y7s5\nr }'='~77{2oӴknglk}>T_8Fx&-8g8W?.wl#;88'z8x! ,)f H 03J?B:PЇs /@1pE“(Qr$DgϤAC"21r4 I nc <2c/=Vw4Hڌ<<{@,䐥 fDY7@D1pʃG&I^R;` 2dl`gB *pa^_+/$LaAHpVLe`s%#\"#?@h\Vsbs(&PE!4 姠@c&YT^7 fQ'' |9ËlS?h``P0~?pWA=ةV`*Ph+c#=As2E@PA@Pto=aVAOeA@=Y&+|-{ TlA|Pg&<_XHGp/@)$<еVpĂ)mrt-C@eVajc6lv.cF{Y p@ o=tptLo}!|V xD\{-?E|D$0[ANVd6@Z_)V'YA.@ AI 4!nYƸ{nn嬳9^/^A&<G2)-;zA!?A T@; p rQw8#F?x.A:OR*_@.GD4!z`2A_Q#P(Y$p#2s'vAHk`!D1 … dR`e:Mᓥ" r|*HՏM HA{l*pSE#3|z"2Q2 Y4A*b3z /@f p_orJ 0D uxH0yB LwHv D0  PBA3$ 0 1@&I&%ą LHS9x4p SwɁLb[q0D`,G D18H'#T De _*Rp Q: U4 w(Zh("0 UF4ABD ҅H? ,W,G-tk| 5 !P$꼤Z td̫QͪIu_ T 3LhR19Fʷsyx@+qʔX 9UckebY-ZDԭV"TQE6tdV3 W[ @>yt $H0 R ,@3aGIT7%+X b)QP>4s= BQ2j AtE6DPm GywW"fc[]Ax>:ixc"¼%b,X HYl ?|I"2hd8<U;!%Ȓ(| >@#/DD bj!7/+u |Zu9CTN(Y9+,JO4'<Ӄ@ qΔH,dQ7)[J4:r]z B\ $יyN>LŖ; rcwJu_arH% fd"2?ՂX:rf6Kfzv ^C(o^ 6ׁ},Sߓt,GXIѭj8gٮ+}V/@?8Ϲo>m ?O@8f yrJ_BV?RZZ|s1{`{J+bD ϧҍ 9̬3F ԣ^]썫9O^v蜛A2֌&M{Є[?x{#N̘ O )m|AZЏ19 26a?s\s 7 U9@WH@?^ wA\q u@5"x^ M[A?/f$.Dc4|(av[rs5+*Euf P)DS! E}bPL0O$KCjܱ A:!e2BX1"V7ܧ tACh8ATw gCE 7 d3 U1" ;&VW!{pqdBABw p)'Br y(ćuuBD!i0!eV?pCX&&} cT`{ r #X g'x)E ")/PE (St'0svgr0Ȇށha0R  S+jI,xrP2#yu;0|XO,@0rk;s/4/Wql21ɲ-2ն0M,t43c$Jx!9;.*dUYU:c"W,3(S31xs#1@I`u64Ncs6_6_4-Q)5K[G-5 Kja`R! 1rԢ08q 9hg&cr"&)(hCS~n9Bc:Cq39Y88yZ(&kf&P:6[N<£;/'qFH("e u @.@1CG3Wjce:xvAv''D uq|b{D)9<EI8$NHAkV!F> a=F"H2EXDpF"۶m FPm!Q)A  uGt aP j0.3@CPrUKw\M;LSQd PQS-U{^.|*ߡu0|A{%O$Q f{2`jj I`R _*@_)}06@Qeg^59 bWk22 c0A aNRF0P X cPhAL&9П<~BE91d} FS "PFfwFF("@_g p*˫&E/*@'D5W0 'i_?8TLjˁ'J W $?LJq|QgL7 t%$%0v`1 XQpg  2}jt!lQQSuҚj61۶ǥP8[^zb]Ә*3ф &ҐKv[!b9aX)+d'pp5ЇH (`Tt1(| r() 1s |s-`KQ #٨'AvYCw'?+x| pSg Bpq|uf6"GGU֊ex"0`' 2]Eqj /"0</ /ԲҢ1oq06Ml_S2APKHLs;}a)&ip!9Xq)’nI-+B,#s0` 3 0)u !}5C/: p8P6Bޓzqz2`X 79;I< "QNȮkR&} 毑隇wpWj)vtsɹ -@0;z`;f!o @  KpEfn Mgx=3*Զ! p|qxgGy$QlyBDu"B5w =Aj"YԡP$DE֓$d B3c9S']7' p< YLIb:Lt0+u"1OPSJ' ۧ|x U,kP@m B0 `R *]4 %!SN PKH$ ˈKVD(O=!r Tԇ'@NT4Y"VEA!l[d+5!؂y,- z(Ұ (LOC_tK D4!ꂻ*x`_6뷯B_TN@`5Rp{۶@M ]a͟_Tj(/ȽU6cp0@4.2 '0 @**#/(; 2*tq}Lȃ{` g4H !^*=-KLrK""HL2Q\;|F1NجM<@R8 d*]&5qMA[v3, ( i*-aDi),V L岪FJ hI5kVt9b rRf~jCFB6Gywy)!\ĩ+7}O{RzV%Eq ֝ziWɎ>ƘSR'B0/eaqږ`亻 d(\b,)O!<6P',cIsQCtaD7 j2iĥ 3T*@aяCOUWͪQdABjÉWT& ˆ<&tZA & cѲaΫ\9=$(;㌋!]G,8#9 ]MiQ* r N:  r$$b*3HĶOp/D xKiA L@Db^Ţj . y ĿF@#dX4h&t@"S  %~ $n`Bz/H/ DTahс p"I  Dmús^\v<*x*A51+*j2SӼ|*.甀)M)I`>G^rB7J>O OIvi!03 թ.mu-ɟs!HHW!$Cv/D2=ə.1 N.J4)k$VB937|uȂC`+dUQ8m^wV:Yx!л!5uT^NweZ&@ _p0{6usJs90 4Uĩ!@EHY*,5@RBsƭpui@1Iu*N$s]Uf-qjPsh+c ☪uXinB^|-ȉh=R *k(!w ^:p 4񀈐'=5d0e),0;Bve@\P2ؔV Ls$S<pSu4PLూd`Hb|+J!3ѵ'\+oY?P i4+>q3~M@${w14]B@ГH P3(˜{Y4ZA6IDAA8oykr)nl'T8(+`)Bux0."lb@*,[b?D'pu)DO  ViS8 0uyAIMNO3񠡂P6801b3-X8$X0D`(CpFFsApXf(tHgHBF@0@( K(Ycےt<08KE BG@ _d}4)z*8&B3xp)ZA" )ڹ.+8. (--H *G Zpr`)y4aIT* l}Hʑ/$Kȡq!p˘ (A0@|ԖЁH,K+P+VyC X]xԭؠ]ؐ0t tO@?#I ܤ0 (wBdX N@a=ӏxj"h$ E*x1$x$>ZI@ QZC* ϫ'2 y NdkY 0*YPNa!Na#&6pq#ТQд* g!ѻ4Ч&zX ?>بDҚ,K&z @DFQF&23<QhY"xQiI`M~2 A+Ep ڀE;WJ!<8e!Mѫr+*`8z z˒¸{ҪA MeH<-衲PhH8{I=҂*p  ,H B `ª8*KRPD=`*߂(%>wـ1 X2T ,&y墰?nD R (>O~:&;6S u 9= )f(f= [~p !f_dfW/Gp,D<3]A7[B ۽XZ@F6ae hwj=I8Qh]th>xoFT),b’VVNJf"w(1,A3ދ prMjP!4\mZ6ëEbCHʖsC?(AFVuA45FCoăD(1ak.iۿ&i1Vd N(e卜`lpńE| FAA@ƆvD_j;t_ oTAk4r ni4h`ǂ(aTk|;@ ,h.65O~6C pɎ& +0HӸF Z1 ȂZ8ɄC4ɂz*P>#X(H-DNL g )cCEd W!zrJq |DqUUxXr)&K'T^H/ \tVF`܃h]JDmA,03H<88!sx^(CҐ9_t`hk\Ӏ\ڵ'mZ79W"X"S0 :Z'Ȓ=7&$ͤnh\;\Gj%IK#E ؑ ; %KTVkd\ZY&$m [;Rbg.(",nȍ[?Yx' VGsȤW $΄~gM[v6m|R嗀D-ۉw(UM҆n\[N/xf!*\V8ip8d7芓BEEJrCBrOdUn1~|s{xU*k(  U򤰭;V`j->sU5>Ȣ x\/cug!, ( N3v=! nEU'؂K( s-#0=0ё2 *{YaB! sY0jll)*XT/)?T)"B }ߓ32#Ŕ 4ɐH"& 7E+TNJ|@-Xyiɫ|p Ai,ڴj2!Ҍ$& T%{o݃z pBhØL9eF8y FIȼmJ~a#7`3v)켏ﲥ8ྀ)>Vj8bޛX%mBLjzSN-=8M=",{`Et'$1^q| #(ysBf3@s$ǂ*S.|! c(CM9A,3!B&L&D")%N(5T$dc 8DA+1.FFS6B(l b 1":(Gm!_`Ra@M $7HrD A sD%P%L  7Ġc oRv&HP(|q+i9z#E PD!dRD 5; ?x'aЃ xJzDROS% H"dP'B!Ƣ"BD[=Ŗ( o`ap:,MJn1O "$Lt MLJ Y+9P!dWJ, "UqD)R8EZg|Rc.IPrVh@ikJ Pȁ Ov``4@q&RM!<ʢv-`.@EU(,B>@ bdr"1q]# Z{#@ [ vilMr(V8){hPS(%dAsX@ N8ćX @zLCKS<@QU8lGNM̨| XZ]eMREKD4 h̚|PA rau@aD`S\M^X ZU_h݀5|,$6\Ib\R_@'2Gr]c؞b$Ƣ,B:Ԁ+EE"11;G1DYJLY FDN*"28c ,ƇkF)E;*#==#>>& Y^~^JF|?H4؈IQK@a A  GZaQ^N8:MTz$$BAh`QEx`teJ{{ GA0<@LC?<J/WwrbbhP0 _,uaݑcy-Na˙EƄ }EjO=Jv\+Bl>J7Ą^܅3xJI i~IDäTRĖe JÇi"Ī*V?X5 48 E8EBY  Uj9MCr\%Gu $R9E TGȹg1LnJ0Y?XM,%̮+ `P*藍nֲ%XJGpojlJ P[.@_,CxlTD?и0q&Q`wsħ%3FONT;]Sl{DQJHrxn˵hy19vO"$ܥ7DEbܭMd@gP"ٚRŽWB31{Uȵb)_/s-]F";`kJ]tZ"Xҧ0ó WJn]vg{Kc ~^w),*M|3,~5E`[]H$8Ǡn栛^'Y*ʋD{ Irj(QLT8t G(Q4 : ܬ 4=;!C#R=JaR:.,y@PҢS$T  = Jiɑ<AfIB ̊ { b8.8\EقHZ>4`u j > P 0 D {D >@τ րX [T&@\ Dh ș<(H m;* $p&\(ܠӋwYhJ!})@@ڨ@l[Pp3(!PHi--8ح+ @i(X`!![g! " Ȼ=:ʄ^b8P򯼓@-:+2$ܠ<.6ZpTUﴰtp! :ބ "&8(9zW*2:#(H~Jhx5hʂTK,h rv8 B;,C][Qe(┡VIIhE%!4A@8-3sډb%K21P C`RюL'm]PD|%IBu@ uΑL@+9jrhhiߐ0 JXG*D P_7km xCUQ Vp;uxx8,'Y GgĦh1} [CteRN\ u[ubZg:K/KJr9BdnI$,WXx5-ƗAnfZdHZ3wNa3Lsv%Ap8P 3ˤ $n@Ê -)i6tY~PiEF)""E9=arJ6seEDЧpfxĠ,HIBbHM8wD„sĝEi}+H]Ǘ,D!{-E&  5SvC1*HUD!LH+ࢻ}%o`R7gU:)D1fZDr!%-ȑ&'k&/kZ0AK*T﨓BjKN oS1Q:-$+NN50S,86qF^ pllBD!LE=񏁬8,6j R} !565$d4Qs .qf1ߗX"@ة%TP B k# :Hم # O&:H lA Ie_AqBd6.C=!/* 8*0xW.rˬ lNeVWu!}4['%)BVʥt@n3`(+ 2ALb_om=-~A@|`8RK<l,nu Uf}A>/GGfDn:iR%/h #)fXJcB@(D`AH%`p{P T}A§5 Bj@p. cؔB b! ]~A5X u~`fb Ǣ;bSS2;B N@NT6@A B.X(\! H8−>N?XA> A9hT ;:HVcax%d L!:!H21mNebPa>H d b bf!У--s)6I;#nRL D<B` zsj% `*[B D-!#.P/&@"gz +J,Z)+,B/-,0wK!%S2-E^IjPD‰si/ * ).ek<  !Ѕ)p8ZbR o:1ς.H@!j!8o# !0aHOJ!"9C*Vä  C6"^ 3#P;+E2 #$nsL#JO~s"/Ԑ: !R!.a&HORAh|Jѐ ]' p̮ .ЊaLLW O$)"$F5 HcKh !DK7T bϰP;BHi Do-!0f_@q ƴG% K+P eMok!~ o`C(fC%Jq>+O8::캌i'ĻL+OH@q ǽƻe&G5qd1CaJ@f7PS狳)@Ҩtf,ſ:4!̣6ag?f=DмL39"BY-.3XjRVS(7[u)5$4 Z[1i\ĺŦd@b{!nln̪,8GZ׵`? xUlh^## _k"&"Jj cz 5aIV"^BndN,f`#Vo@/)9LfU6gbo ` ug6h)"b ͐ "Fa F:-Zha+r#!!h֖C70"x"@8fR Dv%g$Cne@sdi@ON#2"#)/چƏphG_zVV*q @^imn5FMnvf?5Tuk?ƅ,ryi_pfʂ#I;Z&eYdBURu޸d: `qOb 2#c/.T#NUN1y6P6+G* XyY&6㨐О."U ,i, l ¬ W U ,~p.#f-0pkCw X ; GfGf x@U@8$A+Ň|B +bT!d3+B@s  H>{bD@4{>.inbnZD !y'j pƁ H1 D(e BEج(B(w~`¡ )"`@*(ȥb@ud ӈ 3+ k6!PA, t9*4:AI{OL{!J0uZiJj`&;BY#>B@KO.:OpfX/tX d_~kHCPl`x GO;hA:hT 1 p~ `ItM`a!N[o?v&\"̻p bTA&-|uu"D)nD'_*Ͽ5\Rz[zb"'_'_ʖ#bL$&ꈥ߿W! aƒ :|1ĉ+Z1ƍ+K1̙4kxXa.wVÔ k0maС CZ9QaϚ\z 6lFc#0^ HE,P/ʿk7nmhVװC70¿ ;\7͜ir=vÝ[~ ;ٴk 3>4Lc֠@ 4Q\u9hfTOB 7JUw[݃JYNB7{f.mSt}Qr܀@M#?[D P4E(P>4d_=`D#8_V1'QF 9t`%~ph"@D =]QP8t$0AsCt$ OM r(t?sX:91?*0:9@)6<95 ?bCrD@"=Jw 0q C/8Ɏ J?#u dMc2B5< ]BTj?|@" HzTW".jPVpHpVSH$h,0euPǣ#. Pe1@K0AYd6t@`ds5s& yP:/1A嘮y[bw1Ծ90p\d3=1A_80P0F`BPVLCnEcH`&lcaer(0Q[تd@f !T?(6 1AO8@ Do 3F^rVq8_+c#ra;bD(AޤwȑBGzId!Ѐǰ#:݃I4@$Hr ~ֽX!C`!z {ǐsA :!rX/L!>fHB HA0ai/7 `2abD0g<Ѐzp`u8 ؀\10Uᐚ@b @!4KOa-?/0A8\ BOP0L Jh?["5JVP1 C? pi{FT[sI?PSt vS$0 jbU2A8@bl(H BLvD>FG@% !lOP$ zB,%j0 K:83͆  jt`OaI0h4يr&2aD_Ros.Zj&&=h QDpPnuKĻA^e/bRwL[/=ԾŒqi0MawU(ƼH-ڮxog {_&ӓLM6D`1iHT9uPL`2!Ps "NK b7)nC`YϖV\BQ?z/ r NOH,[6L x( EU/ f1u4 79( )2mvyA)p3ayR?Ϗ$-7C+ЀDS=H(j`>r"ҏ$)9*ʸL@#aZ â |T&Bd50bxh _xVtesÆoF"`pWׇLޯ$_,}ssd߱WG_L#ܱp?b oCH\tG0I!)gJ @K A  p ~P(]Հa (̠XSX"< kGqTC7|Y'՗]` AbwYo /7?`X1Mo FwDaU"D< p%rJWW`)`S&1fr $`]ZXHTn" 2} π5ІQ, vK>!{h 2+2!J !Qz ꠆ j*2(?LW-( ϓDpvE|A6Qq)XzsA#P3`mQM!M4G"Q  4<6 u3 ;O0"3<1i(4)3:8%6wn[`9Q9@1Y"rI.TW&?H8=W7CbP|C"TC#b x{y##C`Wə;K@<5#;I< V@Ba6GD Dg?tA@"zI!2way`ш)|{8pEN䘛1DX}RE vw׍)y*ӈzyF%xTKV ZDdLwlQIl&hm"$p` wpQ0ѡ Q񡏙@qn{q.ddRyE0D)Yz YC a{apTqPD P)CP$TGPGz+g(mz)@)@cZ6#3)!k5*7XyBm J'xPukH.%WO%S44?uIPG  FA UPŀ% $0[Qpe1C"3PiG(.Ƈp8j[P W%pIQTŮ5Mspo%%W@Z 2%ūV}J\ jasc+>9F[]Dc@$^Yc}>9A|Aƪ`$`8@$S CYE="3}Zӳ2` XpW6;J]ڧ>+b#g_[n@ԝѮhʱxe`20a {yHvF7+Ьx#j.®NO&dA&s0ix1K"QӨ,(ɣLt, LW**)GlkIX . 24*zq @)'PFrǐon]Ѥ.-DEl2o !O]mhm'Gl+G{PĦcj?c pr6 qn%JA/ xU["B9t!zkCE<T b3̟qǟyLhT:h MQ's!DxC&PPCpw=|@zk`>xEa;R8'E<7!\W>a4a{{F|]{61B|Q} 1a=VǷ 8/p"Iqf` ȧʰ,"  ~ɯ/TI 1곩˿<t6i#h)1QLv]lwQn4:v,L`fdr<Z$43Q1!`V 00!h PUq_.m05[\`S|x A?22A34*2(jj(;r<)E)==QBAק5b` +rϳa96@6Wa+D0"m@34ߞTsgSi?b#|F /84~;6xv;#<8491>BD/p:f;!):;@ٟ'f| J~.W$Q*@6 eO k~JP)7f lQlf` P= =J+`hTּdC-"! ALND8 zqyx PdOmZG L~HnAoZrEA>둨JuI%I`0r˱Jw iz[1 1"HE4! az QеA jFS.5L[;: _>_Dc[q>,ꑗW1 !Q?Xr> Z5<{0_!I pr[{/"RGU{X r ]8(_Qa>H&A}C7u0֘*=E9_Q4DɱbV+C0]!C1dtNBD%be3t3|rcWAz;a]ۇ1ca S!y籺!Q~|*;%/  $XA -(JuuzYE |hJ\Q&bEHfA_ .#OP_"]+}`@<'|A "/'I%~i¼RLDjhA|Є08-hp ux@3ٌi8_H "p!0ATxArk* : Ih,8; F.(1Tg77/5LI'#(40`rH'*Rpd$CC12 /cGi+ɀ8Z X*jW:3h ,O TDN-ӂJe,ׯ"giծn0:O HTjZU"v HA5rUհB o&JjcX B88ASK  &~ O6q3YU il"{ ) ", 2o/. b9#B·C99ENb:QYBHҰR y Ab2a~ 2΁(<aQy '?Q@Qab\9lbR%Z^ ~~z8A|Fn@`&5I~ 6_ +#OKq \!&"ӑKo :?xlGGx!y84`~k߃H $|Q2AS}eY*,9-t8!q֒C6!f=I,iCXyJ* I7= `hݡҒ:ٹyS FQ CDY"KѓIas(tA[7_10sBUٵm;C35408rs63+?C@ D/ B@ "QcDKDL" n0 c;C?L\EVlEw TěsE\E]E^t.Hyp8x8b2+P,Xe숶i:ըCE8I@ b\ P!k4+S"w쐴Yp3~8GAHyH9Źqk YlȇDW–Kt*SGp0 Z8[!}x .j-2E( $  91*MYJIIH~!<1\ٮHXʲ@Єl/؀D:*qʅ8 ! a' -2>0":]i"Ю7"ˌ < N ˁxؙأr>yEd8 )A[|nxcJ8 &VjeTDWJ@Zڻ j*'|N-C% X%xvT$g[eiTq Lm4Ohb oj(N^≁R*OX8GNو(j*%j:I)BB!Q Afϧ<πIP=P$ 5'eGA<8XiR**;*4r--^D⊫83٫=x7$t =8A-+}<hـTaԨ.ʮ- `y իQ@C 2ZI<?@DT@Z\U.@a>* @VDV"kT^PNpBSg؏; r@Յ9`5J Gs9kJ-A2aFSp{`m< 2ۂ@MC4uXw 2C@##D=; }?KGCsXЈKMWUѱ Ok -Yb6r۵9,X)T-0+ XQ݁0:  SݩJk&8ռ =`ڧk:E: 9X:٥%iPŦZS&(ڌ9F9 0 RX@ԃ TCET% H"H硈SՊ3cxV=+` 4yZ,8ޓKJ*a:RЁ3{gmKb0,"kW ;W 5w]PՓhP,8 :\H(F@3m"4 ,-*d d d(Ꝋ),j\ Aː(Lɪ+c  `4I l\RVhy7C n fCGf$iӖf[1-Υ7o{fc mnNkF%uFoy&!:mgw~U YyjItg~gFTłhYNh^h9drfhhh ;^ȅjU1ȁd=*.JuILݹ9< -896[ڽfT0x0<Njy8ji .퀬&tL,ViVM`L!CNT0)X[48*@ˇ" l FTMkD"W"E~IXMN!؄T19P +؃F 茇I 3Q7x&QX VU7a:*ʞBe_ nG OLlPp~%QΩ{ !JJ[RX)OycMӧݷ䤈WBhJP-i QEQ)H'! WApN-Iۛ~lprMr Q.I&_PBz Rai O8Y?>!;vJlUaƩ֫r/ h J#t=K1SFSW@u(IȠSYil>?;-,X༂ЃJMH=;쩨\-ol$vmp,oF (tXT (`EYM J؅lea,+Gxk_!cT,߬‚otH, 0p;pcu@ay{qEtkީ$5frmi)`F91h8i;FC׫ y1@ Ȅ{-%A5=}a h;> /+h x{ keYg|R=HJHjphczbvt7|b o6x*L 07È붳=*Xr⵽6:Df#96(M~NP-Hmk[۽\c 7kO5ȥx#x4h0! 6lp@/b̨q#ǎ 0 =j,)HX S1ńjD}:hqpH(ƒ^E$"eePhHÿ} 骎ni)Q E䱈T4@ *62Cq:`?HVZĦfD@VF+m.i8ip<[Q3@HQs@GY!u^#MHP^ ՟G M'ihHhIK|RY@B=@JLlPM&_Y_ &[rr@e"HpJC@,@"k_FtF'@Ȓ){mZݨ{U.1W-([0EȠ˺.i\OD6f *a0a@3Zaa3sE%G5|3HZ;M1u>@F?0)5oAK4FD hsDkqe@nH'V)HtJJ,6p"BO\D$@&`sHFG|Eğ:-% 萅`QB,tbhC \y0  ,䐜`D 4L$M hD6M8W@tKŘ`Db.H$cگc"0H@C=}hOF|E؎)rYND9rFbHEOh B,@6@Dm8p4N47C1){ PrPD>e5tWr%Y{U X ={ޡ^W]}7El%PC0PC`2?8mYeE3+RlFrןN #D@(_Ec IL` unXK/M=JxMlNP{SOM\?lkh!t2?(eTJvlI ~7DD&~elHlC|  uF$GMdNE!B\8_@ *\ 0p ?_8mS!NJ杅ncˉUA]˄a¨-J~7jnw-q[U<ق^(aImRx4hmD%=A}LW\~$.DG%>,ȱAѣY, 5,,C= (tPŅ:hLQ6*AUpL YlfL3iִYe-}|h0  |YyP$ IUAz4t-_΂zPmAkBfA5[jmY@F^ +q.wAg5.W6^ȩ`:ԑ9kOT+,FkS@"6wB=?,(,$hwFU`_rr6 k&UTo?#+)Ԟ;B xsA,Шn 8tBȈ'Âmh iA0b͡x 2a! L:x++(F@CD4/2 YgL9"kOLHGO$O@#QF"@HJT&&IS9L3M%&/Oz@LU*t&TiW[quD$u``7}&d ^-hj>x" zv!najp\.&G)v-6uJ#tA#2~MU%i`V%-8&i`Z^$4Ԛ[aHB$ 4,x누IKz3F! YI!3Y(Cl/*R)m1i9ZH.ǂG&HRX V8iN,zV( A,.`霤ZIl]͠ Ƣg ۠g3yE@hRH j2x .4p)H 8@}ՠd-(J6(\XЫ4" /~4l+y9h b7l(3`p5H1恬i$ x9nd?1:k[S7""D ۓ c,PgXI ,mᩲ' ELjq!}H(?ʰAt+EFa;o> ~ Z1GC_fUGܫ Fd<%^t1per'-9PL|@E09" 2oVA=!1@B!)h+e)@>f. D@$HF0K JT%{BH9K2ݲ Lˍ6mN#NP 0K%Un,!b4P!8Y:SnsZ>MӛJ J*  #|_:r#ыWs霑I=":IQOKn C" )$NJpN4I@\CIд 6N`2)`-SڒXBB,IE[Td<ʹBKp!h*LTD%eYeЀE SFGwxEWQ!ӘG  j6?c?Ĵ ӡ/*"{l/-1 `UZ\AD[A#c(NFp#,"A;1 j,Dylq CX1ԑlXe-B!Pgo([4 6z6iD,MX`X4/{u4 6{BZ EPAv<Ŧ,JP! , zJj"f(6K( bHJQ#^"dVb%JQPd%.D19MPn!TU0+ NRq V~Nc'C7WZ"ȇ6 /IWe$ho9*H##pQqq nAqVjT߄ En` "B`Rle p`&iiX@f!z g *+ . k8r`vT`R`+F|b&4#>Bj\j!8o&R0&p` "q|g<Dh P;F"T1v Bu ua*ʤ#*uըXքe ڌ-TVAcʓ',A.b:J6 '0D/b}2.@`z~ C<4(@EAtD4 x^r^d#0!2Ba( !R!bh2 ↾9" 6A& 3 "3.,`"~" @B5/Že   ?SO@"ޥǚȄ IV䒠yl.&DGA T@0#L{N ";Z,D-L:",BO(N7l IxFBD%4v4!@_xm{#p-6ԉLޔ KiSIX;v2FpJHOH$%*^iR,)G0 J "0R"-P%Q /We3 NdJh0 e>WVu&J#4Z .B=Nrjs,a'cP#^_&#N'@! F-/1T1p("c8;1 Ƌ .A^^b X ! 0l+.3b bBaUU `0Tt*ƁQt"BtY%6^61 ,!" /x=Ao%l +dүD#k]ckv OY,aZ4aLMWƱ&:.IoM#t9@%ZM& +vBѶ@-(>(xr#9 $b  `_b~;fAA Rc@ VL% %cgb)nlFp^,\F pU!! yBf j ' .mF,^M5'hXp.NkԘ"bF\n`Z%D]Ѭxi, $*UnM7mt%Q5eJtUD `%2`wU0.ٺ-"6tIe0Km&dH؅˲S So"I ؜fٮ);LT"NPV~CTU\`DGnin606nV6e.b '4s"(.bBkB4Ҋ'^AuuOrS<4yQM\ y "zgz)N ?-Vڗ*32)N#f\$C." *\."#9S#`8C|yU0F/k;i3v iaaYoԯa6b. xa:'d.Z[4fe"zSZB:c #hvJ/`z#` =>  > vG ¢YĂ Y܃_]laD " m,^lLfdddLCb EpKr: k Jy<ODi$'ʔK:KK* Ex!Mu(Lf2-ORLH H.L`PD[NPT ;_LX&dJ-XPT[Imb0sū6eBI K$1T-{am}[EQ!z!""N%!%`ۻG:,^RXA[R:@#W/‰wDؕ\|Zޢm4fysonG| #Igr O(r9-4+%s-8!"_!!8# uC m $DF.5Z @r|!F!96 x5 B48s33B=g }}ЇHdR"ӑO95Ԃ+'/eb"ڧ1 8o|v!85rb>!JC@  > H6hOY Y\NsLťL)-v .x4uif' RP!C 2 "*@$ݐ#9Bu@FLTBo~P >Q **J!0N"W35GcF*C(N2.ɢ)#:*eF6bX9G"h/cENY38YBc*=[&+FrEbBXKJ#du! YV#L9Z5.V~ܑsU9L 3"PiB1 H#,B_o=OCG&:A+M OTŁ]Hd46@ B@)dA@SG) 9fMY5?W@#pg%N  oJidN0$rr6Q(U \ T "yNf͜=g &P?gB0I҄뵷MjМ" Ճ d-iju7u@_T uQ?刈Yy0\4"tgO*x 0n mDl4Rdݢ6S4QY׉w WM@mXgZUꦥVzi9sAکSAjqjݪ?];c<c0R;P"KiTύ .ږ{j AES 91C"M+[nýI\^N_L%D%(_4ݠrJ0MX) 3Eyn]$SAu6PSHȒ @]@4Jq'iCIWN(RN d8~pj"SBex*0r&LSeĎڀ <,VHī\$V-i*e̤&uI摋#M, A&WV򕰌,gI7 JՍ9eJU2f:-R `Ǩћn& 6qٛ8}^@tv<qY9W=U٩7 @JЂ:zG-ܧI\VFQ40`r4A@T&AUAQSSl,M` 78`(v.N H䃊,[+ `J3$@Z.[>MYR4֔?frz%| &jS<ݕA`ҋ8 UX"%=íT?r,/ YY2"ELxNʓE.% "pÝ >ːxvh@DÈU\x T[L9lv 9eraa:@ 4a,Sa>\FD!D]G0 , 'xR?1=(D7rĵ::Ā@W8''61XC*BH"LLd"3“#X-2~-M+ ` $Z `GV` BJ%3;2jʽ"dT`r{srSdLDJeUo=m֣>LйB@" GYRKn@ sE*9Cq5E|A "kͥR DN> ':L #5jެUtf9I]蝏vJʶܒh*r{%RT> HYm &~:R}*Ȱ@?夺Fu`; #|G h? d|qy.ڽq^_$'/j D cuͣIB'z(=\j(9qP D$>"b#qa$f1pȱYVsBE5%qKҶ{؏@3[t9HQ~ O hJTBRno*_,'\6W8QrԹ*)̇+L1X3]:Μo: [KC)9GI.%(nĠ5)  8Q?QJo\k^Q5foC&*DBR|ЦD'#(AӒ+wn4h)qdA nWJ+c-k1T+XIIdHՆ--)((dcn04.!DV*y4aOc%rF沅Δ#lS"y)SAy9ڡ3'q(:cz)3xxQE\6) 9)/mWbАa/WAw`w9aws)wh3ix˴Dp-p)WmaTB41rMAi(nBކHBF!VRdh0F-dcm12EZ`.)1rvYOAdˆCFDCFpt;% &>marDrsx9Ju.x2Ix*Ij-$5LB"J8ٚ('F (*p2șyTv9NFܔ"2@1NYbׄN4M9MǹyMTt[ǘ,J*)/Ԟ鹟ٟ"P 09Xtu1ȡGb2#-?GA%ynaRP^A(P PTJAz752yw3v[%94DSd8p YP ? @ dɀOOc p/]U`Ӧ_@WVpVLc lU9q5vϐ6Ѡ./d{LP3 Pc%qp]z0Y#Esrt![Ru 1ZQZ{%r p7@6 &VpU9REr` ]FZN4u)!YM]^7]aYe$z@ f @!9& }Sڰ$Z@ V >  [0`x`` -V` nBbK qeK')DTti! aefrqiæ3L+&Tvd2#`FRiUfgXeGf&IiȢO 09&hps]\.,XFÎJ&cP·*M)P%HU(0)C`lvi9Riiejjvl6.oֳ)&+WZXzL0R0R2ȼ)d9Ki({F;4mHyDU)i3S0Q׀UB-W{<X{?upHwOj4r7q09w[Z$E@NMKv)h?zn-A[z+Ìdq-K! ڔT ^ $r VrwZ 1D s nv@j7+WW`?}"s"k @oP4PPҐ6!1Y&7]KK 0 D[tb»)^LpվBP$2#.e9A^yGKf(䒂T$HBDāBpD9BIddWHUGȡ惗IBbJIgOjm_fmǼA }-bH O V9Ai)D1ր42ʋ>JNyݙ N*^TM-jQQL]n~ȞJo%'Daqs09&%355;% 3y8c4Dq^N74A@S?7U@}ToDA% q-e[Qn:^%ad( OV-\~d~aK~ DAW LQ0^ 8 [:}c;3_ Pp!h;׭He!3="5W /@МۂoN֭ˊS<:!7o*8#THYeS@\0<dsU Ԑ @ i"!%@H0^l1&\=L͝Ax`gGB3̋ T UM 5 aVf L-Bp_لH9"` d"ҹt- ]n;b^O1BŹdAd"*dw*L4܄EYG_RMx/׬2 nʺ/;OXOwɄ"41wMZa!d]^cעݏ.(0)Ll&vi^O1P %@4/5""2Z  jf&`ϹNZeގhT~"j86n IW5"'/NƈքPI bany`h.L%H-q>%QH)'!Lh.D$nUJ ľfm꛱ƫ1ZF"03X/> D#"x-Ow ަV|SY&K0B R_#C'abD!="e8}_ QpK-KH*a1GEn`iiYjՄmڴƵ0a$c.:ю}gapD\.Td#Q.!ϓqvdxa|vGF띎d ҽJ/| (x>hx8dLlB`) F"c<*!O0(GU%jR)CI`NZP>5O'#`PąmZUBuū X#e%YQCl3! V,rUxLԕv+^'T# SB{'#E Zc FL!q" L) H@,"&ԃ d$jnH10hMRa."ZW=Zǚ՟*҈X"t?$Jaȉt?=9;p: Z"6P&TbNkM١L%#F+GVlGY3Pl2bkB6WK̥ ` VEavK:pJnV(F=򰁳to=op]-1C5.}_׿FЇW#(XFu#>: _p.L˄aHQa{aDqe*kۡ╏O3#\D&dgà!\3!9 8ECF4S3Yc-4y5} k-J{HdXU@DvRX.5nI 8PlsI uՅ9l01%|bQ$ÄJO&87 yJ60C|E;!°H12 \B(#F 'aJt'#&̡m:L^lQE`)D=Y0=$DN"/@{8 `Fd[?2ϋܡ ׻`Ĕ'y," "=~?"踾,u>!HH "I9*Faw"H2`plNtR/AhjL"DNU ,j?sn]k[4gjb@e22?x=q8 !Hmb ŸX@ȉ' >  h | (?0AxݰV P (X@X+*Ѝz8x-IQ%@` 83 o?]ʂ.pC'/<!@~ yY aJL9! Q.$ى(YO3a8p)oz]QJ["𴎀@ ,X`(y=pFAeMӧƋF(`PɈкdĈqioDcA<Ȏi Psmao7p\渃`HhwyyJ>8:~BK8ZJ1H11'蛈jh#W$%V`HK0 cL+]@K@B"Jʌ0(K [EBɈ,i !&KK jÈ/I'؂TTB:K<0I %1HU"}at@_`;%M@-0/X!1CA̋4DQ}K i4A?9ʈZ3G2'P@%搷HLB$4%aB ՈVPY%M`҈籈X7Ý4R߼)xE)xsB]< t=4xyM-Am.Z0..?I\2A5=|!F4[0Nl@̞HI!!۳3;5=+Z ۱È{19cpi09_c룱n`08Eĕ٥ݫ;0 ȋ1K%P>&R 8ߥ1.4[޵9Եʺ rReu_C|5UNz:hJˈp% < @@4#ơ{+lmlKq .J N#7va$`aȶ&qnP6u u80)%y^ 9cG؅8 6d z`   #9?xF$&܏h?Ĉ H ›jhËB`Aj9PdjA  h `4(0`.-iA*%dxBA38= ^ 2ꍵ`iXM[!A՛1Ĉ~Y`(y#K̈Rx+OWJLˋ(yo:C+zjGsmFh0p]pTŃEЖE !@PwA^jIĭÓDq}ZtO⤌P ʠZJ ,ҊZO׾mfs=\PD_Q uLH8lg&)X{O!`ILHB@(6(MM 0`0M_%Y 5IʟkͩPI:qz zIgEIȐrrFMTo{"$jrq > F^.('e( ,7<8xxp?~NҶsd<%w`g)vI& y$()Nx2 ;BP>0@a5h _i z %̘2gҔ †J܉<J`0#l)ԢG_BP&RTe 3K,`!DXdJDK Waĺ˱B *?MWA0Q/CFsĮAf]^k T?>#iQMYb5ahº̩:hG] .n|Qv?nԘN;ǯNt~=t"u;{?ݚ x *sEvMO7/%X72/5ȔK21/|8")"-L9E1ɈPԣQ9f("C1KTcaL J1$QJ9%)dA`ApwYv^>Y%i&m( W"Q,Z RRsȴEXM M fKLew.BI"X-!1` F&Bd|$&٤p@!ڈp@h5"!"XMliMDP#(/hd0?^?΋( o!uNpsE%aD A+!%J!9rL@:1KT& /V1E(L D$Td TzV?N40KP2.%e,60Ht,BKZ"o%Dngt#$;s.w'-aSww+TK ֱG6yi%$X -2鍐#HF 0!4D53qҍFiVKCKB^o'THARo,sށ=Pt>B ?eCALg@+S2)O*K^ `2A@JN0ܯ%!  _`H!|63D rP8$Ft\(n'P 8QML:,d>%\ ‚>!V!@D;Q0<@Gfy#i沾214R@! "jMa*JRb,*KyRJdZɉN&dsl Hy*!˧2KK&x`9%@v$KTRT$³o2 ~B%t"DfisSNOa*"5@J@!a8 CtEHEE dT(EAQc.M10/P p0 KFAFl}D%<\dDrL~$FCԘ%ӛD@i#"{?QTCIΈG$ UHƒN] (֬5dLP!DgQ ` ӄL&8, e+q B3& SߞЄL u &p%@ u un H"k!yqkՀ4!_I@P͑pg "x媗/1?Dzѭ}_Ƥ$}`Ll`Ht0`4XH$%uЀl%@ -Tw#?f;g%i2?ױ1&qlo9s&sx:yZzNQ䩠s@It>a}Z,d)ÍJN>_D͙ε2ؘ\C F>i hcHfYʀ/kM QI$|&36&k;ے!uy\5+ ~w ɪjrkq4Qs6^H)a`(N XjPA/M"62[pX0_J)"7 xO78|0a^Nef)\>:J[CpWHJyh_ќFUa"Ĥo5ҕ1EJMU0+AMr\~VU\@>ZTIU'(%V:!k Zd\IG<%yJV^HeY4enjߡl#(CF"(|`Kq&/<_I)vD0L<2KNs}XXoxDQꩋB\4 b2!ahW$>`h_&]3@8u\OZ~oʕ G @>/ ĴIfn ۍÎ#XST`Gu ƠS_  !!&.!6>!FN!V^!fn!v~!!!!!ơ!֡!!!  "!!""&"."#6#>"$F$N"%V%^"&f&n"'v'~"(("))"**"++",Ƣ,"-֢-".."//"00#11#2&2.#363>#4F4N#5V5^#6f6n#7v7~#88#99#::#;;#<ƣ<#=֣=#>>#??#@@$AA$B&B.$C6C>$DFDN$EVE^$FfFn$GvG~$HH$II$JJ$KK$LƤL$M֤MN$OMa; %QQCA0S6%T>%=%UF%TQf%QR`hV"҃eVe_WQZeQYDW e\N Tȥ%QJ]`_!``_VGaf&f;4Y6f%d`aRs\&2effI;g6g>chu&Znfjl6Emfm&vܦw fR:K4 ngr.F(|%j h6IЃ@Ҫni**6+Akфi2kV뷞jɸ6kFdUǺ*kkN$ĽB=Cw+C ƫΫ櫛+-"mi2,(,J nl~r$Ȇ,ʮ,˶,D^>flf֬, 2a;C-ᖆl!&mD! ,)f H" | > wd "9Ń'*R ,`f)Ar ! 0#ŁbjI#61xIUG4K"M+b:^ѵo 'E{ .] ΈlbA 8u_w̨ (bh1f# v؛N@tx\W@uXrmǽ6H̥d)^W d)_Ͼ 'wHʫ νGùs= A-@tPӀP@:nDd?Dc@`H w @$ p7PHpS@dE1?HPx"J82J k' Mx=?QYwB-BVЈo4@ ta:џI6t_~0'"##?m׍??H(?J?lwϩ k<$2ݴ?G?P~I@|벺"p"T wQ\iEm!w4]eiP@K;bP8T}DҶBW@Qf@@N{0E HeG+.i@A2 A@r q|,D^@r,Dw]@ul"BبLHA$ O:KQM5 ;r"1|kem6lPxJoB5c 4MBAw|^׼A/Kyꬷw -ZӜ9'i)%Z'9?YToEŞY<=Jt'@ XdKx0AP,BdQ9.EpWFa `#8Tz A.4= #8‰H > @1eD&Gr*) 3|ZHTccpGy E&z6?` y@# |(G4mr@(0؋MhdKtb `P ҆J 4FI410d<3@39|C_Y40%Hcf̆" &+s?f3A AۡS?hy>l\1 t+0jbt$QTH(|19*|rMԟPB4 @H.IB:D0AZ'=G0NI 8O,lJ B=SP!pǠ[ z@lW 2wS)So,c BJg=HN'ỦNp!|RmS")8x EHv'ZtU VX󺐤t!EK\5`UW'j!!F [Dn+'TΖHG*1{4aD4-ǙḰt@s!IR&3-{t"Swh1Ђ4(BYƉsNR` @ DlsSl=`2eH@"Azxk/\8%= R$>V̩$!@G@0ђ@0p 8Wރ\f@#qR?J<"/ aK ш8ZH8q P@TA+H ?@aGxE"ČY;^pb <.*ATT  2llXcz!.kB9$t}*#B6S1Wn!3lNbÑ<\o3H,n 1:КU:"pHOc>BX SBAj@:G5xH~hOڇN8P3^k&^H=Ry`Dzs@$q;|Y+ojHFVNtҙw%OқO ,D-GIj/ @о@1{ϓ `>—%Z͟NQOl>X$FdӞj"!)/@qLIZHY! E) hQ)A``PP2eaPQQ Iw$tPp gfB;$sPAgA!! 6R!b>X szR:w "0#A#綦:YP.tb{x!\-PGe_}&ՀP E=FzgtfD V][EӐjk' V& p#JgdIze%F*Yy Bki1- Xxfyv'l̲IN^ b |!AզO=9KFaO0]"RpQ'TQиpQOȱȅ/v^+c0Qa`QZU=.y) 艺ҁzOjX_Y6?E]frlt{/s"Qy27r`9$eT7'1X{u" )0 ۱R5V'ZY' cRk?V^S?w +Z΀EY9XUa|ԏu1eyXwGyy 2"J6k\)C;oa1_~d,酪J$:a[ń!H? {P0aA/hU ED_&`gD@{AX"*^$B̓ ęS'FUCC"Y>ؙT҆ t pB%7! +аLV 0 G4 50&A!υ(7b{ 6 ? dXgiKHe =ih *;>4߮^!uQ8ˆ\ZGi+kXܸ!Ƃ *(;A(A'_ 2 L,Y+T| C:_~"P# b Ȃ B 0 @؀82$ 8T$8&P@F2 "X2$)džBgv4B> 4@ ,Crڠ)( [('O2BA EQzDR dǂDD ?IAI/ŴJ3-P6=4 EQ9]!F eUZCXu)[5Zm&B.8!c'>| ɏ#$P !iћE%ޛ9:)&HuXGh4Rfڽ!wc7鋐rZT$"'x/@ ~''fgv-{H"-bf9(N)$pQ h A`MK C(iDlUR++ +L*VAʐ9@ ]Xd!rg݈Wq,0?d`y=3 H~xŚ!wF!裉H<"'AHxGCpxQ! " JcYQjYD{t(xtCa)yLX.0_{$9̇-Г=r&(i] Ҵ+"&C8N@2G,lʨR0VbBV@9T^! mI眰6hDEoVv .- !zA 6 ?$$th!X}< @GZ`IGkȘGIB<ipB&RUiΛ"@bk ;+ҘT(,BibH|GT8 !8'$6PA;A@\380rUdW$P&Ց"؈U"bDPTX% RHlR:(2!LGIIhsB,SlqxPNT0,A0-A}pVI$d%Dc+(,"!Ar*LH!LD8FD! ?!I.H{F(axdm"9p1BVF-- fdި@Q McG?z8^+4?> H- .Ohғ7"hokn2#5[`@?  > q\A&|d{ x@ dhuBJ0"!='Fx3bC9D 2D+#Bd"\Za1@y>RP#|]P&iٚ p[qbG  )A$ 0t*QayuƐlO̚ y^62kSQRV4ARl=3!n=@lEQ35 ^HMŒXX&1BqM!grԬ&YJDzdq\-@wYJUw:{6@٪LdždjIf" 6J:0ęnI$oثEvy{A$QN$?R m'HEr{~ MI֐r 9Cd0!#"7}aH`ns:% ѭiDd[o+PKoc-p Aq[Ww%[ #̂ Q Oy#ޜ̷ ^7'])F# ]M 0O( X]?GЗ wR,1+22 荂ҫ%{. qtӬ (y  Q Ц,cj , ֢B+ @ B PB;1a[0k , 8Ѳ 2lgU"ďԂ#(&BRY< tI 8IVD@]I8QgRC8QQ1FdMTF4f9X[T8SԐ9[4Uĕa$FnRƆBc|FsQP#o @uD k4r+i1y1qsxqzisFG"nǵp$HCqzbP!ǂ+ H@ y@ vlizȒ4Iq YIr ډSE8ZSёbJ (Z Ps(ډ9<>80 -]?]@ 0R{Gv LXۈ3Yų$[Yy ր FQ͘(ۚXEP =(  Ysm:.3; 2xF%H ":Ѐ+] 9:<рH3Us F1b6ÈCEuzD[!D_t9ЪI2X :lYF ^sÉ E&XEKGm` >E]7OY7ILE"!SFf1_`E<+Hs]1MP]]p<{f; ([scs+ s̊2zݑP D]P`=iI07Ke25ԣF̉:P/Xم, x,++8("䒑NȑmdX/CZ#;femK/J d@+"hvf QhQء˫NNS Oڭ4Y*KRY .0ĵ {0P.K9!C&)o~ĝ!9Q$ìI`1AU*LC,,̂g9p ‡+P‹"C+e5 !XT@ʼnSIoz[41hC y q CfbNaIKQE`iS9d \Hx#`VlKP>K7f;f^hU9ufi|NYIPm,=+ؖo4vjVS"d4țn^ GXȆJX#)*nFkl7\*xOvΑh.: e%@X҉YGeʮ,PCRP4M>Px@% ` @Պs}&0x(-4-5B!p\úpI g'|I8[*h9ҬhIR(a9 vSjWb&ZxWH׆Sk*z ]YĨA(/zm(/U@(PUV8g+H' ]Du9);z\'9}GoPw&wh FnG9k3?DYs}ֺljz, O߾gHhG@ω' (|se"OKO ҈(-\Vc^"+苯@(s@ٞЇh C%m#51c0X`HۘBߨ 1lA @pȐK X@ǿ}iҤ ѱFBZ,`H]  B1)gΓRRja #ȣT fDp?bnƝ۰ho햵Oi8%DGT)` ą$! 3Ǫ,)T˥ I[ >,P? mOb2bݎy9ÔA!"9ߧ~p`p|XVŽmO /CxV `)%Qm!gтrw D&xx,ZFh 0u|w(d=c9}=U6Wǝ!2$Ž.n K@[~~fH=c&Bz鐆#U@_-$gPChѝ&e"J:iGQziC+dC6)T]ZD ڑaMV0&U4f6E:n/UlCaZ9F1"udPQ#[= V@i8* C,P}Y׶J+^B1u4q$("YDQG50%‹* 0tC[$CĐ Uc?p C p sN prO <448TY 4C?/BRC=3&0. pBI@RpC Eig74 `ST ĈO  6 ;7 yG[%pCF&??&Tþ^  ?2D%logB7m||)ށ"&cE~yF!yQ MNyC@^X0?$KTב<~< )E&k$[7' %[SLW&D` Kt.b$5ԍCn,s;rCu@'oC~Ȑ@PJ!1%bl$H4 -r^B<EH0G! 0# \1,z&cэ8 ]~|$$#i@oSiHE ēçv$ tJi:I|%,c)YҲJ)֍Ed|'.8SǐM!KjP hH2&Є4B# DC$r*0 jb Ѻİ.8#GPa'YP#1bz-iHG d!"8@6h!aҢ`"АM5tK#CțC0tr!Q;iړ!S-HC= $$8*hj0L:R 0;/ " G`/Rc aXU+R|f aDJ<wIZ<r85{mTs@+f>hE dDҸ]ĺˆ8H:&u$3I@b״Bb "u!ξp\RE0BbMjA K~ @ JOED!FQ=tp@e*`!g:G >C@ (8JP [3eqL0cT$/C T@#%3C*4cqL,$"0 (n~yiҤEQ5 "``9Q}f8߱#@3bI1vc*8YTjld[2=u%#\epy$#G)>ׯAKC,{R+B^>rG,x?{QeD9tda4 df.̮ 7z&s`!)k2l3;,4B]c/>@6m dgDLmj6qBC8$ldӻB4R|L.*@4ny.K| ^T ]򱈦"6gBGt`D@JHS,F !ڈ?y*9 UTK?f9d3@\DJB౲>S ^/ @ ouhCVEAQeTC$І@

z%B- J)/)09г "`WM4T9kLpdA! {ν/}-bǂV B{c :7)#Re)*PSw y6z, OH}̧,۵h p""#yTXHH2*xRF?ce$ ."5$@xZ)^a 1< aZ`e QU5 *P@Db8d9Y:"_7+>/sE0Qc !xAQI W`Z"n{cǻcq "HB"Vz1AEG~XHLfRTIpU#!RjFLIXRe-m K u4#_.̡" B0_XPx B̰ !D&A5g@D c?Q?n` $j‚ip}ո`Fg t3%D7vG.-L8x KrR8hH)6XH!7+M 2zq08*DP9|n rE#|(A.q@tv[(R>B*FR SXADp`Y`&v9"25`P_ "frjF4pRʲqM1HP>.NV k"mz$t Miz%E=fZ.hD F!3i]0M0B `[uE)hva\*R%r 2a˒%9|: Aķ#@p|k(W^4˅ԭD Hƒ  "/A xRi;}4] + nuj$Zd|)[lEKrV|"l"?A \o)F 0Iʄ8~#KiK !PAt2҂/Q7Rn1!tP`Dl1J%B ⇼-Hb 91c"d-W+8֜"z!!Ye6 N !`A}pj3 ŷnB#>|cut.iOsv!o"Lxd| 2NBSBb_! oO ^>A<@x@crerl;QAP!Ģ L|AfIEe-B(}w^GmSgo) /` N2I*U TDHY# y NdҒKJU@{<c-H|"یqm_dȌi:j\ 23ASHF "iR}>&F6B68*FڑzL €FZ@! SMA,hYrS/Z!17W ZL[4aP,.P33>(XYgӉx i(HnC(yq.B{ (A?)ݰ"i:Lـ[Aa@<bU: 8[;Q[s-!I@<: R55t=.B3Is:;AC;CDԠDBV'" (xJԫA8" LF aXXY_z7"{<JJIԺMs6A-M z.|Wr`p@t@|i{64:WȬ% ?"@3aW$&PA2EIRW1~1 x%܏ "|Oj$1z "00 C"L]giOZ_*D.ҷ@H밋. Bmi=R.;G@ONCҝO in,m;.¨P,S jOS"kHL$rFp41+7ə8B4CpM!Wch#\hUU]w f*T;[2`z24``S`5#; %3 &a§% x YBC]B!2֠?0>`c"2VӶ2P:c5b 3;59Ad"CG>ZI#EdMdNdOdpAeP.eS>eYQdTneW~eXeY"ڂ]T6YojBGa6Pyi&ʄ “(H* =-nj8oHu8t^ggpj:kgyZz߂؂$q]r@`UAʤ>?zVoЇ!5u$]"i &y7X#Ҫ8r4fEӕ餉iFp&,Vإ4v((ܴ@)y1,Fpſ>"(M5}Dj! +#W!(h@^ц="f#ztB+@O5h0Qaa!) ;RCEADObmPuo͆r0n0QPc ,CmL g$oQ:3`a3ⱐ~<ʛ6U:HSK@Tq!˃+€^"@ی[!?@w*T"1C >=j<k._״pU.-k40iˡ46kxpTIU6f"iMV"HfDXzS>Z|P\} 7C9Hqhk.)@  h9J;x]G x zz+`OvXvבhVwkE] #m>t촳CvZ?H~sO`:8\5_"[FN: )D=#Wȶu; ֜ <qGOW|Ryn 9d$ch0k3XǍ i@bxKH}qr_8 E(q$b;f " |!oK |<_p`-D폈CF6?936L˾@6P vk TT\14kq[S7mʕI~.t'/žw͟ZdYr8+7W¥ Cml93=Kx? $_… "P")b̨qB8 Ȓ&OLR'= ̚5!4H?< *t(ѢF 8xt)ӦNB*u*UxYլU +v,ٲf3BxTcڵg+w.ݺvd$TJ֥&GCk3DBq +rP^Xt6H^#AMHQk5\a!D@<k+ "7..Gyxf%&kl[+ ,]K -P -B),Psv P8/0 6!@Dc C$@ $Bf|p?B[eA`Q1!Bx` UBM hQVV̑"EOQA#4ס(?(N yЙF+(4o}I_Hn!Iz#A>f'QeFSBS*? G^jesGA'DddFB( HZvVu  "XO:9<iA]CazA0ATP|SW0 =m*񹐼'W(m.TAjPRht.CpLC8l/ꦼ$sܓA1F~&Lz }'teq9YBhpЋ@Q"Pn:dtA6P;F(nIu;d)Ap^R ҒDM k% VBefBـ.DC9aDnZ 9&ҴT#4 9зߥ9$]4&1?C磏]kϢ /lmJDt iMƿUQJ"1BR08| 2TY[^$dts(?BА ;8L2yChZPpC!nA@`'dF R p'SIK<vٝCz3.Zv-LABJ$BAUHQ THFbSMT PIl4%y1X ]E1djQ B6Qefr12H.h$<@2#7JFX簟=g(ƒ٣qD\3fe֦f.\5恠x0iɒe汓,'A֑nLB$ :P,9(:Je )PHIg# "Rxd&D',r!)UJ ?| "M̌ 8ȄN,R VP̍C2cB)T;]6Ad! ь~iF|D~ArxqY mL/-qJïa ‰R {C$Q1~9B7DtӨ-  QC^x)3HB>e+^I Fz͠bKp Yb¦B: .%z Rp]-saegJBu)t]9!\\]t e@Jl"32o 8w &+t#y~vD& f&#GMX H>B8PZ#LgW~;YndMт SNŔlW7,8y,BH9I.%O2/r@`^\񏁲9tIU6uw^HgDςK[RhBIGC:Ac&øBĀ8SْF뉑Kc⃅TGXH~Q .BR"F )q'F< V8ć]U5 ☮JT5A3L[ M*D%^?xsd"hZ.'NQ{WߖUN+dE ߈X^jR~1 G9(WHSXT,tIG.H:"%Ro!WBTn#%[!%Wu21 ^P+3Au=PFYGAR8B'ZŊ6SuuY.mGy!WG"Џ$T@XOi;HhHoZ cnazGTg#`ULINsv}+#A}5w153*Ũc=TWRI4_t`GhFޥARŅIėFT`yI`Q!!@f$sB4ddv|fF АS@DddJ /:EץD(P /HJ0>\Bwp݀PA@iJe]iD"(.hBd1C_ 82,D%^_M C@W ڃmDj8A ", T$}0t܇ P@`tHCboBXBup` 8F?@ɪ @ H)gpQN̑%C!(&)DAzX*pH6.İ)B0KdBBE4%%TC\G`XODReȒCb0$ф5$D T2UTDZXS$0ZOtSQbFfQSQjSvP׉ @*-lJA! p,JC ʮvaV.D<1B9h߉ìfvR,Z8TpE*  y&imt\,Bu(GBܖ-0?܃ 6CY̓9(<0dU l.UW2= 5QQJ@ݸ=Rl̺hўP}<<vDL u(vv- ˗G 8V$Ze ^Tfи5JOb԰[T|ȁ|XӁF Z@pt@ T@)]Օ=H\)H+[ĕ/nYLVQIS.BD) ڒOL MljM@)mC,cAO(1*DyHkE;tfȣDEtb$mrT`~DI3IJ 0DCFC* S5op0&! db2uF؞z J$;aGvBM%85qOϔ$KFZ #xeFMz*/d?ǾX\fҶ@qhwhwvI[KK1Y0SCs]>K0As/FcAI[m&PED,6*BD)5(D27(Ú4Q BDM &*<˧b tlP&Fp@Nԁ! (Hl 2-ABjCBK 8܃iJ* Z]lJļm<|Vf,t" .?KC=4ȉBO+X!&_$P5-P A $H 6,@Am{`A)߫%XDN_O@r5us-t/@{)&/L!J :tԁ<12$abWcFl\ʁb!*:DB'\؍ĕLI|o6\R'kܩayiT/F<@rj@q$" hMCіl#!"=liQF!QxQ :"Hhb٫jDJ j{GA)Jv,?p$pad XJsPՠ \H'*s `EDBR¥1B ;@m*Dxl{%,_"(&LY, P8Y<Ĉg ݂.}`9/"R⒁,.  ,!(|цrmk:Fb ׅ/4"NĴH,h/6R-ڬ"(ɽʠpD;<-[N79bP#k}! rMkz|{@?GqI}!4Wfq(Vhkx;8Md&rbq .W"xoD*H e-dpe D/.m)G>dC\Ib:A`)Xԡ}#U"R X@x69cE(@t#ZI24PR8TLEh1ª0%/ `9 ^ `_=`q; pURpFXNᇀ"UҤ#jo1M##ry$ CДiCZQ5h2Hp $@ &hRb9 9g;PA.AKjd TəHB yFԁ!=^pl hHz"7qbz4*]QpJʐȠBà@u'X< *Yb5E:\:T9tIaXt:.> VZVu|@VC +8k( y R0C,/ B9.~C!\XǞH}c!/X5bCֱ`e1NvY׾K] [Bjmt!pGUr5!37 H:S*Yr%HʤDG^3 |h% ˔1#'ͅyR&s  r]5d<0?ޕd<\ C) Gh@߁̗ ~T'{O:VcXN& eX 31T<QP,f+-{k>2)<z {u.R^!b"Ed s o܁%'-4FHՇ/6%xED`2a1aGT&߾"WT%hHd;+A-@@dq0L г dAZfY&N`EZ2A`%("񕣜Gъ=b-[X|C]5cy<PrT[ (h s M+Bqc2MN>)ȉq h n2Y#8 PvA!9A&s $}LO/6ݺb44NF_"Lhez 26*IR.bKX "ҭ`B!Ob&`XH bk n+E[bB-A|P[#&C  P#/ P!k, L$ج "o@~ZjL@-C 4A%'ƿbIcf# ,@]$ 0 v%h pwp D!a r۞ _ ab bC6 x$B o~H l n6fHm QJ q1Tv!.бqrv,EΛZ3,Dr=jIVx0"~* ؎o"tv/0:rv"l@ju:,hr"GqR{,Bs,y-xr#{!8/ \c-"]p,C"/$\B:@b 2p4q!rO;҈`OӂCbdf#j,. EVrj4#=FV*$"r<Ϥt:+ x4D5IHRT&cH`$U `Ht* Bi&E8D@6ʤnV ~>j9.MC%@*LB.DP2.O+Py,DP+Q P>5p`CQ+Q?TCQy H* AUk!}A˔JUTX54!Ts! :@karUXU[ղwlƏ>t#/r[ו]]i,@bn` 2P,b4: ( Dgg< ý aς4@@a3# pVai+ " ++A6./aΫƄc$V*c ) B,f |DiA½b"+W"4`*Afʆʫk!ʋol2Tά Al/,j R)4L` h>`ڍay!3z͊z$cr- +r"L8Ds!`#qs"<-." ,D+ zm'"Q B*}n A+xQ:I#buhNMhg!\Dc#>@v玃53+0_X-4ZN=xp6" 2DN43NJc%4>.lC纮>z !$/lX,DOl$@:D:c-<؇2|nbL_(#8ܢ/BMϔ<%?:A *8~"BO)o%j^cHݪFvgEB~ 'r!45,#DB'o : 02 k0j& o "p$pbJ!j.d5ZNBWHnvЏaM!hp7`Yܰ%! RaD/icbpԆH"$Ln,dE J  W%ХW$PH+> ZЗKAW {a X>b|ā&@jGDвC!N.-,,G(o bh~KhbvQܬ:ma )veUvʽBVJhQpT_f@hAQB sq/1bFǏh^-,^=aCornڷ_tƁo elM''.jxڑ,CsGq'CX!u&8tN#kuX"[-B#HyEqL?/'5R7 5`z"b$kg/*#Gq$?~v|bT-Hؔ}$fH²+X(SaE"aB/x'"a@T "/ .Ǣ.+F * G& 76"Rh&: h|с)~Ayhjmɂw̄o@I U.# 6Apȓi5P==u6邋FpJ =5=\α J:K"m!v$ I@3CxyL\.:&#!n:+Š ڢZeD@t |P8+S@c蹛f%>,,xǣBF;D_6@"2cC(R%~`.hH"G#"Clĸ%ʯTf,E9!Ms=l|tE< dthFx*NԎtRgxQ<ᓋRoW=tJ]^뷞뻞!`:`A»|>TD}~,6앶f-;(b D€*a,sҾ:7,-?:Bhv,~^ Vjad A~*F~BSA!' jAD&~A ,tOɔ  'Ʉ!vR}u FJ&"ح `"#d H߇ Pp?F 4>TFG Q"4qӖb"T `sD9_$\H0@9q4( r{܉S'O+\;M8)0jӂ؆8=SbB8qk滭S9?! B$8 r$1-(qRcԡ?o^tC< MM i`ly&B5)L@@5LRlz!L .0- C|"(읊9MC1D4 1Ay&%ā NֱuWSR>ҽ-v|tYcԝ=Jhx\ "H8Ic&@rH0D@ǣcԠ2'AEk%r4D>Z͈NF;ZfAC-᎚0:^oJ8Qco"ک'rڄ@gMZָS2ĕ {dP<\l8Ͷ9b͜jP;0Esn{>s%Ȧ(I ~30Dw~=4| Qq 36 O$v;5JE{Y G850DI"cCDEHP!pjS5!EzSCS Zd ud);"Iݘec$sgfBo?<]4oRIt!Γ91Ic`nH$`LIe2$e0)/ LKdLD9%GjJ'7jK5)J>8KD[A#޴kI]+3OpYN+ '@hs؉\D^:4y9P""Ʃ~Aѐ*йS SEADxY"E)g*WD &)"~vxfS\6T s<'1A1l+'52XC YjU{^II12ry/oѬ4PvI 56ѫ}ez;'=yAY0:[BH: J[S;S);jOAEqj % sPѦeg 8b eB.ШaJҲ@I#k60< _`  V"WUǒ؎P$*Fxl&^1F. g_bK]CfpxYJad*0W֖1)cXd*gx`U?&@ n&"f)36&&hdX{MnFrI ֹU"+n@cY 2Wkx[ -q//Ӕ /=uj63˻` ooz؛;Ġm x{'9׶pz[{'݁7GF:Hʿ \fД0 WQv0u Pr5tCP%3P1;At S M_`SQrq<_I3C ?!ttQ''w;uˇY  p!gv*vIqA 71`-xNw=qA p*B~ Q2#Sa-'a`Dq[~1JzOB.WG>*G"@Va0f|A2G3 ZQ>+g:rw[W'1ðx|P0*! |,/ >aQhw&62c$EQwx)p @e+1_1!s$G +P&Z%P2"\+@,Ē` 6ḰEp+"/6\Veeq:"VLA)0(a:bz+2Es/\;x  9  %Ԃ$"yR*m_u'6i13 yAyAy+@- DTzů =Q9ZYٰS[Ss2VS )VӔ[; W*w<+0H3{є33EǹQSiy#x7D@jAJdI ICډC$Dw ;TOD]kTK\t"CKDj4GG.XVGVm, K#JOIڔ, $ڞ$ hd*z/֒J%px⤧dMztJ!cL "/Te(rFHTH];J 젹䅫Ӻ]fa+@6uNluZ_ХO+$ҩ6Q%|ѩ(~?e#*QEi n٨o3E:!\)e`p-‘*!JS.Zlw0 N9k*9Mws:$k20\\ˡ}BA㪣9*"0ГhRIzch`џ0AE",5F-4ƴFq/>kBs`eB+hBAxడÅ<2B/b%:I2"n0/IC`SCKZ,!@]JIɉ9{>XSTUV]~Ve͞EVEj:5beYֻastުk+pո}% TYfΝ=u!^"{Ӛ`[jեun{_.Βf-Yb>HǞ]v&~$1OVKՑ=!X8l+^n : p A0B br 2: ,D"SOjp'dE_1Fgq$Tērbb`#H`!s! 9O$vB$`0Iҡ!ɔ)/pzGX$(bl3BGEtSAp S4Ha9P%JTR%˪X96C$LS@1R4(-,p9g+@) < "8 pxt.t(HEN(VgWud`^ x`fX_Q9.g+iLG8}=iߩ$Rl# ;q5Ys()N՞JsNzO"$2ԬvNFsȹPvh^qR'9&>,Qnv5B "CV\ + !@`Q" .* " * zoppjkv \W4(` v&wvx u 'tu︧v|`WX {n sY4# NL+с!KZ(9! mz?T3Is$ A&T$%e!:F@J־" ; XFЌVaR-@4L ! т%mllCu $'LǩR D-m]$j%@IU#K jL$KvPÒ^CCQ5[&PlG)L;p.L!B9EN&?CvjZNp ;L`C ^+9%R-ITZ˒<ȶ&xv`A\(UtYSګU(S:f9GL%v3 @0M4EMC2ܝXE Abn+f6@Rԉ5٦x*dYʶF\cWBbC he'HT(ZD, `!DHDL@f%P/avSX! Jaosځ'<_q_9?S4v!E>r?h4 CV7,E=^2\i2[? xÒYd?@g>.5'!r F"l:L7Zsh 8Px9X}1E]IG9 !=Q9Hpt9x i9GA8A)Ts L SaAyl$+ aqf !؜CƘc(ZYD )A jYY/ "D Y 86[ `+> I\ND< KDŏ+B[ #ȉ:bF9Rܙ!C <"bm` *+,-./01&263a4X5a6X149c1c1b8cucc d|dB>U8EfEvF.BCFd9%AI6WmdMOM@,`LXheW4eTVf#e!]JaJ_]a5b]XdfYg6ahVYi&ajFYkala ffnp`mgufvvgPì{yfxfe nΌ ̬ggM.h6Ng h{h5fXh:dhޝ`ִ^֎|d,AstvSfigrܦui.چ~i%ii-jjCg>eYjic->`1?:h֎\<.gJ8V8Jk뺾J9<jܤ9W.+\W^8Vݷlnm*p&іnV&nՆFmmW۞ܦݨVF`l6ln;.nκVnexnnWl ! ,rH*\hÇ#JHŋ3jȱǏ CIɓ(Sǰ˗.WʜI͛8ss&̟@JѣH*]0ӟLJJի5>ʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KLrY$C!8R8qHGECtRbVC 1쇬$>&/?S`矈}]GxNtA\R͈a^a!%-b i0oZq 3oolV9DHgQ*vE-N]3"C$lVMᵋ! ى>mSVvo9@dD C>:DD X1Cq6lql'DDϜ[$) 1m?:gw|C|02(lYiFy>DDVfyNG2v>4@ @XkDVvCm9(G[j搡C[萌E#c?54 >5DdNEJC:i#E*r5gVז@@P1sD@@FkGGAAICA{vCdqpCЁvYV3@BI>4Jd2sp(>w` Eth2[1@e]=p @.'Ei"ۃHǔE$}ybb&S1˱U؏7 ȟD&   8z ȸ<.w{6H񢦳 ެ*"H‰@C)3]A"*u+(\F`apCl$~뉢_@UΔb@n)Ҏ(9pN,N- ǥ 4 Azi☳wF6C lX#=;hE\P hlߢ} b)FQ dR0d"4f f +2 bTy1dN!JuɹZ/o_ Ԥe'E2j8g }V:. +ez"ƘǷ5<=Y.b;!3A?#>M$ |i#[vh"1]v+ZfjʹxDHLjp.GXoh>5"kFH1G (ϩF"A@=TH1;x!1Oyq8BaDGu =lj:IǍEx &E$ #GQFH!-!DuK2K(QV$DgY-B'<('^+=SSeX7"Iu D #p!VmH^ $AIBDuH^!!ǡZ!Ԗu.g|k8W76gŧDz|G$ }$ H:((w،d?}#얰F(cGd!x |ufg4!b0uFC2q5E#~4!6.C6Pq5WbQ"4KE?PN 098r%1NY63r15,!~CB%u3Aq5㗆)1x%"#q67jY 6q&eD9A$jS04:RJቨ@':q=T]iZ4q"DY[[}zѠF9?{#{el g04$ma$!`K4EC뗌~(T(7t1wa2wf^@' -puQVr(4T+(0UJG[P*TLOp!TlUG 7'wU8tehXĐ= DY"0yˆOPf%lpJae4s?i(FwkM"/0YF! *O7 20o-0H\2oQo +8I;*$HEp|10i Ue4#<%_T1љ/5SMXA$aHy1yNUuU4i0{TW dYTgZTU$QWQ^sY,a p.bdxC70Cq!yJ1[9m\ ƈ14ɥfq(K".!]эAg8ږ!`)PQ!w!#e]'9.zw#qaa)*0 s"ӟvSg҄=O/#Xh-)c-DzjQ>yc;0d+!`9';E9iG9aҦ^hA;F#XYj Ss 6gcgx{f9ױgZ)3qqat: nX wk09GfL@qypG l䊭J׆J:9R4j:%pI ۰_p"aqJ1q3 V[`'[!/iZs.QLW:rI)t,6bnA[F IFW"t}:t#`T cqS8t9VR4 %z XZ q a09{EJH) E}i`,@| .> .0 vP`uG dòDU< rpċwDlK;ۀ8Es9#~/*5! B9c$H#1Ƅo(80OQMUC,!_7< ™!jCӻFchM/jg&Ý$A8*pZjAk9| 2! reebRceQ<*'vk h1da+5a%Eaz߅}$J(]2t 4cfDQ{w+jFV$ɅӍ4 ^Ek .2afZ^Z=,3Üqr ij;)Œa }'iCG3% 鵮|@Gax8yQZw ium 0P 2 *rN p *ZrrϚc fl%s4>^xRЦ:Z MT#b 5pĮdq*}ϡe(KYv|Ϊ: Ҋm E4 ejP:o:%5 ddi%U<ScjPM!eVRJSC1T-1/E}H;Ye=iɊ\rq5rƛREVTC+ɹ՜~MɅ,7Js4,#ɻ&q *2xuPbH\+8J"0JY] !;vH3mr1[K  y2~\ \H', `A+z$C𰢘D-3D`Q y /ǂY:ݲ.$>bf[Uu,!EJ*)fi ^-AWȀcAj9>ӫ*b6&Jݤs\9#G [&4g`B8 HVAsO@oW¬"z.vd0ai;tAqm>F֡ êxDʘQhQ2[\SlNc,eQ֗$EmMO՘*QݻCPh >鮦( {E֫VGG採L8m,Я(qk|7^o{E"EFRy= H1q,@!F%Y [e"ϑ1ar5CsfW1ur;sˣ5"6T#h1 K+*`["!2)[d'4G"'?&w{Ez(3 Ք|r.l}nРʒ-gfn{nDQ>M?)6r88~~IsdS4Cw0 S1wSl>U۱J/+d.cik,ZUSۖy.s k/:'*rA/\tHNC+=N*pJ2̐"5 g0J?4Typ`FxyRNS/`BX@UI diANì n} 18 @( $q*RayIM+M$Bjq3cX A@yhƂ'WԲX' 98D^\Hx7{i{ IIf2'*Il7!7?RFҕ%wttMa2h7 ##˜64 τf4qs˄P2F/s | 69lJƚ}f:uLuӝkvRvmXڠӪ'< i2ԠEhBP J)Q̀\?DU%7HS⃄$EIQZ/@t-[xțnRp O֤  [kڶH!jA:F# `Bը5FL1}m DցЉ*A A#k+D'SH>5rD Y ÷  Kx&AOI(md.<'H`=ӍLL TSZ0 X+჌ˉ0i-hSC9ఄ `!I8 `ZăDJj{xˏJs Mj0(gx`9CPX#p'Y˺J @@<`C ъG?| 4,51=ۊP6%a#j* ð"| 4k+,G++U%(d``bLx!r P,xB$@ұi4 v[!+Io$Jh2q Gj%r +YQ¢$YʨL h$( 'hj#ˢ4ˢ4p`%`yP *y(b+Q`d'K ^xݐdt#anDf qz8L.z {lpɜ c|dքTB@Ƞk> xPr H݌D1غ6lBCkK3Pe*e:KH+U),ĉQ`BY)>̫X,-(Ҋ#(Xh ٓ  & B y t`1`gKA , j"MƜL<īl|^`& &&,g%\QcYw%Q8pEJP.i&* ja T9<ȭJr-08HιzXq#)@1 ai7f:(X#ɰ (+|ƝʀA:cЭh@8LI QUQ/r[00Gxp 2/?JS 8Ǽ 2"(9 p IQɶhUPx6 2282e 82]ՀcOIKV5> 'CxU0iT |r4|6#(eWti:ɸ Ax"J$PMÜQ(`:`*"PXTx8XR v덫0X@ I19 8iE8S-=E[((/kE8 ل(iI .fԐX+qlL.Npـ8p52Ue-TXQ0&qpeԼ<]a g-mȉ:JBiIR;@8ì55 jI):uC ' pٶ,MX;l;KҘc6Y21M?ʣzQG `qzJF%q{??Ɉ0|:?lqBˈhЀ~JPئQja(&!3d!w<`h:\ ߽B-6Tݒ Q';Al $E|Zhb&F@ؕh-Ƞ(̄(fήmÀ{ n ]⅖׆m\Wk٤mxێ.,P] ^q.8 +xXB@ /Nyr0P aɼROa`O sOO,OZX)#7yXOdI6ehehUY. >iƬ>n /'=#Q0@0(~ 7H ѡ׊(Exx`ƥ0/9Q߁lޟ@Lr%ުFF %/EP Ѐ ToC( 6ٕBЁL,#yu ޛӀ U( Ġ1֭{> 4#KCR>OQ'ܴcu y@?F'vɫP u-ut߳PFcUDŽX~5HDU"a0v ,WǴu Yw9$ɐ[փH u/# rI{E@X,eSYhxxlpe6Eo,t^(8pb%>G7Nc 9!C-iiB {`}{i#{.gܣa ]xT rH̄(jh'!#8肨d~F T595#|@ K_ NHB)w=Eཇ879 K(ц݂A@ߍ=|YZB Q&+PΌ;O3} 6>X(=/?  hp#"A d!@ @d |$9ɿ_yahEvVVnx#Шln #͉W_c̹^){=T&j) k R=/ndǗGdV5"G8!f|i9Pɥc zﰁD/b"& uOEAw"" Z"5IᤠyA Lw!OM;5@,TG Q=?rAb4PDD$QW92UdO)Rq rQ*tL C@C_2 $?D@@$d&H-Z0(H L?ϗAJ \ š@qS\-`+Y#n *PEF:D{SVf)Z,d=`AVԂ-T Q"TZumm*?&A"p ?~/I`ՙp`|] Te [St\S.X斛]E8[-q s$,|39'i=tx7 UkEQijUDYk5pXC@SW@5NpG6/h7 `K]7 >8r1-@9'0;Qk9{9?FW$ t9֠:>;~;@R Gm"UmIFI[䃫 G j3B$O9b':oI/'L $Iߋ7 A/qdDy'iILԕ>HzcJ{.Q(wǁrr,0%a ۲&ED jA #1$`6@c*U`}Ǣ4%cn=#l83*qAhYQ oҚ cHG!'# ~;Ht<DYs/ABHE% S@FX"R2zw%7b<5iPPId!y@ ⱂ!>/E C'H @R̓@*y@ ̡r!lI""`BA 'B͔CwD$\ ad rt|`ډI TRx`:q*Џg<)NV5C$t@ei(u hf!9%yp@jđ>S?! =d3k>~2@0}1' Xj I SM#$Ip2R#q0JI4GcЍF+< Th`m ABRٜ$RFmIbQaXEP )8j?DdĝN4b~ dM}]_ȶBSx  B*O'"b{i-P $~CבRD lU* *aV`J GtYV4,-)jH,0WЎ"΀"ʱ Y5Q#&xWPU~,zMfbI5J 9GNLZs! d }&/ ļKb >%P$!3?ΜħXA4= sG,C$3iHp= ldD#[D$ h\4OUW`RODH pyPcEP\KJ1>R0 HTx@&)n@3* @!MD 4J͗ԩ։ nˇu9`0~of)g@Q YFMՀIe)UHP'P7)j܉N̖EDFXe*v~jNH&?,?4_A.&/΄u%DS~O4@01섁KR6%}A6yfbI.,M)H `GdD4 C ?$%MzX@ 'DMzqd}#DJ<+٤Rţ=0[[ ~ I,@EUfAʒDF 2]CDM -H@U 4B D x?x|-RDBG8t-P,Y?JDB(\P@".2]KDj8iH<S0LLZKh]()^8ISLh@ɡ !Ix:^4UEt HIu 2@nrkl)EDP"RȡU-= R+.Iܓ&ѴS::D,ܙT `Hĺ LEj/;MRx"N J@VtpOFKr,e""D9FD@ T :ylP l L[^p,Y C<D#,*q P D"UL`azN#O SDDX(RL_VH v"I1H<MCt!P@@RY9lOXM<vD YOȤF G M1gqXq0q@ DD@eDPG@@w?M8Y|d0RH輋gK$&FZTTVEY4@1pP@Deօ],D,F.^YD&o&V]g("hH۝:I[@@IlO^DL@8rIe $ N$KPÉxdE&t2D+pzx -9 @,p QkZ3H+U p0NM` <̉ +p?Z@HL(p-88"F Oɹ)nYlPKMS甲TtZYvh"HT:{YDbUL\v ȀM9vEd@˖źdEͤi^ܱ<)@Fx]Ki W%3>΁E&jY$#onVp0(Kwְt5vo  4 80>;r !v}7WDA,2 .@$ [<N'~7.80]f5UK UZ;?xl88H ?`? Al.i@n "?LڕK+وwN!J9(,LOK\ٟp L60GDĖC/DE kDjo܉ Dsp K݄BK:JA0A+I2H]8"E|?d@삐 @Bk@,(@?,H͞ <9iq<@V.RG6-NԊ?pPB@$4@c\.h VU(܋㚧O@#ZgN.U G^`N(:A(A0XŽ>(<  11"IG5pQ,(H? >⻄֩碄T$Hˏz P28z`ˆ`,*L NՐG1Z貥RD06j @irSP7Ԏ,kL5aJ' Z ?D 7R O-cZdBR.Rr'0[sJj&VuMbF' w9w]MX+'w)N X {45Fs0TV+NY |- ] NʪءFgݸ棑hg (@MZꩩ꫱H+. 72Jbx#/P7h a\$(HUx_}Q#2/#Ư{7Z4D@̸.n̸6FKWS!H&=!=+ҁbnhb :db 3HkJj@xO. rf^.OΝFhj)`Ϭ;IP?/ CHdl+DCXo F`Y`Apdq\xIP##y`@єlKL}M%%!?C*j2 ",!$v`8 S"B 5#x1$dI@~Q.c"3 /̃sL+)P ٮ5@&lHh@;rpRK@RJF<(%RIeI6@# hO@ÒwZ)@7J͙q@97 1)LH),_Iа*N'f2 f(iɤvI{uli%[T)rREdG̴aK5 $MڭlZr%]j@48%dHy 3Ji$Om GU"m.S#U„."XAA @>/F`SZEp2ū41-W D"d5*%5 O2ǔQ b$wh~E:dh$lHڢDPsC Ⰷ@;}ʩ{e/lMI Bxf: ihn-b@K=xvX< :Dp^CqDw_R %HpJPp I;΄@6 ZN}S̀m(874 MG2F:1F;Yb)x|ybJyb$LʹXB'5*aBS1y@/SD&! +| )`+i<΂z7#R>2؄%csvmu L_wNNuJeNHȅY+`Lk:Q/($R,:޼= AZN *gh O̙ ` 1A:4F^% ?$X'p lv.ш\,!.ZxIZL0R~D 5KIpΦ Q^ j+ Ȏ%p-MwB(8FB#_!Uh$n$?e GR0{TG-GJ x4V*..r\}XK4> )%SB,@ӥFSD/Ldt)R h팬ъJ@du*<蛀yL@_*6,УuDUy+oIBOta&C$C[D0\fo,q|]|bZ*TT_ (Uj]XA6!~d}Vjx)Nh .krp p.r ,A a oh+MpOgP 0a0gBN0g-N# 0aRk2LlxJ |'#@rT@p"!n2p`,pp,(!$I`:Q!bF$R;r$ 0#xPA==pn'b`)*!Nt s!tFE~0@rRkDIlft| q:VO^/HGu@‬ '3H D :D̃l9Eq 4(!8N 4F x:~fZ 1"0\H#0pc0ad!$5DD3)-ȅT4r D !. *f Im4ɒ,k(2VPlȢ\dbŖ:"&؆,:*7 0FRO$$)i&N`M: xG&#vqP6#"8 iH BPc 0]$Lb+dB "ö"b4*ǚb,Ţz"1pN.OYl#zOGj| NLgF>"L=3i3X ܊ª\R 0 :I$6O̤Ba!1B^#"Ԫ T!f$/p>i: : 2Efln !<!=a2ღ j F AA0G B(D TdKek<; ܺ %PAm_.oLa3H_M R cu8_6m@rZ4XE5\T]teP*2DF"1.E pMz.(<@Qy6eGՎ6 b. ,!8bf "'p&C$6 &f+D$X(`,Q]H$UY$E%$-1MWi Wj $:J)PM&ȕIX$QYoZ$*"*S9R_C*\C! 2(8!YS5f5d!XH%`?j@V}BtV_#Wbz"YdZ.w@n w#n$" FZdnC " bJvBĈ80b$%F Gb Ʃ~ 0+I έK) (bJTTJ@WA&KiJ1喴`!?i~O`%obuRbj-b3JS8 ^BW 7(&̓()U:1cFB V* 6&RD@RzFbroҗ*% "@7Bޤ,d3? w<5`P?*]L"#66.jF&'eaR1I2CK5hr8! *%y/8eh"4߂9MQhBrV#.<(iQѱgs#P~l"87Q Y)^'kej TU5"7"eF@:=VbB!P726 #֍ٝ1ڰ#  M RHT5ByJK4nUAD%RB !"W!⸨:= {ih!bT .oG&΅"߼)[ Ų'n`+P S㺚8=5y*&#FbMd%ŽWV.&#vx*Q+# Û9R$X$6 B`׶tjgBKԄ`dD04q8C"i aQϪE hB31s#Il{ Nb f>smș9K h((z&Hy.#`ӷM'",X'0&[Fּk~ mPP>sp} -!j+H 62bYC *U 8g\#hpQ]GM%'2Ruc]k_YXOE~ )͑1Z e>di2GB`Z`OAq+UJBj F av5NnE`s ju[ GLhD^3!& ~\}<ɯe^7\ tgK 8"Tp…gN JB@&P2+1XWXDML w=eZh|SiNfЄ ZK2e  2%W<:`S՚' SX"0W2ϙh8P bp <0@=x`D%dH )zȑ$K<2J jd ;lY2ʂug͝D=tKN$XfSJtfҭ\z tQ J֤ 6 >M W%+S(n7|aНt >_dhMle-XO[Ȝa<:h8΅ lΐ!Eݫ{ <ċ#,E Qs @@(E <0Fु_q/ ~ Կ@95=՜Em1Dw74W CtĖ!E_<@SmAx]"`'W;h7Zzţ2 U}t4Y#S?D35 ?^ģ_9.V`d `$%A""4H?FBMqZ5EhPGD{ Gp֜_tG)0RPh?:񨣏" @J(jyHN=dU gM4 >aDAP@u`k'"eAL.LGXB[P4P57QOk@/:oM^a"I0ؙP/ڞN?d_{,wLWl)1qOb#E s>3cD$b>R.J7 0Mԅ i/4HW_@sB # I!@xBMDE*1Q7CyZQNBCB@K q̈́O;$=A$ =$p3 r @!#Xp $ @s?=U_EMCE"ad0fT|c5H1A!tFEGIt0A3`BdGNv*QG:rT2!]^$Ga$CI$A($ H@Ff -|FAi>E8"A%QLA4Ґ0gp"1(/xyb(!Sv|0IrRrpPgw Z%V—7\0 2,br(1H4Py "ׁTv% wOoo 0 %50'̠^-+{&w$_OguEE1%#0:4x /"96Nr. tD>"G{v<$s.2v5 ۋX6Q.Q-cv\r31n7Xr"/218Bq7=ESM[ٝ8pq6q6nC8]wxIayvCkoI۠#+8gzW§~ )z#fq} ;|Sr` O*em˃ "{a03|4qqcPIbĠ~ p7~1 ~&w< C1kp-ڐZD<[HH$[<{ѵ[4D i?F5IYwF(yA|Q]$:aH8I]H`ZTqI32h[5?q{.1w[5l؛ ~<<2z`ȇAIiCi6# My , Aaߋox?c%o w1;!ThsW':a vCV [aN(+CQKK5r8cSR)S+W)h;PS@H=(8=` !p80 \gqiE;Q@Yg xAø361'@~HBAx5[#rUG4ZDbkE)f(-|%xpU?!xaӚM\-wp!L-} ~eSk,UZIŤi y M煞EĜVzGr_T_8a ىm!.ٟٗ ڡMq p`=֯ QK&Z90kB;"@)+08zP`"_ d#G77/PXBJ0 $cP  *`$ m `MحvFEj z ƛ08 6bVkVS)Q17SzA`flѫ4z!')lqhS#Z*H )f>K-Qm5^) qz j%Mj=~3DnA  ޗ}` ^) v~TA*5MXQr0wakou(Cvwm(RBIE5(#17's6+T Y.q"e>rd0 ='"{2 ;7 N>-YqTsqR:0 Lgݠ7e4E 5.  tWS!q:iWVRYi:dIVST>ԁwz(LP >2 Q0J0kbWP,×S[H]D41\m`.Yd/~tOԃ/\2A7X5,ZZImN,0x sB6'L}$UVΘAPmk(xg̏K5zD Y2aU[_ #! 7]CpaD8, >x4H<CE /F% 1?F-z(fxYE z0NI:- (^=?|lѡ̮\#k);N`n7=Gլ VOʭP@`-Jz@X 㤁N\e!BQAtB毥k~~ /S,-!› uD NL (iާKoh1<) mW}r-9\@&T7}?[VCh:'L@^Lco= OPB 3pC;C''F D5X\)UD1 gFsqG{G rH"4H$TrI&tI(rJ*J,rK.PP:D6!ĸ$͇ lB9T 1!a=*Hti(āh1Mҁx/1ę4Zdz1d)iúr%9G ""C-YmL2 x&{=\Cݴm\3>{HI s|\҈<5YGqomK򦑆 W)ϛ&谤CnQǃQ(tH!k)~B u AQ,kK7cِ)/$~sbCǿK:H 0 JZ&$0~ﰴ;)30 3bHl2h ( )s@L p0#l :Zs<ɩ:b"GB_Bp!K; ȳq\ >vE: KU8=%oy{^Weo{vko}{_WIU->@@|A ?$لf:,&E|OIEv%. %ˡ1'LP@5;I AMDԣ*PACȨ CC8ez¬A1IeZ$B?nzHXҀ j2AObLA $hs+HgX?rB.6CrH=hyya1KxaR;H$P(NQ?Z5ưS@8HA9.]CbWú+ S&m fnHAX_&7"D(ЀE:@kB`pQlD 5${M$F50vfά+o}Ӂ}+&7 @5xs&TA礁D?b\r4 AQ!]rym8<`wـrzBr>i/p1F%Re^ JaŐ|ۑȖORmu[<>)ޖ{q`=mE :T\mH-Zč!1D xe*݁4Hg`JuJ04˂gC@S?nRJRT T%vD ф4l†āC.JKL9mMR_&@~҆&/A9rC$ $;'@"D*@;9"ɂ@h82T)+S)r :K@$sˠ H8= q#ThZp Wz(§8TD6 )Xs8X 5 =@(c(JDJݨҷ@}ȭzʣ,钚1T`qDyƂ,TH, ` 8 (cCȴa%L@FD,'̢/LdYĵ/،M٬MۼMchsMVܚ N"Mp 4NlN| U"E2Q ?1qgrB(fXxϤ= h_LJ {j0k@ @s$ qByMD*,?2%P 6Oѻ#=4"0;!u+*#( MM5wԣ3 tp pfxwÐ@RŒ/U RG4Q x? AHiC9`i؅x3 /Dԁr2OQ8ˈr3\0k @ E`Y7U ây9}3進FAQԀA-,'9""k7UaN ]SS`jќd%(qǐр3s ?1x/U/*U X PŨ@Q . 8ɃQ-0㺌J٤YBMSG0H"M m H#D_Q -DzHX4__h=8A)<<Į;ve* 8Vፆӌ=|C]Kd`:ha/fD oⴞ IT &r=T) ,a،9D#a(L`̭ 9᲌؃cEṽFYzk'݅2*_:M K@3+=F˯UmD;|:ཆ@;=2?K@ށaG͈ZEe3  CRTz̦J^݄tݐ4*E67u3bHU=\< xp4pr +Q RVZ>=P-x "f膶} (x{8gS-%: %Ȫʜ,O Y" MdUK<1H̀Ќ| @D.[p [,XT+YcC:vTmZt&CN]Z5P¯>nnd )k r^F@fR;)I 0k.l>. lM[1`A1S1#0fY2,qE)惈1nMQxخCymI+蓄5#bʸ :οaox1fQ9K(HA,݇gP Sx{)nVNY/90:;b1 $4<5Hn Ѩ6XpW@Gzi/# 3`JfH֦ γR qC}j@8D*.S톉p 7Ik0s`ryt>l+?{T 3x%Auʛڃ8?׆XX:W:lj7YLF3 C5(2'I6;ssA8,U  8 *Ƣ${;ݬ䆑Pd3W#u:4]QtS louO~ nA=ʘ/Z{46(Ula"~'Y-Jcxh0zHixF`2 s >7PJ Giߐ"s(qQ}Nо`k 3r-ep0nE[ȮBp ]B)(Θ; Fw IP'syM! mHC߇1uu<2Qw23Q/S-0(" 0ȃv6ȃaj=0FEJ* qM+EY7j[%cĖfyADW٢N2/lͫz(&X!w{0q]ѿ DtX(ֹ>2A's$DCҎIU]Q%<} E(GPpzPi1GP'AB(Gv@?B8gHXxR݊[WlX@J}6R_ Ip`BB(=2<-4ЇtN;0C-DdXpk"mFXq㏴A@AtI2AE")eRBmv`X Yi|` Nn:J2㝫686*+AOBFdl|QK)+G-L+bmjN L Jk͌ڢ.): ҇ͣ{uaʹ;Bм|[C1Nq8̓˞5A@+(Ӧj7!l;ܳ?7 c9 <6CC] 0}C]HO UJ$Cn60JuO\R=W|KT()=]ZoC9h}?gՇ5A] T(;%RdG}@e;"J;dJ*%;4L)AhTC GJodpR-(#sGP#iҟv>)$0<$^ [ד%f`{ c6#$@?\s8H|LX:,"6t /6ҡ-*(R?$W1"D$=n/d53`>琄h@(@`q>5g {L1&y@JT&*ɺȅ!$B7)bw0F2AduARcE?-ZtH\AKA  XDJIg@ԴؒZZH3`$ p-{L֙2OS:Ӆ~$/JFN 'A"+nDJɒ=>[q:L CDzO*tP*慈- aZY 4znB~(^PV}F"&Gs-F@~ fsF 0eEU)eA')QArF Hfk;2gL_D^tE,^ a/r'jx Ѡe(AJP%p6gA_H+JÞK:Q@t *Nad d( ZefDE V a_l$?> U 9hsP*(%H=TZ Lmd`'r6=CIQOS@Xu#DԢ%h= 4T4&NVFH|@5 a$SMAF&6J#n )"! cL1LZ?gXp6-̋{eJ&$C9 x L 9FT= LYI<.&iyK8,΁l8}σߗ"ҊN>r핕znu@I^=(:T$w^iC2b'!nw c.1vlb9'6KC<僖3\M,qLUf?c2ͣ>_=T˙d"xYj7bۍG5t&s0SoeA}gbPquG!Khvx|ipU >Mx@K0B$:@$L К c'PDClΘDD :\eUoUmqKtPA(H, JF(JFZpOK l m ( EXJ\ A(E\؆#ԅȄUL=, 0(+,xV m@CAI0˜DÃH@0}PQO9HP=l PCX !LDQB̒(|̒Jᩨ!]Q|"JLP0qq=9$S D|iD(\CY I}YI8JDFxVɵyE5G(ؠQ<A&ň@FXWApdDYIc FDI|M4Ϗެ@яI.K|KH^ED+p@$ C}#+ eIK(0/0EA0'8{1ݢ"M bAa չe ԀR8F-@K ++A}t&ȆCX"fCA0YJPUY[xCq^Xҵ`ki d$mL(ZD֟ ?0A(_(}aDbξܠJ`yF+r: \yRPvjOCA @ Uf!!KAuBV,qTK(.*HX.Eds~6`=Z $ #BQU3AdL*Sr#YY|Y#0/aDLD- )jK$sKX9n0.0Q߮歈 GDck|v| AAE2J'#iaj/AF\pd$乽FBeKVUMsPl2[Ԯ˴MVi dYSA*A,N n^H1K eP-1,@ƓYQ4~k`:/qB S·Rl;RP[K rJN*opDGUy18kR? 0,<|2l==84drXkTnF?L$3Jh VCPThTMEZtJ\FZlכfeᬖpkoszD4JRqQDq e`!B}?Pealf6=~I'Mci"G:lV %FXp1\f VԙsmEo;?hCMQMF#x]V8_Gߘx(cX˚Hrb(|Ɖn,&C |ҲRxv.mb|ݚ-D-/RYGFĈ9D)DDQN@E,Ďs-CԶ^FniM7NyD1wDȄ6{̣ÄũPӀJqedc0JSPV8JbFU4K0:DRooĭT;Ļ& sS)źXSmmPZ|T_9kLDiCK kErN4^|ɛldoZc彉mjX}2J۳~@\C&|cML/DD>AClG֠o-Ct?no:kwn$  6t8PۿS5"8 n5uζC|Km'u Ҡ Xbs2:I@ p ,gA:C 48+p PBlh8tƇ )lƁNoI Xt1 Ha& +(@ 1 @:  અ(Ba΋Ԡ>U'Qz@M )$H:H!PL=USKV]M*чb}ցfeZBW,u "GsJץACd4U`THpH8ݕK}0)`<;-Gm*{CV=&)v!Dx=t #`ψК lΡd*>˝[<' khj` 'a {x@OVSE"p[~"sdD@ZȁT jq:#@!q^X.A0m>8u_,"W RKKPg#}ZKH߯2GΥ'G5K_D`L_CHe)}2BV,)iNaBhB D`Τ"e!ٕ !/A2TZaIS@": J@LP,A+w<,Aq{!MxH"7uՇBƁr8z\2*-'@!{q5yц V%h&JLd6܈;`pMўR=L1L|Iq\kH"Ɋ 9RvZ:D_THK])`6$&5MYC3p>1ZԀ?Q$M=ungPMp>"SI_@Tp""KBD%ZT`DŘrQ0YzۺFSTc{eNUK);M LB6p-*-D%hRJ`RpI^VSL C4@m ּUbؤP07!~8EeE YI!(~Bhg2|Qi!yֳli[[޶E:j0pde2Fqi JnAFV^vˑbe C&΍MC9tD!`؟c6)"_Of'ڋD L Z)\a _0nY2`o ^tuH3@f 0 ![+-r?XL^RQI rTm-prAvU,l*>KnJA O#T?gT&3ndɁD&GL ?C&<Gbe  !Co)(BD1V LXy)qP8Lz?恌%}@E[jYh011AWDCHwXZaeWa`+(xZ1>} C_/s~ O=7lpb814j u UңgCyT4ɀ9 B A 6Lrr!,R!@- S;19Fqrm)%J Mh#ARRCb̄m"hG:yP>%u<  Ln';G9d Q}KoqJ}65ڽw OADA c$"V_ai/g{$T&$=S{ZZ?`X/$Zk ,PbCvAVT;I1jJVX&%@1d)Xb J&FA1v!(jhrBkkAf2i!f  )Tp !>07Pb86`yP 0'UL)6"x&> u(cuFnB.| )"b::@ >"Ҙ!*bzGk“LPwK2u "2 *N%GD}PógpUzr샀qʀ>V$Ɍ1G@GKoĄ@ Kt~, eDV1~ROh HSq!zNpro"GpP@b#P %B !`p&!%Gx(8"/ ]eX/ba ,j1a4! #< |F!x3B,`XbBRBNEeJ"klJRD 2 P_3f(B.Bhȡj B djbA* Zj\B;3{Cp P"1l!h*T++Br), \@7DX*Zs?f*p*rr0"4meOkR :]Q85d! DDeV;! 7"(%j 檮jl 6!*'p*"#SJE&*寨r)B/tpL>TSBE4,+L ECoZ0FoG,L!j@@r7mTW!j!oAsJA,Nj,HhL!4GJMS K]XT<6OeZk.MOPUR  l#n`}kH!( "`\l `!Bl @u gTiTa 4jLS!ƱUH;`rU 9,&ŢU!(PCo!(5͒B&B 5m*#" ՐPb&;[om"FMfdab,i"R9 \!h`v#! dg gb,b(d\8jd!Dh. n"K> -beR@ "(s&b TlbUG+1*8$sp*Zt aH3D=% n /v Ṽ6fg f@dL4Ղ|p3 `B讈0*>/sFD~pa /CBB$뚮CD9&w"KW# C(r+c}<`!J LCt/L,>7.GqSWpͱ4`J5FD Ldd%7*Th;BW>e*d>2EC@BB>oUrЧbHnctE( V OB"sj19ր.o t& aa]uS $b,-@-b\hmUD8,"?/?5hB.";-sEkC6#?#SHPjvlUiI&B<րh!c +kIAv.^Cu#CBno{"Ǒapn)g G.&p$ Sb  #9W$! u`\"W_XapF6&]V .A+-`1!ba!9a@7!.Q6y'ѹIlơu60_]gjEF[c!!#=q `#c)v-tNws%ZxEU$5l4Qp$G~Qz=#dzej,zA NR+XN4'M9/5&K8|&`xR"3:dc e)9; , *y֤!6>0ILlx2)!*lvL%. 4F?~Bj"8/HN#te!-e .-Xo/wf)0x?R 8:?!^@"  t@/8U@k3!A"9TM76XjEwD@ Hb, Q4ĄG !TRv3BJ4UaU)r?%6(6˾u芐bZXʯe@0OJEBBISË0tVz1Mۂ/Oo:2Ŵ>h%Q!JAB5!4 ؔ|R-"4HKMeYoz7H#4B9"MI<'#Kb_B5'A!*6*.Lma#q zUv&?hRW!2H*Tv F;DS#.8dwix~OSfxHBQL~BCH{0z? BA@z{$h,X):DQʪM f)G:)xЭkDplmt AyAE:LJж&C䂪MVѕ&@c/\-1i1t?Vt\Cĩ@ @.[$@Q FEB4p@V} V[5׆yB=^6QƈwV@&zP@? eZ+cxK}7]&DE/>0=0PBTp~ۀ*bp`*HQx˴5';DeD/OH~4)` CI D}GC8̡Ӂj:DW*XϛCiPH*DȂAiO(q,KYQH:1#ǐcK↺!/8HH'Lc]Mˈ-cbHEZ̤&7LjITKȄ,V!1+ }Я!$DO I@B$.@f'Y4 C'd!, "[ mz &2gF1¨ [h`Sv"'.FbS(?9"-D`%z$л4!. @"#LIK`3,5+3^,CdgDJ 0?dzH[RCɠ,N>IIREMNm/ɕ yEJ}]N)i\*TYGbH8p+rC@"  zRp#[A#qSE0b ʣW|U8!@jR!f!W*ZK@-ŒIvz*l2Eʉp͈܌:PKS,;ETZ {ӕ D. HzGl:kH `TAMR6*0T) K!vȇQ?JQ-KCew^p.,IPZ3"ddǡD #"pdI?H2>Y}1 5Wp_~I!K!2YO\2!v8a$iA19!,n36눕ԈIQJ՚xa/Rm:M76G|s>m/2zj]D5lN ̋vU % A "="H2xƐ:ȧ"kд"C͓!&:¯%$jjB ͈w^9 # frFЯ2 "vG/Ҝ6Ni8F`㐑F1^vaа#8@>301䧝C0V26~8 Ch$ ŸlEJQZ!?R^KBP C9aF3my$NC|*S1;ypHa$u벧od9jgҮoIk۞!X5ۋeۓsO7H[~8DO[Ͼ{OOOϿ8Xx ؀8Xx؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFxHJL؄NPR8TXVxXZ\؅^`b8dXfxhjl؆npr8tXvxxz|؇~8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،H0ȀX7tt@ ؍ЅpnGHiǎGiz8x ؏ y6 bhȐQIbPiPbpby|P&y( #,ْ.0294Y6y׈꧓Ǔ'D~Fy$C9GLy()&OٔotNJO)ELO 0eIf)`T>ᖢP |ٗ~nٗte_EYy+SWE ExY( D\y A$j3@ /,P )< @!m@d+A0ׅA#z#IеwddAJ_yHI?_ F n 6ف n X3VT\XGMPO*4n?82Ѝ_TD:P @e$WroJ2R$À% E}r#.Xs(n@zD?Î0ś޾q@Y|0o B"˵'b~-i~-@.tO?#?*wHc 4Tc FB4CC ăQ䞵lG @'dAȖ/!^ ` @1?ZV0yф B`Dq/UF" @P-COÅb(%\(~0 ,#[q [ADR!x$aSl) E`1HD{̍1i#[`B:ۤ%A\2f 3-qA 1LA&*UP kr;)}d߈U*>hN 3;Z:>ia@bӠ  S [̹Ì. HNÁRT3 ħGĂq^[%Ƃ8e;j,P?>!R4R !3H`sW ak$A8IOJ`u1IDy:/Q# 9Z2gh)ly|@EsrF6sY)tzY$@#T=t]Aa54aG[">" "@/ >) j $$ qWv.A4WIH'm ' 邯rB DpAPh~4A9<8a$Xp r ,@^%p,0/p h\ w#߈9o1A(]5斔Y]Y[^r Y! DXeAc4鍁j'%6`x 2iMPOoqyl[6J\Bॉΰ;ZָTU 1H HV''`4ЎMjW)ȴ\!љ JDL&lSnkQ(G `@fF৵q<'Nqq$U@d*}zPe!@xJ d?W0gNs7$xIE2ͨHT0Sn)H r`h'8NbǠ yOh#DSHX+-MYDs'=e f[9SQM6 – X>u}n5>vd- F do_x'7\NAणo@F*5]:_ncR3Vbe;Y4b "Laďpa"r*ufDJc"ދZ$#%{@/?^E8$0[Bp frPNrUx'zH" ~WP2'@lb@!#`!8@,V8n)|P 2($t 7"o7=a/r51++JR+!0k br]?AEZp-QN@1g]bb.*S02TX^- %ph=?8?N.h"PN:r.@.7N5d1W9vu6/R"F#ZP*HrXkƈm(RRkPzjdB7buC7H>#8opV8SXcq75g93m@##:a&x6@W% ;B]!!'b>!A=!ux?|ΈÂq`x@" YB)>boQB#$B DnB#$V6"@(SUHdYAkDDRDEYDF,t4JQF1AVaTu{qWutGFTA$QH%Zi3[mLZZ.p @H,OPxgVKbI[9TrFu?V`IHMz۩gLMhZ@Y|MCbp.x8Ql Ռ#75xORO N4QKXj#N8.%BUkTRUR6-R s1aiS&@r@)8PĦK'1l^r07z6URV&fEGTEDFyTWWijH!7 }T Zޑ,%^4]XmZð#GW` Z\{R 9Q0 ťfSP Ace:)6~a'U^& )Y-)A396#Qq`C>CQs>R(6HʵHmT9]V9T2+N`1^/خ; 6x9"7(aR"'d)P5Ҡb(86Ld*d grUCkyeGh6E!fq!`2" X89Ry80gq7CmOxx?|j.vSCk P95Fiqjaz#[2 [Mk8-gR(c+k6rVR2z6l{*Zz mb ʪRA{{ncivY ;oǀוp7Q;ݰz{v0%OUN&ׂ-S)7RR庸{8 %DGd[rKtOua"pOOWq]W1v@SQJ@W`It!PZ!uv zgw&o1azz{|ruNBICq7G%W*x* QX 0[x6u7@{' qf=q'.C &R#E6l|J2}U@}0I WЛp/X*"+p"'$ i!#ap(BvL0AAEBhzd#>Fr6"#R_`1&l[G{Ȁ)6r<qK V˘󢪯xS|P`8ghx!1U&Q-UXX229q}x3G:(4SE 2_VSˈH `iᢡrE2KPە2s !"L2b.5퉵Ⱦ$p@$']"ŎD21 Vіs_8v687(Qf_7YK Va뱗Q`%egC9{? V!?`UWW5Fyq᪾Y "&kOAt&g&ffef2KTPVetCP5@h81/ @#EͶ'kwCα+m_Վ(mQ?AQl.lZkcߏ֏OϾ|RP 5gH{O 8B >W ^@ĘQF=BDFu5$4aJ'3L5męS'c  qxb /d$bB^2Y RSVMv'\͞EVZ;t̹&l{s!ިkk Wbƍ?YdjrDqAC $tʼn0F%I_$xߗG"It|+4$oQ /EYMZpqR' ~=13?=A'=oaD@'B?BũT. A :h21&d.# oSKR(DNGyҰG.?*7(  !hq ȅt(/"(mB4B 0d2[֠ 8 /hس `0C.N(+2>taݓ. O;** |'>0h˓W@FڋX'+ء6{a*Q&(F!SStT&cx3+,p$Zw`Y+{(t;.LݬakvwLؽ *"j*\X0&`|:&@=[Bt ړe;[?E9FOTU^:?(?8Wa G̑@Zj` 1RpB`.& fע$J," /12└Q?tZ D 8}@w=f] (0DD< r,(!A @m!_V$ܸĎ BI`(IHQ ,"W*G^d H5p@Rm<J 3%+ dD RCLh+[FP<_eLcK,@O5BMkb3IQD #@HP&X̲Xlzb2($}Q\ҶO)dh AB h-  0.1H?'ΝȢ'Ihߤfh.% B; u8M(aCPaD8iMiǵNIP pRɴS&AXcBP;!?X:;:d \@@&$F.AF&`VAPF~<E;R:dؤ)!ĊRZ-]>DC-j0kdNf.ɛ>!:,$/u ,Pw('rU cZ2ySXAQ!r,N,%p # ܔ+*!1~W T1>pW` b.0_8(iUbFJj$|3mB5QK>"J:̒X +*8i+FU1|7lL d &?2 S#P80ؠJ]C,x^\Y hA64'_C!l'򎣲=ţ-#ێbbD$Bh>3It场?n0MS:,~qdʕUC$a*UÐteKMk{V,C'Jm68uMM{edžvsrBPA_Αޤ\F@=k7]+s`%wqbtFEJY}×ҧobT#wdMVjxme[y urgDS/-ye>s9Ar̔h*):`U@^D]VQV8'kӐ:lIԊ<{ AetwDRvD:q@xF @rg=X" >vz.JhLvj*Su` !H$.9 kvI_. q. b:wYay}uo$ q}+Z F( S89(SC9#_OE{h LٮҮR)/WY,xJ}|HPqhP pP 26 @vy2CE)q>??Ͱ$1s-"~!,i.>89!hApg١QBhq ; $/H)`IαC@ĺ1)*E|1̙Ff!逆, ؚ`w .&9EhŠI P1(yqRzIA@h'Ē)qfBUh|( $1GL?yy|(08741H*I9ے 4ә407݉4 ӛ##2,K+̏ p hMM H0=apJ TH%@t,QA%UBB(K@bHTOY꼬Oh% `&h N:|zS8-xlKݼȍ(K@η⣸"c҈ aE5^(LQI\3(ұK: ``))i$}Us BsQ zr S0s(b ъ y[6<^$ eDPy*>"!pȳM$|GuH)I:Cm꼍8ĥ,ʳN[bq bh@1 "Y - uJ +ј{,c**'%L,hip˒#¿8=Hs= uM*#h!tگJsU8"~J$J د 0|L|/ TeT܃̄x[5wỤ.000)TkVRM`;"ˑSDӡ pVՌ1b،7Zz102@;2s5;3r/kHX3jO#=:-SrI's %!ӳ20IJ4ЉS틴CMĹh4O), ې>HkӉg5gM {*5bj|SZم7j6фh|ec`j+hCnФ|V8_F3/88@7 P_Hs߱_*ݽ ^9EQ(A? 6a92 fvȰ=OSV ePh&"P+ja_\|iHwX  gfSf8=߄XO{&0B*WH,qR`\٥IF) sċ:ܽ @.A(QJ!"Tl&Θ}WwF:{0faW ٫i$a.9[?]DXyDiԜN.Aq=̧FB Oܱl?̭VjnL*MydGhIH ^snƈ݁EDmZڡh40nȭa}_Let@``@%`m̄HGH.!LIVY1^0 ُJxjXLUrwt V*q2c;'r!=CGq bJ?AG8p<.9N~P晗26$W( aaXTY^D<A*dm H,sCBPF;'U|Oj `iE @$ObO&yqKDJi#%Xލ7 rң m5J݈w2e@h?NpY~ӐMЀ6ة`ߝӗ)ç&eKے$(H-=4m@x*Rꨍ((r"Ry" J'M5a'+Iyx'`k*N !6 >9oT =cP+I.1$3m&5uԊ܉XRD/%vFzU%yɏIk &XY.&YԄ֚ԇsX0Vlㄝ pN!L1' ݁YlC%I R8xPq8p ߰8p2wu~qJ堁O/]ٮϐ_4j1x3<]XR4#Z诿eI_1E  +/j@KL] n 1U~(E ] PA0p !ؐC;<!< CTwCH"=SY͒EhOPz4:9*d)ԃ ~H:TXIjd(PHjИu 1@+ܸrU{Ђƛ/υ0yu:"W4/mHކRH3į%tͨWn5زg=0c^/"mk*2sd`)רōӮn:ڷs>0r_T.xDŽg^ӯo>*M9dH= Kha&])@$S2K IOP@H4:I@p$P)@ `^*,LAi@Nv6QeQ`5 2i^8Aa I( : MXp2a<9L$LHGӁpj I4peZH8,)Ub80eq߯<`Dr.bsؽfQcruCkX }nWFFŌt y%P C BHoD0 9,Q X`i|X#^32$}!y c:4RGgI!7BnYW*"aD.opȶ$,ddȸW +x<"b£%? 0 hqY2"HHRaB# /ĎK&Q!AΆkY^E7e,<!/L%'c0#)i쳣YCLg{0!`epo_R8c2mYIN$ $HZMT11N֙ij trOMG)9VC e(YV{pF k_b;#f}-ۆ ;1ETY]qS* "^b}ʅ .q&@lӍy!8qэqN&򕳼9FBQ'rDw~0mX ׯƉ8.SV:V2@D5 aֈ }wDj⃮PP.N{*PL E/Ra@ ӫ7+ Mah>]|Eߘb4 XpZ1$J Ц'WYnt&s^@V^ic2|A'BDO0`˚] @$Q,~i \T6U9B <:k=r\?q L8 49Ñ$ `t_eD7jBč@ ĵt Tܱ uMi]W`$ F RMF4HF=U S5.e'  @׹DAҵ|O9 aT@KElOI,<<@΋аPoU)%j4i3*F3Ft[caknj4cֹRD9l|_v ? < x aJ+ 9Fp͔BE8($p1xD5 ?`9Chdf>K8C@l\$ 76Ĝ}, [ȣUTf FeHu0=ݿyY_& 4:d#mu tԜY\xUFy&"E! ʩlɒm*mX Д<ME@C9'nVlvn @7f%upEUs@,'vjsbTX?\uqid񥇾4]1BԈ4gT訖gIt.vlql-lk\ꔎ@|}tB a5sEVmDZ&vr@_A`)hCY,-Gl|5}((}2?詧_~BjdqFM{Id7IiL,️8 >@^y?AIsL* !ș ;…jh^b3H86t?HÑIn\!w| A`۞i@Hky5 0ȯ^>$M N@L+-: K˩l@Nh?r @`?:]ñCfxK'Cuc1{`: 8(?$ 4!‡F(;-sA[ [pF@T˰ A$F d4D `aG11U&~{ ? d vFxEI<0\%EIUI0A4žHHJR֡Uo"0r=SRaO>j 1F쯎*v <UфJt 8yʽm~`Fnl aiDܶ~էiALILYCY]PA4)4gF&?k,\`<<6^f=pȁ5 +W3 G @|KCk4$c@,=@ G?&T_A N`/O 0E( nxP⿕[HcPJJB 0XAt),LT: Nl 򠄅VV=U?>D{/ןzK&ƕ;n݃?!Pcc!#& C6Oqܩu tD?H].3 @ვ]F郪~la|tꂻSwH !EvP*w)B(CkWV7xAatӷm?~h7 bb-7P{.[i#Ϻ[@(*XMpm6]=ӷMD`{p|`[ǣ54.ʠMh6 aZ B-)׸[ "2DŽ.G!(!=8@H^Q2NO5ȄwLH[ɮL{vBR1j!RպE\8f-i}3 BXس3Uo c ?{@࢜wŀ穩& 뭹NBxn^k =LM3V'Gn'5bxsBy+..fIV1\lα<3phD?p5aiq=p ([u8Ї3 'Ȍv9aHxܠg2!wB'!]ϮCGI1"Cӄp"&,i9$>8?|$o%sp <$FG?8$iL%6T=H!KH#1fD GrH4WuA^Ҥ| OZzĝ  !B3,,Ja @T[{qPGWȑn4 A ,0iGp@C>b#?% SXp'X.C-B/PCQ61`JQq(yb@YSrؓA`ظG .!/riBH~ZC"OYY$p~!(%P|B%)Cȃ{g?<1K(*<#XӢ&iԤqic/}>d ᨑ3gWkH4pMp$b^4(LHC yGzcW "SN.RDI?B|5.JE]Jfdgg+5.1A`,KEdz1A i)ʵ*phAڄ!W aJ. k.=ܭABef>00[ YL8,`!.A`@;pG7c|)07b?H qR L@T|`4!$JdG\jb gէ4F MuL=ԑq4Ц?N=F>7G78[Bݚ֭$od4eQKj!|fx(MJADTT{ L9"sht` +9U.*ߒTHwbC$ u8x>g`1|L žGdbv)sI͊#(kZ4Hl  x ,8.b={K,sI?էiJYhqgx}*+!$ao~ @bB`2Xb&d, ~p$ ԙ,%p!AE @=!rI|$_;@`K;S- |e 2#, "ɾo)6P3dUoOAhAMQܱ{hXBVfi<]Wxb r`d^A{ aaRSn4. B$ @q bHVl!sXrf (ERh%=,P6TĊKM(!d .HM@& H%b 7"C+ZBTJ)MMjF `.^ za (2r#(.#H> B (YAESE‰=*Y R黌)xhc j*"T$Bhhf& B!<@\ T /cjd7Z%4K`MSS6/!ks%Ve:V4`j,3d5D5)eBH. ;6D"<&~H" "6ڏT*!/!Z3E!$)*"&" ^ 8VL`%` @!~A](p.}!$!:#i(@a.!r+XDRN @\Dh"F.ETQ 13rd^A&\D( 0 v )")AHV+ND.Oj QЁNL_a-P U! R*@Z9?C4`L N d#LrIB1H"AM f!D)</b_"҂("fTiֆLG^ `B~!.A(ժ !"> 06  '.IJ!50aqH]F)fG/f0rpZ"<&Llq&nu ( A,ϼbL f^AdG! ' `R@v,|Alg6tutpg6%8xg$jГ`;j.+ 6f:\E4h *v@p5,7s#Hx sq?D=`>6Z y |e be=.!mqǪ !tL!DCji ?L JJ,kTQh(b6"am!~)J#7\Sy ' 8FL L 5XmY:4 &3!N9Q$75bY4U!kQu(kRLo' jA#S뫆T"UUc Tl9t; 2-O'YεZZE5Z2!K~/ eZ\"3=`"[":BvD``f] hO>" ˇGIVpT 6~fbcJY)4_#d6B8i˥o7,eF]ƝJc: ; b 76K/`B7 s士5go]Vb 5:2¦+!a!60 bS 6cxpmpb m~ko.cW6ZF[oC::}<> )ZI9: "}!bVZɃYzs9@I7WU=Cu|Cp9dWqcUe;jC}5*#nv8X|` B:HB:x!Bń"C$ˁ xps c(0fə$=ZI/&) *X.e,;P)&_n j޵x⤸%ɪ -J_9~ 9dY $u%xu&j_ I;("gGosa$;.a $".΂́AF'dlA0 eo㧾H3 ^Q@TRpQ@ANQu  !p@?DHĖt8\.TI}Rqc3$p@s??z13}ĸB{!pWPrDd%QYq}I9'? 4 MM4A!ܛAU%K@d)?6x .y`s`qIZP$D6JaיB0xX DJ]'묣+&Tab$$uja"* ]G2Zz-T (X끡@΋E)9tSD>A$D~TU/ e?LÃH{q.0^ԍS:drB LΒԠ@BQ{s>3 Q <4M2p;c L@o_Q&r} ?(t>/8p >),>,uz/V" $u? cOx䶀2 9A5SD[ޡM?U, Qy馗NzE6TQ9TN{ߎ{%~ _(HKYgJS}AxIRh;AB(K3Bhm KGqk4\DP"#q#ǘl3Y76 h d:Cz$ wP;@hVXO4 AGBc(AG4 D@0%oA"@ 118 z2iP<Ahz^f#hXkG{x )I4dS@*|1NbuVΪ͚i P$$rHE%H0Y`3",^N`NpN$AH@Lђh`+HMsA#,I)>Q]q8-ԩܐ@V:9'Aӓl!*I؋2!2i'LH,M&ĬGy J݊t*(1v^t3!G?կ.H/4О$4X37UdMcȚZ~q5h@P *5g TjϏU 09"攐""p$`b%%H#;%fibUEkxr;\ P+$ _; 29~ 6* ?k~c ͹?&VB)`9ALAYFJ?@)JZL*_Dn`L;s$2h FjyX*1[&Ć/3KQx\ U+YB0yYȄ aP->RAܒ hC-0\ATF7 X|q%7J@聀=qD9lWM';IQ\II?9Q e2+fLOiRJԹ(OB"%uiŎ[3˙Su9Ȧ7r+_뼈QP(Hg^B,{]Z](FrpC"#E|6]QJyd3 *5 ȜгI_z J}Rh)F"K)ؐۄ/1+k$H%L%GmmD&Uͥ6m+I(2F(0|hMLH$T&$(z~ꑂ洗;"Gv:C:@LA}Q]/ʍƋEQ6yT4Q?Ҋ?0PP +FR Jh BXKFNj_a,`'FF lV ǶCHm"}X @ ~") @nemfŗ@[K :n`nK(0ۦ0F{noWnsq(j,@ G0)0!/~c4s @Q4$$3QqwbKC(C!*' l4L4V/@DBOrd 7s8_HeS2E& A` pQq8ZBsHHa AzqjVeTs607Wʦ]ՀR;;Qu8&F/Fz5AȊ[Oq/]TI`<< a >8@&Bg0dg!=nA ! dK?QaFa%YQS$|Gsq1@ 1UH1MIvK4/[0E[HG3t@"T>ΨИẌ>iq>; =#ᐽ NXpQ=A="c! *aمu"QSTE5*'LhDE\_xsQ$!>C)-qDXKEX@X EVɐ.\$QR oxі U1cytg5 E^Qlx/sA7cdt$RM!4S}ajW%?HwWtM e1U]ٓ-MQTDasH+wEqn[CQ\1Mқc >W$SS'aArO!*E541P)O+'iPRO 9GzqZ37|ǙQ|s)9Yv88$Uzpp? qY$Q,&?2AxXp t tD1  ng%ױU} iYUQq]WD` aGA[7rXG;hAe6Vc6/_Y[p # Vw`e)06&`hL{0b h1@}kfc1p(#6c -Ċ!Lb!A!Remu&(5|na `` !Z:s fr b+gfccpE acE!cBnGkqFjmvj}OͲP\jK<5jh+"1!+8x:lbQ1r/))!kkkjvP8I.oE!`i!I a1r3s4u[aĶ Ea0wow!p`;j'7hQх '<4336j'PpQ$qt/1YP7-1^%>@j!_y  *OX&K |j@T!H߄vq$a"{+8tIRRS5gQ'6K&xhxYL[Hxтr){ x_|`'GR˲ +?b'9Pa'`-wWG2',KҹNuEa-(V*#Xz8)URkUAz ῧ1zR|MU*?,:F61K )ねŬ0H EĠȭ@ d:(V6,$[gl;P+/Hh :ڄЊQp\/2h|10IC:fǃLXqX$q f%B r" `7[,ӺfLȟl;6RGkj:" ʭʯ 2<э}rϱc$c0C `2x"p 0Qo̐;ӬQ4aA?D!f' "kVg3Z#>xΘш=mB;PPHAЏ's0GCbw yu 1#>)yE@ ܆ц3 =XJo]_U3Hvh&1?C=bi"G9>XN!rP\5uq],z$ j,4`|@wUrWZ+Yhy+2 hQ^ X0 +/XDN] a)1UF=i г!,U1ځr??@"қ!5Н+rݷRT71S/EuƩ] Ia*nQd _{ߘc%pZ[d7EKPզ{'yw5ǡ""A$1Ea\2Z?b>pUqh2 em: ڳHʘBɉ$;^xH!.D10 #X`KhP? -!ĿX0d Bdȅ"pA^1t(A UƬK>mr!TZQ9{,+R&4e[` r?KAPM3_-@PWa  TA".ճQEg>;޶ TkScϦ5 M;$F}[hmo4^3ΥO^uٝ K_5ϿW k^ssVjE,oJ,: Zm%(!k ^%FRB!Hq\1Y WaUbaHkcBS;^0$WEfk' Q F[)~_ZDawŃנ,J{= u4*4XQ>rxbV&Hb^ ?}6 \`Oz"蕭r N$ Rg22l1czjkXg/ u9`<ѴIàuZ* 1!;&/("4h) '@o)q"4𸬝u`c+8=() "^JȬY+z j8E7i7%o)< B%4Dcrtz@@YdW@[5i8D4@PIl@!lp-ALH5{dt)X54/dl!{8ETP^4倏#^1Q cһ,8FYјPj![҃[Ԃb^ӟR%E>ҘRU @ qs!?I=9P!L D,c"8Xi 9љQHX) ;+5w!0H XdH)6HY M/!+L #QSbAVJ/_M?^փ AxflhY1* 0eiG !UY0kd~CD2DGN;(NCt &X,=9 h#1US!M&i " a%E/{I^x *X8f)8LHY_iꗋ)0!B3( 8QJT] 9Jp'\w@'?ї49lr6t#'yMΕHB- pg,8Ȑ1}s޲=_rp4sEzНtG]Q":8&-@658X&4M{̕!0O}.qU"ޯ%<- SD Z D-2[U y YOࣇ YJ۸-|$ ge0,(B'ыfq鑭5kzZ} XA$V]&HjЖaL(??Tb<@wpL(̘؊q#x`L)@P0'3 @@!@#0 GxS^@#L_P30wh% QxA+%d>頡 p 1AàEyH1C؞Kɝ ( XPa @ ֙CH (ȉ P߱ H+A Eh49-Kّ ]9Ц jtC{ 5 ɐ-ʋgpt /I0oLY 铂F 9?*bI8 #!Sq$ɜ|h`!d9NcȽ \|܋ L0t&"ʘdGp*&Z? YxZ(H.:Ap)4)؂:;7A $ ѼM8*iPKȉ1 5͊C̖Nϴˌb ([B s|p QdDz ˪D Fl+p35 XXл< Վ-ɬoPՠ;w|<*#Lͣ|ؼV~#R08 5Z8 y҃Q{|"05kс"XRP4|хزp'\FlHX@| IL :a1?g,Dk$4ɚz!a*LʥJDD1l m 25tS p$5 yժh-Ptt1p\;.pD5, % 6` E 5hS^U<(PKr s%6t](]cP6P p[umt7L{W{BPy;քN+X򐈭XX]:،XX-Y-Y=YM;2&`2L556y=9<ᖼ'l9X*/̿Qy:h؃04(A'S@X-JDXe[=q҄dhL<@#m35(>hQ B ڀ̖ y))؞ :`S 5:҂׍ #]}L? A Xyt pb#S ǃ݂]H5^Pl})A@$# 5*3_8IPHHAߵ3/"%?俗pA(< *K`x]> 7Q+~$@ La`q iVԠR )< 8ЀH x4Az5y ~ԍ&.DfUDz EhG|R yַ7F2nE)ƈ蜗СFƈZx\5@3 GL8JPI?I VPD YnV@P"|+ J#?Vbh#. H㣐Je&erȦ]vbx]Җ_ Y"A@+ \ߥ!G@Rpq.8gMu rsNuX XTT&;UaFlG%Uшo!\u j83:BwR낈\> ӈ\;Z^0] S$eî"#"{;1^`p/0y6ef(5 ^ k>5,1ڰq 煐w1y/ ԂhG(ShQ= S5U# "719VhFPX#IyK!8-'!8Ȟxd Mhw+xG{=Ah_{^^fيHe`I81Z B &-8~Xga р>(:3b)%YF@ F Z/*oFW(I8.1fC~P¿, |1Ă+^!:&Ⴧ"D,&14DPaM"paEpVX˄5E3J俦; B4դ^!lX QɢM6?|h Ǯͫн{[/K\iClɖ¥@嘝?ȿ$ T!EQ"Iӡ $qtJ}`N{`,vG[Cy$ ܹ.}Ўc!y ,֒DFj%[AD$Š0?pOBDԤЙZUiW+ݗ.VRv%? { ^x"e`բ`ĐCyHOIuƒ!Iq+ 98YtYmCH6aU@l"8)Q YrTI >pfAPXX?|gP*_F͙T5 9DL?zjgEDR`IV1yCQQW\!s$u>DmRz(2OkY{YG-4ZD=0P#"(=TEZbB#wEUoK/RKqiq׺ƻW!e]Cuհ#N#PeBPO)EZ`p6^XB(4fRb &ua1׷jX[ѨjDJjE>DwBvwAF6P^E9b!_֜nJVQNE8?%fƗ^fOiKy!Cnpeq|\E]bRt]R5VO|9&X%IXbE~`\dϝp\P-{CP-) Fi+5VIY`u#:q,{H5%4"HGtX.utNF9RA14d v j& 4 >: 6A@LY6W% G"#UZSt:AH +e09D'=\^iHiq'!!Cq]jMr=We2(?i)L\%+[ʼ+ki[fO, ^&1E)P3\fE68Ҝ&5ikڲI&7o3?  F ALPAMX&&!@dK -4Hhy @x@$r1 %2]{ISE8ʩ1G2NE `L E?|iL>TL !C\nV s@"tœ xR % #!NJX)Z:2 igScq@ mjTfYԗI&&=1KhXa ]}pELl | 2$܇:yJ_ AB PX 35Q0I˒Wx2_ BJ` '->蝨//` )>PŖe0^HFפ{)!"z/vʎ1O]C|3krA@~!ȞKb%Avw`m= kQt!(TrĊ@(~nY"`֕P]a!uBD*,4uB6?(MհNHFG WЛliS^ZL8iR `E bT0)??v?h[9QE}ANŻP /c@ȇҔ=\q( :QEjG "l!9' 贗&$ "-O.fu&#QgMMukK V9%Hcir+r4$m,(۰2UX4Ө^dŻYV9%LhF%3DV\F [babѨHegQv!_<_*X_Y*j_Y%. JT We_R]~5\6dAt8`ec Ӯ:cR$*E7 ܁9 cVfh^TVh`XRifllҦ4eNJ TeAL?kD?TUDrbNhV BMDTATډGI9$8@$$?T(X0D[`B(*BIȔZ0KTEIfpDD\iDWVA,BeJ' pT tJ\7:XAWCeE&d]'h\e^۵'c njOA2EM}(mrs . BHlnyEL<6C?cAXCIXBC0@@81#ΌE^J%&߻qHxLP VƘXeeY@ED8FG0fU<KKV rY`l*kANB0~ W@$cySd6!rYS&D A F.0XM, vhwI{ a(,@ h34".SLA>b?DYv$^?o"*DØ1FC͢/U.`P.HUTrS @^>btucHCWL?jA܃K@[AFEHC n&gt`&`PLZ*\,mGPDVn$D R>֖F>Z$ZCk:z\sL+U`R@e!#uYsIM=%! PVtKb ?@T<@A*O~ituB6fn@Ϡvq_c G*e Ǩpep;wd"/Xq?76 8ުDd2 rvfA=7\]>D7%z6C6~owjx$q7q) 1ZySxm.:% `pRXc $r"lA|`Ua0tDuuCv/gA`BBx e)5?EATB%@0T4A F_앁ퟡwA t%-?hAXUA$AոI/wvU?WDybE9h(DB FvkG~ZZ1*)|li8GI)B#"hi6XCl=6W=Z$C֍xv (B 0@ hzZ?0j:m>ć!0Pp?CBn:m9dERCBCA`Q:?ZQ{zuH&rAJg|6ڲ 0wdLJ\RtߴFNO !_XStDXLj? GG_&xE(F A@挘LäJ)QlhRH)%(P4i8@B>$W⇫\zjsxg1oZ"ދc"yPdT_OȒ"ԓ+=ΐ AiT@0R0SdiI>8BsX*xQLKRHAEHP_t),>|\ _4OvI]sNyy̌/O(S{!@0?4+)&LI:H 20x>Tk<4)؋ 0&OT DlC@o1+F|+ѿŌ0 0v?x"(=YpƃF#13P6F 6 0LkTm*$,(7$ӫ2ݜ 2g{%> RT*U٢:<% KՔBDt)IVX4))Y'Y g/4 zڃ (y8󘃜˃Z }~;dhy>UjLҙqd( pX5HJ j)8ixdB8('#{^5Y:^Ȟjࠐ(ΌM 2c 賤\ # n+j䀧 c1A HLCp*ĝ|\/; TH J1zK' js ܩDAl:`H }!0 $dРwV$ɍ<[)ǜQӽXS}o#+?}./C_>G"G03K;E:j@>c^A@W0AQ oт#cZYB:~Q{P :t }Lj XĤ#BРG\0F0(J($E/Zoj ,E3%3r zJE9_`I ÒZ:°(IA6MQĄYGR+HU) r H&xJbsl#º\08%C7yY`4[>+< 11j%Aba^oIGNESV;–y %v.R`BЁqA:D/)<9htA!t LAKT+_vaG 릈 bT< % B)D+eҁ5b40&@M@P^MRÛ81h%a #?䐟6Mrz=ނk E~CF͆z9D6En6c,@HELBZԡ^jaIvRʨ%rۃؖ XPtFt` * יxT,PK HEJhIG 3PP袔R8Z_Lrn %o>БY"8 ="Ap!p]"Lԓ<4nm!M0j R7fFb{,9D5B(RMD2BexK]4BgP[H ,,`K 4 @>ҭtKpX҇a9=4+׹.+tF4.°8.^\AHx)n¾Q ߐ*Bppj3;d]6IV)Œ ؈@>ThǦ?`3]iB릑zd iKiJ "h#9ktƕi Ie-؉NZWK*85֙PXe9 Eh@+smeE<" e=G>,! f E "NqLg9͹s$V`&p' qcZs7`8pQɑF'+=hu Qw1ZDu;Svcُ4y@9͍싩!KH2ϒA >cLDh;aDFer&6Oi{dN:d#  UXzEfs_=6 xdu:d$l ?dvbF'LoDICknyLbR|f("%[^HAn}JFcB<@<Qg,6g>TޢNhVAq/FnRx $) (.bd`>H;$~8"Y8 ;҅#Cr?DH:DL$ bDFj E&cR&g&="*6.b>'ô b/d )S L* ") +2*)>BeX‰0`Jɒ,0M6d3x#/, ਼)F Z>LAU.f0)3q%~8j2d KL/S*ơ$AaJc."zR&$,h8 vB抛.!\"^?&~fF`'hH*(v=ʴ*7-AB a|f. 2 A "+3, f#%?3"9> (#>b.Iã⼮#+?2i' "GkKBԼLoKb(fAv&Lb͘KHROBJ+&K/ IR&1%JC0=3BHcKT8D/ :%:`płB1LAHA@3>HC>@i:,TgɊBnGDЭ%( ?/*{|x#qRv.@ފ 8 M:@5Nm FKHWo)4%6 VK[D/"tebnmDmn6,r jhM5N)V#<@T|<]HI%"j> "P^r 5(#M!a =&"~i!/B =iT`jD*a*)V`HgK(.%$ %"B *`E a c0+3ծbTV |BX^،r}r=2([V2.5p"p۲6!j7vON&"sa CC*2&0V3wwSƀUR+\ۣD>5&X|(WRd|ˮ${C+Aդn7Cɤ~2 2wL@أ?$B.$|82/&Nж/B?$R(BmX8<@(k/?z-*B )plr`'*DNjR"V7Nu"dn31#XHx:-w$RPzCXx)b\yZ ])"@ZJ`kL#C$"ٴ^Ab"^Asb5X$800׆diVe4wa )0Ȑeg=")b\V2pkv A2Bؠ!ph "v 6}FE`,Ԉ3ȐJejQz׫&tY~X&9q"C>0 ,?38 =`?:Zj@/"@RW{,T&~0 @qz4q)Lb:[yf1v 䨭 o m[y:@uDCzRO$}X端H$?J#O}B~Z*xa!2=bl0f"8\ |w83Ƣڲ/{c߃)" @Z6PD3%;< [۵;ZL$|[F@^{۷}P& @Aqo`ڍ ^cp|)YwXc+3()"+c {04<"B(/{(#;(|?^WP{l.#D*z!BP%-7A-(!| Ɠ|!263F3O5k|RYD5|ASK:d?1 (b?c 9a9_7D:S"\_&)ܚ=>K*{aC 4*?Bϵ1,.N3 .>b).^` 6\/"pBܳ6"M#$T^Egv AF=b ? #x^f(%4(^/Z +z>}ZEPQ$FV;L&yK+ݣJ1YU O:dD&}'&VhJB]> 0aPK3^<>$1"xU?<`>c=ޫ\J3@BD4GSբc)HdhVDB vUKdH\Ufk!4 U3áI.@5'Eb#X"UPƨ-`~knLo:D%"1D$8 OBɑiΌ_esWe17e &n-(^81CeU (b g"  5=b7MEZXA)\="<#3gj@^!6ij}gew{FV4 0hjEf< f ÿ~yɗQXQ 7ny`Q >O?%)Q˃ JpÁ_$odZ FTO!]zgV#+Q&]ɪݨ?lJ1ڃ)(TxPz:H|$jDIHX/mBH˰I7XtHƾ)Kv`=D׶nKHX bڻk[F.rOspUn|dG/Iﱓ=/_cyQYHFIeyA)?@DA4D!'U b8b(l[z#r0{9r-UH7GEI|dVMWdH%YH5E_eeH:YR)Zln} Q%5%Q!M m4Fed1Ve8^=%g,"tFTr?8+:1AVdQDS"JZF]J?yc9Ϭ?΢IU#?eSCZrl"ň gƻѕ+DShd,ZE`?)S]]Eafh>)0p\c9lA0NHC25(rO @véO5.#} ouX^eEcYQES  1! @lZ$}@wij1A+uFl [Ђ6/|-I&p3l϶?'sbcXWlm``6T9D ݑ#Sgw={/>[b--QGAGWDޯ֏'`+HL7}d9Fic4 0 |PfQCAZ YfTgH8ġ1!-$> &KL"AM0Q]r:Ћẹ>a.,B+[RlA+M J4,a$)I|QD`A"J!21& D@yXJ WD%N*erc0ґ$H80x/t!M?氺D$QBE %(4B$A bhEt10f|M"BZ9r(3DSh 8>-BMdA$ ʌN`KV!Qv)WD/i@@ $\K"A!"0L0iJ0#vyd.p] A+6DhYczkY8]u!N C&D-% q,K?MԔXʒ.H<]1 5y0q*E8j $ BZlq٩6Ս8i֠gI&T/p1ұAH2R(] Qb q?&*]5@@@޷0jinF"bG!n@ \ECE#  ~@2"G %Qx8a5$(t8Q1}o|%hC"L4]lw#fNy4hZyNBW8XFDIWgWsbfX'FD@ ]ҁHMDBPIӳS9MV(Xȼl@Jzj?6M<0t8ȐTw>SAO) Ѧ}́#u7apmSq <Ap, pf`bBe 9x$̺AB+@*|:**G^Zh*(!)E zs)!񰼻9ى^a"]ܴMԲ,~`RXa8P#Ti=$4~<勑B7&X=%F9HpBI$qD`&0k:SvE 7Tqۨ7!oX9bE8nRtC&$toS]r7JA$js"`n P<`Ky#"|ᘔ1<Yb g~#kjׂsnCj"1:e<ra(@@C5z ၞ:'t:S(-B) :& @ Ǡf%5B W$Xgf@BX =<8#Wcj#'x02:4:@q4"DU [ƆcGRd,  Q g%D$TKJWj'xoE\ zP 0z*pwڝMtu7, ] flNOrc $0,)e 0*{OP :OZr(5[q8q!K.z(>PӄF HN`K86ri4p#j(E@P- $rPNUmp $R !Uzn7Pxn|Uh+ry? LWqWd@ w" 2pN,Y!Yj9B)Z@y&#`[P e0A]4eг s@ "A&@-$ @8g1`J*a'[]b"X h$`_ ``:qdu.S)G]b.Qb^lLvg U%'A#Ä#NFX;l3P07m|!Q3<-؃_Dn>> <4 uN{.v^Я \7k>~i7R"P~Y^>^붎@D3kc،!2 {<87dzK41!W0Y1z@NXz>U@P ɰI>1vu,_-s #HN?bO Q סOOYux1,|4>J`H;j%;Wj".`'B" x+0"1@E(B/xU:UkzN w_o,a`nﱼ E5H0+.o1,M^ qT6̈Z}'J=:׈70-q=᳅Wָl+CU"~ldF_v;_ja`5H 9AjF&Nc`/ v_~9=#QbH!QdŶ#\$@7hYO >X…2t(@Dlh| aE EbtXbċ+( `"J5miM8KړψCwEeҞK>5yT UU*TN#kR\/E-X\ U)჻lq ;:lS8.`Y`[G;,pW;A㗰Wh^MtH9WfB,vA?!fڤѿ wcbK?קp/P]r*hpH ꫅9ƿȃ0rdZ=4;LK6; BJ&4B4I` P+"rh/ x:RbD8 ('I4ӶB`4-tߒR8:ojD(Mɣ9HH7DɃ Ȓ%KR8`>PV^}&TO,:pD[ :>@D􋋜|SR蓪> {: "*@[JO pbgL۩vO)D_oOrtq:г0}b7Euxj^}mۅ@W`۩{/6"5 y[,va?-H1R7iܪPɚDILjnꞎFkS*.i)%Q/0H&U ^}h`H]QQ(_;AխZ7Mi,`঴%QN8lȊ #¬d󬔱b ]VG-O^ Qt)tGN_]R@@<0Z!gno^pkj(%A2?Bײ`*?`CVnt Jl`@"!(L1% Q -GT09ی q@-$Xp5#,EB,-@ҁ(>@6(`{ŁhdPlE$j< v,^J-2JY9HBKf9M%@*mb ʷv&`"VT2XG vt#&&3":Rb<~#`^((^sRi(#T$ D~Ki"),D`&-(U],ɿA~\lRE% xR^u r>!g-#rK&kV%wJڞlpfƈ{MxːSZ=s$),/eOAk"vF]@, ]l:hl7[[&͑=Q2﵉ %upF$݀U䫉>r8aсŅ)Kd$Re@<tKd?#iXw7lLƖѭmԨ\RRh˸@{n/Ь-4gڣX\H;e3@x-0 _0U#(5 擲8 : { 8`Z;C"ZA!, S A› 72!/ >b;BRA#A#"T IZ8{z>*=|BҦ;C>$3%C$DVzKhG%E4VR8 7ZNq9Q$R4EDTS\6\UdXYZ[\]^_`a$b4cDdTedftghijklmnopq$r4sDtTudvtwxyz{|}~Ȁȁ$Ȃ4ȃDȄTȅdȆtȇȈȉȊȋȌȍȎȏɐɑ$ɒ4ɓDɔTɕdɖtɗɘəɚɛɜɝɞɟʠʡ$ʢ4ʣDʤTʥdʦtʧʨʩʪʫʬʭʮʯ˰˱$˲4˳D˴T˵d˶t˷˸˹˺˻˼˽˾˿$4DTdtDŽȔɤʴ$4DTdtׄؔ٤MMzMbFNuLDNAJt4Mu<͚ܔFP 4 MpKOŶOOg\deP4PXPe PlIp _ P  @P G ۔LQzXQqGIvm RuOR%%Rɇ'(|ѵ+ҚR,mR')N)1.3=SQ,6}R4eS8MS:}S;E=S1S?Q|6-SDUB SDG-OCB=T5MT5}J HTIN}TOPEOQ%J()$T<M5C: WUTMA@U8[mSUTSEMBEsUK-V3UIդ\VպBacKE9UkdhuaM VVpmPuevPnRlVg5׮qM /xE(TztJt}qIV%UYS=׆5^%؅=%jE EW]UӲ؆؇='UWxR~RX ʊ]eV חقYcEGYŸmڇ5ڣE}:ňuZ}ڨڤڪ=ګڢU؁[ ۯAU[%[Z3cڈ[ڻ[ZtlԳk'UZU۪M\]ܩۺUJEE mڽ}\m\\E˥[Ͻ\ܾ-ݿMJ=]h]x J%]ݡ\]=]=^X\]^]]=CX}< ^^-SQx^UA-^^]_}ߕ]Wu_53E^53&EV_N5! ,)d H>Xg9 94Âz0(N(G1rwc˃>e-#%rKL&C" :NH%4vW  =3 @N' +PsoD;7'>ذ$Bʂ8li`' RW@ @@aM L%̰1R =P@w|)@^0%=Oν}^UhQ+ Bp?"S H&z`} bO@EG  Xur,[-"A!C,%$YAD#P#$"yh##,.=C,$?%/s{ T@h(H~FXcZ$?@ C 0a4@!|dqf}uf[\w^b= GT9Y AИO1pQ*P `p=A3 %m=" 93ygxE~ @gvIzYAKA3TZ‚@eV PhT!? t@HU[?d,I]e/h~ bXEztn LI' BlA)Ҏcui4D"j?R< 4OdA >U q\RD$,-)GF 9\bl0q`AqKh)M% |[D#_B` ΃!e$$ DuqL#[ hxt^AT HAS6Y@Ja}4BN [?9 ~yHy7h.1@ LNT8Xl +H B[vIE%?Ly8  ]8p(2Br`4.L⌶T}(%"CĂhb>-МĽ"\h@`2 h6yrrI F&2wy4lXб/_UN}Ήکj:Ѕj;; Z%wUN{l*RۊԷATSi}@*?VhQUj^E]Ѓ(`xAd Aẉ 75n{eKB Q! &g 2ojkUP:U^ W Բ9DrBY[ϸ7!:B3q#TANdd, t '!ȭ!)8&xCfI8#rKfZ2F ɑ Ac TD7Bz?A@ u q ih=?!9YGd{o$ R ,N 0Ct#a#P Bu9*qx F &( >.2$`҈ c9 29YlA6]Q @FI)%)0ϦA>00 $&hʴxc4VQF1MPQ ^YGz=QGG!AWWxAYÄ*ɴȤYט Y5"Fctt(u$AU_IN[!qHqDaL3 P47QiMQ$Ќ 2LtYWJH@E@iFFt1%Ods&%3jPcdIrFk2%_v &6%zr.|APT&Pe r/ՠo#Cŝ]cO{kf Wx!:/h[P /6mhR1EcceqF!u%z~KRzE#D"P4DB"Aa PN8gW/fj&59tWEfZAZoڦVJpu5.{WAX\\\)0 "7]e]] #)9x9d$au9V)\3i )@Z+W>i@#KHQ+Av& c6J#z j)T)+;2I`"ʕf?FDfrs8 Ag"ce1ZZFqU&Tۄu3e\"7Pfd7bqhm:d1 \']bguѬcg5 Xx*d8lS1Se!=Ħ!ƴ+,ps#Ӣ_6bl@œc۶@n{p |MQbB,k6+ N Zkq 8;:$:P^;QNq16-/' Y 9a)K/79,3gzk_x ɡI'IqHW] mj!s|яhw5ȕ! F1 U!~tqu2@j')rq gZs Q:UL%Ju ;%su  vq 0y%nŢz%1@ae1{@_{,| `| /p u!2@<<=p4y,xuKBb BNruo\aU#1BǝPflgq8~ }߳s"ttqw$[/aa~!քC20.HLZDwCL:@mc..q0- Ox5B`& Aj*s3.IxeȋL^#6E.NgdQ`"#S'3T!@}6zc=9xgΪ2!#QKx0+$܇ќ+.$ņ 40ΐ^Թ75Oxd}3!L\v̡q/ЉF˸aPNy7A2Jt9fcit#:˄g!0Oq02&X{9 C4\P' 0}@= J % !t^#0S~j0P#^94ZvC];~B%IQ!++!ՇC[ TCQ=ϒ<ۆQ0rz aK5+(C2?DDX_vT5(@KkR1 G0JAZGFkbV$HLQH q5wNaL4d` YZLФXQz5R m %#NQ@kUDҙhR6K>LGINaL*! /M)Ԇ1Mtu Tw`pm {9kUK5n~RKLQVOlsQ3;JO鋼R"M8*K/gn2)S`kT%Q:mIZ!TGAzTGvm9-PGHqDL$H$x5!XKj7 E4Tḁ9!$٤P]bU5.j/ puW?80w*LbItGՑyme}Nhi:q 2U]]8]P >6&!A*~i|h݅Ҫ E3:?[@e5Z7R/SQRA@}t PQr(<7*zFb(Ta=ߝg&Okcxibrd?e ѯAP8`Y&P^vg Vgx+tgsed ۪6-eAf&5/*g~ x>'#LFCcuf.K?C{7V]7Nim`qYRn_tT\H nCOP_Eo3ߵR QI(UaJ+P;UO@ DPB >LX` C(ņ)К=DRʏS4ȑ4YRN=}&A51A-8п0UևM@Tdks͔K//Kn AJbNC&*C[6'.Q;j`_[`KdB2' >˅hHbbɆ[a9iAd"(dKRx,@^vӁ8=$dOdYڜ)l*! C ᒖ (b N HDx :N[* 4Mmd` z&( *2/>8:@wcNʁ(jIN: p⁀FL,^o a,DDg *i*b1۲|6(MF"/W,cUZ!R R!gKҝ6<8!CcqqW(0o*yˢگHچrqAH . qR (ly 8(;TxUBhz!p0$p9Z U}]&#^A ?APŃqFPPmxE^|@S b=>}p 1 Vr{ D<*ZVb ti)_" & _0L >Q;PSri?(dqb<,?y+]Rօԩw͑ښŔ#dKB׸t1x4,[&pȻsɂ Ɂf4AJS 9++bY $FxD:'qf 4Ȍ@hDC $\xkhnq 5H!Y+jER8Q6T!%5K BK!Ɓ "*P"գE xSЇR4z )\p Hb0f 94 0^-R [X 1p j* ;4?8x>dm{BX` q=d@6ơ` 3țb,I`b_,_hMFWg^Zd!ȉ /?x{>H,x Y˕s@ YI%ÛLp`Ы; QgP4 `1:G%bgmDC2)޹!],:,˞kPZ,!f6$+ aR4)7_:;cALbRq["#VǓE]b Ԝp -:*RA/)5rāb QR4nu|xG5 gxcHR@ʇ rTٮ ?!잯|WFq9@GأL1lEu,֫w|Q}g5ޅE{٭.Es@KAĎo]0q{w gGc4z4FVB--H56!|D[9 z>қ#ѹA>2Ug'0x *T5AЁ=y2 52O^D @HoT}I0\0 !N<.C`ah .ȉXj) *1(wRXd(+p +&9g,9ؖnLAj`>Gks1VOAk_``S ik dáNP,2{A|; ,i >@*A'p04D+ nX"@Dd;X @I>XX2HaYD7Y(+h yoo і (ӐYbFyٚ\\xѐ1Gv"6%耘*ꀪY&(9YY|i9g$)EZ(4ipF(FN D$"Ysɂ b# @KS d0'l܉#ꚌQՑXqpA˻$M8+(Ĝ$344(*ʓBg h!#j(!c!V1Gر3j(}аxs!RM(!$!&$#6:p"iABY+0A"やᒗiHi6X"y@ò+AHaĭt`=D$،Cʉ(Z 0H"KڤςP؂۠B p(U*e:ω%(c*W ѥ Wb%ۊQm mJMwRA@OrXu/Av*- σp` ,*%t@8pO[ ܨv<}+; [H7)B* (ڪϠ)ZTJJ?: )J*!UO4ʙ!B {+J;pC?%ۨTUSSӵX,H\VS`çl%P:bB`%>>hyxׄ0'q'.&W /1mTȄxvpU6,V ӂ0OՉE15Ț-@հ 'Ȃ(KH j`˻K]JiY ؅pG][1h1{8! LMH9Jx -CЈB+; `0GЀ'r M'J΂A6[ 'D)Jࢢ]ΤN5*Uо&25;7sYH1i]m3 @426ajiSE <d+5][U4>L߱zQ^ K;hFۚ4NPde6@6?ѳ:]e30:U$ )]y ?Jm+ (һLm e{1'`5I F*9T}c`EH?`ŀc  3^WᩋapK"[ (V%fbH:Ѓp8}T:..k1&2^^M]?Lܺ34;<=c@18"\bҗz? 0-Sx?sdց$dKP ).(Q@bٻ>%"{KhڳJb02e-Wp-=Z/}ſ6LĽ) %h/l4Akv3+.H#Ѓ-H Ȃ@ȂXZ΅p,`=  =aB >A\6 zބ>,1bDpDAE~.h%| T` ^BaֱXS84 yzw`+¤Q)k)x85NG8vY &\- <]ka:OCq;@8DP;CHy޷ \ ϶hTK9E(JN á& |־UWd4 C74 1Hc$n;)DȲ(h!pUn pGX`{ `5-fI"pYiƬi6ƁHʡ2j$ |HE ڪx;mBE_){)\m+lYmoȽFD=c𞝰:m3 ?"f6rh9(^J4Wc)px݅`T:6kCxX"xYxNo(D:G4@FAى"2R1GyBͮEfZt+`0@B9Z Ãä (LlW@Y8+ipyϞ˂#NRh@u `o'-M C/ p,,h?==B/$"z B8 MT@UwT* ajrp`Ho= 7^o 6Զ_\%,8.8!l(x|/=zJ֬@.x8B(`$j(:xxͿJNo'7fjZRpׁ$X҂pg-}!}.,hA VdAD 4…A+> 1H e' .0sW. r߱E ,J?gBV{QFAUL8ͳJp?wZ B5zc Tz G)"ygWp/Q d0`$^lAFH 2HPrhՖ HiqҮ0`qD m8f1Vnp!HPA d ?`-p7`囈!&L~ @#`D )x?&ul'0݀]~#P 87P|)BRw@y2A 5מBa? 5]Er IPz&Q9&ey&(AhyF9SiTxޝ9ޛYgBaf:(J:)h@/ KYTA覝&$A%*:+1}A*A43/A:,J;FihxԊ;.{.骻.N@[x88CzA+?WC{; &D3!AW\roc(S'UbXe0z@*Ea2B\"(+Aq3A1<"$p A_ @'m1EchPvaS [Ħ5aM\lNN@Jӹh{ Id@!P#} CRS'H j$z 1ƙ?Iq z#@ /7:ox A, HkpVWڢ_!ĉ&Bp1I#&6Dq & rvQ qqؓm2 VA&$G$]34NRKjiA=$y qt] '0d!<%q.N>UU 'w4*B22# Yt>4JxA( TE*<ȀA'r גmh8ǜˆ'5̣NU],E3’gTς6BQbT!8 *X*BX+?U r ESzAJ_5OqZdsKq$BH 3o&GJ.ZYn(}^e}\,0K <6L 9՞ < O 9( GJ:LsdJ(ޤL9>Ǿ#)A $WP6 moݩNQsr8y9P##9~F!cQe@?/+E/OA]Uԟ- 6:J1QTߕ= fn v~`sdTm!F-YAF4H Ut²$FA8rOATT X̘m\|LUtX M5 A XJD´ =A BļCKtBͯFӔxA "AN5"mMۨpX⾭Iȏ3D|B=0;X4R`4r,A$b@5CQC2?A\CACX=,NA=T$POrS JIK uBȞSR\dކ!cDfqNg^VYQ~>A rH@̌AZ:mȸû`AUhth4b&dT_iCnbAh\/h~AxxDv9 CLd5i\N@qlԆoQB֩BXD`DAA@ ÅQ#\T"G@=Inlsh ]8Hf&v"]Q:*Lt@Eju@@]iM"`Euq J)靬ڝ ~~m@n"T5&|}ih[hB2D+n6(2"D9T`Ug8[M5ZXnBrra]%uHJev+B̆$\%>Tp*E>րiT;El24̦Ԧ~q?8oppzmDhB+cAcAL\Q=mC- \}p'A0|2D}.V Cb $FL C \$&B'ls ^LoPEwḦ́D@B D@ގ (P@  Āwtx^AȩlGEU@41mz6-a^oLԗ+QY  ̉YEI\䪗./ÖI=,!-1A+0,!v@y\n݊I̪Y|(A<B?E D1BtU^ζK%A冥DI&KQ{86=*En*AF[p|iJD KQqlR3e׊ҝ!G%mpADNAzI8F0iuکwrSDW6x:O:Jx8mjlQH#A A5^Zߎ~LjHTy|:whG{fɜGq`BJj=@StS#3sƯ:L11Ciۊxðz .KKSb>XyȣU;*l|w!:;ǻw".@M"A\AA!A\%pDEOaJ¶0HFn0AmZ)K7qISL-&njĜ&6Lz@ *-,yiգDժq2`E$+v@+#a?^A^WQ!'C`?!_xߚ/394}5\×B;#0PҮq31 ͢X L)p_ \ |as{}!"t.pG'kB4c B9* '!_ꯠq&d)J(h=B-XOlX+Dth)f khrhhDbrh;O>C &(d$&EƐHB6lL꒚S:3N2h<5:tF=ڨ۪G2@(P(5 4T4Iu"Uք@O֌V^e)=2 Љ\h$:ARӫ! VJ8,2(4{< 2c ΪDGLcL~J|Zch"T i'ᐧϬ8jbhj'm<|jXUk"J&c $ Yygq4&1aXH04P+ޔl3b韨Jڀ+d)ryG+!`IOdbƕ:/v'! Z{^X'Im%?˹HlZow/U:,BgAh";x@.}D9 @ 9k:?#In2cC QhpR aPRW/ bD!C4D%.OYD,hH|!u؂qt@]@_pA j SCB_&̱C ?8x$.ItO#t. ZxWC|2A:؂@0$s ?,фX`O,RB"-? i!jAdg F zh,bx/?'SHLnr٢@!EL v‰W"G  ظфl #WG>G)9:"k "pȨiN [G$@iI;&+%FW#pa)X? Bc( ((>t@H*V"\m7m X?, `Flw(ksh#+D.y@20VW, L Xo"t m@ZRx w6M$Ab*"hO$U%BPey$`I)!&k+N6İ6qJ$ŵTqC$  :QPf(r"AI bx"PkANdiI43gמKL!c"GZT$ W>H*q3E2Eoj ĨEd>K` T; !`!݃2,q3?Q ;G C ҳp2C:c CHzp +\0ISczGKa@*1?a 2#w3:Րi81 x AKUOI s !|?Z4 Tʎ\B&lȖ|H Z!E2xbJ$.h#,ОaePdll b|3(P4!5CCJw!$O?q@=/O2$K ii/R?E_ ^z+EUޛpATI"|@k">=V椱TZG!D"\tX\b]$~S*ĪUIuY1$$L X$<#熢g$<>'H u̴s,ue24<!, Ķ0x&ȷ&ieVU$'  =|ZzT4QsZ%D )B1a*")Vqb%G2O2r"4.!V1{TQdWp_+@܇ * Ej+/~xB @-p1#4^&#ZNGX 4-^-X*J1|",q/uH0%b "K .b" $/+K|x1LU| b| O!b!(U?!|"j&+'O"$8쑰 TglI'B-{0U"* "["àgZ8',G C|{W'h oLbn8 :&3;gBp#j :! 0@ g:3La=<":˚ʬ/AzT&f",^3dbBNa;/*ceu #Z!ܓ0@0PE0Daf Cvȓ0%V1ʌ.xDadb;0;RTaAF(h!t KHض#BLA*KFsvI)#!m. "5-M 68v%#L<XE /DR&?r?cDWs/=+Ma"n`$L* .!$D#Z/=u6YY7r7zs`rv~Dޢ g0L1vd"s%BTl}1m̮*n#%$zMzWT:a[8`!@ `E d@!2t:M"q[G!lQ_:l#Ab°[ +A=pn|΂$)Pثe{zeJgq;u;'؈xq@B%B0 D1"|Τ#D9y'U<9 8eUte<@G"N (t +&#V%$QNb'܋V("ZB&26J"SI3P M'=PS`%"NPbUC"] * O4lK O"S?ںPced 6ٱVY5Q`JCy.~BX0e BliZ&6tX&] `CHoƷR "+N䰸#Z,gtEd]tTGLS Q1EӝsTV2_M?\-@PJtUVe1k 6K09 1 wr}!P cmMsg4YSĸ?u# S?jÈQ?#ÿ_Cz'@ /&t ެ0ñcAZEQɟOԗ5&}PiU8O~柏~up|@H*DB#tBhg=B!@)g ! Q1Ft_BB B` XҔ{ ! 2&dRB*$80P {L|h)8@!!!A&Ё_~#4BD`lH_#$x[[ $!X!D)5)B6DQdBD/"X !lBQ,`1a)y-> xDB…!$A "2ߥ4S(.1 Id(jB x%?0_#q(BJLc;jA%]>dBtΐrȁACp@Bp ~G8qT*l$+Hi Вi^ KZL>0p} G"˗sB4*/JHp hPS~[C Dnl aCP >d T<6{kXAY>MvR>iNIf"T=qJEzU]MJhX@ :;ThFqTE'@X9۴e+K*jT 2ZRK?E8"!@z|>`Q^LJ.2%16^T7jP֐FrҫbɃd}f×E!4%-5>@ z e-+% w ҁ5{lAb+LC'.jL|tIa==Cp~`2*%TnD#\*B-< x\PFHcc/!2'7"PFfdMP U ΠP BJ"(`&fd5@hd,:BqaLb+0Q+Q, a#c1,B2[.*28+*S/:(?m*?[1q`>&2?a]A1^"P+a1R:~%-,2\D%^++Kx$;]4QS6Y3ڡ\[c5KDU]Õq <]fwgrKQ<au1s^AtR45(<:S^ qd6A9_Q;-t7c9Vn6u Sb kr`8@ƍss ]gI a 2m̠w ` lUdP "0$Ðps=˅=HXTAx3>Pb!bk3rt քtP4iGI i $JЄq fe5 @oG a @DAUv@'?!q]QI&wɗ]"vR?C!mwWVArB-6LC )D 9V_O)|  QF v_E|kAA-rGF qAj Q@X;KU!ǰ *t_I01ΐ7 D"砌tAp@_KaII?d" iy!I66!Z5c&t_!(M 3MQy Hh@'ԗ9 -eP9i; A6PAP9a 6=Ž IQ*s^Rg-0 ` eQ ?icp`e%_D/XF~ +`41eA "r} TB/띯Ia0J0`db%'3^oRc q$ M[BL 1&yZ&K  / @*d)"`5/R{dR6`U]8ǣKAtpD N4u]Տשӌ 7 7{mփ-<ְ(atO>pDZ;S6e/3C3eM؟]>@`$f  q 'Zi Ӓq۵ǣqw:tvmǍ2gRB~9Q?Y?ӕYyК 1Gd{A$۝Oq?ə qY9*mH]u[qÅkx-lU'aqF2[RqzvA" \$ -~]P 5 Ǚ*F^> a }-1 AIgǐIV3Z 6Yx DqPa00fV0Qw1ztYެ\ n2sc2pb A.Dv 4p^a: cA+, 1ZJ<42#0ʱЀ9"؀6!fLK :!Vj;QS½ݼ^&6m:0tЄ,Cy,5[ D?)^@缇.w8C{gEua7}g=s9dsn0ф4mI+Ŗj?裷z~{TQjbIAn U̻gݗ~~:OH|8@ҧ05]UR.u`-xA㉊>ԟCqo$a MxBP+) Nb:ҐLG+ibPѴp ?HĄ0q OBsrYgHtrC@)Fi`h"KHF!CB1#*.P?CI<|(Ǎʱ_h.H&Rc^%Z$ C[eBhyƁ|qt# @A9hkDj>!;PYݬ(́AY U,B1$?eEܓ&c!I|9jюA#X m3h$?h9>ˆ&B 8?6Yy}^"GAa!4 ?%qUE)I1!d0=dV4!Eꀖ6?v)+ A ]B+u zl"9MK@I'JɵBz-]:l")թ& GHlDJ_IT[B1Jp,Jy%$< ,c)Kd΂p5-;1JmiȠrK>SD,! 7τ㝹x N9k`s+n,(WUS9cwsѸGYBq.=Ynh@ 7bs* A@ 敠5Hݮo3G#$uSM D;4zuYbD>*KN&{4gDzIU 2`KMʝi.Ey9dPb܍Py).m&B$cy?PҏsI@ \uIĠ,Y;2"(? 8p,<L4,Rٴ3){ z3T8D[JH`4ɴ4)Cѯ/ y@ AY$y 8?BuyNah/RXåyCcؚ㠙y)3NɹEBěì΋90E E:9@UE^E_b JkAut`Ɓ8BCIR_mFotcQ !(cp!0z  ^A8:F}G~t0 [Y Tt>r~\HlH|H)1##0W cy7ЃHB{̼HЫSAE 1'ì'pCQ 1T 8|rS pƪKTFrHHax|q옇3#R5))ܧK4#K˜I{t|ts&[!8 -„8U(hX'*4{99#M;0ӕK "LAJZd ^`'%PًˁEP2FdZ@<>p2~ON>QxGMOrs` c3pAcxGwT S q8ucxWY+Bv -4 ŢRI(H@P' [hӀ+,, B gJ X-̺(|' D:m %'d22+j-|Ӫ 0B :Ѐ(آqt;E,GTz N̞>_T 0.{x=8f",I)w?0`S "!xQ08Ϧ)aF(ȕhƣJKE*{OPS1?#Li٤Fb0W`VS X|؇ K f , e+H X$ãKXؔU !ڊ/ 85>|{ʁ5Zi04 ` {TN 󥢍 \$ͷ}S땎KPAMG u5dUÒ;,E-4ӎ=P:) Kԙ cч iE=B|(IԨ ,0s܌P9 m],;\^uȫԯȂ@` -<과(Cش%:0i;ݿ&Z2ڧ^q9$2B*Χk[yۇ `ƈ~EP[,i>E^4cU.H7*TU㾺R^rU >?,#zj?/ \?jmN 6s]P^k1QoUsߖ`D`Qz-94@QLa`yؑ (.ca+iB.L$L7+J7;, :>hv`dDMx(hĄH#Ez D >ƨNZ/AU2\GZIeQe^ZD%H Utc)AMx fkflN!jFkFB(lgr.gz$azy@X VxRVhk\+>NIyhϑi鯀%X ] v oprźHi"Oh,zԊNi[lc>x+roa3BnrL0I3x9#a"n'^Oh-k .5 00RC8hPQU0H{;(u sQ "GPS2"XK&ʈRb1њȨ@1:J6⡦HŊ-Ը2$x 2ߺq/|]VdRLffqA/ O .'!4̝AP)0fR Ig 'LA ң#쁀$t%{ɺت8lGanL[Jq:>(mƌhgپxWCWizJYKROWt2^v 1%Vr>xpBp5+(`ׄ؅X m)اXtV is,8 (!8=굈}4fYNr MtA.;& 2VNB, -BdyZ5@;#+tNMr*&: mZ-]=b–wlЮ]%h &gD!|*^H;Dx8Ə'9~ԯc*ܺRK=;ϣO?q/>ϯ?X" 2ؠBI $caeOZII15 Y4  AUIDÊ5S\Fp1l:1\rÙ .b/kބ( UP[NPYP =1F$WRGO{t:TCp2OpkSM4*$s8 $a? A4`@ ]DB7EtdN GpD HPjKubdAAY9UrDʁ^p"P@PEL[ -Wz]A [`%}OS?h.s/yhNO(S<=!@_}AtBhPUӓX.1/R@l8fvڜ'PZx-(^p Iq5e zBL,"箲)|2/|RQr- FS8A(P}Z%ف DdM%04Dg}V)RG! b)O0R7'AfFp\wuA(=A{H@KNB 4"YA.GF zRbk1]!.UF *H$|I֑ -#>R$ ] ';4R$C`@& , R˞pV)-65?+耞0)u[vb%+p"SP*/&ق'& ]̾/h nI;s"gXf3?V -ȅ4Wj)+( 7} if>ܩb[X|6;f-PWt/O_{;;n7 ~< n$AC<8+nǷ U4qD!-.ZsaC̗B`9ڄP$H0>_8Xh0Dj($}(LҺvb%I`qb|JȓdY]iSMbXOp@ wH+HTF>-"3TTbRD6 {Gc:$q).LaK1óx\t ]Z hC lBhbY+=D#"a(ӎta(Ɔ$`iHGQ.D;aDx9$4CH`;# Ȓ:̊D1M` H\XA_ɤ ^KO@d n  `|eA  p @m*ل/\A_>BtűdNV<@Zp$rhE bDXaaF,fEPH]@@hNT@`tSAbK\[8SޠSh@da)mtEN( ES'2(D\\U X!!ĝDN DA@Y]Nu 80!G!}TZEbES(RſE M ?IUqLG`#q AOLƺA=F=F"|L ]e2bUp?C OUYD`D _)KAEĹމȐ%? & C 0@#D]A/a XCA_6ِ٘ 1?46ÆR. 2Xo3?^#,Mp (,X]NMX>2 ?eD$ Ok=LU42'PL)S"C@Ñ̒ڍNC (b"fACQ!Nxd,.SvF3)?C)-3t-JmNOaT@1D[TߩF-D6L)tO4O*DN"X4f)>@ OEE " D攌XUG[{[=DwA r9: A઀IԃR Oo pm(^@FZRsHHp5OcOO5< R,_D\w+@;sArgƶ2f%C(M,A,Sr:5L3<(3sOH:hlOp0T(RDBrV;"PR @\Nh yA2D T"eYDX#`uQA8{3<ϮNHF(؃}htA~ Vm;`w.0Dd!'EZ?C8M 8N@!?dg Q%DdQms>'u8%@4A| p1z'4 ,Lk\yo`9|ڧ)&T5 T%L%` pRم`hbDu `qhd{hAB,lHŖZU2\}GW4\m[W9iXb!Vڅ\UՖRuRiִygN;t̎=}4ziR ,A3;4ӥ[vlXcɖ5 v?3# 8_,4ѺEI R.>,sID|P&DB˼ Ld\uj)n,݅'D r~畔;% *kHX`jXdҠsc^ "fl"* p A(p8̎H39Dbd"(Gˀ' z(dO71rnGr* 0 %ʕD |]ق4p ~)=H!:mu)E, ;մu?d  W]I3 赩]]c[jym V X  \%Z\*X%Rޒϧ~gK'Bx `Dr Ri7Hx`  !ݚ.H*0נ+6$7`۹,jˮ8H1\H61Lh;j;Fv>#r")_h#z!9Z>J0"q!oGaM`ڱq50v/yJe O3=CQ#<ގ6(:Ȉ)^h_Sgus! gɂ|7U"hAA?V1Eg$pA "Dq+N@2_̈́AT첐"",p'4CA Aô\PD40KL 0AGLц(`hFE< uЀ4Q^QZV1hźA  ͠VA" 2A`7ڡD^Eb!dQ! dx>G 5FQA "pbFLk! KH8"z//r2VKSfA mU:4P .$$X^QGK6L / 9/a_PIb4e6(MSx@mb,)b~ "˅%ndCKYjƐO4'YZޘ ȑ}/F+6-$%ӂ8?XX=r?%U*b  U# `& jNA6u ^e>I P %1%T'Gs 7GdFAʑ ;Ȱz} c2[ͽ$zEPgC9Nk$$!kjԪ k&D.ם]4 tl3Ԅ6t_vgB 7ME woo@zm0*mށͿ./VNnW[#{1QȇaOH*/TŅ9 Xo<#-H9d<8|*X\f3c!yw![y39)lG R0C ANqgG?LJiEg'q2iH:'톞 D5oVFP&D27 =y2Kil3^R&QVlgd,@qp'. 8)`p/$au`+q K41A$o$z@:H/Fl"̶K kb%tZ5'WM@uA!#nSơ\Ph)t ).nun .ppAJA8$!.Yr`TɁ~? }; !/1"%0Eq`D /"|S=x%I*!a))7E~I !iv 1dB\p 5_y/ IRĂ8ɃCT 7ErA8Q_ݿ!x Ȕ-gKdq.E@Pt}%Z"[cm":[,]ZeZ،^) Pm*f!"`ani. E Ik@`h. %T _7xC|zgf+H%*`ZD@3\"_^Vdd֢Fתj" +n&T$Wޯ,("n"o<t8,V `"afBb<h ^AEp~ "v`>v4Rr hlL@DFrB*%*  }BB0"֧+ 쨤riJ8LEԐ ! B6'yppr* GV`&` œ`PHʰ#8".DN~(,$@D/X "}#N1*@ ~B\-bcX'".,. :p b --PA5h xT`B7Pi!r"T!BL x# #%£Kg-I$ʯr.,bYb-'tIBj ,+0)(X4$/j`^ W0^b[b"g YnEi/ܒ3j Bf\C!b 37i.'Zb#aR( $&RxC)w"99  b%! "@XhR#Ʃ-"p1<h(J""-'ʫ ?(R Vw?C|$#F"8C34ALDM. t F2eﰷ^A:F!$F]nN!,F.a~f|~Fna!$g~aB~%ط|kuntN|BnyCM܇rWEF ix!% A,%JP7 vd%܊qS`!wIl!L=J E "z^jBb c&() %=c0 edgX *F ,0D~#d ZQ %!@:dpoenĎ3)p_jиb b~2S"46 @:] p/ )zIEx_Bgb^R`}K|c**8zJ,hbhYz,b+2Lv$"RCR+"Pp+RҒ ,fY")ٜz jWrim!B Oq.5^GF;4TGrmO( q+I{[%m lJJáÜg<8|NF z&jebJ {;ČDwH bI ⷺ4 "5! 0$D-#4۳p2${8 Bd|K "`1bj`5 &M UBd $a6*( Yf"gݡ2I%C ClnuA>*Xn8?AP""0tPFP3A@BT"uAգŸTW* ZPxieiS QLDخfGAofA,I›f rҫnL-ܨ>ld/0/#PLE E:p1€JCMYViBY,G)TW!'8"ӂ9tCP)1 1"CPL\5AUW@"2]` K@J p"zA-04G(O=4MC)?B``C98?ԅOA84_9A4A#=t$C|NnwLU2o!)AAvLT(5 GNvAP@Aux-" `?o= @A% m?3Ő_~o#2@j5$RY:2 !!@XMHuCRXn yJ\xP(FrؐPG()G}迁%.6;ִ--cYAVi% C>Ƒ3 X2]A)T^Hpn,bjYnT*G 4tMىQW%kA_\T!DR+@!\Z㐼b 8?^Зe:1AhES!:!ׂ4; u8䧳 8 QrG:HP:N$-Y ]12f@+Uh@O|:dõLpӭ.~2 H,ـn Hˬ}PS26d~ &H"J ixF!eOl"$ ֔|A`*j9p FJ!E+kȶNm[?lZ%Eڈ L`+MK&ߋɀtLL61$CfF-L`2(yQrB*cSeaFpL:/y eck;P 2 M³'MJ[t[_Vg:h?X{vʾ!/,Zָε!tH4U6e䚑[7ЎMj9(Rp%IH K~!9Wl-S|[ 7{\A-Bj:H)XD,(uP*sJ;{N~[<ǝ 80K ];t\5.KCD$8|([{)n.A}?@]@A> c 0 bP&n)]A]/HPsQ'(c q#cEKmfS$1t*M?T)p0QBk)Gi"ST ЇWEhD Ħ"F 8\jug DȤ `WXA(  hCIXF0ffsS"e0>Vˆ %\Ifnp빂 ֵA|>_2zlln  FzUBT3R : ^q&$2`' F F!}1/c\ &yd&h$bH[ab*B)Xr%)n!$nE`Cw)&2t%aD!\^U'͢ *q%D)0&RTՂ f0=!I f/4TB0"Ic)!@(qx!"@X)q)0_xRv0'rN@^~`"[xk&tN+[X)&5F.&bmRdh -.hwgĵ\-&8L,[+RF, 3 BDЅ&hю"8g-3-y -!z T9;wF] e'Q6r T p5\Q 6Rz3T *`|QyS HcPcsa9:WUYu 0A)vB@[s #)eM4.+q#<^0M4?t#osPwT#C1<ųƓB T@ dN"`a-Y Dq;#!&Iɳ<  AnR#ENׂ 'JˉD/ \,5 Ac_& h `<$`Ey -1YimGs.nRkm%"Fe,#"Y(LfV2Gʹ&nl"H,lptNF}Ri&AGQ"0&*J-jJ0aI-EoR:[ff1,`fikɍ g ї6x'1p NcnQJ0ajkeYlyt} }qD1 HD)Tg?7yCQ q1D"?؆U)n1tzi` P'ղD?AGUR%`Tz ѐ}!8R&{pNYq\vS; qw$CGpyMR12V8Ʒ}REF9I9Eʁ9Z}Ra*E p ^aY@[8'X-_ox q\aO^Č(f^x#c=6R^_2_ij"HEfe0;%Nr] j7'B]9ے-"`cR& '2d+b䶘C&dM[c V"cb0]}v[ G6_ gZ/Hit_EGeo2sfk!&db䣨2K"RL#8lFzij;db j h8{ī7m0Цۼ1vU kjk)ɕ]9л;mcv)&GʈۿҶvOJ8-@%-ᚿT1Yg&AGPRsMqssapuxMqpnnQop!X\s2weŘq ;M'A@[A@e6_og;QMX 8 ݦؚtWM7POE}t lvUq/!Z~\ P0rM+*[\MCQp܈i 05,1ю \}=pX)Bv}vw,Cq [ʑA0ܑ-_ i A >W"NS{ta@c/X K! ! luc`r@)Gכz9~AL$1j Da|z[2L]H$/%f-B*/r% !P!{b5,ax#ࢄA%"I_Gcp›VAե\E*G(#gkE)I f>Ҟo*'ڕO$e))X:Hrx+W 5pIC*`K`,rIZƲɂf/b2'kg"\MOM^F1L&02[GBa`"eD/,FF_Т_1kҎ QPM[1;03zM~Q2'\ 8boE Q)TQ-4 `q&pXqÅZ\x&:y $`|*A ~1qRe#E`&Z9sYxF‰nMi=z>3=AG.LSRR10 ŀ+Kωp/PC@ CrEr㘷;k&hY0,e{ifɋ4/ٳbGl^!>lHơ+09"24IB>PAtb2)/j+ ]bI&dpI]TK CGziVDEDN En`#!T0ZʘJ]d,櫱&EP qeǖ` vnh<"}zD 1\+c>qN4B14( 1.׵B !m xϼ۱ǟ_~~n 4:-r DB /0C 7C(lp # ` ,br@'\gjb+a 9I⋋"%@K$%A@ D ڲR>@*'ͭL ZK|#$zS-#/ B5 QHIT J S&HٔLAgf&Thʚm$6ǭlDCD#L-Xdi+X  P0s *6 AuBj9tv188IaN,(OE(Q% MaKdeA>poT%^@a8qWMQN+ r]V<Ή+xBXEʚ1OOZ[Aiy;BY_Ֆ* ) (YӪ$ ="gɩɺĪ?@\"!m @L}*@SvX:t - ļ* `~_2%*up>Ksj!8)Ա,n9[?:DX &˨k˦ j4`#h_ 2d#-  [@D硵Ck!& ȉ'JJBF 5-z! "CTp.cPuh'\Kq,PV5. ,c͹6L FVD}5 0HJ1Q d,Q\W8A*0^nW B"+8j𣏑*X 4,D+쨔5 1@xF"jyRPJM jDR2GR dQg 4& ?xEP?, C%Ơ` `Jhl.6G4 )p?(L3xB*dAj%"@`{:G" A,:E[$,@zb.R#>b*m#R\qr 7̤fNG9tB:X $b(b-`)eM 9K,ye,Q5\c-@bC5%Q&8CV]Q8&D?ݏ,A@=V>%JTb΄d10Mh,sW,) ‹7GGzһq̖ My*Wzֵ1\,)V6lh:Ȓ|>``&d(cRsٔ?+m ,g1f jHJ %(ME~y4i"")[B^q#EeZ~ʭx Tv}*k%W46^i]p6AAgHd^=`̘V° -Em6< FZAҀF,oxB{%K,`p(@,Y^He3X@u bx(Whc(&{bc LX!9 gHk@y %8 !R=`AzF0Ӌb!8  Xx(qۊ H5++ɒ?z !i)1f7ɋ¶Eڊ ٌ YEC44,)7T=H%< {Jl  I80่FJy8؋ !59p !tk 7}cp3&@(3Fրa\GMR42p\kl 10 < p #8؍%;#cp  "tTR B쥊an0bHXN4.A$ǂ2T\Ȯ$a#Ba `P9`0RtH]`4HS$rzQӁ8147m0QPIyzعҵ@$1SӋ Yu!Q+d)^ӋصbFA xԁh ] O 2 ]`,s{*D> T?[Ou|㴈ȵ55ܚqO(#Un?:suͺ ^SVv9ҶTzWKbH"lPȹ}ݸcHWUؤ|υ 8O؊؋،؍؎؏ِّ%ْ5ٓEٔUٕeٖuٜٟٗ٘ٙٚٛٝٞڠڡ%ڢ5ڣEڤUڥeڦuڧڨکڪګڬڭڮگ۰۱%۲5۳E۴U۵e۶u۷۸۹ۺۻۼ۽۾ۿ%5EUeuDžȕɥʵ%5EUeuׅؕ٥ڵ%5EUeu%5EUeuf2nVy%`]`6&Y ``z`Tu:0a: FaN6 nau aU1c!abb #>[Є[b**&Jsj}bWc80aH.V63c9FT= @k(] AA>B1 GVdWNLM7V\H\zQ&RO[ n Q5We WVH^[` e e\^,e e` U8fvfgTJghic+76M⭸m(gM0tFr.dgK.q֊͑g~g MHh[mxhphm6Z{&׏YQR.iz8Qnmv闆v``&Fm甅XFۙ7ieݠv|@Vd8. vꕝꩮjjWjYndf-hMk>k+k+2~O]Vkn(jk&-k~V feh&l6lo^l^.ڱkƮ]F¾l]dZ~lͮk~݋&muem;.aІlUm0j5ծmmXn~nj:Po:.o.n"nnoJ8V8JoJf9E.gvfpsnbDhn  Fހ! ,+d H &$P? L20ȿdĄ5<GX 9D$A_D IJI!D" ix D2/%cFH ڶ$w YB~;P\ r ONȑ/A!KJq%L8 yq*a9 ¿1[ ̑X L(4,灂0 !jf y@Μ} Q,WV?TIH J8Pw#p+S( /p@ ?*hc ԇ}7ywCuQ?$7@UAE؎,4E:  kAI@"XA1.@NTC#beAQABhmkf\4 瞷AP@n KT0[?t@\wAPA<d%FP @Mh"*H ~ cIA|0WJ0f?!(#\jhaA,bkV2?"\Pk8j-ArW"?K\L X $tP *p$#@ROJ|Ɯ OIWialPM 7db@BO)D-Xi.xY/]-4ӂ&?4pPL+ dŬ\Gq4hFpa5 \VH}"# }"}#} <pKM`1s9Cp(@ $<nƓ @J{Yep\;Pq9y?=M,"DHҦ/I:",  p  bƁp5:,p`F#rE"Ň{ȒS3Bmn5jw"xd m.D@|qĪAZ,jl@W=eNX<Ʒ ISH` ,Fpn+N 1 N$H"c D`8Tݴ g ԣ([z#L+bwxl4c o 2\M%WL +R؂ ֤}DZVj Rrm+W~6wjy L P"YmMs b򢶬*= -3&L}M6_Ik bցLc%RB`2x ֫2!@L_@SL~fQƩ!98-4khM2Mee|.+mJ&L2f^lpU#D/@{=7r ~A*BbN1xGE4\mj d,$dk+}iIԨN%d#Ky"xf<`}Rl.5W^Q=1SgKh;   AL|H/X$Ʊas+RcqBjQ1kxI 0?$+w޲.a:V }(4CW`/ׯZ_ sOJM!#J B6ah(MT.Ker˃#^@Md#ІX RYM*ED|e2u!$/VP%k (R- 4r3,#2A$PぃsLi)x3&=sQ2, A(=B7SC,3Jq@4W1â!I|c[ 1Q1kqs3)p0!*E1m'%q275d&s5/c6`(EC,$-L* Hcg V5cRd&8l:Dj2&};> @r>uc3+#J=x79i@m6"Ԩ x4. F#P\ ;HP\>%:0 uVVfJCʶGm%d^3#pPWB!BSbG`B($Q&Y`t0T Q0R[y$$E"R`bFUD102V1 [T 0'bE]T\Q_AF_D5b1NJt"ai'c.qni*U)b$YD@0H2aJ/@aHgHgZ x)E)T k#$r6,sXM8N2Sd5ʢ1,Ԃ$qJo(t'saLad*O{5~H.Cj`EoV v%igJ~dv,؍{b䶡LWKI!5K~ ܠ.1"Ǵr84>vL͔ kJ|sOR;P#MJ5n4|N\"qY5MlkjA^Vf)@,pABe&*EcE ]4U]5_EavN>Σ1 .xG;5dSZCTN]kap]=#Tt:A5{^VZ]&c&AQ>|eS ׮(;3[=ys? s\{YmxZ 8P\ I#ٸGxU8_!edhSKJjQrWD_r_% +Vb-&_ҳ5/62 *2!b9kɡF)FЃ`#2!2qdg4^Cӗ, W$IV%VQ␑&PH^f4J41VkLksϵDf+ eeU'Lkߙif| ;hNO|ukK_TþZy57V*LKTas4^FEFzB Nq@ qPB >QD-^hCnTIF ”*Waʇ%,RN=}TPEETEVPA'D`pŐchd >`d̙$p]!߇JyL"H:QNo%(⟩H DUPh SHO c$y`]R/vhazXt*C'B@g5<.6ATG( e dN߁ի+ wKn'l/QJ`%|HaokȎ'h&Zʠ cZм t&hPH") +ǁ0@BII!(FyET1+1QlG^QTz<%g&nJq dGy!@D1nrSNTskl >,M9yɍ2ryk.yp*3JESQ g\Q4 D\zց%Ke_/ݥLiG0A t҅4`ă ϣ~=[ R;#[@`%5–`%5#&D# Dج:a.+k~`aD֠H١}/" c4oG&H؅#-ŗs%' ZҮ Tfؕ VI"&'b:w` Z \Ƕ' >hē\ۃL\B&2_-0ԤJ|%#\hp4lʈ)@= ,GثdV]'GH^ 3f;UGA Ӟ0Rg jajS\C;HDDȰjH#[jx2@YIe}&%Q@#ѓ *qZ>D% ֽ\®H' hTuo+qێg i<<)8#CuI ЊΎ GS 3IvQ!Oe16ġ Bع4cAh5>1AqGF2tT RdzFbK Fg tH5K)MB場0c )cQ -V˗FZpJC'0:t%,Hl3m اsAEzʏJX$Z:+/@mM F+ơnBXA|H2xd) ! r?gA6((PA IQ`k2#t+DW&"*ДD>!Ah]I"A[+6; `WIs՜̳%dJB m6C:} J2DN&}StrhBӰ@wV:# Bi$јk$D`MBR< ͉@]9:{ဍh޸;^.3d>` E,):n qs)/@7>P 1j;GMBBOs YDu%j5U{Yw%vvo{>wwȷ]YēF©@ppȴ=G#㳢8G_ ?2I@DVRExd db)D90nDR5sX{՟e9w?ԋ5ccM:l[=|ໟ=$0K $,^_O+I@qvM|2%.} (A7Ɠ, 9< 1"ȂjMH090X1A(X(sY(RYQpnb؞a)}bb8`:n0dq GFy&̰;Hr&((+b)N:isJC~AYU p+p2J r(qC$p@typp(Ĺ *Y䈨l~)FXxZ̚pby *9pp)I1HsD) )yP@PȪ1 ~ț}ƿ uQ9'hHe;ر7)hR )n$3FɂXJHJZ[\[ L8_,86Jʇ`b6 )<@#/S(M$LF(dC hisNxj00  ۘ 09;s0Z@PA/< J1p)#"ȱj1$GYH!)=U0/(S$(DļN/쮸 1*aN)K xg#lB⁈0 M9p# -HYq 8'h{߰aQ N5H!0 ĂHBTDXdYiXR|[IŎ EXS`- aQ3O{hT8{ *ˆ4]l+H43313up55Q;X"ѕ][S35ҭIi=ܴ$#]T(nTu H! **i__ >^EV*·7|7بS8U `s_ԂxxK9tR a8Q۟8;`Ef aȎzP66P#&v'^ 7`fXHVE >01&26FnBp")5c?2mXQVt2 x?  +.lϠHׂzg+l KT=|׈܋N>["Ū^ fH澸о 0ţ+ZM ȂZ5V8s.5^ X/x< yHCZ,"JTğ8H[H4!D| 8('hh9Dh*8h㙥 *QPÉhͰ-0by ip!I;H9Γp&C`Ɏ8='hꨝcLILChWR,9 mXRiUȫ@Ͳg(0p!A@AD&9ٟ Oy YD~9Y}W`d$|!9zWq&ɧa3UrǓp T0 d xn{|CntU-lc[Y ؔiƏm䗟$yPI$6zb`wCӂ-zAuXqWSc~Uqϡqس ΁;ZS[sbJI U xʥX` }.|@`&&=H8hBIR-iP|K]&!$2i>!wȟlPX֌ 0FM:oe3(:HN'i&K(ٽLx(рM/]xLY[S̽(BNɹ T%j赿P[qaY/Ldb)" Wm7V H/J#PVj0 mvn25w: Ё uizX*ϣ'(iB(IaშQ$'l:7Ii-X*,`ь9 (B*m.z"}Q,t 3:tboG X'm؊ lI` pǪxa*!D}`ճ)|{؈zyQ\ն_!P XN C`G/W{_U_*et_B)? ؊~$k+vR+:gdO.,(J'xW{ ~ b3dA !oҁx+X4c-֏P%P~A}XЂ@P1񏈜%ّeXwps-[ d`HrsIpƑ? 1jPpÔ*X9Й^l9Pc4kAP?f#EbCaSg!0etF̗1>90jOLY+| G}X`Z9e;œU9P4Qs8_e)Q:<#`R<F:&Xߢ AR BN,>pDAf?7 ̫1O] !-4-,-i@̫p d-j?ڨŌ%j) ,'FQ5aB( Ni  |tH @m%qs:/(K4=`:d!!0J+>"`Lq"`@ m&$P\&o= Dw$ŸQQ&ᴺ;h|~r0+qsW3C Ah-A>JI=rs*Mj"Ʃd:`,'.LH(,Bv T!8I=QSc88[j%t D"0Y Aa7l J>wϵ1+UStd/ORBm-<@(כ!EEC8}("D5 מL`$$?r 2MX"Ğh[šW=A]fQL"Rx`i`Ixd{i dP2bD˼-!]&i$-тO B\&L#md7\bS2$rΩ۔<$k4V2]۲6kcJ)u$Ym#1#H8Ǡd[j#gaڴ 4\1V% S^AiܰUba 8<>͈ɳb SXe$S$ !|*J L =F+up[Ԩ#K[۔ *sWr%VsW69 򔔉]DY Х-36jBS3!m& D4C Q'p!u[2 %)¸PPp>ThIQuT(&tJܐmwؕPY U%JT r0.:j`$I|cu Ek`'.YD@Jfה!wU3 OII(HCιā0O>3M(Af@d:V 1Y~v D \DE]?btXG:s#Aс)C)W乒ʃd:^U9턫D7qu%q;#kʂMS)Y]q1uM8޶1, >n.ꂆe%^y饮.I=C9F@j,lB̃ .&@ )ֵB@-`fn/v*75_B@K_J$)&< KnXz!@ q3"!!P.o`݀jP t4;mL: DvM Ih8@SO=W*#`fZ,>EJ߼JY[,DKlAnu8AvT$"E4mL(C2ԥ:oZ vBQVDKH"=R&Wɖ8Qo ؉Xa(c޵Rz $?N*%h J \U 4D2Z2Bи3h]r D eE?Pffnd=H@4&S@CL =GⳔo=CLʶlq+YTո(l ? rh0Dk栆J,@lU2sGAbXvWƊ&$yvitlE km &!Ĕ(u0V9y* 4A 6}0~ ?wCFĴ0Ch+4bn1 i="v}zp2 KP~L>mTi@TJ#pSAun78KEhXBvv39q,YOun z\0lAY*D^/ Ȫ~\l8rIao8k:ՆiE3-GjlE$LD0t@DtLl?дJ\vLRx!(\uTs8SX|!ćJ]vȘbҠd@>g] 3 @. tilGsȥA8 mT͂8 .v8S$]3?3Ji @x^؉,7iܤG!K_ 854 !ChZ$4hf2P?zO5胾 :gm:ޕ%YDfV>;gY?;G;#t SԸfՋgq Dþ SFʠw &3)C,:7fv 8@ Lfo b)5fhW|^J`RrJA.?i'F,"FTnjV8TI?ˎNB\X3@S4[pMv  ݴ]:E2Gp|\/">ѷœr.}xoǬ# F hL rXK<@ /!@ᇁ !"CU1#G"Dpƅ>|-QC , (yyB@LzmMJ챢(z4蠄Y>lhjL7LHžZkH7@@I`+{B ; XK13: UtBg==?H E32!IʢC[gGz R!ΐUlKrÄm*fVb" >d !.! !s0}7?S ~@]68SuIA%ɹD; Q JQNZn\(Q鞃KOMSK"9fѪDlI /堦Ka41CkBE. 19(3#2la!s@̎Z21, "rS@8d&!MHc wpY 1<ZeYM.0 _I3<"ODB?h"r€>&DnpQ y&o# KBovt$ B!9EOSh"{2D5o!( L D A A1E1g3 1S?gс!#0ZQN+[AT<3(*҇KT` !JA9Hu}8?<Rq'qlln(BDHv"C<;Q [MR11QQ4|-m '@NWhzBS04o8"Ydl)bL|S#"FKT, LJvO(@L.E8bIYy@vBvf#l@j"FTB^!b ?EATJȴiB;":S/,Vev PR*.)p) &D3 P~(-ge Ai-xaalWɕl%#c SjEOJ,nWE8c) A6C""x!!tJm$."T =C Ae*-,!LEƶi#EgM!"ovv!#▩ c!bb?H0#P' L7*k#<'sa<t]t7B"f!Hu#du#v!g*~$ByC|bV Bz͹z!k"7sǗ$76"}+@^S݁8wt ~3Ë6@}X9HJ"?HP#'i rlcB!HE("Ƈrdh+rV4@+L6%(:Xk3"N*1;#GؒD,&Ncb'" &4>9zh"Ƒo[0:($yZ{]Ɉ⒒9≸`ӑbw`[0cƞ"3""~.ۺ{=#IB8b{6"O+,X$0-_[|M4%|)blt!vD# j26bciA۔.A):=L8aOܡT"j|j<" t?9ņK< #!D$!"2vA*oJxK7BK!!Fb,AǼM,@ 'h" !b'o' ab-žȁIRc^1JBdRR pó w"pT87s!:džOgIZ)e42` @>?'` )mw0&j3Ĩut"i"2D.!axa'n;uI\8GऑJkbz&bj "p6L@ .t,ld:@f'bo& 2&DB3{xb Hb!["> Z. 'cj|#qU"hiB^b\NeRu+̶a?'-~1wcM NɸݗٚpP&cn0fm=b}~$[p]/;|q6Kc t'b0`!ߒ.Uu3#8ڑ,A vv7ɢ6>J#A6 13= !z!ܨk! arVqo"!0lnh2_"ubw"]qc$oɌ?ĜMOƻ>j$F .T(#"u0"p?RHPq_,l+Rb Nhely&$h $b+.gΩTZBu>tii DHbO0mwT9$yYPNI%f\}p_Ht_TB]F:b%Cv&ahY_fQQABVI#^|`xQZB  {3D B <2?rsHBB8*`tBBP J+LR)v-pM % \KY$1;$`DnhT=.8(CPZZΰ4nJV^jкB8Pjޚ%r2:zfw`T A dgp ` q ؀B$nORgsO!wAQ-!l\T, bzuKY}pS=P1+5A-@^IUMuXRe>њmvxpi5u:^X 3fNhtˎ|Yfݼ:_B[UW=B4|O~U9O$jEFrD#< hJrK!HR168*@H**B!8r˵D8u r̪L-iL0BGjas J"bJIjwC0\H:y;2\Bx@t2dB5 `2D04Z?"`5HQ)`!r 1C2 dcR&BЁb#{9D"s`؀ǫ=fc 2 VH@Zz>@9L]c!?|@/d>2 D( |3aj%\9H`Ep!}DBȆ{J0/R"CQ*#$9e|X/ i=jO@f6=P4"@*m݄ ^i>@?2&D ' tW`%֞T8X}P6#'sY%ߺƒur eZ$ a݄Ny4%䝚|bϿ0G-'9QjW2{T1/cU"  8'|y&Wp„Uz(*ݜx0†<8-y+v툄6N { _=uTFN'#`-*H2B᫱C׀4̡ 0\|Ohb4X"YIЖ(@,/gR@}'ӊĎ1U& M  `*+A˜Cx6BԒ!lgr=e7n! MQj0s(Bba |q>I|0Hy/T`OuIH# /Gh'!kvqHZi<ln,$1 (Gǭ<%ʹsςσqةftYv·V yx.@r$x餢%'w8|d'h`]ƆҤ#K[;F@H`¢(DmۗiUiH=b"XBTPF#F?k'aWCLHODES,p8!zH!vӂ`q/!-Ah1E.s-EH5@"JUBHO%WEa}2W!08K8|@4N2vnAX&D0NeVe0~Sf T/gzF0}vc&h,5VmJXU+21iAii07a661!`41m4L!##8BxYP48A2a=71>$ÄT)y q8V1jTcQ-P)Pa]E&6  a/8!R ==hܗ78"/ #aR2@R*`3iT4a}Ոd(c_ȊQ1v91hCG2O!?E4PpBHfZ!x(` 5`=HaQ0@0q/>Au`WAa @DE(հV??U}A@%  B!DĠ B0P "Bѐ#ӏ1 ;1ƀQt;C^vH5 CuRq4f4 %$C8AQhIKɔMT2 , !!DQPA"TF5Kz~xpp$Gr %Tz_]B!q x9xA0DH{"'.}47AXTq#J01J::d[@M_ H ! zG?` / ! #RG@Z @\"c } 25 =PM8q)&@#p-O ݠĐP0T %@IŝD b SH a8P @t ֵveE" V+#u!r #m) 02 K 0 ]N`a b RdU˂ WSZu)Ċ3^9)>rA[Z #7u4&]FA_E$%˱b"uʒ`# CWxJxuw@,@ZyڅWCZ0 r8%)ih@IpI]50Q] "@QW^Z$I 'n1_$8YT#6 uц_ rY7-@wrQG4D: >vRa0D0ɰ9a e!d-M 1C13cۧA@ZJh2d&(=p `b@ .c>[J8@mFf?dB e*a.ا2mQGO?LV b NӠ\P ]fPfQlJ#вbJUic. zfIy k)j]Q)p-sAB?AeWkkt I'k|D"1lx.ỡF'`DqdP.ă< cmTsU'2H +tY"0P)l) |Ip A&zQ9 BK@ R\ȧu}hBҼb`ƌ'vFÍ" N5{}@NFVp:ꦆ ^!+(*Y.NN,e (R!@V4A@8PheG>8@5B0M*\y5\3>D4t=7_zkp )#APXiծeKB~D%J~ _  PNuU)s%N8s@j) Z0D6ш-hsS.O@\shIB ,F@YcCi%|mŒ MFHY&L?T"z9b:qJEJmR`BAF$hF`gā0㨮T#ns#^(t"b<Σ6 4hH %+H-:2J&RR<$z 4r"6ӪH4m, /,;I!:;,#uL E:SNPrq<5E,ńSB2hԁ0"E+8T`0@ ^GED@`)Yʂv㶹@,SmE$e ھ&G+P<Ơ$DeuHR[)!=D(;EBl X#B?10x 4QɥK- 'rSD"[;Mq@>bL2@* X "@dS} E^v>RdVV ^x$ d y Jb j)mfeE&j&偀R$yQ-iHsdz񦢗^t. D} 4@܁k=f 7s"Ph`,hјb"zj>~4al֒X6v 8EpW 1̎42ؚdBd d 8|]vHd>B# YhL{c6pg"EFlSk CLMk% YN25uZ½ Fd7&ᢂxe ?$2"C2o5< ʫ@ G&kق&1}``! ? STeLAt=ȅ$BJX4ܙ9׎IZ4!e)4-g\G3$BbAt%^1%" h^2+р B >9?v3]_5Cވ[&i8\jO1?N-w2d/7Z[(4IIq xbE H!#Hi >A A?(JFڿN2_˵ ( AdVQKA@(%̿(;H)hZI/BaI$ (9YyNCa bxw)IXwI~цKS=Û2\ AX|mK k6صDJ]9Rз  Wx K,V.(@#%XF$N!elIK; &?T"ܞ>HE@a%.(#pаx9U.H:ݚ+VR9ءKZĚhUF]C(RFQ% OUk Lr׎@Y7gqȉ$G XGlV {r Q`́pTC H_0]=8\:f X 1<@ʰ;Eā0 ѻ~!eiP=  '@(`) !#j  kp Ap oRhyц(ibAh_abb# ~ ~.vj F*?0A" 33YNaْM fbMXiODoY@[!&[Mq1ȋ '-Z"o9*DE$@]Z?|b>~)anSa&YQgveu֓`[L Ш#𮠩 ɍЁ횑ga g<HMz,1>!ѴE.܁XQT{:ziޛށV^@pgmhhT]zȌ$2[Xa@j@h<ŖjI㹝n V#S]س/jq1ԝpbĕ@JۙkXFMA8"EN&iW_dԞ֞D帥mn` 12ˬCL#k+~n/Kݴ|%[bPLnoA]>oNo>'W]!Ȓ O>߁)L.f,B %Us^+O]B:yO9p8Ui ϋK P^ֽ6}PKrq!q%h?QG*+ب9F#蚆шMITߢocW,CS3/uW+jGcxGpp֢XZYaZb=V }(*(HÒGqd},K ,G؅@s bPĴJuvT@uBnsCֵs;Qc@-XB2BQN=K'{8F} 3ÎKӥ>`4uwI{9]N~wQن2j񛲗2Yxw1tڙ,sڑ5xj[ p3_%84<] jW9k 7o򀟆do]tb(k~F}(tQŗζZ{Lz-{z9Gkg8HܶDl/ DRxu{^I)QLā(VuE<X-,g"YZ N2ǛtP`1N!~RY_O~f on~(((bwoL8j.*W:"$5$@CQCŽF'MPxB0Hj8P60:o&OM3; $O=-O < Jc^"9 B,Z |hĒ1"eʒ",0EB؜ӓlٲm;!ƬjKg7ɝ;li_f }O[5 TN~=ߗ|1(| _.yYM0XS@BuUY QbtYN(A3$YŁ!$%"I%HP9CbWz. ~!YO%?tDxPepИeGIP{=ד* AЁTC'l0qם a=Q --p](D1WV_wa]6}kb_6vwis]w %ܖ^)f3޸CnwߑS^cs޹Mwz8MW7'B<9ߒgwD܀QA@QQxB_mʖ^~A|"BJy]" Qd1d7{ GϯW{g %[KD%Qɚ#aG^=!/ʓ.2  8@Қ,/2!,tBX!tGA0m /[/)&yſ 1b{BC,0 F "EDh> <;r!@ K1E$ s@a Y*|̸&34PiS 1Ŏt, T`0!E՜@K{X07">HCy4pFWꥧnOI?DTg2M%fJXS^20-cl MkFfa P C0Gq( T 9͚e&یD!PSƼd$QaDLxXSq::sIa;9X2,u9!A3GD :;' UadȈB> Vxezّc`y^ L/S=XA>,"#Ȕ:b.QB6!w-XɚP`yb"6Fg^8bzG2(=ZO 7Q*0 | Be `RQʢD/ mLDDL,?ҙ!]K?{ [@ ~v1n2Ϛ! ƴW)"J@FD%z Yא uaB_Ń!Aŋb6(i. uc&7sP3Խ5 ~&%BLlv@&\BI9ѹ%<'cȓFչ= 략l~ 'Ku̗&.E"=t!b3?G& /3׿|ךNJ{dBZ^;C()0cf%Ti~ZS?4+/jMO-ZP&뜷>nd[!e?Vl9 Mo8aX)!?&'Ybv1 g=ҩ /1Wm#d MH.&M +v:!H<&΄D/FHb?DM4@n׽e3ӝ]exi x"&4-@8 YHk9(2wY-ˋ #yOȻ]HwPf \]Rab#c3W^Q; ++.ϧLv`ُO/;?ҟ>$ a"hA2MC(3MP%D"v¼䁙z! d&:Z8PdA-|A-@dA2JD2I%$Î9$Ex &L8$Ë%"̋`PF|T ́QJ?fe ^G4aG@f,o`ЇϫO E$H\BTE`H[93L?^V.FB@ M% PF|U! xPMlBH4̐h#%&HC&dC $@!hD9``B Ԋq_!|8%D0v1ALð 0$VLQtqџrajŝ?lR"= QdAf@AgНd:FbBtQtChc`lJF`?hK9@A G '(V^IxDpJhAdՄ^JjpczDcca"LmI'$FVF ݁9( \9'[_D?=XO3ڃUA$P{Q?,aaT{}dz hb cѷt_A-t `-?P@%)HA\I%@OI&?D DW%ُM98(tBC#2N/CAEGRސJ3A^$ SuX{5b4 #%7.m?+#4Fрu<0/BĒz`w`TOCCA,ldT}L,h9Q`Ȅd? BDFC8GS;8e XO _BSX_F|ܓA*DdmKP%j87NuvI>iߎXOHMD^<@Y&AwFLȹwvR Et vCYO/ŀRMe7tsFJ͗4żfjeL?dTExx_?!Άn E#.itUQU7tnBx~l jrIV^mTBh1B&?hVB )CC (APO$3½\3ˆWF[yGXFBy{LAPaJrAs$DGC6L9<Cɒi݀ h!&B|:xULr@,īON6LD[T4DEjZX(:)ZO%B¤R{@RC:|0E{]X?@HD*;'ĭ&6 \FXLXXv9*BU4( TkB,A[*|?BBT򘢆y}Ԍ J%WMq1܅`Fk<@dDP`HpLk;uGZtlzXxT߆,Y~xfRfD"l GGX:^$أRޙ:jC{2໷#A3_.J1f^L(Z{Ň }k4m6N--AлG܇^XG-^y@+J[hCc25\+o)ָˁҠ&̌>TbJ F@ၣ0*:k7*x肀hf% {![5 4@ [ƕt @o닻^Tgu*L(RhBEN$%(5(uTIx½hxR^"9`|h A^ ^c*h4M?A.+0(@p$ U8p@`h` EUlB UkXIRDAH! PYzą%tdH *:!TqQ"8p }a'B`hC@FB8"-dk? Á\tQo 14$[ i!Ctf4UJ\5(a P"2Y @p02j ?Rm p|?Q  é`s ڬh"7_Fƥ&+x+A2Nҭ5H'?W5a!ti!oIс/[\Qb!x-<60q(p0H_d*Zg@ &[jr8D̛7c̨1콣ɉ ې>H0mX^^!ӺrMݜIUqv$ZM \G7jd?;#H<(hj'@`P1hI(@͐z@5Z<1 nwE6©860dn}z /On'Q)^q_<(B$ Y1wGKW_1G J9dzH al-O:Ȋt:}L&assi"޸p X390Pu2ҁ&JG.=)N9g@ "9BJ6Q' x6{ dl^W0LKS~ Ln# ɐToΐD8èv_# Bɰ;H%r3D[ V /kkBll #Ԇ"Ac8flI:"q& 000Nl^ॳr], 'G#"JLP&*`Tp& V.%n"`v00|2 YDbZr.B K|hO"jx*(7D.9Nҏ z0 &iB rv''Mg B"ҧD"T}g @(Z (RPL(g}DBذI*@!4$b" 6$N D (LGC`#f8M0N#  !\]e "]I1`Jf vƉc:bZ $?alhZ0 Qݱnc@n 6gn %T&"@ (Zh"BhE`>ɋDvVHDs,E" `xi&i:5@/R'C 'nJ T%*Th%i348sމ\j g(B r Zb"A0#lI# $-_R)P!Ԓ!%2) P)^f}!,lʂ~(4a3a-!^ ipک+ +1_nndJf\>B["Bnyώ@x"' &nx;2H.+79Kb$#$s].֔Dd#/5/;rCצc3;"%F# M1?4.^',(µ`+yB$.cbA$f BT#6"#E:-3aUT#0BϻpCA tt ҫ2.<AC2AETxqA5T !C1ӂJO6!l x2Hb t Q|Pr !%=bq!%kCa^Ⱦ2EA]!?&Qᑺ!"h > d.%0"&,>r#M'+=* Q!' 0vHh'b "м:>bq #lq ,(U&L#B 3Lbp> `:lLbJuƒZ" '2@rDM)@D"Ҟb2,4-.ZOL% Y{DD,:W x3JIUA;G7sd ?SD#gR|?K6AxALdEA٨ ;".!U>2e9 %V hqTo>P$%݄|6i<Vk$a5'6%HBF[Vmזm6JBI(XJ( @dM8q!.p{oNjNrir Br# rʢa4A SBzdO~N` Jwf" vlr9BCcooJD9.N̆"ޘe#!+qt#D@ !/aDPeO~H^.@m> T(X5C"  hܢ.^!zom-/[nf‗F$*)^.D!Sr.:|D.i 8q^_^q? ]F ]78"U5 v\w&ׁ `XBjnbcPFd0VĔ !FT)b6a8blRGd'Gn/U.PV.vE"qG"^u.>) Kj ,Cu2|D`~䏐5rPhrГCpWc%̇+hf.KDB*-Hp#&#@78 s guJ:cDH$YЅuRp'4qt>@T~jB kYCW 5P1`cWc5d`cfu#¢@V42f j lf:qI7&(82.v%8!2I 3C(LKA=fD=6`ΤR vn"yA$n]Ioe!48/bf!t ƕ!m(-4 hlДRfF);>) F:/~ IBe2gne$Jsb ;Tk!NU) BzrE3P. W̲,;T? 1@ UE4@)c.醃DB0[b5u5hf}C(bqF;t) 4!@cF%B2bP.H%ܚ!I5[VtCF P>:kEcdBI򄧩t Kscʱ,f; =6óZiq1*0s&<@lK^&s3nyl* =7@>1+s:<F>b)0V"`G9y6yXl\E"ɩ3ЏYm"%&-\5r"FE5#&L@ G($9*sD5Ajd-0ZEKnC g>/GL W#+} 𮪏i,iUģ j8ܿ ǥ` ibEU!? fʥN&_˛_LBvb-B#S76!|ZI$Lp B`Q~픐;/0N9Lգ51=*<1=PȨ# <),=8>%.N?Q`?Oa9 E!AhB‘? /OUHA]ZW\vhb=T~!TD PU@uA'U|9fZZu?dEBLDWOifE`BTgYugzhAlE`oNe9jQUhuXXE^T*ڷׂ`Bt ~`*TP"|p Ah(UPKTV6/ATSH+$Fl@T&7gZ<)AOUqu 1[%0wUBQG']t]&܂LTT5&RV& `3W^k%SM2/8p(VV HsmV) TPh@S3K}'FA z<@#aٍ/Om0PB0"%{Nc-Z =PD_r7/1LF}OmH)8@*Ƒj[QLx "bZ`IAi+RfXe2c8yӐtdҁ٪'8z-"`Aef8=O'^-1!,A>7dkfRk@ 3D6/&^uYZ/`Ϧ\*Ѫ„ l5sRǢ0K$`%5N*$&M@( 2j1IE*`*d*h9$)0 1AR 4; jRm͉*Ԓgp!ZPt$x9.-k#䀗]9 \]1G`f,tKu|vkkHxI z aһބL# .A&d{9RpR[&IIB h oCgB9`uRh?'I$@! ђ]OR΂IڟژTM YBf a*@ lfN>32 \-'_nw nש6˳HΉ|sR|lh]YքfEP[ҵD4A:e&Ixj#5-M+RjS[l i7eIu3VdƱ.uB%碍s6vl97P=kCl~{ܳvMpO[53,ị 1 Ge#>zl GN{@s =c(>-Q@Fŗ-ŒΜ4җ;vj.m㿊g\:7NhO;(8fADU^8? \C@YT,RPΡ.fMUP("f$FEDxta2IN BȄ!?laRMZ]/K;Q{ @Q^'>' ,ge K!7!SA0L <)#v!0!:'ş R:v˖񥂹TA)mf&G1& gYP8Ds#u!1)>A -w 1 @Aa0p$_QV@x`_>1DSۓ` 1;ҀM Q 0aH1ӂ1O 21 O a s D$LBb9Rb9A!$^b~APBk tl&fTr"'5 mVfo+1 c&0RыA'hQ0(}+!+ ,gGsgq,QTwzg0((, P".{6-* R(&~R2lIq)1$@#.J)/ X2/) AږXK&%B\(/8K劀tVLMMV *MFfqEH%lbqdrP|qFfjוF ;S\̣d\ЄI38n-ysã<#>0CUz Q5quC*#zD:Ǒ8ZZ§KjK^#C 5Icҁ{=X9Z^$b!$$A$IQ9C%M`%kE!M'[tUzNHlFE7-e2$5QCGytDvDYx|E 2Vb"EbUZ1CybV{TWr)8 x9'gc2rF@ZQxRoUOD\eYE!ML UBqJbʔڐBz2CDŽHIIr+5+iNF2,SN'Um-Q%Oܤ4+š4kl%vHvN9m֌96Rd0UO0N*V1+udY ֔YhN%xjI r[2)09 qEzV7EUXYZuxs oZKuBzO%$T`EUWUÊ7C J CK*@*DJI%J0ZW}1Q)WupA] ;#nPESH^Ódфa\ȥ\fiA :;u3^yCi3MȄD y_b n4]4Õ\z1$2fAQ ~YKbXA9O-#ƓiBԗS MC6uVمqbe!8d]d8smC#fTgɒkasP6kVFRl!jiiUkdOf "`&55);)끒&QHIn5tzJNeN^ǩ^%$Í9qfr,qJ4Bf SDn64ۿ$so8Hk c_4 @Bc|rɜ6S`S1s6 1s9Q I|IO\6|ê{17K4mKtE8|HJM7`b5$>`G36u 14Gg`I01z(Z]PYqWyUyp4Ӈn\zpzzu{%I66&Ul zwW> c1اڷ*~ 1|!CB `%*gc$9Q|!%-l9 @P;`KL! PKWa#1)v6r0p$Qӛ\ 0& a PqP0 a<¥cXPaуc<iQ9Gfx$JS!mvQ"VI > Ѷ>Y OY[$BԜbCM Q$򣵶a $  I6)403Ae*AeR4 [idJ6exk!43p'ub(4,h!x(,_ZQ=*,9a,CeB':KĪ(gN)I4U1#AVBS,1! .*BSZQ͵2Y1SL:e`.2qҎ|zKPR%hƓ%Gh-Sm"٤OT)`lK\i25_eVr J2ۃ`߃ކcFMEpWN qZĘcxt::u#U 09uh^ qgK=FvbD`>!V A]Cm`B0›0Bh+aٵ]>t"09,/@Ӡq G$Q ᱜss&GI.C[ wl9DCXCDW3#/DCTgV_GF\GMDnF',M2J(33v S+*M Zw Z = TA? O) ѤZ1*E@ ڐPҐyB PE23{$ljq]Ry*ddtضMD\?+^\d\ PKdP(PAt=d]QMU"ushn(S.2 %^WTqT5sFb42OF? 13C9 K}S*JZ6B\'ڭ8U  ##MExYSq߮ ;¤ "PZ2l*/X YMDDlr^A1焆 so2 C!-*, 'Z 9K NSs$p +]/* ~\ OB514-D>*`D! p" dsps o]3ڋ  a>pQF;>` n( @%aSF *B@1&L&y 0cAKS72͈?[g꿩LzdqCF ;#>`& xvi b񀄍EL3ț -P, ҰA -XkPhBeBm\ngoq͝?o#rա*g+#NRjx݅_|ǟ_?C|fs23GN=!^@6,C?1DGH3ꆿtr!FbN#E2RD!$H#4>:$/ȐI$2K-K/4 g ب*ҞP 9.wF#19 `ʛ h ~"0LݦT֠K y xz~ _~)l%x7h{@+r***oW4 5mz[A1p ؗ }<`M hf'j;ЌNw{@*A.&6Y":1nH!%DQK |)!{rCF@4g?zf9 dDKDT`8c;CY`9 ,:s@`xIbp Xd#2D61a/ R+R+mRFPn Y3Kj`kjLjQ#2H֐(h=fMż?Ƒin}!dClHU Ap! % ¤ J GP P`'JP)*!AĂ-8h"Љ|4tE?.vntF(9.zAPVA B5s;pQ!!uDd!DB̩,U#i ){Ql90o @p&UI5 v=E JVz U'Z|:ʱhء'JAr'&%y,Fج+|%lGg?yQY=neo7cуvK+>p }?ҟj!P[!j/8qw%/Xg!jV ⺖I wo7ǹ1k(E=;͋+GGzғ8UnOwD;W/H@RB씡h$yG&aj)* Bzݕҝ0YFA ! [-"'u%}lm%vvQ!CEO6/9?NUkt+a$ ?CV_gtU*!2ɼR$|F$ލ-u4rBuHb2m9XFPL!ZW Ecd]1Y8 O_ | &"3wxtપ?]8Qȉ?)y@#>+9iKb8^KiR 'X胂F_پ/T5/ ߲CKQy'I6$h Ql@Ba! -Ѱ" :D )^DF4zYE=d qsHÏ,D ^piP&o3:iF> 8W~y-B`L 0)732s|JonT^sDjy]*lj)&fs۰K2 G($Ic$&F[,ꈂxx0d l8(Ȅ8Hq?#h@##Bp/(2zԑ>i 僮( @*௩yKQ(ssR(j=MHN.:#^LH۾{(*0KsL+0*ʷ̄(BH!RF4Jr"5*L#Y,*EvtB9ܭҷН Pr Ϻۍ3A70][ RzNި b"N HOf,qاc ˄J.(/bͩ(/T: i@\>Z* %M/$:MS͂hmCM\) q Ё"Q. =p©01H*3#JSzX@I, n22ԕF;]6USII0";0 r81) H+i QXhN3b X\⾌"F؀X JL   E rXS R+ [Mn7 21jVUV@ D wm\{U߲GW-qR@~U}m2ѥXNE؄uS8K83khku؍昊' 9 sxHfX(*ِ٘Tbuh(iݐh"[}Fie^ned6阎]2#EzPfꦖdg]F]tjjj|0FZv볎ok1k8E~k}v_V%=lۀKl]~춥ju.[̦fulFZf栶^\^mhuW.mmj>^ȥ1㦃?>: j-ynfnFnn~}އf nVo.nfT"^whVҞ[؆+o$2.lP>cGh9oO z6ހ;kew/images/kew.gif000066400000000000000000001730761512074754200144130ustar00rootroot00000000000000GIF89af  $1 ; + # #$ %%#A&"&##(! (&3+!.0-32)H26H33#3& 5(!5356(B9:0:0&:?L<">A&4B+!DD5D5(D:3EHRG2PI?&I?5IO`J&KQ:0TTD8V4#VD#VI?V\nW/@[[PF]8Q]Q2^$'` `L?cC\chwd7Df5"fI5fV5fVGfmfrgD%h#(h[Qk^Tkf`n^4opbHrVDuf5ufTvD.ww&-wT8zk`{NZ{^q|:C|vo-5@OrVV=cOr?ucCPh6oH,)(}N|hS[hQ2s7>$v:bzGHNU]1s2/:@! Bwr?HB7h?UKNq([VrD9=Ie6k?ĪIJwŮ]BLLOƓI0rBɲ̥̯{7%CLwB9Ӳ[ӷhӷ̺o>r:։)ֲַֿDQQYmI7Ibg|)SY6|{ 螂!Ȍ6M 37_xf(ag3K2$2h^%M&o!g=} ytd iz#5R(#5ٔȌ]Y(k4Ƹ25v4*&ckfw½ya.؞2s)N~R) 5loHM| Mx3nL| ~zGe6226>ז%#ae`]Xv5g^rhf`y-^Xfri:E5bjje1(]tg'nbxnejgY̴]igtƷb2ڮy"'ቪ^*((gk1,p3^4![,|/e{d&{.nf2V*t1^&ҳY 5PceɆee8+rf(@xo'r*^^*^Ǖzxcz%.Z}X38O|9<-ۦ82Nt Ba jR XT(lq!< t<'>ژX伌1oХ4X+~ -_O(Oܪحhs[줩7nuj"^q]&oko8 Uoha=8F/z!f50J{Ojr8҅D^ۜĵAsZq#Eհ^\Ӿ9fI_{`^ȝCf468jGMÒ =3ljm&ˈJ-iK5DOUT(/ \5:is^6-J P^I;`iSi,_B@T Fӏx [%4T=ޥ/RTQ%()/ui0 `{!ڴP:D 4Bg;~Zkd %Bc OJbնa\4PSURQRTȸ`Tg:^8Nz!굧io)&'o|f1zD(IP❏D$>9qHZB'ls8+V9 -lNȪ0kc#Ψ60,ryŷM`,҈Ik:W1xK/=z&|K_ͯ~{HwNcC2 h#/h ,b_Hǁ`C)F14ktn[5 3c[H(rqw*nj}<S2A0qq,zvF c 4aeh:Hܙ ;bw$E&2=A uԃ0G^bH?qK0Tzy}>C7G\)k:e%#pG͌fmKjء 5%xvY\[ZɄAmVzvƥU|vZ݀*rc}jX:Ƹ 0k8~G23~:2$xgL2 2=k85 r|<)3jw;3kx "z6}~_|1{6?F3dn8erxηo_ЦalnXG^[6aWZ!9U\=pi\G&w{63=jTK5bȗt= wX/ī~/z=e_AFC6ȕ|2\JSo# N3qš,Lml[cnJ?Zѷz'` x |Vd7cWvfc^zdՐfJjr`JVxL7hf !8Qe hfhr|v0|iJdyK 3 s0manАgǂ.sXl'-xvc)vu3|w7}v}nh wk{}Flзfgkl6Ww! ƈϷo~Lfk"rpסz ` h 1euJn͖qXf@HxˀGDŽ6 jr> dx[d8kSVIw|x*:8tMd! kw襌% /bWk(Vf$ nlfkiAFlfz@~ QlkD\/؉~Hd$u量9pjXiG&ҦG~m|b1YHu1Mv {5نv16{we:{lʾm0pҘҕ.j0 ~9h} NKƫ.1EOOuco+?]ҰB^a_Nnu֟wp͕vfakmFo pTm.^g~.=`E @h@|>=.ivFuAN#=KQhe`hUtp`1@yq [<8;򚱠P&2 L֜HK/S ‚5KB ; \(V'‡F=~2ddԀ:cI=Sf=PQcUR)RPKVOiwuTRzA^*RV]~U,gS6 ի虝ڍء*RxTN/"eoC¦ 58p'':=.1Y2/dވL \ "YkLV\sWߦY&=_0,7S7xq}wބ/^X'&\p&&J`L1h֣Gמ)3}A)9qhQw馚kMy QG=n6Yf`ֺR,ϔ̍y pkrse{#q{\ W"Z[10hVr#&B#`.hAӮRPIbc+ JϢ Լ M4ie5Ӥ99 &*f Í=YE q G"TOBIbRU#jt> h(ls>([7WGC|<zYVӤ Z!똻THz6ݭ[ϕ٫Lcx2 KRg_~gneڬ2J_|^mVD%*:<dAܗ T`>AӽC6{+b\u=2. ֵ V6?B:iZi:rxh6S'p0zcc&{ ;䁜ݽjZ%k0  w4^*lٜfbR9j7\<{(4(0@LcH E˔7p, `t#`z%]T?D,[I% _ٓi 0CH _NC&bN!T 4J ẮJjJXĭ( 2G>i_8A'7iMϞ< RaHA9!" +*AHG Z{_$#) 'X"i^qՕO\΄+haLL9NBh4w17qh%%@8UAH4Qn^0dj-M!5E?NM,RI榈ir2Z5 [Zܦ(Ws[Kah4T(@fd i0e}BȑvȎ̊qJ8)kԻ&6{ |gAsLgW7UaP+|d<4tvʹrH0ETIF0ьf@CL\74wD ڌpzVv8_\L4ά8yxf~~+mɪIN+YFrA-Ty5#lPA0A r ">'NB"hJ$4:L4TXYP=3,Wn%FK0ѥuxC2ŀ[Cau 7;7<ݤ Xz F\ԍSOAB[Rb-%KVV]40)(VJhH ^%@ҍdC k{Z448loT] XT͖ԻI255"Kp80)N>wh\]~Ž"IF]dSDv?bHw+ϋP'N\DfiJ>ߏtr8=4:ReC2V&d8HИ+=` zV恽'|쌫wZС#<uv_C)3;ҊЅ>eU@ʰ4b-^D>n | IiY.H.5정_L[3V !ԀdE5]6a2pE_:=Y@G&c2JV-$qMMggr#J$HcBJ&Q'{Xc.vx=P"d*+D/$P򯴭 ;T[Ii=y J$$V)Fĩϝ(q79zd,pPChL9X(8hG H",IR(λ >;whB @cW%+,SQ %0d$u kG/"06;7kOSB%ltB&rku/[_ @Ƃ쩠M$H-Ҥ xzG2XM(㸆k,|Eܐ`*4 QzO3d HP` [:x&;OoXuR{0x\tLGSs3å:wpQ9QIg?ۜX6m TxHΕ:tx?H - T®\wEj4WdI> 9eKS538쌹TGP&hE3A{sJgpa 1$SV "@4Խ, 9=}݈7LU}6/H)Թn5 1ώ@O{#si.dmq+DSrPkMUМSySK ʱ81T>AsNi) W5eV4QA!U D@UMUVl;F ࠇg`>XVb Kwm&iOT=6-聀U?p- ) I̙?H׶{QZ qPz{wɹZz Y$&zZUZY|%0QdBōtmGupJ,HÝ[z 0 5ׁMUo0E|)СbmԳz wSc/q:3,[]gm} "pZuR K΍5ړQ:Ȩ~Ӟ y (g 5ߙ^gjU^;U@23ǶlׅWQ $K=_'"Jp &}_\ YPDgv=+W sm2ln^ &L!3[F t5jxOta\ Ta!?0Ti=mUa0r_3]3MX%z/ʋ5 hUd Fc#TTE*٦Huo *Z![;T@4dɫ'xƴ쌥MqH6:®B} z*CQjd,^e߀HD%6če[>Ye4Hbr4-y5( C_nfA'6f(1@e9+Y rN0J!ee~ fnA%\@zL'gTD8>ȋ]Z 8Pc⩱MU~ F:ᱠ勶5 撊؄tN.j d܍le;iFdgz]mdhj%l&ckh걦kj뷎3[`kk}0 f&fjp>똎ׇl>ፄ6k4!`^&n`nҮl&e㠵m~`DmVHff6cӆnΆ銕&6o"g N8ֆT|u0o.i7Gp ؽ 4pJSx_/  c'7?in|ixfxi/qmJG[ox$` g&w'qr&fq,-r~up/W5r)gs79:i h2LGH;AcCGDWEgFwGHIJKLG/Ho Q1MgVwWgtCYZ[u\9pH H/(nb7VcWegfwvJ/gvg?rklmnopq'r7sGtWugvwwjyz{|}wt?vv'vvgxv/x7w?yx/GyWywygxm''7GWgz?6x'7GWw{?x'7GWgwLJȗ}Ʒ'7GW}?ypׇؗ٧ڷ'x}7~oww~dd"߆m~폇GwWdPo?@7~3O&4KÈ#vXQ,@cG*") J2VQĈ#*Μ8gB%ҤJ2mtԨRR,ZXiذaU!#kjצm-\ [ J/85fhx$&S2J0tPA2̚7gТG&bʼn4VA8dbEʕ+X`)n|g/3?NuBڵ'{3*_RU {\ɯ džs *XS :N4!PBY(zvj6vpƩr1t{,ctIG {#bȏAjw7y}' `>UZy%Yj]z%#ezY &m;Rɦp"x}>&>9hJIhq*zfyfs z"*o")z)V*gv'M**:+ښ'5hԭ ;,K,UWʑlxxAv AW60y8J/ 2931S93L+A` &h++j//xu9#vaNu򪠥Df"g`Qw@0h1]{5:X<w6_s5l*EZ E굇c<8e"-!ѽioqAÆ&\Dkpy>Q %oHCX8/g]"_~1HΊ?] Xa(XK5g[,vaSeKOvd]O 7.~խ $ܡEL0b9?G w|)A]2M} 8p` jZB(Ao8>BC8ZĖGfm$ 1!,aB/l+YiFsE8b`[6ezՋ֞<`#̖=OA8:D[XKdc Ca ܂HEnΐ;$$`#MDÖ<`06 µ6nh l@fGGdbGa%0ajz,7/joP 8!a[G/Z1aD֊bhN@sZ"~bh6}mj2)PaN,kBŭq}(01 ^ h>s鼱=/K&=)>Ptm 5i B k_ G! 1M0b[A`^zmθ^ qHR ^7iy>\Jڽve=-:}tmgܨZCQ_ĥד%ZOslA'8b! 0?5x^8*NH/܁[h[v͠y=c/*O2wu)*j`ZmsPm*o(>Tm-V!q9 a?lsU0|c!@f7SQBelWƔVYuO:bJ֦֋ d>{ˑrQ{(=YeAr%B[q,@s`+?ʅfC8np܄W,Pސam#8?ˆt nHa]#ZJVWt[PJk[MZ|MoYaU&Mf"xafqH?gj/6VQHx3 0 | /`u%1Jv6$y@ h=l`Kw!e}zz󤽪'"QP1w :`DY Y; na܊jFK+}=J+l9馬mJlĖQr)e8Ƌ `QiP0 x@PH=* k 0E7hۑT{j%` Vn ЅV_(} Gq?\V> FK`]l%tt+`Ƌ[ܸ 2u`38Skx!;t vu@v/; @"N\A ؃4 jX!tvl&;v_V8;+;^6=5TyP9 rs=K/à˃ Xj-|q[Kg%b_}x6AqApX>1ݽ Z^i X`Ę@98ٝ ߣ` ᐴ D,& !P!V\&aą 0-:!9$O,y< Z}ʵq:Y\Riy \9!&F5a! r^ aa~Y:܁fEApYa!ٌP "u:@8=[iU6-1]t؜ @a/ܡ-:"a"&Z*/YlA*fZ5Zd ń%:2vT :t">|ReL!(UQ D)_.#"0Vw )2 /[ML5"Rݤ^}ibEZ/:](XI 26b d#+zb+\"AHM#O>@cXy`]H.ڢ@c6ΏtiLU_-0.֛1`@nUvA)֓܁t$ >'N )8fA P$af% #Q*`9iя z \0 ɱ9(A@I6~ɝ܃XBmf6]XeZtep =-<Hx"+x,>B`/0PYm!)AozXV%ч\R͚VR^Yݐ#\A< P, ̀V=R𰅈h "cMj "D3qR@R<RH,cL1= cE/ zEяP%( 2%+aɣeVQ|!lEm9("(Ʈz+.# "U,;-# ]Aƒ,R%q_67AxcqL`'s'=ieRb;!  }Y-_N^-YLLH  iDB?-) dlp@eT -gXE-,ʣa  dKZh'6g6kv=B\.$fU-Ps&c4ny`Z_hyȬiNAgA)^)6_65%PB _Z.6qR/Pr62[h"@&TMVmhX(,0QBSp h@<Y>v&O!YAo^Ҁb%Lr/K+ѵA PB-6vgvgw-xw@̘^ky03KwR1),dO,*_,NQ(w.7a nDA 17oxԵuo8xwCRgk8>A[ rwHx! z?6.9'XxPBGO-H<8J+-T@e+9#?' @O99`ۂ@DAȸOw/{Xyᚹ'z>By t]˹-B EP x"A0v"x)L:0PtHWO} M/bj -z'z- zar+:o0Xza@KMhn6;a*yXJu":oh9)Xto;`7Ba@Y\:#4|"7"AUtzuFڒ1پ]¸Ɛ/ GLp] *7t!)@0m*x@"z0 W!˟ mp"Wb< [P D$ޡOޞJ/ "TQD< qT00$@<Fނ8) CT}|:*y4.>R$lgR ),LjB)w`of6(QȌîX~nXnͦB[ђ+lGu϶7yE@+aЏ~2Fv&`->hB4 @0s`o ]l`Cx2!A  Q6=XP 2o^N"㚩&WlϼC4̷ʎl`Q$K/{Ǫ ׼ާ\a"@2PD8;؃n*aLUπ\HPVp$3gUenYK<Z*CRBg0 g?+ʎ㕢y+6-@a"D"࠽ N5-eLX-,[қ!| Wh `K)}b+pFԅu2cgnz / ^L$bޮo C<&א"0U#dLZL]9 {eR {C]m\YjprcB_1ͭE;`Plo '&zAt@0omΎ6X EF`nuoKN&!_t`FT !H>H аasAnE,@:@,\YA /J@" ѐ,0 l L` @ Vt@@ pdA2z0 RpE+$@r;@`Y@@tAtQ- CLdP+)NΟɐљL@ "@q & (d$2#: 8#=2 h.B ZFX,*`s@ Y'rbr&pqkǼEƆ0db(_=<&0ʎ]K:.0n! Rr ' R$x;@"rBZ,W‘. bMN` vf*:,$`~p")k& l.Qܒ j @&E3{o0Qhb'Bx$ޠz, nc%%5 aJs9aQQ-2I0E%63,.3 k5 5Uo)"C| sTS _PK wMKt>>)(*z"j@ :8h 2*`Sg jmE'TnQPFz+8"`]oE~YP&`5k X4%C+@RQXf4Z9`/c >aL`s>v W& YĀ ^ dhJ׆ &U4sA8Cv5Yw p *oAĊ'BO|$0@3~1 3x H3Xq *'#[3XXGf * }`@ `YE *8آevǔC 9v>` >6@u, @ @OifqbW WdsR>.Ij8#@deXKwofw;_zʞ!3n8 * @ښ;`bWoj` F*`୷̴c\&EeocH&FG}`& lv8H)x 𨻏am+t?PF`Ue{i{y@G|g%L@5u Rb .mWUK4lVy+ȩ,bu( " {ꓯ5K;I...ٻ_eLb @` @8B xQs uoۙހ(AeT9g ABI s >k2 |~ۻC+4@rJ! Hjr RG ~HBbv @, Bəy|r V(#T`H (= nȃ`}` < x&pYaZeT TRvoGg|8D LJ@l|q a*` `$h.A @ ّ  u} ܠ< ~y"`{ *@ an!TɿœqԨBq" 5|#*gx9 VեHޤY5%!f`;A} HM @΁ݱW$@ـ *ລ\EN-~߇ޱM aۀXQxh[cg(廂T`L` ߹9`ޱ@n`g{)}B7lyj޸pO_!i7; @kk G{ Ti{, ` J~?- !b" RޱM !#@^ghlk@fJ@ &j2as[c <0… :$x;]|xDJ`XAXga 8(aGAV Q 84ҥL| 5T=ԭ\zmx%$MܨaXzA%;8۸r#5󲌒唏gA(3% 9ɔ+[~ $}FomqF(ABuUl_qnq GGVx:бpǗ?0A  \ dam,)7h܇HeAiAX &j&QXF[+cu)%DP2H @pP i=pN'Y> W` w`i/$|3 &TD[PBP p''Ož+p7v)zJ nG|C¢5 _>bVޢ0 $DjA  kB e*[ + P&pG|1b ;<_~0C@{ 8 z9L7")<P 3e9p@[ ܰ G1CD ALtr28)& 7Exnw^x)0l@/F B,Xu+:'R60 ☰eWG M0enq/<  "-f̀"*Iy1)aQJAn!H`wC0!!l j=p 0P0@W.a q^RXA0@.naG~{ @>(=IJ$r#ȗ+ÏZ _,sxoɣvzBg'5؀OB%PK6IqT`vMw4>+ *9wQ7"o0_\{QyfvPfXyB0mu>cG_2p{QOA E`=1 jbƆ @ ݈>vW"Mw1xXb3Dd7xo *4x3tE^p3҆h Ӷ7 SwD(5k0dyڂ >x& !P7X{pLIWskwN8@v8 ǁp, bo70XxMR92CX ?40&p7[B6E @ ,0WpzƗ8tTv?è@ xt8PE*7[ZԏhaS@9PDa<o%& 4U)dWe}4,b)XEc0R R(5PytE4CP 0NW(d?GC@bXq p65 `S ,qE' R 9P|(, i4]A !WI q k.+Г~1xc~yqk(k+jaL {QZ&*L \jBP[q ` 06zqrKl@( 23.H`) YakZVPt 5pb*%ʥLP' 4 ujoV@EH Z%A`+Ua) 0RԣB bҮ\& RŭV q c`"֪q,v Qqo`tjJ_E 80N3gZF0' wBW \,[p,]Яq4n*IAP;Nz xj,9 dz[Pk*ArB7po &E}|X+Lk* !W0k kn@{)d&ccV`s&T3P@P{*0pYARm@!uq {P``rk:>ʡ0P_ Oq ^i`pBK_[")fb {x9вp&{ >(A KZ"%p\iۨPQ;G0fyaB V@ 0ƿa<@Y|۩*w@L)j`Kc"p˥fi++L.>жQyQ k{P4_9,5µwŵ EuĜ ~{??``L: @JXQ7A{m̩9>aq xRyiIn2ĔG, vK @`!^s3p1 1Ic4p/oP  `ciu6]q '*k,2 .wt& [WP,C/PXw'VPʡwŐ3 "tLy{Єᜟ6_FciK`6kK"8Fx^`mt%q Q0NS2e4fN{%yœ:GR*WE.%b7`y pSq::J6P0H%o|,5q ">0`lzr t'"@4p!cVP7(4U_qg o4rsP\K:VOs*_`Ga4h%8ڱ\A AP6%1mCp>m #{ƱP0 ڑz{`٭tx%du  Jb* ĝz9 `'.e Q8ڱPwpq'gOw^ J@~c? ėq y"`2ȝƥS[:(Г_q9NWM".{'D"Q~.BΒ1&.t5˴-["`>{pN) :1`9.珳U bu }SvzّtM"h;E@)@^]43`}Lk>Pqms8!}k 8ctV 6j%.>&7r-pַ .ߝ!R[Xe+0-k=B<-O"31j$@~.T3j` o. XI lAQ+䡲N)x/⑏p3svx.~c_1˗V"C;;-~X Q'?P( (%Op n6j7u +8prt { |Aqj6`jo uow7l{}/Oo/Oo/Oo/Oo/OoǏɯF 2K mO ɰ@ƐRi } @$X ɓA%NXE5nG!'C D @YX @I.9 AK6@[ <`UYn5cO0 TdX) @J6:+{V̲gXPletHsX=H"d,xA}v@̋Vm9` UwHuaygqɕ PO1"TjBcЦ倂$xr@ԭce;E@‡r$I JΒJ'X%."ŪҬr `s/I ?0 ZtE&&ezZc!M5tr'yѿbpzHs'I-:0A3ρ$ZҠl8Y {2)blX$0|38?SJ*c4PDX" XfI$#P (1!z?P"($Tˈt TsO2Xa=VSRZlM.ٟb 5Sp,=ik9Ul'>ҧrV25W|/2YLj䩤nMhVGuam3xp/Xkd[{dAHU`5!;H%;M|alV26A_ϓ H*@0q.:eT5s.cVXpKT9}mpK50\nf,Bx[KC2h_[q`(*a!h3I,=D"-+0q;X 0HC@@XBPSR':6lX f&Sp@OMȓHE<1QPGO`&K u< 9f@,dQ:c'=Ȋd1v0&0NB$BbIX@Z@80j$P2p }Rx"56Te!HPԓNf),BH#FLp# yl L0Y fU^4 0@JهV‚"byPRDpE8h͕* 3@f&ngCxH*^Èdp eiK"O {~)qK"tiO}SUC%jQzT&UKVjG2#Q=UYbQ uH CDX&"Q~!V6.P哲&PI[3@ꔺJ {$ =$ adĦFއ0 RU&0*! TxBHk> 6$qKG^A Z q%s Î3gg֙jb4 {hY!xgxn7\mY3^~HJ䑱sfT0zA`Uxg&8a0`ҸQ_ .>iM["8͆g0b! " 7[$± MIQ>|J_"@ۀCPt$$+X?%g !户zbM,W@T$ɹx56C4BCz-g4 |'YpȯDW;@ J(I4 $Y`j%A80` ! Z8$F%,̊t5MĹD8>-X}rL/${*dɔHY:۔ܴD,@ؔ͝Í:]*G4dIJ#1ʀ|[53ptzOOSƂ%Jt$ @>MO%MJ܉<&FkɀCKҒJ1ɥՠ> BZMΫb΂6XAO[,#m"<!8I@lMc"<\1}ҦVzKO&@Q).,=Sm+p@5xȀ4|:mI2)0lJOD3H='|T IJݙGb+l›nqJT`u)h7{EHuBFXU4a,\b5U8K3EÇO  Z B0NjC zj;SZFը&I`IcLA(P<YMÍYY0Y==ӞRPZ )"00i1,LPQ,QA4>\RD+B,D@3\, \*Dj>H=vZ\H@Mkgխ5UC W]] ^^-^=^M^]^m^}^^^^^^^^^ __-_=_M߮m_}0P_++/:__ p 9`%R`=n+Ϊ  6 `V*}8aNaևXaFN~a>Va$b!b"v>b$NBba'fa(vb&֣(bb%*#-%c.b*,-a2.c4c5vc^)2゠cD==c@>Z?.d?cAc`d`db(TLLdO[pdQn3vd*֖Q^RWX~uCieXcReW\_[6\e_&;cfnfg~} hfjfkfhfmvfnfffp>_pf(j>tgu^gqwxfrvsgXhngtglri~>8hNhngf爆牖犦gu|.蒆hPfp阆i隦iiijiiFjVjfjvj&_Njꡞ&꥾jVkvik&6FfN^Nklj6lVv뽆>fĆΖȦk6^6FvҦ>^}nmmlnؾNn^~~vn~nnnnn.Fn+nNooovoo^o&p6FVfv&>& q6&W g ׾7GWgw Ǿ"#$%&''(!7*W+-,w874G)g=><.;BG/'EW0gGw1I2K3?9s:tP?OuO/PtM_sCGQguS'SOVWwXotuZHu\_J^bL'aGfWMge7vsivUO6hnmlr'p?ouwgywzw{yw}w~zw/x'xHx/w7{{Gxx~x}xy{'_7yzywyGw>W'GWzw77W'&hx{{iz{/?O{_{ao'7GWgwԇOOzǗ_zp'ۗ7~7GWgG~~~o~~~?~~~~O?~ p lH!Å&8pCV񠿎?0$I#K<ʕ&92L5WDΔ0cvD(t(с,xHK OSeVV]u~֧AM2(۶f+U.UPbkZoիU0W^ E,V1ٶeNak͜/ؼqnW^-yϫ_Ͼ{.P ?)}(gy& w6UPH DZ8sY`"ؗ,fxb*X׌"6֎$ bH&)L4$=AR6i%JUFC2xeIHYypR@sJf|B~'$g=g$)^g^iMN QNj訜~Aʪ(!kz(`k&"nxds*N+굯nA@ /k뮾zvopܯh / +/˯G 11et P${z5O鹵T7`CۯsN(1mw |ȓ+_μiN}tճ^\ܻ8<ɫkv˚O}3_ O?(@ h[OɃF(!',h!OC^V aC#~hJ!VT,[ (cO1hcM5ި#L9P(J=iEEDI.dCxD)T⑀OfZvIP^yPb&g"\9{M ם7A矀R)yJ\! /褒B (:LibMjZꪤ >! , z6 H*\ȰÇ#JHŋȱGCIɓ(S4eǕ0cʜI͛8sɳϟ/D@A(QERʴӧPGGիjʵS`zKٓaÞ]˶ۃi"tݻxW:Ǽ j_/+^x`aolL2UA䃈gϝ?>`ihbӰc]=۸sͻ Nȓ+_e#RG!H4IO~~p|>dƀhw `w (!Ar7ᅾUH榡v'$h≜߇'I摈0F߇*!$b>@(d 4bIB#vGVЍIR$IY2#dBf9&j Q tֹAmgepJПdI"ʧ=-t)Pf  Ahmڏʩzmfڧ譱Z.,˚ڬflAj보R{FkZ+n-mikۭNne K}yon뛰 W/Jp[0k<.1;[12p0P4^xP-^%?ncah~%8@m2TBya`cӟN$hT|jY蜌Fz`*ZP5f(EBJ&w*)&UjB*Uf@! , a, H*\ȰÇ#JHŋ3.Ǐ4Iɓ(S< eǕ0cʜI͊.]ɳϟ@lѣHB䃈SMJ=իXg ׯ`%`ײhV R۷p. .ݹvn˷߿ LÈ+^̸cl?>L9h䗕3kdHU AdgCn^ͺYZ˞M۸sͻW/cNœ3 ZΣCW: CZ8X賲} [MOϿ(h& z[2nVhZEn >Xhbz(v(8#hZ4cwh!'J2dP?P>iLJId]Gy@%Aa>4j5lf oAtiESF'aZ ! (V! , b9 H*\ȰÇ#JHŋȱGCIɓ(SfўMɯ-ʝ%ƥ˷TyKa9^X`"C~,Ƙ3k̹2)\ah KS 1ɞM>ظͻI>;qĉL>sm/N!!U~TIص |PM/n0m~˿h͐^Hz6(AXV u v8!Q8h& 4/2"x-cEC\?H& I: RN\VQbAfei"osNf-g feY'RY޹X:Р2Y)hD: U?41C*z)2Ftjhֺح٪Z,+laξlRKbªmڬN[vk膫̲[W+ol/] T?'o, #<;,kL?( ) 2ϼr6l2n9|P*ܳ9m<33 4tH65] 5[U[ Jw4@s؟ios7vc]v`Oiۀ]܇ӝ݂sFxA@ Ty]yސO0yf uʐg,նszQŻCk4SX@! , yY H*\ȰÃl/Ë3jȱǏ CIBF698p ɗ0cʜI͛#n2Ο@ JCdy @IwѫXjzUC ->M)\Ӫ]˶Ex́@0u˷߿? 1^̸ z\p#W|̹g ' |^ͺ51 vM۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($hb}| G+hm4b'f>ti`|٧kxZ}i` *(Ix(BR^|n lAH QU M*C)WZ*RjPx1?dƲ6lAVJbjjZ^Un˕ kx.w"/kq˩KܽSg cp F, Q<"M #(s̘!˿<̏ܛ9η|(Вӫ_Ͼ˟Oߢ#gjMw u؅f|ixvxP{}h_$UXX,"v'4h㍜iŨo`=HddCO)%TQTb[&i9b]iّ9&xp-\ er(j@ h2*皓Yi:zte訛 (Ǫ*q 4iꛨ~Ҭ*('Ш'pk.É&P@D]T-/J;mbK^^RCkAR";+( &ثokH 0! , z` H4#JHŋ3jȱǏ CY#:$ɲ˗0cʜIA $(p͟@ J(I0 ѧPJJUC xʵׯ`I^ iسhӪ{uӭkʝK%&0P߿'.0|+^8UHKL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOyt_=[(L' }߁W4 .x_=ANH9U> (phbE$,F-$h?4xc9ء#A "H&hgE A Yy_| G_z `[ET_pY2,gx駠| ("6裐F*餔Vj饘f馜&ȧ* jϩ>fztRT묹$Fjkrh lqu>ˬ2m]k>I)[%b&@ 4p'ϸ o,ÉV֭TEr{}[P!,KA /W+qc1w\,YlS 0`0r&$`2U$7LP7ϕٳb??|CEL74 LGT[ZZ_ԵE_oX@! , z3 H*\ȰÇ#JHŋ32Ǐ5Iɓ(S\IK,cʜI͛, ⣓Ξ@|IѣH*]jbD.Jի&W]mٳh $۶Q]ݻx҅9w/ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνO^}/aϣ/x裏^?_AM4CZ7\A8Q%?ǟAdt A!:}bA(Jc]a!':c@@!xP Io(NF eAiQRiѐKeD_f! , bC H*\ȰÇ#JHŋȱGCIɓ(S<Ǖ0cʜI͊.]ɳϟ@lѣHFD%ӦP*J՚jʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ ;È)6ㅕPU-h`^ wFHGZ WaA:hPmؐ" `ԝa*hih8a!(7hfc@b )A9QfVfK^陖]r) %c`)ҙ&sfpr&gLf@#蠄B&:(&h> M)MYJr:ڨbh*륦ժ:릯j+Z+Rک"2[F,ή:ʚ-*춼^{ɒv->ۮIz2K+{/;/c[o-Lh4],{E1|Ju,qݭlsu! , y/ H*\ȰÇ#JHŋ hȱ# CIɓ(S<ƕ0cʜI͛ɳ'8 Jљ@*]ʴӧA #ԪX@ʵץK_Ӫ]vٷcѶK˷˿} \oKˆ+^̸1] (D YB u̹}Cϳ)[^ͺu &IgͻWݲkNܯœ+/i^NJभk>hOӫ_Ͼ {<ٯT~GXG _ Rt =Q '\*$KJh҅ m$>#m8aƍ8cl("@&Q-3IPV!4hWj٤\#UchYby n&'^dtErg)h5 izV( &HjiNjBB(PM@** z$! , I: H*\ȰÇ#JHŋȱGCIɓ(SJ.alIA͛8sɓ`@ JH*uQӧPGիjʵH`zKفaÞ]˶д`AKnPv˷߿ LÈ+^̸ǐ#G$kx9MӨS^=ɐ*? A ױ`{p>gMpϡ+_μУKNسk )GϾd0LJ//w?( W7 `G F(ЃI 'p‰"*6jC2j|! , a: H*\ȰÇ#JHŋȱG}CIɓ(S<%Ǖ0cʜIMnɳϟ J4IH*]ӧP,JUI.jʵ+Ub赬ٳ6R;#ڷpDCݻhlQ߿E-mÈ,RǐC2 '̹se͞C> zgK^ͺװc˞M6s€%N}+Wx9qHTQe$MK>;?8?w![~o=CĿwr 8~d -(} Fh'VhᅜP{Qyj !{"V)BX.Ђ2D*cH&BJ6y$N6 eIi%AT*9eFnɥY^)&HIQ餘WAnv gj6i9ƹn֩Aw6("g|I藏 9jaJX陟ixjꩨ⑀wJ9*h: 4*3Klp@^ꩳBK栽v=l۶(Q[-ZՔkVc"A`,Bzno쭼[o 7? #D1_p7i;?!{|P?i+2-\s/|3\G,rDtAO3K@WK2FTW [$ot^_ bMԲAcJWv{V}E~c"! , z, H*\ȰÇ#JHŋ32Ǐ4Iɓ(S\Yˎ,cʜI͔Hrɗ/o JQ*]TO JJjզX,ׯ`Jٳ7 ۷p&RݻxR[W߿E+$ˆ .ǐWȚ50-koYe?{MZAX5&ZgӜcF!msͻ N3SMsFN]t~ɐ*? $C`W_ovdKOЌhz(~g* "V(?fa{nɈ$h"'|fifP+X!(#C;#^1!E=hLvx䒜%9$P6d0FiU>\8%_v)]Nn9d~$lap)iPdxV#DwF8Ǡj( DIFʒ/6&C*v`n$TZZsDQ 무JzS@! , z* H*\ȰÇ#JHŋ32D±ǏH4Iɓ(S\YQ˗0ccI͛8IɳO43h΅2pӧPU*UjԤUNdׯ` ^pl̰hӪ],̲n]Kݑq̫ݿ {ЯÈkXF#K ٱ˘R9ϠCMӨS^ͺװc˞ɐ*? ؄C ̙%[Μˇzu3سknFA7Ƚ|v~U󫜾Y" 4& G2 H]|!!ddhS?( )(#i0b,Ψ#h8xP+($f@xc-dbHxdKF)XIDXuMf%Ki)da@Vmie}&IPf]xoǞ|' PQfZd1Za> 4p'Xi,É\"$*r jC,q69u)꫸&뭶k[?klW6[:! , a, H*\ȰÇ#JHŋ32tǏ.4Iɓ(S<˗0I͛8!ɳO,c mѣHtҧPJMٴɪLjʵkBYK鵬ٳH ⣖ڶp[rwJ}ÈI68ǐ#KL˘3k̹ϠCMӨS^ͺ $H]{`3 q~ƓX%KWuy^\ Nӫ_Ͼ= w> |`j4~!D 6 Fс%(9Q8vH(q$8f-xXxu9袀>aH&GPؤ's=i'p‰"KU&kWQFmjUM4C/ۄ/F^pD! , z7 H*\ȰÇ#JHŋ ]hȱ CIɓ(SD˗0I͛8o"ɳO$'c uѣH:4(ӘJJJj1 Sկ`ÊhٱhӪ]˶۷pʝKݻx˷߿. 0̸\_>˘!ϠvytШSS5} k̪c.rښgޝw۵y wnOXμģMڷ؛KN:rgEOy3w}ßO_SsϿNT[~q~7` Nvo\6f\ NX~ $'n(⋪x"4&5؍!#`<|!tD&IWx 1PF)TViXf\vYev!&v^h i)tix5&A~:gjgHhBZhD> hNJxIz `HШꧥ*5ѬNi*ENTlR1! , ad H*\ȰCD-3jȱǏ CIɇ2Ҁ‡'cʜI͛8sHs JѣH &RM)JJUT:AcKمY+1`gʝKgp ZTu L\ ǐ&P0DUp#k)LӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ8OhOwAP`: h! Gva~Z!~!8}H,"q0(4h8<%XaI!YdAN~ƤrQ"•Xfe AY唰}y`UP)6"jeIdچ3TvjgƟ*si衈&(nQJ(u:tNZ]f馜v駠*ꨤj ꪬGzڛj뭸#+qj+Uc&[ +l4-О?v9vi+@{+kViE*)5lp ں pj! , z= H*\ȰÇ#JHŋȱG}CIɓ(S<%Ǖ0cʜI͛ɳϟ@%JQ&](]ʴPJzU,]ʵדWDBٳHj۷ZKݻ"˷/Ka >z] #ʘ@̹sM{M4Mю+ְAMvBk?ͻvnH{ Nȓ+_μ /6uӱ^~=ރsC 6(r.TVwa` a$6b(nB 0".Ƹa46A7֨<@)DeyE&yJ6ӉNFRVg \%b9h ip)tix|ɣa'GW JP30h>AV)"*A\SꩦdE`뭸*f! ,!J= H*\ȰÇ#JHŋ hȱ# CIɓ(I^pʖ/]͛8s0ϟ@ J4ǣ*]ʴӂH>J*D$Xj݊djԣVÊٳhyq۷M5;ݻx _s%w$; )c̹ϠCMӨS^ͺװc˞M۸sͻ@Mo뻸O"OnHAУK.Hr/לvE9O<'݋?pCwa(w]^ ]`4ataRa >a$*4b(Bu`,T͋0(c5" h#'∐;v?CBd Y$)"9 D<\%Zvy$-З`Fƙh& bn)tҦO۝go\蠂J"god@ 4飸 vh騛b[@! , z< H*\ȰÇ#JHŋȱGCIɓ(S4eǕ0cʜIM.rž@uJѣH[ʴS}IJJjD.>Ԫׯ`RٳhJ$[ pE*ۏj {źSKpR> Vx  <ϠnagϡS^d׬cN `r㾭w-ȓ+_μУKNسkνwЧ8 Tl˧=!$|&h WiT@Fo.aRaxx"&*hX -1hc.X@)DiH&L6PF)% Ni%KU^eH 2`edihflYz,dp >םxwrpj> z=I6Nji )yex ^q]@! ,Q2< H*\ȰÇ#JH(\̸Q#F CIɓ(S@˗0c[I͛8s4(˝@ JѣH*]ʴӧPJJBXj݊ժׯ`Ur5ٳhAgm[nHK.]˷/K*$ˆ^ب>K rO3k̹ϠCMӨS^ͺװc˞M۸sͻd+_μЛ#ԭk= {O`ӫOc@oyOϿЉ r/ Ffv ng!6"%r6A(.)vA1t`Ys@(dȣkF*$ PF)L! ,J9E H*\ȰÇ#JHѡ3jWǏ CIɅSbDx! -_Ƅɛ8sS$TIѣHRӧPJJիXjʵׯ`ÊKٳhF۷lʝ+ݷtzގ}00\ F̸`"C~,)W<e̠CMӨS^ͺװc˞M۸sͻ GIGΜ(gРKN}u3óOӫ_ϾhnݻV|4N(3"pv CFh^fvqu!x^M)"&c4h8cp>5idH)B$B4[TiXF)\v`)cfhg[@! ,Q2= H*\ȰÇ#JH 3jPǏ CII"\reK*) Hɛ8sɓb5JѣDDʴӧP *ի'hʵ+S5bK,D}hӪ]lXfʕ˶nړhϒ};av:%<2q]Ƈ#u̖䅔^<ֲCϜ fVdЦFëYMkl}kͻoN\$K 1YJXУKNسkνìOӫ_ϾI_OߨCNVpH&| 6XQ?41C@(Vv ($} )h (4V#8tc ><dDiH&Jd!TVIPe\v`),^fd! ,i= H*\ȰÇ#J8Pŋ3ȱǏ C,ő(S\2!0cʜYЅ͛8sɳO&MJà%*]ʴӧPJJիXjʵׯ`ÊٳhӚ%ڷgʝKݻx˷߿ LÈ+^̸=ALy ϠiM)ӨSPnװc۰kō[7]޷} Nȓ+_μУKNسkwK.NџO@E@tW(~O5O! , zf HA}GP#JHŋ3jȱǏ CiQɗ0cʜI͛8!ɪ@ Jљ Q[aѧPJJ4b䴪ׯ`Ê@ >t˶۷`pw&$)LAÈ+HIa0`˘,W`̠CMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ\8d& : B…f nRG2c(O!0ƈD*h"h#`@ZXg/H""!I2٤(B"|l%^ysai]i%AXfim%rxY4|RI{u ϝ!BdܣFze-2ni*k^&VꨱƺD*ʪH\@A*R뜽*kM++z} .U⺊fy+n~UՖ ﴸۂ{o[+H'AH QU b.JL_1;L5 +?+o_?so _XC)}_7AU~7 πEXtܠFNh0/\@XHЅ*Ĉ 8laQ(^Cb HA&ZЉh$.QEbD.FыS"EьIDXQ!8G7юmXG8R I. c" ?/#d AV!)IvRz|c(8

.箻 Fo߻G/H<Q}58<\8?&I"EHZ`TvC$0d`x#QqiC(0S NHech<^6b"JĎ#J8>Ï>IBr9" E*4$$#ȋPRd%#HMr%%EIRē4DPLz>|+5IJG\$,\2W|ibVNJi2fDe.HRմuYcgWXJsD7)p+lGnd@7I#{';iy#І:IBjw'F3:3 DZE<! , y H*\ȰÇ#JHŋȱG}CIɓ(S4%Ǖ0cʜI͛nϟ@IѣH*ҧPJڴ)ªLb :ׯ`nuzp,Pfê]YD;݇r+0/ݭ\/a \pҼC Agreʖ3p6qAAs 9@^+oW|Ql}[&ޣwV)m;^xZGWsua_<}kϽ9u]^w  "/~ f]_~ xtyރ {War fH|5Z!f5% ႥA8H/J8#5_qX^=XCTCC DJyDH*OF:X$nY\ZXAjBa|(8 &I|ipz۝*Xh\h7>I@Tj饘 PZ&ʕg)Z*FS@Y{j뭸r"@I~ "R=>+m DBXvԵ(! , z3 H*\ȰÇ#JHŋ"hcGC&Hɓ(S\ɲ%JbʜI38sɳϟj yѣH*]qТLJJ*RVjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿o@Ä+^\Ì#KNZ Uʗ-cތu-;MӨS^ͺ5ǰ]^ ۨk#͛nȽ q)'Ɵ޼:IGW~Ќm겢ny:䭹8 ! , y3 H*\ȰÇ#JHŋȱǍCIɓ(S$Ǖ0cʜI͛.sɳϟ@MtѣH"ӧPJJիXjʵׯ`ÊKٳhӪ]˶[hʝKMxǷ߿LX+} +^<1bƐ#e˕/k6paCh3G^=pYe_MAYM_Nȓ+_μУKNؽm/7 csD+$ֿ_RA5T`*$`j-с Z'`\MfEv!aAH<(,Ƹ0ނ B5xz>&H"Y}G9dyK6`NR A@"< ^~ ' D5yeA@V٠@Z JfB{)UsҩOJɩ="j݇r@F:?By! , & H*\ȰÇ#JH 3jXǏ CYqI$S\ɲ%ɓ']ʜI&K&mɳ'B$C2 &FbӧPS0/իXjʵׯ`ÊKٳ3qD˶-Bku 4xt_SKX"܌KL˘3k̹ϠCMӨSJѣH)F,JJJU- >`\feI8A.axAP0b'H"A.XPWt42!Ht9#;HFBd>B9d= #W*y\vhX܅d򔝕3bY(&qeyY'9\j@hleS2ZkZP%TB^.h>(EjA7(*Vʷjw'ɊYAjϰk>,.l mRIh%j{Q+A;Rn֒Un;һϒ+o5Bɋ{ƻ/K 11D:0 pUZ.c\7s;sS,l,sGLEU!TWm14[ݴ_cIZ˵^wt> `MR|{nG}t8ًx܃T8]3C9H[S^聓! _蘟î췃:|P>|o}Bc_~S~KI_gO^w? L` ? P|$_I<4_A}#"p(g\wݛe?t7~!(2,` >(ILbX 򗠇v!JbH,s"r^s DD#7p׍9xAEy#>dA-V⋽xh}YebeQ1Ҩw3›p)g Xݹ"zxfGs 'Z괡ng|蝉r*uf4er*fmܚ jT.*FVҥ[:Фٚf 뫧_" /jjlZ+9 Ma-rkmʎnIڒ-jQ۫^/HJ[궧z0T0ϋhA@f@VǢ}, (p-LI02C3l! , z/ H*\ȰÇ#JHB3jǏ CIɓ(O^pʖ/]͛8ssb@ JѣH*]ʴӧPJʱjƩXjzӪU`ÊٳhӢI۷pKݻx˷߿ LÈ+^̸q[UB2ɔ b(y3Ξ;j-ڲiAoVdאOH4q + l .v_s??`ӎdHU AdwC^MgǗ^=a;_Dğ@Uo 6h}#9(haIwᆊe k!8؈_({4%cd5:Ս($X@n4=%އK&t6"X:4eQa`*M c鄚hfso:Wet~U^2YYN)(rqm&С i 4p'Xi,ÉM馗fjB*stD6Ch5`P ,BgG@5mz! , z. H*\ȰÇ#JHB3jǏ CIɓ(Mr\1˗0cʜI&E,mɳϟ3qJѣH{"YʴS$IJJիXj)t֯`Ê}ؕسhn-R۷pݻ/+޾ _*^|21X"KLY˘. 3͞C;&X䎙Sjװc˞M۸sޭr.FZċD2ʏ*C6Y&ώt{q}<#Do}P=|_z_7؟G`| .XрARɅf!'TX` [C,"D': (@HҌHb}.($WANX$H& dO>k# Zre`U]a\l#f7FXih)|F'N}jԟ9 jOzu6%j|'g@s"< n ' T饙nZBjfrM@ Co kA"$fk+ncq ːöWeg2im tmk-! ,fDH*\hÇ#JHŋ3jȱǏ Cɓ(O\ɲ˗0cʜI1͛ iɳϟ?q ѣH*7tҧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmh@ B$Ҥ,  u'gѤ`H;w0gyTz0x ꬷ.n{͈DHi's*<ǩg>ù >⏿#Ds/q?o? R0|+"F% ZpAx" D@#$#L0 E7}5! [CH=H"R"FL5*qbh=1N=dS״E!_tÈьW\jpc5 0c<^ď %*q) .bDEJr%B&4'CIDh9)uDV$ZrSFt饀! , J< H*\ȰÇ#JHŋᳱ#Ǐ3Iɓ(S ˗0c ͛8sYPϗx JQA&=ʴӧP%.Jի:bդ`Ê5ֲhFڷplVݻx1e߿L@$+^`#}1寒3Eùhh9*iS =-i˗U`m|ɽ[7MISgVnzA b'ݚm?w뮹W}}7@{_x[ƶe螄"-2^ha*] Bc:ڵϊ,>(#1(c6c4ģ)c;X$G昤KC>F cQN@>W&Db9$=iio"s2Yl禚YI%g.f{(AԨ@O>IhvP} rJ*vj(ڥҚ, [{,2۫" 3Pkm^!:Z,.J Xʎm>n~knIkPڻo 0Appp'\1q ?1J2盲O#V4㌱-3C{\4As_@Դ.,;x==q`_]a tgvk=6bCms˭tFq}7ۍ6 6p]q3^C8h)~l~w8eY9nk>:{8]^:y:곫ΰczzO{U$P+|OO='oCu}|Qw˛ԫ/AKPߏ׿o W@P8ʂ`C&X< zP$! , z' H*\ȰÇ#JH 3jPǏ CI2(LR%J [|ٲ͉sbɳϟ@{GѣEʔhЧuJի]hʵ M O9ͪ]v$ٲ+i[h7˷/ݺ~0F gD̸ںE⋈/cv̹3Pv N4G3̺[Дz<]P3m]Mqtl7s};|I\9xKwiW&@POuѰmz'Ƚe//|A#?\;~2,`. xfؠ2 fyUHv#`@,B.Ƹ"4XJ)xA|(#BhHV-"NT"M^bWVjiAbB%hT\٦j)'IlpΩo.砄.'Ah?>qF ]Rff驨VIj:Y\Aָj뭸뮼+l kĽzhT: 6af@Rm2$^@;kew/images/kew.png000066400000000000000000001105451512074754200144220ustar00rootroot00000000000000PNG  IHDR+sRGB,gAMA a cHRMz&u0`:pQ<bKGD pHYs  tIME 4ϰ IDATxkx^y'[,[d[%0>6drHƱB)ӺK;3ɸILLv3W3f_{']ݫʹۙ$(L4;[1Zȯd'>MwY_Z$('(p%J=#̣(|̪("̱ <`~̶d8̺bܫ̿ ` BmL` 33`Nv`0;g<0`~x`  30Sg<0`Q? 2d<0 x`@ !@?B QYtJqH 9|?iziCAǚ5+bV</Egn9Âuz,-~Ӊ |F/]-{!@2c*s~v (@Qq7?[П d|:?z2l>]IWxy,_H``\t73O>L.`FWŲ(ɇP4l 12ԫL> q^gwX?$>I `؇Bw?:c?aB!t? v~Ƞ|'6 ~H &+0A =kP@`(ǒ5 L矌^@ wԗ p`05 gB  lC?i70CC?P!  ,PӰ!**0`?WŲ( `C@ 0 (`i 0?@wDW^|/0=&'0~E 0 H!0A@0 H QӰ!**B@ 0 P 2V @ 0a1pB nC`A 0x,(` 9*F!r!0@QwDO[B?L! @`Quq5q|h(bxh0^n:]%ꝶUQ^(U-wDDDWք.X-y+c3wN|A Q層~B矜֡=K/qZH0U8{7}j6LxGXSTC`.k"gmԭ UiyU,[{B()x'~8Xgc 4Š?@hIGsŲ&uZ'?Znb ,XbUФ5+ 4pv1pBihfƓ.X@I;eBuk4 4d`@Gc= "PLJ˫bkk< ~ȎuziB `矌^\@dLMÆ^h3z@SH4lk )ЬIʻp`G30!@hlE|GtU. U\qNS@v@E폁#HB層~B`$@u;@"4By1-֬B@h|4:&6gF 5=d࢈&KΤ3?VO 4.\Kq-hXЬ @Ba9?Pxy,_B@4%@4 bGk0 ?g@YMÆ^ >4h,pƵ@Ck"4h"p wq@k9 ͂fޯ*F!\A5:!5|@veqlhX14pvˎ(_ޅ kd*?#rq?ý~0@.#"b~SS󖭋7>COG#߸Ɂ}x_| q.c xypϒ9'zX8$j]'OvA oݵܾ_;Ջy EF#=1|;#a; +_%nMbk~VCOG}A@..ޤvˎټeKbeL_n!\]Iq_'OXu{b GE0XP*6(`E\U!88]틡) Eg }Я[x#筫=>dj}Um=͆~ܮx_K 1SzX~ AC_9 B=.c-7-ׇA‹ /GKN <Ν'oV "#?ӠzuՊ+jŕ'fWhv@_R@:sca@. >EW)D W?;zEЯ\P]H)vEEʈ(ԗ{$K. ÿ (E1/ZI!` vx>6y= ų0;ZY!Pi{,02u} B%Hž[!*/%kB_0H2}WnշV(@#=_|NȠX߀ .Lk/P|ZP^(bȘM؂({.r:[!|VPGDD\ߎ%^0YvՒ @^႗=3!D:?{}z#"8rX!2Ѱ+@QDWnK>yo̩W `|NP~ QO}5J+(0A@A;L `]OLyF`WO ө|X}n]wSc ݮ(IEϔX;ߊ>C_z*dݲ#\U!䥁z+Wʼn ޴=~g-J?@#@> 0|GK5+Dؼst\@艦;W)^KpA K+՛P <\\0wk"`  t_CS}^ Uw=sjH1=pE&|/K1#)12ԫ6E#߸S1gBx9J+((t>;]!ie&\XpAɨ) @ .&dQ]h&2))л!4n~([B=z8ᢑEL1Dbc ρ  Wʍq'950 ׁ  Oڭ 0N[/^:,%EŁ[uSl^;+7*#;02 +7_⥁9P~lXܷ7 }^( 0 VHToPoV0vvŢu컵N}z?"P__A E[uS>}qB}=o٭5ؕ滯RX-?@ 7;B_7D X-)Z@"$ ?#"=޷/\艃_l |L1zE2K [S?o7$bAW|F.Yk_5ͦ'No[v((4D1&DBXE6)gzG#[0ZK/B!=9Z2Fz@Z6hUrc~QJ {A`2 5ZLH_no NPQߠL?ZpvGԂnj܃7+߶P=),^%zښb )rB0&#=qkuw1ݲ#"".E6)Q,aWZ㵡R`T ]33)7m%nQzR!,X\M3XYys1-aE?Q#zS}*,Uv=/Xgűe YLݲ#\U!QZP)g52"? ֪B}U )b+ =Sgm{wxG~Uih}UIRi{,mO#|CLfnBT\xtF_R}bЗ 1I>[ Vr{)IYw;_ #W,̳%އ|^"i?/ "i1xomR "BiJs~""i,x+7[v+DB| fJMÆ^z\-Uro*i*!z\.,ĂSWP fہ/oJeRxǚY '?3{ P ZNqewToڮ)#,IZEcykuv D3(`^míA ` 5r{u}B߶+ʪ.ƉJ#nikV?ȊM͟Ҋ!# ""J#!^Y'v82KOpFiEU/U XzoGȑC'(W`@Ѡk|+˯ E~,cN\czlf,P&?3I[)ϢvaI/ʢ=$0[]9v?%۟}<ݹ渰jN)+9FF@X\G}f=z?Y+7+n(3'GN1rd(qOjFzn$ Q_aE @j숚+n YbpD?Qzr(!4[@AiKd/rէd7jtƑ>^bN {I`!*@?#C q8tu 7ŕ𛱿XW'c8F d17wKPt+GшqdV/h@g2YQbopf3+?~ɢ}o^Q94KNz`YΠ'5NX;7T 7"',H t(XgS[ܤ+|vPt,,g6ÿ S!K_#^~HlmEsKe cn"8,`[x]@OW c⋢zGE_+1<ԣL-;b޲uqf}^dO׫gG,* 4Eh(~+.;ϝΏK/Ƒ'ncٍ_L QrB 0Mcw( |bw?tB0nuI`dx򣿷׾:EЯXL,"3|X}nQ~eO燞}Xq8Mڭ ڧo׿#X<~†+V!1(kmbѺM !лX@"=mEkynV4W`@0}FzNoy׿ X@, s?MLew̬|{B$;\zy cf; +D`/BQ$R?̛|^ؑk#9ɘR![rEqbp^ Bo_@(? =hgb>lHR@=^1li`GylεSotP ?!P܃tSomB("v0 MmDeb^fdCx$`z0K 0)ÿQ f⼫j(ƝֺZ IDATQ 3L02`,NMBL!w*{BcW@qXwOsV,PV_BP<8r|U Qd.:**72|"i7H †+4}Ut>[![ՠK`I-gDS7{aZT&/X!HRG)؆^j%nU)} i5 zB?͊?: 7Gil7y<a<;xBL #Lj0O{5_o3]P8󃽊0Yo޴]!g%~v8˖3@Og|pEˎˣ(-ekQw ГS9?7ݹJ! `B7m%nf?r0e?jIhwF/q`:s!pS=J1Ċ˯_s+LbQwՊ1!@č S91pBDPIlG~Hi쨨#` |,6)`jGDY&y=xB̀ˮvي˯ PiԨ|FE,pSi{V,PIn +.>**f@UuL܃7 &[v+I]s0Ir{bd![뢻1._Ê2Nxd'–QivC_sA1 ϝQo}6ɗo`**pN矌^?6ؕ滯R)x,`y!(T|v0RWnt Hɗ{f;&(0D- 0A})nM,_  ܼk7,XE~aK_b޳60!@B裘Dn ΂U`Uw=Doܩ|Q2zi"[vĜz7{z1nbSB @d@%nu"h ²>c;1^ T_C0P^>wBB1ؕS XwOs5p e vM;`M),w_%Ҋ8 T2zvqk/ލG/@AXbd17ޡMɚs:N w#(bKXpA['ސkkN޴]`F+-u4ǬsbrV 632ծ| 1NQeUDZ~2/ Fl TFyE9xEd8xm~;qOqCVӦvE0O:?6oDAMRӰASOd_rV#@ˮ\B ?8bѺ{>~N^ÿgjsk+*dXCp 0F @?>,4H]^8vo^Rqrܠ޴qnj)g?P̼p|Zg8M@05J˫>|GKo\Y!0X4t=1 uA0&iK995`dsee bro]T/mPLԿ7;NI ~퇭sc 6 (p]Ii ֻ hvE_]&Uɢmecca0+s`RޱiIUw=` :حlRUU]+gcM!mʔ̋6۲hg ȫ$7MB~mTTV+:ص?t{,\1XwOG,&3p?MRqkIp@l@ ήzX~ o1ϘkKMs@rه {†+,;cdG!F)~9;'Pomr򟇻gVV ~X!2:dR\ 7u4Giпk7c[a;ˮjȀܣ qFRļ캞wҺ{U y^&=>s~}>gOfh$;z\îⶨn/^`<vJ;<`twxf C?n^ysK3fs@i3ClGlGg:NmaJ  + 8Bp~>83xo/fC *pX/W'3q:حϢlf<.r{cѨ*Q $8n].sBn"%x/K*mA@ Hi0snxY1  q_.T/mp tn?7Tums-_'8[ʋ0B@~U΂\q"@OO_޺+9(ӵ?t{T%CQvr$ekq$b"}6>8城 ż rsG;5@{ v ɂyu{lvS\zAITİ4ijI[zo4"h" r2d=~bqEBEI@@RhIk95^t6}>y4Ts:a|twH>HUO[srǼڭ~艣MFMcA Rz>9s`gB/ 7`'e}8 v ły St.] (9'Rh*ÿ$CY?sW!3U{ z&-Z]9͡p#HP:O @FM,^I5N?8InjT?;giR);fh2o߀`f9r{Rc)$$ُ~@#L >;vOwuBHL*_1J+(컵N k-DGI!R.Y~4gٍ_A 8#8qeŘBwt>[cToP3H}˟o 7t7Z0] v>}8Z<0ο\w]j/2@tѺh\Π ߑ%CdjaNg;=7k |,rNH]ȀޤקD&SHPT|RN.:**Sw~!@g'S%nρ/  HotXBLb9@PJ˫ViԘ?BדI&9΋Q Kexekq6g.͚5 hЛoW&dc12УgPY!cMkRTRZ3Bז ks@_&7n e2o,({=g=pB@qW-F#Żoo>)lHmNJ[{#߸3c>֯ߪ^?Q8jc39>]CgC"""7x4(ߪEL`:1#ǣGpQ(+7z 3HX~בknN@ih$C(X$5vlp'-M5~xf!:b8fezHa(kyuz7qtQu̟K4ő?7GDĉ cP<$>1 H\tt!@\e8tu A#g ;RڂEVVEشhIqtY**XEEQsAĢ7ԏ^խK/V}h{{w]h&'@=XMÆ^j;(:iV7xo/Vo_qU폊ߩ</bKĘF~D$;Qv#^QYYɯ4Xsh p\9Z3Ia=j_sy'hDmmOkjgy7y&lmw__~}CQwt(J_Tq77~U k5nNGz$!/>@j6U}!{{((#EvzX'f\:Xgң}u,E.r?xDt>[`Õ a ^y9J'Q3maR`R-E޶[~t\9[\vADD (L~⻏B $6EȠ=f>SRy` "zۮg5SM7?ˈ꥚{'.8/U 3Sf&u}|j mlwaES>a?XB{>BySQWq/"N9/~'~(֛IS>w~R{ɏ.KMcfJ_#f?h&?vDOЌ_R|~usfv0R~pToڮ12c?Sͱ?|Q19 0%+BDgM88ucLB푁?8Oiy_njt8 ;s$uyѯ(»5 ( ^U?{dwQ{B[+7&͏݉brnUE8c0 Ɲvy5 ~Jʏԗ$5GD,^zq{}|GDmQm(awvx,Z<]d]eR)iVnL9xyUt%n9e=G`]v voJ~{1My__}Ut| ) SsOܤ2ЗWP7-v" ѨZqeǾt8z=1{:on=iw^_nH=E=wݤ)H2( /TؔVEq9e._̬?iEVZJYYxReGǾvP*N]O(3jNkK>΅S7VB L /1>/5d8VVzs^i,|YA( ^z!z_x d|[e!`R IƟMOF_Cm,Px!c۷m'@/]126do/ [v'y c0.7wN4׿-6+%QҮ ׺rKp -՛'q]- DIT.){.j{]A(u?|0tɐ8/<%\#D)+M9v1u츪Iӭw"sj`J c ciII+9f%o'c>;VLlҗ-<"bw(B,j@8r˜`VItw'.&UN2iC"$$ 1[:'gMxmyLvwIBI?~`4dC\9em`ROUΙ"a78:$*_jw2Y~x ~{! EEeGՀdıQdߏaICUY@LZShfWHLY)[O7ЗOnQ%m$uO<vYg\~CR am/|/7Ɗ8FoTt HGBCv)B?]&y/.[ïcko+\#Z燎3 sZnSǟo$HPz˫bׯztu܎n'Iܓ/~:f_ uo٭8; hdOL=s^yId4ߣ`JOĩggum4` IDATtHevkɾ@:RYYTx9/>$p_: $/7ǘǪmr'᭷(7t1EW*(-QFze8SXx׫pskWnTRܑ\J+A0&^WˈMۓ=CO;ȼ 0\J2$G'Y@@RUyrPlYTk(˯YɴFEF̲ mtDqgVƴu^hUP˟Qeڙ{f_ߐqSkR W{_ ڸWI ^{IkN(_1cm,LJqҋ[#`>EHP:}@NPh'P]mtڏ}: 'X 'dc1% "">Ù?N/?ɶ Ppg _槒8%wxnUo=7J^Uum5ͷ'w亷z54\ T^IWHV ++_Z3`bikG2[]G[ "}SMC$C}UlikPi{1d@z=dNѵ)L#=7&q9wSMsYb]揱ى.Ҋt _W0E^팿;2G>%G>c's)+s8ְT!`m?$qIъ hyQ}?Ɔz\t(L/LK_#'ʒxHV2eq1WnK r1t1H@ KX?pa4j/g=C_Wo1,$+B LBMk1TЏӬK ЂU^w3ȞyϽ0Rx@JSKYRAWrc@N?~ƍ 7|LBo 2k姾]c}N`ڤy)dF~+np# J+1MܭzeoDW9k@\zqo$/\$GYzM~P)](z}WI1K@&x ˶' #/4cY0Y ;tI2sLxc'?cnH-(O* $l/~2ذJ!,neU$wƝ?Ƭ=mB4?lz#Oqlǯ+@P Rx6K`P͍h`wɮX4ǸXR_7 >H ))t͎_PL`W.6]G F(V#=@&ZP.鍁K"**3}|O#gS٣޲n"dqhL:suƨj>B_eev]e[ M:߯dni"dPwD2IGK0<& 1~EȠ)EoEEª]י6}5 Eb ʧO8b\IHL@zEjo(pY#؂ h)@0&eG3B:ޟW0ݾLC_ 32У*VE@a8†+/w* /Qwd}${k!@ g""zښ?,M&x9?~QŮLq?2@B((m3s~hȀobfGY ԯ eDiłhk{͵+d)蕡x(Vݮԛ`߿ cIw_KJ (/_]Q K!ɓqqȨ @Q(򲥬g~~dRqℵ@!w(B{$s~۶㻸"{ӯbr$¥%3?zI 3DI8y2^ lIgv|[)>s+BƔ)dƩ;%'#{=E_>X̀i0o!E y{ѹ~@_^d]׫3`򚻒Y812O|'zE@u#CLqDs?R JwS"x^VJPlE"Qp" `22r Kjm_5ݍ!и;x2iTM{ٝd 2/3PSL-[e[ewI:EuR٢]]LlvW mp7m"]kыd_Z#zҽ`T/(BJ)zz4Pњ֩^,@4Q޿xV(}R@bZE`-+I?pi7ݩ@e)$iE j꣼˜$lyE){/XPJn\gj֛}ȤΉ_c-jj_mRHU2>dRpNT ޷"ëCaK@+$A@o!s]eڷ*H"@?!Ŏ{xl=}@5nܒkt@Z T뿩)VӼ& @!\<Dr_ [W*c@(B^BT__KSC"QP^}"PUM}1[ |_/]wb@?1@10Noڶ0-nߔkjH*d1+387N}ۓ;QACB}G*ЊH8"S2?0s7ݙ}Ȥ_Jo} bĪY*7nP5*@߯nTPI?z&*Zcvm8HlC!#C|M8~Mh_Yxңf*Į6!~xgX>t$+* ' '֦?#_I5^r?j1V\;n&~EE RU,|g jY*㔸8(܇«_絭7S.Ӿ5T+HS9Ek>nV!/uufv?շF{S1_.+С~; &O}J)xVdzCO %ӉLc.E2_UEp%%^SE@iSf"\l:C2BZ{v+BZ?g\wG aCt[Q\j K|'E-mȆLV 5b@?Z=(^ xxEѮhؾr[qH y',5\ⱴ! ħ?a tzhbk".{V 8脴0@&>ؼy{ԟqa\^UaXS'V8>D?L5f/\ҍ+;BX`OԉȩXx_RH_];!ɇ YQoG*{ϗ!w߿Pxc}u,ڰөU<g<9/:BkPk)[;wEs+o-zޥ)7~䗩Ƙݟ7 *PUu]o"wL+tb:/̇N4*KIR2_+ݞ;] ϼy@x'-{!BT95/?XwZ`bz FT@y1E bQ䑈Pgb8?!I]@Ypi\UMo/Le+\ ƭKCMmƇۣA;`pܸN"~[wq0!4eG_ 'ׅkgź?[>->l0:*Bbw|D}îB•5K} 1:L#g>dE zS'rq7_hu?q[꯱uZP:^Şݞ>y\8=!wBKhjUb&EgS}_e !'+Dghÿ]P_0g"LPVŞ݊8RGGΝG޲>I54T"~B?q^E {c ywh^%,}Ѿl;z8ZNE1;} by/5Ӆ~%?-oYKkS}/"Y; iZ8\!h/4ld Xi Ag6.3xG@*ޱwB(B &ss"$nK;5x~@hq .ec~!̫q!bomVЅ$ by +5ʗ ;w)u瑷iִK?uzu)w  7n\@t @ BXrt9#^VJk]$07ç=}1[HLVET ^{_(j3MQ|H!TY;Y@jӧNa0E@q@!\{BZ"\FL{8vRsMe !Q{xSSQ"]G,< ;rN(3CYy  X^45yZ43vK!ea?\IϠvPY`aL>鹽o;8ه>8bz&8`fkSO9<y1:,se(BD[zgC";sE`rsGXpcSÝGR$nj|45b8Ѡ(s!y$BX$;EHA3eETL)70wy+4v cSt;w sVGp?q/{v+L"ឡ~lct(4v:Z4?VN5zEl}kI-4ޥ\G\B,E8O]Zӿ-A0wyK{xSusj8*Dʤs$Pm)AGjj336˙Qo n5 R =F,B[]st@۞o),[Vc.%s!} Z.gx ^-{9?>Xoܿ(S`1x`t=֦64ع+l;zX!"<4BXgП@tTѮETAs`{UeB({O@P}NL)d# jq<4v2Fct(y-k[oV%5oÿ]F *Fl#s]8sL"pIW\PJیiKO*Ba_HMdļ6_;Uv؎g*5~4$|J{ wL!#03[}sj}@Yx w|=4ι LZW Ǣ9;Q_ ١T =p/RyΞع˯֍p`-!0Ubj /0*p,WY9^<^ErQ\U?/g뿩%tsN(";J+[Uw[@I垿/Lٻ}TS,[F9`l;z\0C(r B6HhW6v ێVع+Δv_Jblܤh+ع+MV988ߚPڗ6d}.`KYia *[lkΩv?G; @ßw,4v ?ә UKO;*L.+5`;{ux`GC9ោi޼T?B[M֛L·N4Ӿ)x{ϙ` 2ܝi7.sNv IY}-zMB*S1 n|?go4t=KaIuv5᫓WTuM/ ! )|}/yF:@(Y֯z߶ RM7D?!W,| IDATY~nE!ePlY8lٙ%aɒ²ںp䣉B7.S+U}߳~8MpřINOßc77 !O,Ta, q fԹ"/m1ۺ(ƭhV꺰a5!ҫeF'ϼ`t1 O/G:i}T}kXuϣ\fX`ãjqܫ> x *ߴ$ _K-p YM<OWU8^4jYJbnԃ 3kסz&_i@ŋm@cvmyj8/x !{ ]] ,_m ϸpXw<zh+M/yM-X H_*$)4=[,? b= !7j/ A_HS1E \m2Þb"?"BUu"2ي.0hI`MDs+$_tx͛G{텉/Rwo&O޷wnyߏX8] *A-pD{ ̈_0vbL? H=^Us1uG{탹._B "`'= i 1xQUSmp|$Hw[[@΍ڿKi3Hdi D^BsF:k HG0@By4kVh W3FuV }b| `/, o ɦ cf`<q=ު8gt1^2W0wSgx"Caa,?V/d:ғp 1+@[q<_PrT[b]?w<þX_ xWlR,7b/"b{tcvmP 7_TUS{5`yBC|"[ a_t~^Q7\^ow-X˯{>E@o҇X fԀ>SCHYX#.pl.6Ac1, l1c2t?q" $1ɝiʮ7" :(;?vI5n2[5}@2${ ο!1?}0{_Vh5TK+z/ 21{̴=Wèa4 7LUM}^Iлϗ/ n`eڷ* jV򥿀ӈ5!xް! 9'vg.^2{~ly~";v?q}N苍iH0x\MsV˛w|Uusm#ƍ[l-@ۻ!Ey cSwEwݱ=y"lqdz~Aܻ/uSS*]!}f3M(F= \PSQ:?Tt c{}/#sbbҟB{a y`4p,Xus6!21l>[v0hp=oNt>d}K1">/dw±?>.zͦиqBhWG(o݌.PbUhhgp)-kÊ)Dl[w܍ FroU5 _cfK5q#GC8K~!Em^ /qmpw wgh0f7#`6GL@0K!B\$?eB]9e x^Gzm͡.KřzM@ʪ{ !!%jʮ!>ޝސLJ.o6ԩ\* Ҝ01oQ]]Q0œC.Ky:0gGћ[H`hlޤԆ/>bnL~fJ%0[H`q o_x80gC{v(,=W? (`[kr<KY#+gLV%i(wȗ_PZg碪^0Coaf?%oQp#)WPfBhuٙŞQ^{Bp`|[b 8Cai ={.E0!@0Լ,]B U&O?D.Gu/%^|$LB(!fH޷w),~x"́_A%X` 8F[aE̹ 1K1?$Lĺը1\x3܌ż?ٙ-X4^3S9EcмyB!LI0^C{abÿ`mB9jܸE0GS Tcb6dab.xC=;Zg5O x%hW/A f7h<ΏSiy8߯ H$S,fV"cw?9{ H`o5j`?W* @=DS1铧Ӆ@5 '\)7ݭ`wgVgF0'< Hjj3 BX]!iëf2Gi Uʫ7 (`7sI_* Ҫ-䞿ϛT%aj7+4>/Ph!a~ʪaiCV!JwuPFRH??p`ds~,H8P 0ᄅz :y4,mښgO/uR*NϽϧll`Q(AMb+E2@%lj?%{v+>_/`a--' 'z|RJxx;B_Cz|a9pwB_uz{ EB@akjbLWz} Ţ (@%q^?/aHjj3 a'eoWÿ~^?a @0Vp|$uo !-܁p䩻B`x( cp=ÿ^/bXLa=*"S}*K׿ XD GJxx;B0-oY\gj(w(F* |#LbPkUKRY ?=={Nb7o(D΅'nS֛C]9Yȇw3ooÿ~]bQ1"5 \,0YARRm K Hzn`Q,!ozt L%RPZ^Wvt`uæt6dm`"w@0U5aiߪsvW1S_i5, ١e2͛+ eڷտ7:"$G !eHͿG~JXQ&4/}5T5\&xҼy{By6@ Oj%q!tͦe s_~nK(V=;<+=pj8eYV/Uu`.I ;Hi! Y(%ßϼ2Q QfSXB,DH@e8GQ=~{vڜ[fhW_Oiyp'!҆lXReRyv85 S?)$WfS QAF:4f@j푳 =vvy5+VZc < BmI!,Ps` B ̕hglzk) UٖYa HLְ!7nQ ?}*CC0rT!U< ^|D!ӆ2kC͊U 1s0gw*`gVF: y*py^Xjm^T >x_ýô,*vJ̌_tN`݉TsuNO䩻BFf `+5š?/,oO Ja8)=R)vbpBLh޼]!Ӽy{n0WX8ÿt)`A]0I[>R^)H;e5,j%ް! a'0__׷+_OF   fԷLSB,ri0Ə2 )rj8M! %3; #GB@g:lgeڷ3 Y8&Ta[ ?n'綄bn`ѳ "AUBa (_>XyfTT/շoE`"X HIpxމ`]~_[r}Sq`!0;k6fx $CZ9۝"`NĂXa/'K#%F:B*DZg"w  Z+_{҆Bȩ\!% ,/=*^WK]a!t?q۹tXڐ Kys^ÿ?̘H.C`2?nIM)ϦBy4ou~)`"mHZ$on}K*vY8*(/շZ? ge_ ;EӢ)XF@O׬cpV`oV!4U ])\Y=PfADP@WXH-ц5^D0䞿OQHBB𫿞Uߊ ŔG_OӫW`a"N Gݯ;HP @}3qazr;F*+,Bu3ް!\zTXd-̐_`=ToV BLB[=_?H^[@Fiz6d+;9F^CPiR(ݘTIR4f݂FeëLP[)H)~nK(VUV= k_`!љ*f>eB33i7nݗ7U c}?7o!P}MSXRpղLXrrC?eS0/`"]I!{Ua"ws~+G 2[C[=OEÒl5s-$Ta?O a/XA}[?H=%,lP`2^);ᚶoI`:Uua]V?3f7(OÁ,<{XlL7_rH?Ex=!ET^g;+ a垿/ ١|ї4yD,hxSgzE`\M07#dT=5~6d{ }<X`ן|dz"P,a8WR=B(~ ?~EHѮ{>Aᝡ6A!h0kS03<|piEMi}[DbTSù`_\1$P2FdibT=;(O Xy7ɩ\~6@ypM۷=z:5]WQ*?[ڎ WyIDATBU!r@a[!*Ha 00#774˴o uo p7z> 'U0CSXBn u+I=g/_  _*ÿ pSIw`s$;s?=On,$;)[U8qE+ pОaGB?n2ɤCMaY(|0^ B8>yE8 2ӗ@ n8io~[+)Ѯ{>05+VZЋ!!@|#LBBg0=&=XZxe3p0m]wUׄ|1Ta,_e-В_3L.m0Rab0qt'CYs@nHnHL'Ӿ5_%O>N-QM#Oݥz-ɍ/گщSaxb*|0~UhRT?9muC4) ܠTnOC5(!ѕ!?& BQÿpsÍ-auׇo7~#`17X<1M1GiG _ ԄSv@C=ۯ7BopAzŀȆB?~/5a˅Bs6=z!!37nQHQ7A&?"+w]hώxV1<A"n>hܸ%,m*T<~ƈ"^aq/ )}pdqLp 8zx`YQR^u;'JʱBp *K}kXڐ 457p_8^8e> 7Mp l +nNj$*B({/LW|.\wBAn츩Cyp1sxBg{=O@&} 7{@nPP(U 4pw){=́vc ˁKR Hs-H?;@oBo @C&O> @s 7cp~{1MJTU]V]!"? @C{nZ A4(ϺРQF {)4.hd?qa&aB_b05+VZpO @H*]QU]V]!)H 6Ӥsf}6@#4LiHijU .@(4RY_Pp & {I 4]T`=@04c7Д:}Sqg(`oV4*_iOhм@#FQCPiR/@ /@p/}?B4@ +ݮX 4&aB5? 4h"k5l@h,deK?e@pBշLSB` (ECPi:ۿ^}k- S~7w+VbUḫU k'X?P!.?{olC itAKևuX`>:[Rsg-?@@#MTUׅnWY@TܡP9XCo~#}﵀ 9υ?oE~"=&AhA^iҥo~3J_Ǘ'O;~}3I.wtײ韶/ ?@Lp@z5n& Z~@t `(a `R:@@ ? HoB @bԷLSBP6~4 T#2; #GH`oV@  la~ @@ @HoB A@T)f?  QB z BmI! 3dG@BcvBR.? a@b`a!0_ 0TU]V]! J?ba@Y8r!0`^P_ 0၀Rihj3M TXv¯ ʹa/D}K[4*`$$]l{XBlD.,;eK?@||!@@:3">! (`=C0 ϯTB @~*x!˅@꺰rFtUU8yz@!f0P%`Z7LB`fŪИ]1Pv-m!Ԫ,`oV5/T 8[ @|ʹK>!|G i"?Fbx z"?{( p=vYz(.Y*\]b^AD~0.x ax1B(l~"_0_Q"W7e.B B B B B B B B B B B B BԑBӑBґBϑBΑBˑBȑL|E ] 0*D2̺"8̶x 0*\Z ̮!YUQ## 0*pyP0{Cp0_2/?{5:IENDB`kew/images/kew_architecture.png000066400000000000000000001340311512074754200171600ustar00rootroot00000000000000PNG  IHDRIsBIT|dsRGB?tEXtmxfile%3Cmxfile%20host%3D%22Electron%22%20agent%3D%22Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20draw.io%2F28.1.2%20Chrome%2F138.0.7204.251%20Electron%2F37.5.1%20Safari%2F537.36%22%20version%3D%2228.1.2%22%20scale%3D%221%22%20border%3D%2240%22%3E%0A%20%20%3Cdiagram%20name%3D%22Page-1%22%20id%3D%22t3Mx6ZmEx8IS5CmvYVQP%22%3E%0A%20%20%20%20%3CmxGraphModel%20dx%3D%22928%22%20dy%3D%22632%22%20grid%3D%221%22%20gridSize%3D%2210%22%20guides%3D%221%22%20tooltips%3D%221%22%20connect%3D%221%22%20arrows%3D%221%22%20fold%3D%221%22%20page%3D%221%22%20pageScale%3D%221%22%20pageWidth%3D%22850%22%20pageHeight%3D%221100%22%20math%3D%220%22%20shadow%3D%220%22%3E%0A%20%20%20%20%20%20%3Croot%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%220%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%221%22%20parent%3D%220%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-1%22%20value%3D%22UI%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22366%22%20y%3D%22120%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-2%22%20value%3D%22Ops%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22200%22%20y%3D%22240%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-3%22%20value%3D%22Sys%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22520%22%20y%3D%22240%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-4%22%20value%3D%22Data%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%2240%22%20y%3D%22360%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-5%22%20value%3D%22Sound%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22360%22%20y%3D%22360%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-8%22%20value%3D%22%22%20style%3D%22endArrow%3Dclassic%3Bhtml%3D1%3Brounded%3D0%3BentryX%3D0.5%3BentryY%3D0%3BentryDx%3D0%3BentryDy%3D0%3BexitX%3D0.192%3BexitY%3D1.075%3BexitDx%3D0%3BexitDy%3D0%3BexitPerimeter%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%20source%3D%22Ja56WK2j7F-Ryi3RwjrD-1%22%20target%3D%22Ja56WK2j7F-Ryi3RwjrD-2%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20width%3D%2250%22%20height%3D%2250%22%20relative%3D%221%22%20as%3D%22geometry%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22366%22%20y%3D%22210%22%20as%3D%22sourcePoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22436%22%20y%3D%22340%22%20as%3D%22targetPoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2FmxGeometry%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-9%22%20value%3D%22%22%20style%3D%22endArrow%3Dclassic%3Bhtml%3D1%3Brounded%3D0%3BexitX%3D0.842%3BexitY%3D1.025%3BexitDx%3D0%3BexitDy%3D0%3BexitPerimeter%3D0%3BentryX%3D0.5%3BentryY%3D0%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%20source%3D%22Ja56WK2j7F-Ryi3RwjrD-1%22%20target%3D%22Ja56WK2j7F-Ryi3RwjrD-3%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20width%3D%2250%22%20height%3D%2250%22%20relative%3D%221%22%20as%3D%22geometry%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22456%22%20y%3D%22210%22%20as%3D%22sourcePoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22580%22%20y%3D%22230%22%20as%3D%22targetPoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2FmxGeometry%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-10%22%20value%3D%22%22%20style%3D%22endArrow%3Dclassic%3Bhtml%3D1%3Brounded%3D0%3BexitX%3D0.5%3BexitY%3D1%3BexitDx%3D0%3BexitDy%3D0%3BentryX%3D0.45%3BentryY%3D-0.05%3BentryDx%3D0%3BentryDy%3D0%3BentryPerimeter%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%20source%3D%22Ja56WK2j7F-Ryi3RwjrD-2%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20width%3D%2250%22%20height%3D%2250%22%20relative%3D%221%22%20as%3D%22geometry%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22236%22%20y%3D%22400%22%20as%3D%22sourcePoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22100%22%20y%3D%22358%22%20as%3D%22targetPoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2FmxGeometry%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-11%22%20value%3D%22%22%20style%3D%22endArrow%3Dclassic%3Bhtml%3D1%3Brounded%3D0%3BexitX%3D0.5%3BexitY%3D1%3BexitDx%3D0%3BexitDy%3D0%3BentryX%3D0.5%3BentryY%3D0%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%20source%3D%22Ja56WK2j7F-Ryi3RwjrD-2%22%20target%3D%22Ja56WK2j7F-Ryi3RwjrD-5%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20width%3D%2250%22%20height%3D%2250%22%20relative%3D%221%22%20as%3D%22geometry%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22316%22%20y%3D%22390%22%20as%3D%22sourcePoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22366%22%20y%3D%22340%22%20as%3D%22targetPoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2FmxGeometry%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-12%22%20value%3D%22Common%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22680%22%20y%3D%22120%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-13%22%20value%3D%22Utils%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%22680%22%20y%3D%22360%22%20width%3D%22120%22%20height%3D%2240%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-15%22%20value%3D%22%26lt%3Bh1%20style%3D%26quot%3Bmargin-top%3A%200px%3B%26quot%3B%26gt%3Bkew%20Architecture%26lt%3B%2Fh1%26gt%3B%26lt%3Bp%26gt%3Bhttps%3A%2F%2Fcodeberg.org%2Fravachol%2Fkew%26lt%3Bbr%26gt%3B%26lt%3Bbr%26gt%3BCopyright%20%C2%A9%20Ravachol%26lt%3B%2Fp%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3BwhiteSpace%3Dwrap%3Boverflow%3Dhidden%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%2240%22%20y%3D%2280%22%20width%3D%22200%22%20height%3D%22120%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-16%22%20value%3D%22%22%20style%3D%22endArrow%3Dclassic%3Bhtml%3D1%3Brounded%3D0%3BexitX%3D0%3BexitY%3D0.5%3BexitDx%3D0%3BexitDy%3D0%3BentryX%3D1%3BentryY%3D0.5%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%20source%3D%22Ja56WK2j7F-Ryi3RwjrD-3%22%20target%3D%22Ja56WK2j7F-Ryi3RwjrD-2%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20width%3D%2250%22%20height%3D%2250%22%20relative%3D%221%22%20as%3D%22geometry%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22410%22%20y%3D%22300%22%20as%3D%22sourcePoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3CmxPoint%20x%3D%22460%22%20y%3D%22250%22%20as%3D%22targetPoint%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2FmxGeometry%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%20%20%3CmxCell%20id%3D%22Ja56WK2j7F-Ryi3RwjrD-18%22%20value%3D%22%22%20style%3D%22shape%3Dimage%3Baspect%3Dfixed%3Bimage%3Ddata%3Aimage%2Fpng%2CiVBORw0KGgoAAAANSUhEUgAAAZAAAACgCAYAAAAisjrVAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAAuIwAALiMBeKU%2FdgAAAAd0SU1FB%2BkKBRMqEZtK%2BYoAABNrSURBVHja7d17mBTVmcfx7wxy9woaEQXxaDQ%2BxYoajBeyRuLtQaiYiILKozG63hbXGGOyTxI14jW6aqJxRYxxE1ZRFPFyxICKaJSIriJBj8FLKoIuuCg3uV9mZv84Zx7aoRumq7uhu%2Bb3eZ55uqe75506p07V26fq1CkQERERERERERERERERERERERERERERERERERERERERERERKVldJYMnUVwHDAD2ThliHvBKeP4tYM%2B0i2KcfVWrW0SkfLarcPx64BJgeMq%2FnwBMD3EuB%2BKUccYCSiAiIjWUQJqAKcCilH8%2FM8RoBJ4BPk4ZZ4ZWtYiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI1Liir0RPorgT0Cv8Ot84uzILFZFE8Q5Aj%2FDrPOPsWjUPEZHC6lP8TT%2Fg%2FfBzXIbqIg5lmgMcqKYhIlL%2BBJKrKYN10qRmISJS%2BQQiIiJKICIiIkogIiKiBCIiIkogIiKiBCIiIkogIiIiSiAiIqIEIiIiSiAiIpI126kK0kuieFdgSBH1uAJ41Djb0CJOD2AQ0K6VcZYBjxlnGytUrr3x85y1drLNT4FJxtmmFnH2BY4pIs4nwJQ8cQ4A%2FrmIInwETM0TJwKOLCLOB8Cf88Q5GOhfRJy%2FGWenV7AdHgn0pfXT8Mw0zs7ME%2Bdo4IBWxqkDZhhn365guU4EehexPNOMsx%2FmiTMY2KOV%2F7YOeNY4O7dCZaoDTgF2KWJ5njTOLmwRpx0wFNixiDiPGWcXK4FUj97Ab4Eurfz8XOBxoKHF6%2FsCdwGdWhlnDvAE0Fihch0M3F1E%2B3gF%2BFOecn0DGF1EYpwCPJtnh%2FHNEKe1PebHgBfyxDkWuL2IhDYWeDlPnEHA9UXU5%2Bgkiv%2FSMhGV0XDg0iISyChgZp7XzwbOLWKHfQXwdgW3r0uAwa1cnnpgBPBhnvd%2BDHyriKMyJ4dttRLqgatDwm%2Ft52cDC%2FPsu68D9isigcwAlECqyAZgEbCqlZ9fsoU4HVsZZ2mFy7UO%2BLyI9rGswEa%2BNsQppmdFgTiLitjxLy%2Fw%2BuoQp7VWFCjX6lCuYuJU0sqwPK3d8a%2FazHIWE2d1hcv1RRHLUx%2FaSaF2taiVcdqF9l9JS4toP%2FVh%2F1Bof9LaOHWbiaMEsi0YZ2cnUWyK%2FJt8vYbXgT5liFMuk9l4z5dSlufx0FMqNc6DwLgyxLkP%2BH0RYZoK9BruAO4sQ5xyuRK4qpjlKfD6j4DLyxCnXM4q8vOFlmdomeKUY5%2FRkETxwFKXxzi7Nonio7b1PiPNDaUOD10hgJONs09lIRkkUXxm2FE1AP2Ns7OUIkVENt89EhERKZoOYWVYGKnxXfxJwY%2BB%2FzTOzlfNVO362gE4H%2Fg6%2FuT9WOPsqgyUazdgJH6wyDPABOPs%2BgyUq08o127AePwIwsYMlOsg4AKgA%2FBf%2BNF2TUogbU%2BMPyzXfHL%2BsCSKB2dh482oa4HLwvMzgG5JFN9U4fMnld4Z1QP3hi8y4EeMtQMeqPGdbNeQNL4RXhoKfAeYVuPl6ok%2Fd9l8bvc7%2BKH4c%2FJ9Xoewsvtttg44iS%2BP7OoP7K3aqdrex3E5L9Xhhwt3rvGi7Y4fht2sPTAkJJZatg9wSM7v2wMnZKAp9stJHs3rb0ChDyuBZNtCvjyCYzV%2BaKRUn7VsOjx7EVDrvcUVbDqMuWW7rEXLgDUtXvu%2FDLTDRWx6PddnSiBtTDjsMQaYjh%2F%2FvRC4vuUVrVI162sd%2FsKwBH%2BB6Bzgplo%2F3GicXQ5cAywIO6bXgTtr%2BbBcMB%2B4AX8txnr8hbTjMtAU%2F4q%2FOHpFSJB%2FxF%2BUqwTSBndKH%2BOv5D0Yf6z2HtVKVa%2BvZ%2FGHe%2FoBRxtnX8tI0cYCR4RynZhvupEaXFcNwH%2FgDwsfDAzLwpcz4%2Bxa4KfAofhDdBcZZwteCKuT6NnfKX0BONVEzayvBeHbetZ6w%2FMyuK4aQ48xa%2BVaj58HbovUAxERkVSUQERERAlERESUQEREpMqlOYneiJ8Oup5NxwvXsg2hXA1U7j4bIiJtOoG8iZ%2F7BQrPv1%2BLHgOeDs%2FXqGmIiIiIiIiIiIiIiIiIiLQ9aW5pux9%2B0rd64Bbj7JuqxuqVRPEpwGnASuCnxtnFqpWqXl%2BjgP3x08%2FcnJEbL9UBdwHdgJeAMRmYTLH5niCj8TdeesQ4OzEjbbAXcEvYx99pnJ1e6LNprgPpDpwODAP21CZf9aKwvk6h9u8t0RYcG9bXMWTnOq06%2FI2JTsdP6lmXkXJ1AE7F3ySrb4ba4E6hTMPYwv2DdCGhiIikogQiIiJKICIiogQiIiJVTjeUqkFJFLcHDgd2Ms5OUo2IiBKIbClx9MSPZjkbf3vQ%2FwaUQERECUQK9jaOAkYAQ4Dd2XjosUk1JCJKIJKbNOqAPfDXbpyNv5ajS3h7AzALeBR4QLUlIkogQhLFHYABwFmht9E99DYagQXAk8A4YKZxdqVqTESUQNp20qgHeuKvaD0r9DY64g9PrcZP%2FTAOmAwsNs7qZlciogSi5BF3Bm7Gn9%2FYOfQ21uPnQRoPPAIkWZgPqcR6age0L%2BJPGtp6nYkogWRfF2AQfpI5gOeAXwMvAmuyMOFcmQwFrqX11y1NSaL4h%2BqtiSiBZFkTX7597gD8YaudgOeSKF6inSAAOwJfLSKBzFaViSiBZN0S%2FHmPEfgp1%2FfEX%2BcxBJgPPJVE8YPAbOPsijZcT38BLqX1s7j%2BHQ1xFlECybJwiGoWMCuJ4huAk4Dv468y3wv4V%2BBC4K0kiicAE4G5xtl1baye3gXeVYsRUQKR%2FDvJpcC4JIofwl9lPjz0SvYG%2BoefK4Fp4TMvAAt1nkRElEAkX6%2FkZuCE0Cs5EtgFiMPPPOBGYIxqTUS2Bc3GW%2BW9EuPsI%2FhzIsfgbyU8B2gAegOHqJZERD0Q2VKvZDYwO4ni24GBwDn4k%2FAiIkog0rpeCfB4EsVP4of7iogogUhRiaRRPRAR2ZZ0DkRERJRARERk69EhLMm8JIq74K%2Fwb5fiz5uAacbZBUkUbx%2Fi1KWI0whMNc4u1BoRJRCR2tEN%2BAN%2Bmvw0CWQQ%2Fn4suwNjUyaiDcCxgBKIKIGI1JAVwD0UNyV8bs9hXni%2BDLg7ZQJpwM9vJqIEIlIrwtDny8oQ53P8pI4igk6ii4jIVuyBfAJcgz%2BR%2BJ6qsOq9FNbXGmC5qqPq3Q88DyT4w15Z0ATcjr%2Fw9S2yM9X%2BauAG%2FKHRlzLUBhcCo8I%2B%2Fm1tkiIiIiIiIiIiIiIi0tYUfUVtEsWdgF7h1%2FnG2ZVZqIgkincAeoRf5xln12akXN2A7vjrGeYaZzeo2Vf1%2BtoL6AysCtuX7jgpW7sNdsDfCRXgU%2BNswcE3aYbx9gPeDz%2FHZaje4lCmOcCBGSrXyFCuN%2FBXUkt1Gx%2FW11igg6pDtoH9c%2Fbx8eY%2BWOp1IFn8dpTVb3z6Jltb60nrS6p%2Bf6ELCUVEJBUlEBERUQIRERElEBERUQIRERElEBERESUQERFRAhERESUQERFRAhERESUQERERJRAREVECERERJRAREVECERERJRARERElEBERUQIRERElEBERUQIRERElEBERESUQERFRAhERkeqwXSWDJ1HcDrgdGJIyxJ%2BAfwuJbjRwbMo4E42zPyljudoDDwGHpPjzOmCMcfbmJIo7AhOBr6WM82vj7G%2FLWK6uwBRgj5TL83Pj7MNJFO8ITAW6pVyUy42zTyZR3B14Adg%2BRYwm4GLj7HNJFPcIcTqmiNMInGucfTmJ4t7Acym3m%2FXAWcbZ%2Fynj%2BjoIeCKUtVgrgWHG2TlJFB8OPBzKWqxlwKnG2SSJ4oHA%2FSnjfA4MNc5%2BkkTxYOCulHEWhOX5NIniYcCvUtbPXOA04%2ByiJIrPAa5OGecDYLhxdlkSxSOBy1OubgecYZxdmUTxT4CLUsaZGdrhmqpPIMFuQJ8S%2FrYu%2FHylhDi7lrlMdWEn2yfl3%2B6S87xnCXF2qkCPtBewV8rl2SE8bwf0TlnvdUDXnOXpDeyYMoF0yVmevYFOKeI0AJ1ztpc%2BJSSQTmVeXx3D8qTZsS0HOoTnnUKcNDvsxUD78LxLCXE658TpWkKcduGH8MVjn5RxGnLi7FhCPa%2FKOdKzU1ieNHGWAPVJFNcBO5ewL1wQtrHq74EYZxuSKL4Y%2BFHKEGuMs41AY%2FgW0DFlnNVlLtp6IM5p8Gm%2B%2FQGsBY4rYT2sKHO5VgBfz9lwivVFeFwK9CX9IdJl4XERcEAJDX5pzkazTwlxluR8K%2B2VMk5TTpxymZWyt9i8PIvD81eBHinjNOaU6%2FkS4zQvz1MlxGnIifMQMKkMce4DxqeMsyGnPd8B%2FL6Efc4K42xTEsU3AXeWEGdNuRpg0RtC6O7OCL%2BebJx9igxIovhM4MHQcPobZ2dlpFxXAdeGjfyfjLP%2Fi1Tz%2BnoFGIA%2FBDjYOLtWtSJbuQ32BWaH%2FDDCODtuc4csREREREREREREREREREREREREREREREREREREREREREREREpU1FxYSRT3B5qno%2F534%2BwtOe91Bz7FTwz4R%2BPsOaUuXBLFDwAjgIHG2RdbvDcL6AfsYpxdmiJ2R%2FxU8efhJ9pbhZ8s70XgTuPsP1LE3A8%2FfXMhdxhnLyuhPjoDFwJnA%2FvhJ0Z7L9T3mEo3liSK5%2BAnNyzke8bZJ1LErQNG4qeo3g8%2FKeNz%2BOnh56Zc1gnA0JyX1uMnVZwK3GCc%2FXuF6%2Bo3wA9b81njbLHb4fP4WxscaJyd0%2BK9ycCJ%2BHnP3ikybl%2Fg7S18bAfj7IoK110lts2K1NnWqLskipuAucbZPmWu55Ljpp0FdgMwHLgl57WhtZI1kyjuBPwZOCwkxNH4SRQPBS7Fz71%2FX4rQi4FR4Xlv4Ach1oTw2owSlnln%2FMyiRwGvAffgZyf%2BNnAbMGYrVuFNwLo8r89JGe82%2FIzNf8PPWNobOB0YmERxf%2BPs%2FBKW9XfAfPzU3keEdfK9JIqPMM6%2BV8E6mszG2YDBT5F%2FXijjI1W%2BiXwAFJpAb12NbpuZr7ttIW0CmQYcn0Txvjnf5Ibhb9pzQg2U%2B%2FzQQG9teaOpJIoN6e4%2FgXF2MXBNiPPNsLN6xzh7TRmW%2BdaQPK4xzo5qscxDtnL93Viub6FJFB8Yvqm%2Fi58FeXV4%2FRX8TYWuJv3NcwDuNc6%2BkfP%2FbgV%2BHOKOqFQFGWcnhyTS%2FH%2BPCAnk3TK1h0p6fxsuY0W2zTZSd1td2tl4ZwIfhqRBEsW7AcfUwDerZgPC4z15Nvyk2qZyD3fC%2B0H49npdnmV%2Buobb4BmhHY5uTh45PYflwLAkist535r7w%2BPhiLZN2SYJhJAshofnp4bu%2BtQaKXfzjqp3jSzvoLCuHgs32MqS5h35ay12Fuvw9yTYBX9epFw2hMc12vy1bUppSvlm9wjw8ySK9w89kYk5G2e1mwScA4wPd%2Fd63Dj7URUv70Hh8e0MtsF9wmO%2BG101n%2FvoQ%2FrzKy2dHh6nZaDuLkmi%2BPMWr5Uj2e6fRPE1eV6fZ5y9v8a3zUrVWTXUXe0kEOPsX5Mofg8%2FWuJo4PpaKbRxdkJYyb8AbgduT6J4AWCB24yz71fZIu8WHpdUyfIsT6K45WtTjbPHpYjVfB%2F1lXneWxUeSznufUE4R9QJOAR%2Fju4V4JcZ2H5HVijuVwvUz3Q2HgKs1W1zZIXXyTaru1rrgTT3Qq4EPsMPsdujVgpunB2VRPHvgO%2Fih%2FcNBC4Azk6iODbOPl9Fi9t8j%2FKmKlmefKOwkgr%2Bv1LKfX6L3x8DzgyHyGrd5oakltQLMM4Oyei2Wak6q4q6q7UEMh64CphonG3I8610a%2Bw4mkpoqPOBu4G7w4na8%2FDDBu8FTBWtp8XhsXuVLM%2BNZbwWYDnQA%2BgKLGvxXpecz6R1mHH2jSSKu%2BGHCl8Z1vF5SDV%2FwauVbbNNqy9xJTvjbJ1x9uIKLd%2F6zbzXfPHVujI12A3hYrxngX2SKO5ZReup%2BdxH3wy2wY%2FC45553uvZ4jOlrN%2FFxtmrgGeAc5MoPlKbf80kk2reNpVAqtjCFt9Ec3UGVrcY%2BlkOzfG6VlE9TAo9rVOTKK7PWBtsHn11eIvDCh3wgweW4IeMl8svwuN12vxrTjVum0ogVezV8Di0xc6lH37kxJtpgiZRPCyJ4qPyvH4A%2FpjrIuAf1VIJ4WLNCcDX8BfA5S5zXRLFp9ZwG3w4JMeLwlQtzf4Ff4L9UePshjLW5Sz8NCnHhov7pIrU2rbZ1m1X5cs3CT%2BdwblhnqnpwFfww4Y34M%2B%2FpHEofpjgO8DL%2BGPvffAn7ToBw9LstMJx9kvDr83j2PvmDOubEa5QTmMkcCDwyySKBwEvAe3xU5nsy8bpUmrt8IRLovhO%2FNXobyRR%2FDTQC3%2BN0adsnBqmnG4Bjg%2FtZ7B2A5soNBQV4D7j7CcV%2FN8V2TYzUHe7JlH8hwLvPWucHbct4m5X5TuXDUkUfzscdjgVuCJ0Y18GRhlnX08Z%2Bv6QgI4HTsGfnF6GH0n2G%2BPslJRxu7HpEL4o%2FICf52lyyrr4LHxjvgI4DbgEWIufe%2BdnNb7Dujx8q7wQuAx%2F0vxR4GclzoNVqC6fT6L4LeCkJIoPNc7OVM74kkJDUQntt5IJpFLbZq3XXVfg%2BwXeW0rh%2Bbe2VVwRERERERERERERERERERERERERERERERERERERERERERERERERERGR6vP%2FMngOTCDZUEkAAAAASUVORK5CYII%3D%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3CmxGeometry%20x%3D%2240%22%20width%3D%22175%22%20height%3D%2270%22%20as%3D%22geometry%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FmxCell%3E%0A%20%20%20%20%20%20%3C%2Froot%3E%0A%20%20%20%20%3C%2FmxGraphModel%3E%0A%20%20%3C%2Fdiagram%3E%0A%3C%2Fmxfile%3E%0AG IDATx^#ŮXPADc ;[,шDcK,;PlHyٷ{fw|v3{s=Ӭ[n@ @@3D3 @  @2  @I@ @ Ibf@ @ @ '9@ @ /"@ @ MIi6c @@$E @@ dm @ %Hʋ @ 4@$ڌ @K@ @i"HJ+ @ ")/" @D&k3V@ @ /DR^D@ ")Mf @@^8 @HDRX!@ Iyq @ &4YB @y "@ @ MIi6c @@$E @@ dm @ %Hʋ @ 4@$ڌ @K@ @i"HJ+ @ ")/" @D&k3V@ @ /DR^D@ ")Mf @@^8 @HDRX!@ Iyq @ &4YB @y "@ @ MIi6c @@$E @@ dm @ %(ܚ/ܯ"y^Z@ $@D͚/fًڛ:[O[ޚ7-f @@~I ~9ڢb`;ۻrֱy>n=08 @H<Ĉ[B @U!H f @B "Iu\u!ҪK᪝=P @ tHZE; vٳJvv/mK@ @C "im툎=9?Ss&n.Cϖi!BK @6DR =i @*IHn@ Ď" IK!@ PIER Mo֝O_?v\/UI.b+çg+af-lvKYkWOP湤/ϝḇ%Kpwit}:FI%mmK6hgƢm;ge;AާUתS'<|q%\43=kVE2Mm58nhc:k?ϻ5J[7Zw|2XM^>cl ߙ7>pP @Gæ$ti7p:7I_;[nf,غ>Dɉ$ek\f$/$$4i\4ۉ -d]A3]3'Z.Y&-gw u@2˄ǖ[f+81 OBcIw .r}yȉ|4CۥN J}mv&nܽ]'}]l%ׇH$[nl@ @$ic$g:i]GY{ }SԹ_Hl%ײOs-sy'r|mؚ[&.Y&G]g[]Evkll ? @ yIE0߭Y+;ko{Յ @:DRj6תmnI{',3[H yVHB$Uk @DRk}N`{sv{H T @ PDRQ?8H 83D" @"Hv-:=RRϲӵu})$.!] .$ @"HئHjgmT"HnMt%6qu. Hn}UH%C @b)l{Fޞ?SUre4vBU{ڳs,NmRGI]J-r|!W]yUeWfi*Ӝ1G9ۜE ìg4kopKN @/X @@ R$+ұYK XG@V.ԅzPET'cJW}ǾWo̫dS @ PaI+Z?5'1;,g=[vK܂7+g79*!~V.U8A @$`Hj|!rA$U&E @I" TԄ`@ C$DRQH*jp0 @!D"iHB$5a8 Đ" TԴE$!0 @bHH*j"IEM @1$HB$5mI& C @$!$DRQ!@@ IEM[D"  @ #9Wv[Vlf13|` <ִ_y> ӔebvZ5k9t^+SOW.ʞ;#\?3]fΫ[6mѢE n]1 ;֭yvn}'eT{=}㮚pL ;w{YK9.]Gul8}-]̳g|i̛?e9K@ KI˷hkv\F;̜C'\myKݢgv/L"H^-CLbBDO;AFucs (mBܝ3< ]6Z>2Ӆ]{۫s <ʝ+>%D`FƦŭyE]cw/qms{a%\e;e5_Sv?g_,˛ @@,ER{YUGy^0't[R-]a5Y_o1 }þe\Hmצ=E@ H+l @ D")6 @@  @ H]1ze?ޢҢ[ re([e{1R҇nO'.Zv5ej^'=7r^%)2-rs{ޜ7-g];[wI sY&/V iVo8y|{%Jc2r~<f{jͺ6keS]=6>]p7e x=8ߺ}J^”)0Jx1gf~LϹ;su kƱmcX}ZUg{́[|Jkx(郲_y0cQMk|ϛ-5э]R @A "iCNЎ+ڵ.Z-;6q?)$dm&K}n[f=_7@Zho>o;ѣ.2e509 ϸ {;1]ںߺT3xn3~9򺹽5;wksQ;_>qu>o'^ef~cK91g? zn~vI/%&tqvF?$wlݷ)v^^lBV?$Ju)qDcRjBD^.ӠPW'JsʳfGY.S>"Xy]vn4oqnL9]uҥHoHos.â @ b(zγ'!gޮݒvI3*[432l꧶[ԯzwQ[ϻER?=N =*'Ig{1Eޙ}y?:ai 4.txPꇝإҶA7XS7# K̿H15U$wTǕ Y")tu"i'::$!tl†ċ$'.tICTٞ=!e^vOHjc;{+ż9VʎCI$]pN҉fh7 pC H  @@2V$-'9/^[ˋPHh6KN(34At!"I֪]<+;ȉyvar٢'BG"~@.C'D=N0toʿ_􌐹VH:HVo\_6uÓwCf/mH|OQ!~'E祒dVILY0 tI{r%tl '$9w ykdVv? I;9O(zkڕS?IBKٻ>D^(|݅e?/iG t!kJfZMr2o:#IR{bkHcgkețl7ډҩYx vK +)n6p6YesnoB` "Ayt6VW*;rNˋ%{ a[ @!;$hE˳p+yʖzG뎒7#ױ(]sꛗ(ŵ:ec{|$x:9r,h2+r_BF{%7P<%!xHҊN4L]PEOZYN}\i$"!VK8L~ٵg28,$ul>pI T]\k?hJcVE5j.q9)e; @ b)⍜C @Q&Hu @ Pu#A@ @ IQ} @NTu4@ D")֡o @@ !@ (@$E:  @:DRՑ  @ e([A @U'H:r @Le7@ @IUGN @@ l@ T"i @2DRC @ @$U9 B @Q&Hu @ Pu#A@ @ IQ} @NTu4@ D")֡o @@ !@ (@$E:  @:DRՑ  @ e([A @U';ꫯZϞ==/=ܪCKK\s~/ }W~ؽ۞zꩂ @ Ԛ"jѶk?|5%"&i @I56@fgyAfϞm}ӧW.]'\[n>WYe{WEoQ x#9i$իWA @!H- .#b-ϪOb!(￿]uU?g'l/_ ovwcկv^{5J oK9s|M}}7|sԩ- 9#w޶2:Վ?~|c3EJ 4Ȕ7f͚tIןX+W;ck׮v '/~ GkܹGynF:ڶm!Cȑ#}@̩Byv;3lrѢE)b쳏{BeA @&>DM j-^3h{ꩧ\KH]weGuT=諯o_|E/h)uGd#Fųʭ?Eʙgij;H\tE~=q"iVk6xT]Y2EYcƌw?&lb'O+$⮸ [nrU\$2i;JUW]^j 7P?LT̼,h"q @@!zd…?z8-:,w_ԫ /Xޏ (۷7%V]+Bn>#={2믿n;b?蠃.'Hc(QDꫯ?g$8_㏾l?f̙~ĖJHR}iBHgJžh*/.n=I^ ^xdI!!(ǴiӼ+e|z]|ž\JI"QMq0,' @@N$ijH;xnF:thX~h)$-(Z^(!@e.]"UVv饗z "ϋ$qx$%z3KfD(:j$ͥ Q,d>q  @@yb'p7 [ ,K?+ooL,u|I YJ9 @@b-?e/Z2]Q@ڷ"o¥JϭI H($P1%lP|%3BcѼf$y z|F?yтl˗HKh c:Y%WXJl @H ~!y$Tƥ)JL)Q|@ PXLu >e“gE/EUF $M{|QeSJ3-?ycǎ/5U 7ܰzXA(i\fʬ_)ŕBޠ쒝`I#T $)lo@A/=IFXxHQ '[7^'&WtBu({;wWMC V[ٿcI.yΆ @G 1"IמxM~z}i_'$oB-/;M!35js1\}MC,H+)vI'۷83sH◿?_G[u(~SHM gjK;xkVC^@e1cOj=Y*K2 *.(ʔ'oiml0RDR2߼s@ $F$ C$Nh^ܚȃJ(d=2B$8jaGG/fmd(w '*%r䵒שPs#鿃>ʃe"Iez&Wev! 3YSqgf+e|87;vl`*yC%3/{^Ur7S=IA]&'B E Q"I8ҢCJG&FC|Mޔ[oAT&;ôgGE{|A<#JϪ=?NYgU"\lWƊBB$ IyۿIN^*뭷4_)PI5ϟo|=vUWշ/{OSfRƧsd[[*Hȣ(z?dgJ/OT2bD(e^_|@ P:؉҇ʙ @@$g @@ Rdl @ 'Hψ# @ @$  @OG@ @)"HJ* @ ")?# @RD"c3T@ @ ?DR~F@ ")Ef@2L4)a޽;t joIɟH-0U*n(&nM5E _.bvoʘ%uP,x"5- × f*-g&Arlk$q/")sA $/bڐoPP pD8!t-E$`h PMqr&}fQS R8DO!xqrT:H-s$E$%{2:@ @Uj0' MI _.䑴o,xJ!uP "3) O _.tطtv\ɱedAF$@\jC¾UCMC&uaе8@5 ˥<MEO)Js`_DR|= SNΎ3C 9589 ×KWmHطji("l"B04U@&8|TG¾I()A)sN+tws=g#FXl 4ƌc:c=lСֿ[pq6o<6lX嗶v~7VC=d7xcU096ۚkYԩ_|mc92j!P8|1ԟ}S?p${I{]uUSOСCuv2vyg;m==vYgً/XvIIar*t"I7B?G$k8|:kΝk|*};=>$$7bL!@?ka'O~.2{*"Fվc'>CzmW^;̎9;M^'xw_{lΜ9XVl%S)bv!XvlvW3k~ٶnk^xOW`h"k'p?G$Wǭ^)SتݱcGSO=Fmn]r%Gcj+oGqv}o~ׯbn慦?M)xϺAK޻թS'^E=vWz>͛77 \U۶m +ث=ڔP}:OEvתk׮͐!CO}{׿}6|p?IǙbVZk/ѣGNy;.V[3y'×K)9$:w8/YxZJ%N5jˎh[o馛I gc=ףꕈٳ-2*90oZ~Q߽{wxr-}{A^K'[o 3^IiE]b".-5:9]t{[nž;? .ILNipO?Eu]m _W+b\poKvSVZi%/%Q۪O4^[vRԞl>߼ Dn_}u]׷%HJT>M6B _. $۷yy#@- 40E N3mY`YǍgw}+:EkT6xckѢ^x=zj-e +*E^$քkmR$Ikѯzhs%}ҧ)!&/Kfz^c-ʵ`[eUfM6.`kA~:Y @I-^i 6 OQ9s?O'y$DT>["Ia0kŅI,.fo䤋3lOTzg'{XVI6 VHXј}tIGu K艭x),A yC%y֭6}˥TIB$:w8/Yv & r.:)rHk5kj=ZH\q~c5WbLmk@vR_Rf\\bILܐȡ)O( Snފ[·Ih*!AYл̅ d_,b)/*詁B)(@\Jk"2vXA7J Qim'ccG ӟࡶjB+Lz@z*J$>SU,}s1IHR׏<_!GZkBӴHU\|_x+K _=%$^X(DJX淿gvYPքiӦoW(˛0тY^mQz%ϐ~ssgϞI8A]drJh :Fdݺu}BdHЅ*φ<W!q<5LBﴟGE|H3-v417G"E1'Sy"x%"D物ĕ9NyoK{!bdؖ_~yWLtPKc"I7}!I7KɃ$Ś'(R}Z5H7Q J%/RǦc=XSՠ/^"T}N$%hݢݗ$) " ?_DR8<1"I _Eaj YO%MҴxHZV8<Av;Ml-WbWo=EZk8S~xy__}"YT&oFBY!^ē>SHJ,d|@%qЫ8 [E4Y\5M(}IJ T@\G#y-aٔOxfq:#E$iFW@@\0To839c\#}IɞH 8|${Ն}"L kq/")CS j×K5y$-42RpB->'z @× *-g&Arlk$q/")sA $/bڐoPP pD8!t-E$`h PMqr&}fQS R8DO!xqrT:H-s$E$%{2:@ @Uj0' MI@_.d޽{ OIɟ*L`֣G>|x[z@DR5( $J+d#FBiwq%z @ "d 7vekӦ[oe?#C$)bZ2$ìn3yFWb@ N$vekmZ0 IDATwynv;+Nv76։'h뭷׿^uYǖ]vY{l}#<Ҏ?x{wsvYgY׮]7b?SֿO>Fmgq}gg4 =Zh:tvz A$0` ? $ XB< @b%$^|E{k:;#$j]I[s=A7tbFO df͚\`zꫯ]w;l-}Ѷ&;P5 !N@yc$$LlaJ-I V"3ϴ 6 ̲{'/v[vy^Dz?џ+ӒK.iSN>}ċ> ?E^{e? [۰aüP{gCu]'m۶馛oN1W]u͛7;ӹg϶iӦفh|J㑗)d$}ַo_{gTw_^ǎoO?+5g;Mn;k߾vڬYSNI6lc~ID҇~h+{am<[1ZI%N[M@?]lr$Ucj P IC)&Y, E 7`n͜9}QX]r%T)#!'bvI'y*sXr T: TK:!PB Q^%:I T@D{9h:B ʳ>k]w8fEČ(YDHwqޫ=E>M%IjHh(M8{QTQ9FEDC=XؠNbEE@vi')Qۼ@o$-/b[wuQđDRf=JyJ!is} )MhLe=I*!Ttƪ?y$N;4K7G/Ixi|Ϙ1c#Qꒈ x$աT(u LRM T@DꫯٙEOEk͑'&:F$)$Lw!*3z7Kڧ#pE02iL$ɻJ'Xꣲ)Fm)Ni*_yqg~Sh' y$H^.%h,Nazb!q$O4^xTɑ(uE# )~ (؉Rm)ԫW/-WDEp;YeUw@6p#Ux< @I`nݼE^)=-(NeƓDvP Tuդ]ٶH^Y@IWh/(A!Er.$%$Hr*@)CJkLB HHJm2 P KRT(#@ٔA"H- b֥ O- -hك@IN RDR2 кaÆ$@dڕQA%Hm b֥M @u ÙV D}(aJA O< P%*@ 5 @$U-5C(uaHδ PpV@y Z@yT$ P$DR8@*5tPB€I@aѼd9@L2r: bZW --@WI)GYl@"i@ d'@p&   ./"RW*UB!HJ( Pmd6qET @@$ό3 4I:&H أfD")@Z+sxRgr HT"8N I'̇ Ux\,E?!Z@$Ւ>mC'@؛0`ަ ("X @ u̇ Ux-H!J@$U,B%,,ǏoC q%v ,H|3B@xq R()6IRbh DT&Nкπt$3@$g@ 0Fa֥xpxRht 4 HbB@A@OԵPHҾѣG "*|K, Hb@"@hS?܇7rHX@HDR!ZW&J!R )'HJ`OO c&@i g@i"HJ+ B(I@< i7z Iк2rz dz_w}w:z60x$")veT@#2C: #W<~ D")g@ (A U8H"DRʘ 2C @ d (A5'H  P)U,B 7R33 @$%Œh@:&jCTN@I6@h?I=@w!Uf)'W)C I4 ]@ #кƟ69L/K]3u39bHΤ@+u) Y'0% Z@$PxСC K<`ET @VI"OH9BR>> $U2d=39 @IL@єS &TL@ZI"M;Z$!@rq. P DR18(@fhnƍ+N  g@&H4a@ Zb3tT+ !rOJ Rd T"Rd)%iDEh]J'Æ@ *i )!HJ&*M mȑn! U8")L .g BRKP厠"D!cDzych1K;bZ  DshIlРA 1i 8h,U7l 80à@  j&!>m DuqD >;{7i$Hbb@9 0.Cp#FZǜbE ;Ui*}'Vc@u ÙV ;zڽ{w9s)NE)Ꮭ)0 3{[lԩvr_c@@$1) c eذa@q$ t 7X~M6CGkgT"i"0x`>ϙ3~Ge]H+YQ! N:$i!'94M@ksl҇(nر&aw$@ $THevQR8ox`IT]޴3^uA@{(%VpKaI"/&׶|\3r 7֡oQ HCd %Yӄ1 F*I(aadۗѕOT>CjH0Dl\74l2p}/gcߨ[՚"HK$)sطlT`\ 6.m\F DR($X$$ղ? &۾<\7&Hh?y-!$GCdхB F*I* I,d[хA_[mY4 azQ|D0!u jI#fM}]I3K$uCþɶ/+Gy~6_ jmڏ4D"m;}FH &`(et@$JJEBR-Ӹo#Q}n!WkZ[#M/Ha߲RA p}$ظ<$Jq](I``TIJe]A1u`چE")v%HD RoH &>iAadۗѕOT>CjH0Dl\7BJ+ٸq A Bŗyu ÙVbJ I_"]r%ZkY۶m޲;nZn7e_Əo#G,1j@$׺uk;mM6UW]>c{W .ɓ'2t7I @!D3MHʗȒK.i/=׿վ[s=3ϴ38ro t=l4r%9;CmȐ!E^zJw\*$Ҁ IGLq&/aÆYϞ=mn`#8N?t`olj$Njr{!bڵӧW_mZwum6^#AP 7{t_NJ})@`-W\і_~y{GsαO? (yOD^b?FR $k]ve`e]lvmk+MR}R KDRX&ENn|"apiꫯn'|mƶ{CI:SmM7mpAE8͙3hQ:!מ@,)sϵ#FDU#)W_^s~-4o't.~H%I>DR.#1DRG+O )6|A뮻fdy;<'#{ΝSr|'F` }w޻F@ $')txq:wt$~T 7߶>'zH+1KT,1O$-v[ɮކ]uUpB/\sMhXalڴi~A1vXw}xҥ-2>,% YxOR.g[$$J,s4a2w{{ lj{"ȋSIJ3B $ioIZ<vaMiob@F mׇB%={(q!$m- @ "&KĥdHO{6n7qDb&1|RcKP >$7I{gg3 %-M-ce *ÕZB/a`dۗѕG<~Q?FBIG_"6Oٝþe#Hqаo'H*!5$_" 6.dѕM_#]y\ "`]|D6a A:J#i\7et@$ϐL/EB&lFDR !IqY$$۸lF oC"@#ЅK$ g7 ԑT\IO¾ɶ/+"|Ԑ`|$ظ,m\FW6e#t7s HBt %]ۄ3EH*ZOR-"H $$ d޽{ P"%ibd,Zu#ADJ+d :Ԇ fF?|  IDAT>Sj @ ,@PK` `#G4Z鈥q  @@$19 P"Q*xbp "C@ #FX=4ZK#B@@ Ren[Q9js.b6i w[WsžH`R@@b kZV.Lqd Hx@,~ P-jP"bc:X* CRG:3 77K9# BT%I4Lq4d=zt'XbV@ I|H-Cڸq qi`% @"Hb@ 18H@ b@H7DRퟚ^yG1{E, @$Ht2ѰalԨQx qKL@@ eԌqSWu1@@ j+AqT ԙMĜ $")Mő^K@ *M!@!H wZ ($TSRY8 9șB {QjSiJ~@@u ÙVB"8 $Tbx Pq#0hѩ0Fix J&X4a T"2\5$@RMM j!@E@$AqT ʴQmj=@@iIq "rĈ֣G*ĘjkOT{@@SȈH@E tKUNs  a!D (2P 6YB@ nqP2   BCIEP9*Ǥb)g "GC ѣG'}Ȍe@, @%@$ %8*Aq%f @I坚$=F7.5cgRR/ @!D3"TPqRr@,11 @%H,T,96l5 Q*, kMTk > $")¸GUL(bH@("X3$X] ď")~6Yk'Xbv@#H*_*F 2K 4*C @$Us<^`95K@@qIJшTAb)Fg PDRIؒy(veT&XbN@&Hbluz lȑFX XJ% OT<Ĝ8J)"X* 'C@ h|C҂hĈ֣GD$@bI6H3 @ $HQ%:([b%Hz衇qa5@%yu3Ɩ^zON͓׈nAKz͙3Ǯ   @ JIQ} !ҵ^ˢ"vK@$2q?p'1y @ RI2gwO>9sIGxth@0b?xə< %(Y@H( +gՙB_sP b@ "iҤI1H!н{rN\$Ě~`ocFXqYQB$Eb&d UOTp}D WjMdرQdȏ.I!Hio#Q">޿8\$ 7Ë0'a REb p}$ִyHHJ FDt>j Mc*@ xH*ȔT)qH*5v@>\so,Z"j=KR~.אG Wi[4[\5]A}I*E ING#x}m?z_Y\[`_DRgIۏEr1!*4}&bK#+q/" SrP"ةp}#ϱoG+K룲|k]{H,IyqHRn"_C\5_o Dl p}tu<E$dJ8\$;B |9}e p}Tok}I%)o?IMkH룆4d-ؚLA"Rc^#P?Ǿ,uq/"ֳ$"I~ p}~ƾUL%[8T)9RpTj |>7%QY=E$Ux 4nfʂ o^zɆjSLۺ3f}yqHȕ>'@ܮ={ڙgiݻ{g?]veU1ȗ_~im]lqoUH#@<~or~uYg:c.=cvijfvs=ۍ7ޘ9"*<$r=\hh߾[~lVWWd^ꪫ쩧pOkS}.ڐUlѢE6|p0a 0;;טּIIGLr)D$y֭Z1 6mTad̦`xgꫯN:ٕW^i{͛ѰaO $Gy\Pp( *O5]S`iecs̱m.B6m۶{o')W O JIG͛=G;'餓Nm7_9cƍ`_DRXc"㎳6gz` WymM7 W_}.ORSUx>IOC noz]|_AQ_olm営6*@I^x#ϻ?F BSN9{7rBp}I}ĉޓ+G5=Zklܹ݄괽H&> db`"_RJ˜#QJ!eRF$)#Ef25g?sovg~~yڧ ׬Y3i̵b!Z*>ܝ?Jo>|(S^x"i*wZZ5L")s.ogѢEݣGJQaÆ2~ڵw'I"ݻwet=Y{7oOvׯ/?Fc}$EQ$ov7o,~@tsL'W$UIڵkWw2ʕ+eRLCȑ#eܑ܍jͷpȞ{)듞u/.`ϵk?T-Cdtumdˏ͛7B%(tbMҝ;wJ@v1ӊc[r_~-baS8#K񢈸Rۏ-qύ)Ö-Lc;GC[IQ4txxӆcz˗߿_^LOQxy_ER+sϐ$ 0BR4pc em=C|Im_ wd#:fG-߶wM@~7HZWQ!I-`éohV@~44Im_ wn >DNp@1 -@v#m: U$J;Ȑ$](ܟo9Эg"idʏ!IŊFAʣ1cO55jqEqVQrĀh"AK*.(7_ff8gڡyy:Nۧ))Y]%'Q\ NbJx-`ϴb}U[D|pz`xʿLq.qJ ""5@)?3h>NgVԸDO+:82 D@eS.xϗRS*5΃2Ĺ}a ,Cr)\*ghcѐDR86⣶># ]!Oe!$Q|fQ5"ED6=)aeX]I4WT8:X k7`$~3 O(nx _vMJ mO?,|r$gaͨk3nIT'/2Glא4^ |V?w|n;s}^m8/کq9/ wݬ=0$$ZpH'd)Io@+dB""m: Ͽ_רi=ini~ҷz웪D)iJŪ^_ܜ/w݀1L 7^z8;1#mpKi^i &_N-1d::NOYruN$oNLt!"""J """""UN7AIv2NRl)qďf9{@DD D 6FCxI5$"J 4=nKx{0 xx@%"J BY=68`qvjLD@vҨzh=+6:OO0 ,6fW"wnƟ96A<$Yzj/Oz(d_`~9_/k0\ -MIꭉ(dY_}a(^ ;_-"V(dy)_1mm"""""""""""}Emŝ^ٕY$wz_gf\݀g7W ۗ8)[ v Sl7i?eP9*P7WRKuXCs>Xu Yvo|&[[IK~ ED$%Q%Q%%Q%Q%%Q%Q%%Q%Q%%Q]%'Q2ğ n4pl8?)c:`q$;koX`sIL\ˍO&Qx>E&bsIq:k}9s)Y):x"X+a9I<Ze$)| 5~D`ஔq4aR\4$N`qvY#Sna]DORƙᚪO n@.|8Lua''g qv@W!%$Ne^_ٱ-:B4;@K q:ZBvcqrXB=9ҳSX4qI;/\g(Qk@c1ee.z ii+a=(sV_pEx\ %!eqp@ ~iFO q|+2NSNrؼO G9.\#[7 b]"vGT'[,EN?R?}s!wg/+>:g'F{/J7$'7,{'RĭF⧨?)s\ М'U `{7[Yl[hy)2n_-|l ]%͊֨$>e㦝v0%絡5(8,$I.Ͻ_ЋQyo!քڌyg̢Gg'6p0f+VMۣV nD .N86>_E|*Y| R}~ j憤 0Y:2 hmtk8Jh󁻁ÉL{,ύe`9 ,k^Ϥuq$ _yH5mM/q%;lq -ͼ|պ25 bg}(YEG y3٫gs(>R$j6@DsuVYVQ=L =S(Xl}ux /ukN5nJ U8Υ~ěi&Q<,~"R b .s]ŧp|8$NjT-Q2,4)džڶmW7 ?aWÆ7Ͽq(~;c}':qKï PNc$p $/SRjKNo$Q4 ѧlngv(4>'63Pw&Q=k-nW; I;v8"tc_Fg_ON^I씔q(irʺ,|c8 X{g5ú<| Qg%΃U.O-$5TBCQ Rf]W[JU\2xL PIIENDB`kew/include/000077500000000000000000000000001512074754200132765ustar00rootroot00000000000000kew/include/minimp4/000077500000000000000000000000001512074754200146535ustar00rootroot00000000000000kew/include/minimp4/minimp4.h000066400000000000000000003635511512074754200164160ustar00rootroot00000000000000#ifndef MINIMP4_H #define MINIMP4_H /* https://github.com/aspt/mp4 https://github.com/lieff/minimp4 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See . */ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define MINIMP4_MIN(x, y) ((x) < (y) ? (x) : (y)) /************************************************************************/ /* Build configuration */ /************************************************************************/ #define FIX_BAD_ANDROID_META_BOX 1 #define MAX_CHUNKS_DEPTH 64 // Max chunks nesting level #define MINIMP4_MAX_SPS 32 #define MINIMP4_MAX_PPS 256 #define MINIMP4_TRANSCODE_SPS_ID 1 // Support indexing of MP4 files over 4 GB. // If disabled, files with 64-bit offset fields is still supported, // but error signaled if such field contains too big offset // This switch affect return type of MP4D_frame_offset() function #define MINIMP4_ALLOW_64BIT 1 #define MP4D_TRACE_SUPPORTED 0 // Debug trace #define MP4D_TRACE_TIMESTAMPS 1 // Support parsing of supplementary information, not necessary for decoding: // duration, language, bitrate, metadata tags, etc #define MP4D_INFO_SUPPORTED 1 // Enable code, which prints to stdout supplementary MP4 information: #define MP4D_PRINT_INFO_SUPPORTED 0 #define MP4D_AVC_SUPPORTED 1 #define MP4D_HEVC_SUPPORTED 1 #define MP4D_TIMESTAMPS_SUPPORTED 1 // Enable TrackFragmentBaseMediaDecodeTimeBox support #define MP4D_TFDT_SUPPORT 0 /************************************************************************/ /* Some values of MP4(E/D)_track_t->object_type_indication */ /************************************************************************/ // MPEG-4 AAC (all profiles) #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 0x40 // MPEG-2 AAC, Main profile #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE 0x66 // MPEG-2 AAC, LC profile #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE 0x67 // MPEG-2 AAC, SSR profile #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE 0x68 // H.264 (AVC) video #define MP4_OBJECT_TYPE_AVC 0x21 // H.265 (HEVC) video #define MP4_OBJECT_TYPE_HEVC 0x23 // http://www.mp4ra.org/object.html 0xC0-E0 && 0xE2 - 0xFE are specified as "user private" #define MP4_OBJECT_TYPE_USER_PRIVATE 0xC0 /************************************************************************/ /* API error codes */ /************************************************************************/ #define MP4E_STATUS_OK 0 #define MP4E_STATUS_BAD_ARGUMENTS -1 #define MP4E_STATUS_NO_MEMORY -2 #define MP4E_STATUS_FILE_WRITE_ERROR -3 #define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4 /************************************************************************/ /* Sample kind for MP4E_put_sample() */ /************************************************************************/ #define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame #define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame) #define MP4E_SAMPLE_CONTINUATION 2 // Not a sample, but continuation of previous sample (new slice) /************************************************************************/ /* Portable 64-bit type definition */ /************************************************************************/ #if MINIMP4_ALLOW_64BIT typedef uint64_t boxsize_t; #else typedef unsigned int boxsize_t; #endif typedef boxsize_t MP4D_file_offset_t; /************************************************************************/ /* Some values of MP4D_track_t->handler_type */ /************************************************************************/ // Video track : 'vide' #define MP4D_HANDLER_TYPE_VIDE 0x76696465 // Audio track : 'soun' #define MP4D_HANDLER_TYPE_SOUN 0x736F756E // General MPEG-4 systems streams (without specific handler). // Used for private stream, as suggested in http://www.mp4ra.org/handler.html #define MP4E_HANDLER_TYPE_GESM 0x6765736D #define HEVC_NAL_VPS 32 #define HEVC_NAL_SPS 33 #define HEVC_NAL_PPS 34 #define HEVC_NAL_BLA_W_LP 16 #define HEVC_NAL_CRA_NUT 21 /************************************************************************/ /* Data structures */ /************************************************************************/ typedef struct MP4E_mux_tag MP4E_mux_t; typedef enum { e_audio, e_video, e_private } track_media_kind_t; typedef struct { // MP4 object type code, which defined codec class for the track. // See MP4E_OBJECT_TYPE_* values for some codecs unsigned object_type_indication; // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... unsigned char language[4]; track_media_kind_t track_media_kind; // 90000 for video, sample rate for audio unsigned time_scale; unsigned default_duration; union { struct { // number of channels in the audio track. unsigned channelcount; } a; struct { int width; int height; } v; } u; } MP4E_track_t; typedef struct MP4D_sample_to_chunk_t_tag MP4D_sample_to_chunk_t; typedef struct { /************************************************************************/ /* mandatory public data */ /************************************************************************/ // How many 'samples' in the track // The 'sample' is MP4 term, denoting audio or video frame unsigned sample_count; // Decoder-specific info (DSI) data unsigned char *dsi; // DSI data size unsigned dsi_bytes; // MP4 object type code // case 0x00: return "Forbidden"; // case 0x01: return "Systems ISO/IEC 14496-1"; // case 0x02: return "Systems ISO/IEC 14496-1"; // case 0x20: return "Visual ISO/IEC 14496-2"; // case 0x40: return "Audio ISO/IEC 14496-3"; // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; // case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; // case 0x69: return "Audio ISO/IEC 13818-3"; // case 0x6A: return "Visual ISO/IEC 11172-2"; // case 0x6B: return "Audio ISO/IEC 11172-3"; // case 0x6C: return "Visual ISO/IEC 10918-1"; unsigned object_type_indication; #if MP4D_INFO_SUPPORTED /************************************************************************/ /* informational public data */ /************************************************************************/ // handler_type when present in a media box, is an integer containing one of // the following values, or a value from a derived specification: // 'vide' Video track // 'soun' Audio track // 'hint' Hint track unsigned handler_type; // Track duration: 64-bit value split into 2 variables unsigned duration_hi; unsigned duration_lo; // duration scale: duration = timescale*seconds unsigned timescale; // Average bitrate, bits per second unsigned avg_bitrate_bps; // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... unsigned char language[4]; // MP4 stream type // case 0x00: return "Forbidden"; // case 0x01: return "ObjectDescriptorStream"; // case 0x02: return "ClockReferenceStream"; // case 0x03: return "SceneDescriptionStream"; // case 0x04: return "VisualStream"; // case 0x05: return "AudioStream"; // case 0x06: return "MPEG7Stream"; // case 0x07: return "IPMPStream"; // case 0x08: return "ObjectContentInfoStream"; // case 0x09: return "MPEGJStream"; unsigned stream_type; union { // for handler_type == 'soun' tracks struct { unsigned channelcount; unsigned samplerate_hz; } audio; // for handler_type == 'vide' tracks struct { unsigned width; unsigned height; } video; } SampleDescription; #endif /************************************************************************/ /* private data: MP4 indexes */ /************************************************************************/ unsigned *entry_size; unsigned sample_to_chunk_count; struct MP4D_sample_to_chunk_t_tag *sample_to_chunk; unsigned chunk_count; MP4D_file_offset_t *chunk_offset; #if MP4D_TIMESTAMPS_SUPPORTED unsigned *timestamp; unsigned *duration; #endif } MP4D_track_t; typedef struct MP4D_demux_tag { /************************************************************************/ /* mandatory public data */ /************************************************************************/ int64_t read_pos; int64_t read_size; MP4D_track_t *track; int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token); void *token; unsigned track_count; // number of tracks in the movie #if MP4D_INFO_SUPPORTED /************************************************************************/ /* informational public data */ /************************************************************************/ // Movie duration: 64-bit value split into 2 variables unsigned duration_hi; unsigned duration_lo; // duration scale: duration = timescale*seconds unsigned timescale; // Metadata tag (optional) // Tags provided 'as-is', without any re-encoding struct { unsigned char *title; unsigned char *artist; unsigned char *album; unsigned char *year; unsigned char *comment; unsigned char *genre; } tag; #endif } MP4D_demux_t; struct MP4D_sample_to_chunk_t_tag { unsigned first_chunk; unsigned samples_per_chunk; }; typedef struct { void *sps_cache[MINIMP4_MAX_SPS]; void *pps_cache[MINIMP4_MAX_PPS]; int sps_bytes[MINIMP4_MAX_SPS]; int pps_bytes[MINIMP4_MAX_PPS]; int map_sps[MINIMP4_MAX_SPS]; int map_pps[MINIMP4_MAX_PPS]; } h264_sps_id_patcher_t; typedef struct mp4_h26x_writer_tag { #if MINIMP4_TRANSCODE_SPS_ID h264_sps_id_patcher_t sps_patcher; #endif MP4E_mux_t *mux; int mux_track_id, is_hevc, need_vps, need_sps, need_pps, need_idr; } mp4_h26x_writer_t; int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc); void mp4_h26x_write_close(mp4_h26x_writer_t *h); int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next); /************************************************************************/ /* API */ /************************************************************************/ /** * Parse given input stream as MP4 file. Allocate and store data indexes. * return 1 on success, 0 on failure * The MP4 indexes may be stored at the end of stream, so this * function may parse all stream. * It is guaranteed that function will read/seek sequentially, * and will never jump back. */ int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size); /** * Return position and size for given sample from given track. The 'sample' is a * MP4 term for 'frame' * * frame_bytes [OUT] - return coded frame size in bytes * timestamp [OUT] - return frame timestamp (in mp4->timescale units) * duration [OUT] - return frame duration (in mp4->timescale units) * * function return offset for the frame */ MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned int ntrack, unsigned int nsample, unsigned int *frame_bytes, unsigned *timestamp, unsigned *duration); /** * De-allocated memory */ void MP4D_close(MP4D_demux_t *mp4); /** * Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS * Return pointer to internal mp4 memory, it must not be free()-ed * * Example: process all SPS in MP4 file: * while (sps = MP4D_read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) * { * process(sps, sps_bytes); * sps_count++; * } */ const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes); const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes); #if MP4D_PRINT_INFO_SUPPORTED /** * Print MP4 information to stdout. * Uses printf() as well as floating-point functions * Given as implementation example and for test purposes */ void MP4D_printf_info(const MP4D_demux_t *mp4); #endif /** * Allocates and initialize mp4 multiplexor * Given file handler is transparent to the MP4 library, and used only as * argument for given fwrite_callback() function. By appropriate definition * of callback function application may use any other file output API (for * example C++ streams, or Win32 file functions) * * return multiplexor handle on success; NULL on failure */ MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)); /** * Add new track * The track_data parameter does not referred by the multiplexer after function * return, and may be allocated in short-time memory. The dsi member of * track_data parameter is mandatory. * * return ID of added track, or error code MP4E_STATUS_* */ int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data); /** * Add new sample to specified track * The tracks numbered starting with 0, according to order of MP4E_add_track() calls * 'kind' is one of MP4E_SAMPLE_... defines * * return error code MP4E_STATUS_* * * Example: * MP4E_put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT); */ int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, int kind); /** * Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer. * The close operation takes a time and disk space, since it writes MP4 file * indexes. Please note that this function does not closes file handle, * which was passed to open function. * * return error code MP4E_STATUS_* */ int MP4E_close(MP4E_mux_t *mux); /** * Set Decoder Specific Info (DSI) * Can be used for audio and private tracks. * MUST be used for AAC track. * Only one DSI can be set. It is an error to set DSI again * * return error code MP4E_STATUS_* */ int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes); /** * Set VPS data. MUST be used for HEVC (H.265) track. * * return error code MP4E_STATUS_* */ int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes); /** * Set SPS data. MUST be used for AVC (H.264) track. Up to 32 different SPS can be used in one track. * * return error code MP4E_STATUS_* */ int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes); /** * Set PPS data. MUST be used for AVC (H.264) track. Up to 256 different PPS can be used in one track. * * return error code MP4E_STATUS_* */ int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes); /** * Set or replace ASCII test comment for the file. Set comment to NULL to remove comment. * * return error code MP4E_STATUS_* */ int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment); #ifdef __cplusplus } #endif #endif //MINIMP4_H #if defined(MINIMP4_IMPLEMENTATION) && !defined(MINIMP4_IMPLEMENTATION_GUARD) #define MINIMP4_IMPLEMENTATION_GUARD #define FOUR_CHAR_INT(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d)) enum { BOX_co64 = FOUR_CHAR_INT( 'c', 'o', '6', '4' ),//ChunkLargeOffsetAtomType BOX_stco = FOUR_CHAR_INT( 's', 't', 'c', 'o' ),//ChunkOffsetAtomType BOX_crhd = FOUR_CHAR_INT( 'c', 'r', 'h', 'd' ),//ClockReferenceMediaHeaderAtomType BOX_ctts = FOUR_CHAR_INT( 'c', 't', 't', 's' ),//CompositionOffsetAtomType BOX_cprt = FOUR_CHAR_INT( 'c', 'p', 'r', 't' ),//CopyrightAtomType BOX_url_ = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),//DataEntryURLAtomType BOX_urn_ = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),//DataEntryURNAtomType BOX_dinf = FOUR_CHAR_INT( 'd', 'i', 'n', 'f' ),//DataInformationAtomType BOX_dref = FOUR_CHAR_INT( 'd', 'r', 'e', 'f' ),//DataReferenceAtomType BOX_stdp = FOUR_CHAR_INT( 's', 't', 'd', 'p' ),//DegradationPriorityAtomType BOX_edts = FOUR_CHAR_INT( 'e', 'd', 't', 's' ),//EditAtomType BOX_elst = FOUR_CHAR_INT( 'e', 'l', 's', 't' ),//EditListAtomType BOX_uuid = FOUR_CHAR_INT( 'u', 'u', 'i', 'd' ),//ExtendedAtomType BOX_free = FOUR_CHAR_INT( 'f', 'r', 'e', 'e' ),//FreeSpaceAtomType BOX_hdlr = FOUR_CHAR_INT( 'h', 'd', 'l', 'r' ),//HandlerAtomType BOX_hmhd = FOUR_CHAR_INT( 'h', 'm', 'h', 'd' ),//HintMediaHeaderAtomType BOX_hint = FOUR_CHAR_INT( 'h', 'i', 'n', 't' ),//HintTrackReferenceAtomType BOX_mdia = FOUR_CHAR_INT( 'm', 'd', 'i', 'a' ),//MediaAtomType BOX_mdat = FOUR_CHAR_INT( 'm', 'd', 'a', 't' ),//MediaDataAtomType BOX_mdhd = FOUR_CHAR_INT( 'm', 'd', 'h', 'd' ),//MediaHeaderAtomType BOX_minf = FOUR_CHAR_INT( 'm', 'i', 'n', 'f' ),//MediaInformationAtomType BOX_moov = FOUR_CHAR_INT( 'm', 'o', 'o', 'v' ),//MovieAtomType BOX_mvhd = FOUR_CHAR_INT( 'm', 'v', 'h', 'd' ),//MovieHeaderAtomType BOX_stsd = FOUR_CHAR_INT( 's', 't', 's', 'd' ),//SampleDescriptionAtomType BOX_stsz = FOUR_CHAR_INT( 's', 't', 's', 'z' ),//SampleSizeAtomType BOX_stz2 = FOUR_CHAR_INT( 's', 't', 'z', '2' ),//CompactSampleSizeAtomType BOX_stbl = FOUR_CHAR_INT( 's', 't', 'b', 'l' ),//SampleTableAtomType BOX_stsc = FOUR_CHAR_INT( 's', 't', 's', 'c' ),//SampleToChunkAtomType BOX_stsh = FOUR_CHAR_INT( 's', 't', 's', 'h' ),//ShadowSyncAtomType BOX_skip = FOUR_CHAR_INT( 's', 'k', 'i', 'p' ),//SkipAtomType BOX_smhd = FOUR_CHAR_INT( 's', 'm', 'h', 'd' ),//SoundMediaHeaderAtomType BOX_stss = FOUR_CHAR_INT( 's', 't', 's', 's' ),//SyncSampleAtomType BOX_stts = FOUR_CHAR_INT( 's', 't', 't', 's' ),//TimeToSampleAtomType BOX_trak = FOUR_CHAR_INT( 't', 'r', 'a', 'k' ),//TrackAtomType BOX_tkhd = FOUR_CHAR_INT( 't', 'k', 'h', 'd' ),//TrackHeaderAtomType BOX_tref = FOUR_CHAR_INT( 't', 'r', 'e', 'f' ),//TrackReferenceAtomType BOX_udta = FOUR_CHAR_INT( 'u', 'd', 't', 'a' ),//UserDataAtomType BOX_vmhd = FOUR_CHAR_INT( 'v', 'm', 'h', 'd' ),//VideoMediaHeaderAtomType BOX_url = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ), BOX_urn = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ), BOX_gnrv = FOUR_CHAR_INT( 'g', 'n', 'r', 'v' ),//GenericVisualSampleEntryAtomType BOX_gnra = FOUR_CHAR_INT( 'g', 'n', 'r', 'a' ),//GenericAudioSampleEntryAtomType //V2 atoms BOX_ftyp = FOUR_CHAR_INT( 'f', 't', 'y', 'p' ),//FileTypeAtomType BOX_padb = FOUR_CHAR_INT( 'p', 'a', 'd', 'b' ),//PaddingBitsAtomType //MP4 Atoms BOX_sdhd = FOUR_CHAR_INT( 's', 'd', 'h', 'd' ),//SceneDescriptionMediaHeaderAtomType BOX_dpnd = FOUR_CHAR_INT( 'd', 'p', 'n', 'd' ),//StreamDependenceAtomType BOX_iods = FOUR_CHAR_INT( 'i', 'o', 'd', 's' ),//ObjectDescriptorAtomType BOX_odhd = FOUR_CHAR_INT( 'o', 'd', 'h', 'd' ),//ObjectDescriptorMediaHeaderAtomType BOX_mpod = FOUR_CHAR_INT( 'm', 'p', 'o', 'd' ),//ODTrackReferenceAtomType BOX_nmhd = FOUR_CHAR_INT( 'n', 'm', 'h', 'd' ),//MPEGMediaHeaderAtomType BOX_esds = FOUR_CHAR_INT( 'e', 's', 'd', 's' ),//ESDAtomType BOX_sync = FOUR_CHAR_INT( 's', 'y', 'n', 'c' ),//OCRReferenceAtomType BOX_ipir = FOUR_CHAR_INT( 'i', 'p', 'i', 'r' ),//IPIReferenceAtomType BOX_mp4s = FOUR_CHAR_INT( 'm', 'p', '4', 's' ),//MPEGSampleEntryAtomType BOX_mp4a = FOUR_CHAR_INT( 'm', 'p', '4', 'a' ),//MPEGAudioSampleEntryAtomType BOX_mp4v = FOUR_CHAR_INT( 'm', 'p', '4', 'v' ),//MPEGVisualSampleEntryAtomType // http://www.itscj.ipsj.or.jp/sc29/open/29view/29n7644t.doc BOX_avc1 = FOUR_CHAR_INT( 'a', 'v', 'c', '1' ), BOX_avc2 = FOUR_CHAR_INT( 'a', 'v', 'c', '2' ), BOX_svc1 = FOUR_CHAR_INT( 's', 'v', 'c', '1' ), BOX_avcC = FOUR_CHAR_INT( 'a', 'v', 'c', 'C' ), BOX_svcC = FOUR_CHAR_INT( 's', 'v', 'c', 'C' ), BOX_btrt = FOUR_CHAR_INT( 'b', 't', 'r', 't' ), BOX_m4ds = FOUR_CHAR_INT( 'm', '4', 'd', 's' ), BOX_seib = FOUR_CHAR_INT( 's', 'e', 'i', 'b' ), // H264/HEVC BOX_hev1 = FOUR_CHAR_INT( 'h', 'e', 'v', '1' ), BOX_hvc1 = FOUR_CHAR_INT( 'h', 'v', 'c', '1' ), BOX_hvcC = FOUR_CHAR_INT( 'h', 'v', 'c', 'C' ), //3GPP atoms BOX_samr = FOUR_CHAR_INT( 's', 'a', 'm', 'r' ),//AMRSampleEntryAtomType BOX_sawb = FOUR_CHAR_INT( 's', 'a', 'w', 'b' ),//WB_AMRSampleEntryAtomType BOX_damr = FOUR_CHAR_INT( 'd', 'a', 'm', 'r' ),//AMRConfigAtomType BOX_s263 = FOUR_CHAR_INT( 's', '2', '6', '3' ),//H263SampleEntryAtomType BOX_d263 = FOUR_CHAR_INT( 'd', '2', '6', '3' ),//H263ConfigAtomType //V2 atoms - Movie Fragments BOX_mvex = FOUR_CHAR_INT( 'm', 'v', 'e', 'x' ),//MovieExtendsAtomType BOX_trex = FOUR_CHAR_INT( 't', 'r', 'e', 'x' ),//TrackExtendsAtomType BOX_moof = FOUR_CHAR_INT( 'm', 'o', 'o', 'f' ),//MovieFragmentAtomType BOX_mfhd = FOUR_CHAR_INT( 'm', 'f', 'h', 'd' ),//MovieFragmentHeaderAtomType BOX_traf = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ),//TrackFragmentAtomType BOX_tfhd = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ),//TrackFragmentHeaderAtomType BOX_tfdt = FOUR_CHAR_INT( 't', 'f', 'd', 't' ),//TrackFragmentBaseMediaDecodeTimeBox BOX_trun = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ),//TrackFragmentRunAtomType BOX_mehd = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),//MovieExtendsHeaderBox // Object Descriptors (OD) data coding // These takes only 1 byte; this implementation translate to // + OD_BASE to keep API uniform and safe for string functions OD_BASE = FOUR_CHAR_INT( '$', '$', '$', '0' ),// OD_ESD = FOUR_CHAR_INT( '$', '$', '$', '3' ),//SDescriptor_Tag OD_DCD = FOUR_CHAR_INT( '$', '$', '$', '4' ),//DecoderConfigDescriptor_Tag OD_DSI = FOUR_CHAR_INT( '$', '$', '$', '5' ),//DecoderSpecificInfo_Tag OD_SLC = FOUR_CHAR_INT( '$', '$', '$', '6' ),//SLConfigDescriptor_Tag BOX_meta = FOUR_CHAR_INT( 'm', 'e', 't', 'a' ), BOX_ilst = FOUR_CHAR_INT( 'i', 'l', 's', 't' ), // Metagata tags, see http://atomicparsley.sourceforge.net/mpeg-4files.html BOX_calb = FOUR_CHAR_INT( '\xa9', 'a', 'l', 'b'), // album BOX_cart = FOUR_CHAR_INT( '\xa9', 'a', 'r', 't'), // artist BOX_aART = FOUR_CHAR_INT( 'a', 'A', 'R', 'T' ), // album artist BOX_ccmt = FOUR_CHAR_INT( '\xa9', 'c', 'm', 't'), // comment BOX_cday = FOUR_CHAR_INT( '\xa9', 'd', 'a', 'y'), // year (as string) BOX_cnam = FOUR_CHAR_INT( '\xa9', 'n', 'a', 'm'), // title BOX_cgen = FOUR_CHAR_INT( '\xa9', 'g', 'e', 'n'), // custom genre (as string or as byte!) BOX_trkn = FOUR_CHAR_INT( 't', 'r', 'k', 'n'), // track number (byte) BOX_disk = FOUR_CHAR_INT( 'd', 'i', 's', 'k'), // disk number (byte) BOX_cwrt = FOUR_CHAR_INT( '\xa9', 'w', 'r', 't'), // composer BOX_ctoo = FOUR_CHAR_INT( '\xa9', 't', 'o', 'o'), // encoder BOX_tmpo = FOUR_CHAR_INT( 't', 'm', 'p', 'o'), // bpm (byte) BOX_cpil = FOUR_CHAR_INT( 'c', 'p', 'i', 'l'), // compilation (byte) BOX_covr = FOUR_CHAR_INT( 'c', 'o', 'v', 'r'), // cover art (JPEG/PNG) BOX_rtng = FOUR_CHAR_INT( 'r', 't', 'n', 'g'), // rating/advisory (byte) BOX_cgrp = FOUR_CHAR_INT( '\xa9', 'g', 'r', 'p'), // grouping BOX_stik = FOUR_CHAR_INT( 's', 't', 'i', 'k'), // stik (byte) 0 = Movie 1 = Normal 2 = Audiobook 5 = Whacked Bookmark 6 = Music Video 9 = Short Film 10 = TV Show 11 = Booklet 14 = Ringtone BOX_pcst = FOUR_CHAR_INT( 'p', 'c', 's', 't'), // podcast (byte) BOX_catg = FOUR_CHAR_INT( 'c', 'a', 't', 'g'), // category BOX_keyw = FOUR_CHAR_INT( 'k', 'e', 'y', 'w'), // keyword BOX_purl = FOUR_CHAR_INT( 'p', 'u', 'r', 'l'), // podcast URL (byte) BOX_egid = FOUR_CHAR_INT( 'e', 'g', 'i', 'd'), // episode global unique ID (byte) BOX_desc = FOUR_CHAR_INT( 'd', 'e', 's', 'c'), // description BOX_clyr = FOUR_CHAR_INT( '\xa9', 'l', 'y', 'r'), // lyrics (may be > 255 bytes) BOX_tven = FOUR_CHAR_INT( 't', 'v', 'e', 'n'), // tv episode number BOX_tves = FOUR_CHAR_INT( 't', 'v', 'e', 's'), // tv episode (byte) BOX_tvnn = FOUR_CHAR_INT( 't', 'v', 'n', 'n'), // tv network name BOX_tvsh = FOUR_CHAR_INT( 't', 'v', 's', 'h'), // tv show name BOX_tvsn = FOUR_CHAR_INT( 't', 'v', 's', 'n'), // tv season (byte) BOX_purd = FOUR_CHAR_INT( 'p', 'u', 'r', 'd'), // purchase date BOX_pgap = FOUR_CHAR_INT( 'p', 'g', 'a', 'p'), // Gapless Playback (byte) //BOX_aart = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ), // Album artist BOX_cART = FOUR_CHAR_INT( '\xa9', 'A', 'R', 'T'), // artist BOX_gnre = FOUR_CHAR_INT( 'g', 'n', 'r', 'e'), // 3GPP metatags (http://cpansearch.perl.org/src/JHAR/MP4-Info-1.12/Info.pm) BOX_auth = FOUR_CHAR_INT( 'a', 'u', 't', 'h'), // author BOX_titl = FOUR_CHAR_INT( 't', 'i', 't', 'l'), // title BOX_dscp = FOUR_CHAR_INT( 'd', 's', 'c', 'p'), // description BOX_perf = FOUR_CHAR_INT( 'p', 'e', 'r', 'f'), // performer BOX_mean = FOUR_CHAR_INT( 'm', 'e', 'a', 'n'), // BOX_name = FOUR_CHAR_INT( 'n', 'a', 'm', 'e'), // BOX_data = FOUR_CHAR_INT( 'd', 'a', 't', 'a'), // // these from http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2008-September/053151.html BOX_albm = FOUR_CHAR_INT( 'a', 'l', 'b', 'm'), // album BOX_yrrc = FOUR_CHAR_INT( 'y', 'r', 'r', 'c') // album }; // Video track : 'vide' #define MP4E_HANDLER_TYPE_VIDE 0x76696465 // Audio track : 'soun' #define MP4E_HANDLER_TYPE_SOUN 0x736F756E // General MPEG-4 systems streams (without specific handler). // Used for private stream, as suggested in http://www.mp4ra.org/handler.html #define MP4E_HANDLER_TYPE_GESM 0x6765736D typedef struct { boxsize_t size; boxsize_t offset; unsigned duration; unsigned flag_random_access; } sample_t; typedef struct { unsigned char *data; int bytes; int capacity; } minimp4_vector_t; typedef struct { MP4E_track_t info; minimp4_vector_t smpl; // sample descriptor minimp4_vector_t pending_sample; minimp4_vector_t vsps; // or dsi for audio minimp4_vector_t vpps; // not used for audio minimp4_vector_t vvps; // used for HEVC } track_t; typedef struct MP4E_mux_tag { minimp4_vector_t tracks; int64_t write_pos; int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token); void *token; char *text_comment; int sequential_mode_flag; int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode int fragments_count; // # of fragments in 'fragmentation' mode } MP4E_mux_t; static const unsigned char box_ftyp[] = { #if 1 0,0,0,0x18,'f','t','y','p', 'm','p','4','2',0,0,0,0, 'm','p','4','2','i','s','o','m', #else // as in ffmpeg 0,0,0,0x20,'f','t','y','p', 'i','s','o','m',0,0,2,0, 'm','p','4','1','i','s','o','m', 'i','s','o','2','a','v','c','1', #endif }; /** * Endian-independent byte-write macros */ #define WR(x, n) *p++ = (unsigned char)((x) >> 8*n) #define WRITE_1(x) WR(x, 0); #define WRITE_2(x) WR(x, 1); WR(x, 0); #define WRITE_3(x) WR(x, 2); WR(x, 1); WR(x, 0); #define WRITE_4(x) WR(x, 3); WR(x, 2); WR(x, 1); WR(x, 0); #define WR4(p, x) (p)[0] = (char)((x) >> 8*3); (p)[1] = (char)((x) >> 8*2); (p)[2] = (char)((x) >> 8*1); (p)[3] = (char)((x)); // Finish atom: update atom size field #define END_ATOM --stack; WR4((unsigned char*)*stack, p - *stack); // Initiate atom: save position of size field on stack #define ATOM(x) *stack++ = p; p += 4; WRITE_4(x); // Atom with 'FullAtomVersionFlags' field #define ATOM_FULL(x, flag) ATOM(x); WRITE_4(flag); #define ERR(func) { int err = func; if (err) return err; } /** Allocate vector with given size, return 1 on success, 0 on fail */ static int minimp4_vector_init(minimp4_vector_t *h, int capacity) { h->bytes = 0; h->capacity = capacity; h->data = capacity ? (unsigned char *)malloc(capacity) : NULL; return !capacity || !!h->data; } /** Deallocates vector memory */ static void minimp4_vector_reset(minimp4_vector_t *h) { if (h->data) free(h->data); memset(h, 0, sizeof(minimp4_vector_t)); } /** Reallocate vector memory to the given size */ static int minimp4_vector_grow(minimp4_vector_t *h, int bytes) { void *p; int new_size = h->capacity*2 + 1024; if (new_size < h->capacity + bytes) new_size = h->capacity + bytes + 1024; p = realloc(h->data, new_size); if (!p) return 0; h->data = (unsigned char*)p; h->capacity = new_size; return 1; } /** Allocates given number of bytes at the end of vector data, increasing vector memory if necessary. Return allocated memory. */ static unsigned char *minimp4_vector_alloc_tail(minimp4_vector_t *h, int bytes) { unsigned char *p; if (!h->data && !minimp4_vector_init(h, 2*bytes + 1024)) return NULL; if ((h->capacity - h->bytes) < bytes && !minimp4_vector_grow(h, bytes)) return NULL; assert(h->data); assert((h->capacity - h->bytes) >= bytes); p = h->data + h->bytes; h->bytes += bytes; return p; } /** Append data to the end of the vector (accumulate ot enqueue) */ static unsigned char *minimp4_vector_put(minimp4_vector_t *h, const void *buf, int bytes) { unsigned char *tail = minimp4_vector_alloc_tail(h, bytes); if (tail) memcpy(tail, buf, bytes); return tail; } /** * Allocates and initialize mp4 multiplexer * return multiplexor handle on success; NULL on failure */ MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token)) { if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) // Write fixed header: 'ftyp' box return 0; MP4E_mux_t *mux = (MP4E_mux_t*)malloc(sizeof(MP4E_mux_t)); if (!mux) return mux; mux->sequential_mode_flag = sequential_mode_flag || enable_fragmentation; mux->enable_fragmentation = enable_fragmentation; mux->fragments_count = 0; mux->write_callback = write_callback; mux->token = token; mux->text_comment = NULL; mux->write_pos = sizeof(box_ftyp); if (!mux->sequential_mode_flag) { // Write filler, which would be updated later if (mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token)) { free(mux); return 0; } mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding } minimp4_vector_init(&mux->tracks, 2*sizeof(track_t)); return mux; } /** * Add new track */ int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data) { track_t *tr; int ntr = mux->tracks.bytes / sizeof(track_t); if (!mux || !track_data) return MP4E_STATUS_BAD_ARGUMENTS; tr = (track_t*)minimp4_vector_alloc_tail(&mux->tracks, sizeof(track_t)); if (!tr) return MP4E_STATUS_NO_MEMORY; memset(tr, 0, sizeof(track_t)); memcpy(&tr->info, track_data, sizeof(*track_data)); if (!minimp4_vector_init(&tr->smpl, 256)) return MP4E_STATUS_NO_MEMORY; minimp4_vector_init(&tr->vsps, 0); minimp4_vector_init(&tr->vpps, 0); minimp4_vector_init(&tr->pending_sample, 0); return ntr; } // static const unsigned char *next_dsi(const unsigned char *p, const unsigned char *end, int *bytes) // { // if (p < end + 2) // { // *bytes = p[0]*256 + p[1]; // return p + 2; // } else // return NULL; // } static int append_mem(minimp4_vector_t *v, const void *mem, int bytes) { int i; unsigned char size[2]; const unsigned char *p = v->data; for (i = 0; i + 2 < v->bytes;) { int cb = p[i]*256 + p[i + 1]; if (cb == bytes && !memcmp(p + i + 2, mem, cb)) return 1; i += 2 + cb; } size[0] = bytes >> 8; size[1] = bytes; return minimp4_vector_put(v, size, 2) && minimp4_vector_put(v, mem, bytes); } static int items_count(minimp4_vector_t *v) { int i, count = 0; const unsigned char *p = v->data; for (i = 0; i + 2 < v->bytes;) { int cb = p[i]*256 + p[i + 1]; count++; i += 2 + cb; } return count; } int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private); if (tr->vsps.bytes) return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; // only one DSI allowed return append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vvps, vps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes) { track_t* tr = ((track_t*)mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } static unsigned get_duration(const track_t *tr) { unsigned i, sum_duration = 0; const sample_t *s = (const sample_t *)tr->smpl.data; for (i = 0; i < tr->smpl.bytes/sizeof(sample_t); i++) { sum_duration += s[i].duration; } return sum_duration; } static int write_pending_data(MP4E_mux_t *mux, track_t *tr) { // if have pending sample && have at least one sample in the index if (tr->pending_sample.bytes > 0 && tr->smpl.bytes >= (int)sizeof(sample_t)) { // Complete pending sample sample_t *smpl_desc; unsigned char base[8], *p = base; assert(mux->sequential_mode_flag); // Write each sample to a separate atom assert(mux->sequential_mode_flag); // Separate atom needed for sequential_mode only WRITE_4(tr->pending_sample.bytes + 8); WRITE_4(BOX_mdat); ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token)); mux->write_pos += p - base; // Update sample descriptor with size and offset smpl_desc = ((sample_t*)minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1; smpl_desc->size = tr->pending_sample.bytes; smpl_desc->offset = (boxsize_t)mux->write_pos; // Write data ERR(mux->write_callback(mux->write_pos, tr->pending_sample.data, tr->pending_sample.bytes, mux->token)); mux->write_pos += tr->pending_sample.bytes; // reset buffer tr->pending_sample.bytes = 0; } return MP4E_STATUS_OK; } static int add_sample_descriptor(MP4E_mux_t *mux, track_t *tr, int data_bytes, int duration, int kind) { sample_t smp; smp.size = data_bytes; smp.offset = (boxsize_t)mux->write_pos; smp.duration = (duration ? duration : (int)tr->info.default_duration); smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS); return NULL != minimp4_vector_put(&tr->smpl, &smp, sizeof(sample_t)); } static int mp4e_flush_index(MP4E_mux_t *mux); /** * Write Movie Fragment: 'moof' box */ static int mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int duration, int kind #if MP4D_TFDT_SUPPORT , uint64_t timestamp #endif ) { unsigned char base[888], *p = base; unsigned char *stack_base[20]; // atoms nesting stack unsigned char **stack = stack_base; unsigned char *pdata_offset; unsigned flags; enum { default_sample_duration_present = 0x000008, default_sample_flags_present = 0x000020, }; track_t *tr = ((track_t*)mux->tracks.data) + track_num; ATOM(BOX_moof) ATOM_FULL(BOX_mfhd, 0) WRITE_4(mux->fragments_count); // start from 1 END_ATOM ATOM(BOX_traf) flags = 0; if (tr->info.track_media_kind == e_video) flags |= 0x20; // default-sample-flags-present else flags |= 0x08; // default-sample-duration-present flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008; ATOM_FULL(BOX_tfhd, flags) WRITE_4(track_num + 1); // track_ID if (tr->info.track_media_kind == e_video) { WRITE_4(0x1010000); // default_sample_flags } else { WRITE_4(duration); } END_ATOM #if MP4D_TFDT_SUPPORT ATOM_FULL(BOX_tfdt, 0x01000000) // version 1 WRITE_4(timestamp >> 32); // upper timestamp WRITE_4(timestamp & 0xffffffff); // lower timestamp END_ATOM #endif if (tr->info.track_media_kind == e_audio) { flags = 0; flags |= 0x001; // data-offset-present flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) WRITE_4(1); // sample_count pdata_offset = p; p += 4; // save ptr to data_offset WRITE_4(data_bytes);// sample_size END_ATOM } else if (kind == MP4E_SAMPLE_RANDOM_ACCESS) { flags = 0; flags |= 0x001; // data-offset-present flags |= 0x004; // first-sample-flags-present flags |= 0x100; // sample-duration-present flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) WRITE_4(1); // sample_count pdata_offset = p; p += 4; // save ptr to data_offset WRITE_4(0x2000000); // first_sample_flags WRITE_4(duration); // sample_duration WRITE_4(data_bytes);// sample_size END_ATOM } else { flags = 0; flags |= 0x001; // data-offset-present flags |= 0x100; // sample-duration-present flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) WRITE_4(1); // sample_count pdata_offset = p; p += 4; // save ptr to data_offset WRITE_4(duration); // sample_duration WRITE_4(data_bytes);// sample_size END_ATOM } END_ATOM END_ATOM WR4(pdata_offset, (p - base) + 8); ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token)); mux->write_pos += p - base; return MP4E_STATUS_OK; } static int mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size) { unsigned char base[8], *p = base; WRITE_4(size); WRITE_4(BOX_mdat); ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token)); mux->write_pos += p - base; return MP4E_STATUS_OK; } /** * Add new sample to specified track */ int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, int kind) { track_t *tr; if (!mux || !data) return MP4E_STATUS_BAD_ARGUMENTS; tr = ((track_t*)mux->tracks.data) + track_num; if (mux->enable_fragmentation) { #if MP4D_TFDT_SUPPORT // NOTE: assume a constant `duration` to calculate current timestamp uint64_t timestamp = (uint64_t)mux->fragments_count * duration; #endif if (!mux->fragments_count++) ERR(mp4e_flush_index(mux)); // write file headers before 1st sample // write MOOF + MDAT + sample data #if MP4D_TFDT_SUPPORT ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind, timestamp)); #else ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind)); #endif // write MDAT box for each sample ERR(mp4e_write_mdat_box(mux, data_bytes + 8)); ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token)); mux->write_pos += data_bytes; return MP4E_STATUS_OK; } if (kind != MP4E_SAMPLE_CONTINUATION) { if (mux->sequential_mode_flag) ERR(write_pending_data(mux, tr)); if (!add_sample_descriptor(mux, tr, data_bytes, duration, kind)) return MP4E_STATUS_NO_MEMORY; } else { if (!mux->sequential_mode_flag) { sample_t *smpl_desc; if ((size_t)tr->smpl.bytes < sizeof(sample_t)) return MP4E_STATUS_NO_MEMORY; // write continuation, but there are no samples in the index // Accumulate size of the continuation in the sample descriptor smpl_desc = (sample_t*)(tr->smpl.data + tr->smpl.bytes) - 1; smpl_desc->size += data_bytes; } } if (mux->sequential_mode_flag) { if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes)) return MP4E_STATUS_NO_MEMORY; } else { ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token)); mux->write_pos += data_bytes; } return MP4E_STATUS_OK; } /** * calculate size of length field of OD box */ static int od_size_of_size(int size) { int i, size_of_size = 1; for (i = size; i > 0x7F; i -= 0x7F) size_of_size++; return size_of_size; } /** * Add or remove MP4 file text comment according to Apple specs: * https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1 * http://atomicparsley.sourceforge.net/mpeg-4files.html * note that ISO did not specify comment format. */ int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment) { if (!mux || !comment) return MP4E_STATUS_BAD_ARGUMENTS; if (mux->text_comment) free(mux->text_comment); mux->text_comment = strdup(comment); if (!mux->text_comment) return MP4E_STATUS_NO_MEMORY; return MP4E_STATUS_OK; } /** * Write file index 'moov' box with all its boxes and indexes */ static int mp4e_flush_index(MP4E_mux_t *mux) { unsigned char *stack_base[20]; // atoms nesting stack unsigned char **stack = stack_base; unsigned char *base, *p; unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t); int i, err; // How much memory needed for indexes // Experimental data: // file with 1 track = 560 bytes // file with 2 tracks = 972 bytes // track size = 412 bytes; // file header size = 148 bytes #define FILE_HEADER_BYTES 256 #define TRACK_HEADER_BYTES 512 index_bytes = FILE_HEADER_BYTES; if (mux->text_comment) index_bytes += 128 + strlen(mux->text_comment); for (ntr = 0; ntr < ntracks; ntr++) { track_t *tr = ((track_t*)mux->tracks.data) + ntr; index_bytes += TRACK_HEADER_BYTES; // fixed amount (implementation-dependent) // may need extra 4 bytes for duration field + 4 bytes for worst-case random access box index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t); index_bytes += tr->vsps.bytes; index_bytes += tr->vpps.bytes; ERR(write_pending_data(mux, tr)); } base = (unsigned char*)malloc(index_bytes); if (!base) return MP4E_STATUS_NO_MEMORY; p = base; if (!mux->sequential_mode_flag) { // update size of mdat box. // One of 2 points, which requires random file access. // Second is optional duration update at beginning of file in fragmentation mode. // This can be avoided using "till eof" size code, but in this case indexes must be // written before the mdat.... int64_t size = mux->write_pos - sizeof(box_ftyp); const int64_t size_limit = (int64_t)(uint64_t)0xfffffffe; if (size > size_limit) { WRITE_4(1); WRITE_4(BOX_mdat); WRITE_4((size >> 32) & 0xffffffff); WRITE_4(size & 0xffffffff); } else { WRITE_4(8); WRITE_4(BOX_free); WRITE_4(size - 8); WRITE_4(BOX_mdat); } ERR(mux->write_callback(sizeof(box_ftyp), base, p - base, mux->token)); p = base; } // Write index atoms; order taken from Table 1 of [1] #define MOOV_TIMESCALE 1000 ATOM(BOX_moov); ATOM_FULL(BOX_mvhd, 0); WRITE_4(0); // creation_time WRITE_4(0); // modification_time if (ntracks) { track_t *tr = ((track_t*)mux->tracks.data) + 0; // take 1st track unsigned duration = get_duration(tr); duration = (unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale); WRITE_4(MOOV_TIMESCALE); // duration WRITE_4(duration); // duration } WRITE_4(0x00010000); // rate WRITE_2(0x0100); // volume WRITE_2(0); // reserved WRITE_4(0); // reserved WRITE_4(0); // reserved // matrix[9] WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000); // pre_defined[6] WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0); //next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be //added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be //larger than the largest track-ID in use. WRITE_4(ntracks + 1); END_ATOM; for (ntr = 0; ntr < ntracks; ntr++) { track_t *tr = ((track_t*)mux->tracks.data) + ntr; unsigned duration = get_duration(tr); int samples_count = tr->smpl.bytes / sizeof(sample_t); const sample_t *sample = (const sample_t *)tr->smpl.data; unsigned handler_type; const char *handler_ascii = NULL; if (mux->enable_fragmentation) samples_count = 0; else if (samples_count <= 0) continue; // skip empty track switch (tr->info.track_media_kind) { case e_audio: handler_type = MP4E_HANDLER_TYPE_SOUN; handler_ascii = "SoundHandler"; break; case e_video: handler_type = MP4E_HANDLER_TYPE_VIDE; handler_ascii = "VideoHandler"; break; case e_private: handler_type = MP4E_HANDLER_TYPE_GESM; break; default: return MP4E_STATUS_BAD_ARGUMENTS; } ATOM(BOX_trak); ATOM_FULL(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview WRITE_4(0); // creation_time WRITE_4(0); // modification_time WRITE_4(ntr + 1); // track_ID WRITE_4(0); // reserved WRITE_4((unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale)); WRITE_4(0); WRITE_4(0); // reserved[2] WRITE_2(0); // layer WRITE_2(0); // alternate_group WRITE_2(0x0100); // volume {if track_is_audio 0x0100 else 0}; WRITE_2(0); // reserved // matrix[9] WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000); if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) { WRITE_4(0); // width WRITE_4(0); // height } else { WRITE_4(tr->info.u.v.width*0x10000); // width WRITE_4(tr->info.u.v.height*0x10000); // height } END_ATOM; ATOM(BOX_mdia); ATOM_FULL(BOX_mdhd, 0); WRITE_4(0); // creation_time WRITE_4(0); // modification_time WRITE_4(tr->info.time_scale); WRITE_4(duration); // duration { int lang_code = ((tr->info.language[0] & 31) << 10) | ((tr->info.language[1] & 31) << 5) | (tr->info.language[2] & 31); WRITE_2(lang_code); // language } WRITE_2(0); // pre_defined END_ATOM; ATOM_FULL(BOX_hdlr, 0); WRITE_4(0); // pre_defined WRITE_4(handler_type); // handler_type WRITE_4(0); WRITE_4(0); WRITE_4(0); // reserved[3] // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes). // set mdia hdlr name field to what quicktime uses. // Sony smartphone may fail to decode short files w/o handler name if (handler_ascii) { for (i = 0; i < (int)strlen(handler_ascii) + 1; i++) { WRITE_1(handler_ascii[i]); } } else { WRITE_4(0); } END_ATOM; ATOM(BOX_minf); if (tr->info.track_media_kind == e_audio) { // Sound Media Header Box ATOM_FULL(BOX_smhd, 0); WRITE_2(0); // balance WRITE_2(0); // reserved END_ATOM; } if (tr->info.track_media_kind == e_video) { // mandatory Video Media Header Box ATOM_FULL(BOX_vmhd, 1); WRITE_2(0); // graphicsmode WRITE_2(0); WRITE_2(0); WRITE_2(0); // opcolor[3] END_ATOM; } ATOM(BOX_dinf); ATOM_FULL(BOX_dref, 0); WRITE_4(1); // entry_count // If the flag is set indicating that the data is in the same file as this box, then no string (not even an empty one) // shall be supplied in the entry field. // ASP the correct way to avoid supply the string, is to use flag 1 // otherwise ISO reference demux crashes ATOM_FULL(BOX_url, 1); END_ATOM; END_ATOM; END_ATOM; ATOM(BOX_stbl); ATOM_FULL(BOX_stsd, 0); WRITE_4(1); // entry_count; if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) { // AudioSampleEntry() assume MP4E_HANDLER_TYPE_SOUN if (tr->info.track_media_kind == e_audio) { ATOM(BOX_mp4a); } else { ATOM(BOX_mp4s); } // SampleEntry WRITE_4(0); WRITE_2(0); // reserved[6] WRITE_2(1); // data_reference_index; - this is a tag for descriptor below if (tr->info.track_media_kind == e_audio) { // AudioSampleEntry WRITE_4(0); WRITE_4(0); // reserved[2] WRITE_2(tr->info.u.a.channelcount); // channelcount WRITE_2(16); // samplesize WRITE_4(0); // pre_defined+reserved WRITE_4((tr->info.time_scale << 16)); // samplerate == = {timescale of media}<<16; } ATOM_FULL(BOX_esds, 0); if (tr->vsps.bytes > 0) { int dsi_bytes = tr->vsps.bytes - 2; // - two bytes size field int dsi_size_size = od_size_of_size(dsi_bytes); int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1 + 1 + 3 + 4 + 4); int dcd_size_size = od_size_of_size(dcd_bytes); int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3; #define WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WRITE_1(0x00ff); } while (size > 0x7F); WRITE_1(size) WRITE_1(3); // OD_ESD WRITE_OD_LEN(esd_bytes); WRITE_2(0); // ES_ID(2) // TODO - what is this? WRITE_1(0); // flags(1) WRITE_1(4); // OD_DCD WRITE_OD_LEN(dcd_bytes); if (tr->info.track_media_kind == e_audio) { WRITE_1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD WRITE_1(5 << 2); // stream_type == AudioStream } else { // http://xhelmboyx.tripod.com/formats/mp4-layout.txt WRITE_1(208); // 208 = private video WRITE_1(32 << 2); // stream_type == user private } WRITE_3(tr->info.u.a.channelcount * 6144/8); // bufferSizeDB in bytes, constant as in reference decoder WRITE_4(0); // maxBitrate TODO WRITE_4(0); // avg_bitrate_bps TODO WRITE_1(5); // OD_DSI WRITE_OD_LEN(dsi_bytes); for (i = 0; i < dsi_bytes; i++) { WRITE_1(tr->vsps.data[2 + i]); } } END_ATOM; END_ATOM; } if (tr->info.track_media_kind == e_video && (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication || MP4_OBJECT_TYPE_HEVC == tr->info.object_type_indication)) { int numOfSequenceParameterSets = items_count(&tr->vsps); int numOfPictureParameterSets = items_count(&tr->vpps); if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) { ATOM(BOX_avc1); } else { ATOM(BOX_hvc1); } // VisualSampleEntry 8.16.2 // extends SampleEntry WRITE_2(0); // reserved WRITE_2(0); // reserved WRITE_2(0); // reserved WRITE_2(1); // data_reference_index WRITE_2(0); // pre_defined WRITE_2(0); // reserved WRITE_4(0); // pre_defined WRITE_4(0); // pre_defined WRITE_4(0); // pre_defined WRITE_2(tr->info.u.v.width); WRITE_2(tr->info.u.v.height); WRITE_4(0x00480000); // horizresolution = 72 dpi WRITE_4(0x00480000); // vertresolution = 72 dpi WRITE_4(0); // reserved WRITE_2(1); // frame_count for (i = 0; i < 32; i++) { WRITE_1(0); // compressorname } WRITE_2(24); // depth WRITE_2(-1); // pre_defined if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) { ATOM(BOX_avcC); // AVCDecoderConfigurationRecord 5.2.4.1.1 WRITE_1(1); // configurationVersion WRITE_1(tr->vsps.data[2 + 1]); WRITE_1(tr->vsps.data[2 + 2]); WRITE_1(tr->vsps.data[2 + 3]); WRITE_1(255); // 0xfc + NALU_len - 1 WRITE_1(0xe0 | numOfSequenceParameterSets); for (i = 0; i < tr->vsps.bytes; i++) { WRITE_1(tr->vsps.data[i]); } WRITE_1(numOfPictureParameterSets); for (i = 0; i < tr->vpps.bytes; i++) { WRITE_1(tr->vpps.data[i]); } } else { int numOfVPS = items_count(&tr->vpps); ATOM(BOX_hvcC); // TODO: read actual params from stream WRITE_1(1); // configurationVersion WRITE_1(1); // Profile Space (2), Tier (1), Profile (5) WRITE_4(0x60000000); // Profile Compatibility WRITE_2(0); // progressive, interlaced, non packed constraint, frame only constraint flags WRITE_4(0); // constraint indicator flags WRITE_1(0); // level_idc WRITE_2(0xf000); // Min Spatial Segmentation WRITE_1(0xfc); // Parallelism Type WRITE_1(0xfc); // Chroma Format WRITE_1(0xf8); // Luma Depth WRITE_1(0xf8); // Chroma Depth WRITE_2(0); // Avg Frame Rate WRITE_1(3); // ConstantFrameRate (2), NumTemporalLayers (3), TemporalIdNested (1), LengthSizeMinusOne (2) WRITE_1(3); // Num Of Arrays WRITE_1((1 << 7) | (HEVC_NAL_VPS & 0x3f)); // Array Completeness + NAL Unit Type WRITE_2(numOfVPS); for (i = 0; i < tr->vvps.bytes; i++) { WRITE_1(tr->vvps.data[i]); } WRITE_1((1 << 7) | (HEVC_NAL_SPS & 0x3f)); WRITE_2(numOfSequenceParameterSets); for (i = 0; i < tr->vsps.bytes; i++) { WRITE_1(tr->vsps.data[i]); } WRITE_1((1 << 7) | (HEVC_NAL_PPS & 0x3f)); WRITE_2(numOfPictureParameterSets); for (i = 0; i < tr->vpps.bytes; i++) { WRITE_1(tr->vpps.data[i]); } } END_ATOM; END_ATOM; } END_ATOM; /************************************************************************/ /* indexes */ /************************************************************************/ // Time to Sample Box ATOM_FULL(BOX_stts, 0); { unsigned char *pentry_count = p; int cnt = 1, entry_count = 0; WRITE_4(0); for (i = 0; i < samples_count; i++, cnt++) { if (i == (samples_count - 1) || sample[i].duration != sample[i + 1].duration) { WRITE_4(cnt); WRITE_4(sample[i].duration); cnt = 0; entry_count++; } } WR4(pentry_count, entry_count); } END_ATOM; // Sample To Chunk Box ATOM_FULL(BOX_stsc, 0); if (mux->enable_fragmentation) { WRITE_4(0); // entry_count } else { WRITE_4(1); // entry_count WRITE_4(1); // first_chunk; WRITE_4(1); // samples_per_chunk; WRITE_4(1); // sample_description_index; } END_ATOM; // Sample Size Box ATOM_FULL(BOX_stsz, 0); WRITE_4(0); // sample_size If this field is set to 0, then the samples have different sizes, and those sizes // are stored in the sample size table. WRITE_4(samples_count); // sample_count; for (i = 0; i < samples_count; i++) { WRITE_4(sample[i].size); } END_ATOM; // Chunk Offset Box int is_64_bit = 0; if (samples_count && sample[samples_count - 1].offset > 0xffffffff) is_64_bit = 1; if (!is_64_bit) { ATOM_FULL(BOX_stco, 0); WRITE_4(samples_count); for (i = 0; i < samples_count; i++) { WRITE_4(sample[i].offset); } } else { ATOM_FULL(BOX_co64, 0); WRITE_4(samples_count); for (i = 0; i < samples_count; i++) { WRITE_4((sample[i].offset >> 32) & 0xffffffff); WRITE_4(sample[i].offset & 0xffffffff); } } END_ATOM; // Sync Sample Box { int ra_count = 0; for (i = 0; i < samples_count; i++) { ra_count += !!sample[i].flag_random_access; } if (ra_count != samples_count) { // If the sync sample box is not present, every sample is a random access point. ATOM_FULL(BOX_stss, 0); WRITE_4(ra_count); for (i = 0; i < samples_count; i++) { if (sample[i].flag_random_access) { WRITE_4(i + 1); } } END_ATOM; } } END_ATOM; END_ATOM; END_ATOM; END_ATOM; } // tracks loop if (mux->text_comment) { ATOM(BOX_udta); ATOM_FULL(BOX_meta, 0); ATOM_FULL(BOX_hdlr, 0); WRITE_4(0); // pre_defined #define MP4E_HANDLER_TYPE_MDIR 0x6d646972 WRITE_4(MP4E_HANDLER_TYPE_MDIR); // handler_type WRITE_4(0); WRITE_4(0); WRITE_4(0); // reserved[3] WRITE_4(0); // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes). END_ATOM; ATOM(BOX_ilst); ATOM(BOX_ccmt); ATOM(BOX_data); WRITE_4(1); // type WRITE_4(0); // lang for (i = 0; i < (int)strlen(mux->text_comment) + 1; i++) { WRITE_1(mux->text_comment[i]); } END_ATOM; END_ATOM; END_ATOM; END_ATOM; END_ATOM; } if (mux->enable_fragmentation) { track_t *tr = ((track_t*)mux->tracks.data) + 0; uint32_t movie_duration = get_duration(tr); ATOM(BOX_mvex); ATOM_FULL(BOX_mehd, 0); WRITE_4(movie_duration); // duration END_ATOM; for (ntr = 0; ntr < ntracks; ntr++) { ATOM_FULL(BOX_trex, 0); WRITE_4(ntr + 1); // track_ID WRITE_4(1); // default_sample_description_index WRITE_4(0); // default_sample_duration WRITE_4(0); // default_sample_size WRITE_4(0); // default_sample_flags END_ATOM; } END_ATOM; } END_ATOM; // moov atom assert((unsigned)(p - base) <= index_bytes); err = mux->write_callback(mux->write_pos, base, p - base, mux->token); mux->write_pos += p - base; free(base); return err; } int MP4E_close(MP4E_mux_t *mux) { int err = MP4E_STATUS_OK; unsigned ntr, ntracks; if (!mux) return MP4E_STATUS_BAD_ARGUMENTS; if (!mux->enable_fragmentation) err = mp4e_flush_index(mux); if (mux->text_comment) free(mux->text_comment); ntracks = mux->tracks.bytes / sizeof(track_t); for (ntr = 0; ntr < ntracks; ntr++) { track_t *tr = ((track_t*)mux->tracks.data) + ntr; minimp4_vector_reset(&tr->vsps); minimp4_vector_reset(&tr->vpps); minimp4_vector_reset(&tr->smpl); minimp4_vector_reset(&tr->pending_sample); } minimp4_vector_reset(&mux->tracks); free(mux); return err; } typedef uint32_t bs_item_t; #define BS_BITS 32 typedef struct { // Look-ahead bit cache: MSB aligned, 17 bits guaranteed, zero stuffing unsigned int cache; // Bit counter = 16 - (number of bits in wCache) // cache refilled when cache_free_bits >= 0 int cache_free_bits; // Current read position const uint16_t *buf; // original data buffer const uint16_t *origin; // original data buffer length, bytes unsigned origin_bytes; } bit_reader_t; #define LOAD_SHORT(x) ((uint16_t)(x << 8) | (x >> 8)) static unsigned int show_bits(bit_reader_t *bs, int n) { unsigned int retval; assert(n > 0 && n <= 16); retval = (unsigned int)(bs->cache >> (32 - n)); return retval; } static void flush_bits(bit_reader_t *bs, int n) { assert(n >= 0 && n <= 16); bs->cache <<= n; bs->cache_free_bits += n; if (bs->cache_free_bits >= 0) { bs->cache |= ((uint32_t)LOAD_SHORT(*bs->buf)) << bs->cache_free_bits; bs->buf++; bs->cache_free_bits -= 16; } } static unsigned int get_bits(bit_reader_t *bs, int n) { unsigned int retval = show_bits(bs, n); flush_bits(bs, n); return retval; } static void set_pos_bits(bit_reader_t *bs, unsigned pos_bits) { assert((int)pos_bits >= 0); bs->buf = bs->origin + pos_bits/16; bs->cache = 0; bs->cache_free_bits = 16; flush_bits(bs, 0); flush_bits(bs, pos_bits & 15); } static unsigned get_pos_bits(const bit_reader_t *bs) { // Current bitbuffer position = // position of next wobits in the internal buffer // minus bs, available in bit cache wobits unsigned pos_bits = (unsigned)(bs->buf - bs->origin)*16; pos_bits -= 16 - bs->cache_free_bits; assert((int)pos_bits >= 0); return pos_bits; } static int remaining_bits(const bit_reader_t *bs) { return bs->origin_bytes * 8 - get_pos_bits(bs); } static void init_bits(bit_reader_t *bs, const void *data, unsigned data_bytes) { bs->origin = (const uint16_t *)data; bs->origin_bytes = data_bytes; set_pos_bits(bs, 0); } #define GetBits(n) get_bits(bs, n) /** * Unsigned Golomb code */ static int ue_bits(bit_reader_t *bs) { int clz; int val; for (clz = 0; !get_bits(bs, 1); clz++) {} //get_bits(bs, clz + 1); val = (1 << clz) - 1 + (clz ? get_bits(bs, clz) : 0); return val; } #if MINIMP4_TRANSCODE_SPS_ID /** * Output bitstream */ typedef struct { int shift; // bit position in the cache uint32_t cache; // bit cache bs_item_t *buf; // current position bs_item_t *origin; // initial position } bs_t; #define SWAP32(x) (uint32_t)((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x & 0xFF) << 24)) static void h264e_bs_put_bits(bs_t *bs, unsigned n, unsigned val) { assert(!(val >> n)); bs->shift -= n; assert((unsigned)n <= 32); if (bs->shift < 0) { assert(-bs->shift < 32); bs->cache |= val >> -bs->shift; *bs->buf++ = SWAP32(bs->cache); bs->shift = 32 + bs->shift; bs->cache = 0; } bs->cache |= val << bs->shift; } static void h264e_bs_flush(bs_t *bs) { *bs->buf = SWAP32(bs->cache); } static unsigned h264e_bs_get_pos_bits(const bs_t *bs) { unsigned pos_bits = (unsigned)((bs->buf - bs->origin)*BS_BITS); pos_bits += BS_BITS - bs->shift; assert((int)pos_bits >= 0); return pos_bits; } static unsigned h264e_bs_byte_align(bs_t *bs) { int pos = h264e_bs_get_pos_bits(bs); h264e_bs_put_bits(bs, -pos & 7, 0); return pos + (-pos & 7); } /** * Golomb code * 0 => 1 * 1 => 01 0 * 2 => 01 1 * 3 => 001 00 * 4 => 001 01 * * [0] => 1 * [1..2] => 01x * [3..6] => 001xx * [7..14] => 0001xxx * */ static void h264e_bs_put_golomb(bs_t *bs, unsigned val) { int size = 0; unsigned t = val + 1; do { size++; } while (t >>= 1); h264e_bs_put_bits(bs, 2*size - 1, val + 1); } static void h264e_bs_init_bits(bs_t *bs, void *data) { bs->origin = (bs_item_t*)data; bs->buf = bs->origin; bs->shift = BS_BITS; bs->cache = 0; } static int find_mem_cache(void *cache[], int cache_bytes[], int cache_size, void *mem, int bytes) { int i; if (!bytes) return -1; for (i = 0; i < cache_size; i++) { if (cache_bytes[i] == bytes && !memcmp(mem, cache[i], bytes)) return i; // found } for (i = 0; i < cache_size; i++) { if (!cache_bytes[i]) { cache[i] = malloc(bytes); if (cache[i]) { memcpy(cache[i], mem, bytes); cache_bytes[i] = bytes; } return i; // put in } } return -1; // no room } /** * 7.4.1.1. "Encapsulation of an SODB within an RBSP" */ static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int h264_data_bytes) { int i = 0, j = 0, zero_cnt = 0; for (j = 0; j < h264_data_bytes; j++) { if (zero_cnt == 2 && src[j] <= 3) { if (src[j] == 3) { if (j == h264_data_bytes - 1) { // cabac_zero_word: no action } else if (src[j + 1] <= 3) { j++; zero_cnt = 0; } else { // TODO: assume end-of-nal //return 0; } } else return 0; } dst[i++] = src[j]; if (src[j]) zero_cnt = 0; else zero_cnt++; } //while (--j > i) src[j] = 0; return i; } /** * Put NAL escape codes to the output bitstream */ static int nal_put_esc(uint8_t *d, const uint8_t *s, int n) { int i, j = 4, cntz = 0; d[0] = d[1] = d[2] = 0; d[3] = 1; // start code for (i = 0; i < n; i++) { uint8_t byte = *s++; if (cntz == 2 && byte <= 3) { d[j++] = 3; cntz = 0; } if (byte) cntz = 0; else cntz++; d[j++] = byte; } return j; } static void copy_bits(bit_reader_t *bs, bs_t *bd) { unsigned cb, bits; int bit_count = remaining_bits(bs); while (bit_count > 7) { cb = MINIMP4_MIN(bit_count - 7, 8); bits = GetBits(cb); h264e_bs_put_bits(bd, cb, bits); bit_count -= cb; } // cut extra zeros after stop-bit bits = GetBits(bit_count); for (; bit_count && ~bits & 1; bit_count--) { bits >>= 1; } if (bit_count) { h264e_bs_put_bits(bd, bit_count, bits); } } static int change_sps_id(bit_reader_t *bs, bs_t *bd, int new_id, int *old_id) { unsigned bits, sps_id, i, bytes; for (i = 0; i < 3; i++) { bits = GetBits(8); h264e_bs_put_bits(bd, 8, bits); } sps_id = ue_bits(bs); // max = 31 *old_id = sps_id; sps_id = new_id; assert(sps_id <= 31); h264e_bs_put_golomb(bd, sps_id); copy_bits(bs, bd); bytes = h264e_bs_byte_align(bd) / 8; h264e_bs_flush(bd); return bytes; } static int patch_pps(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd, int new_pps_id, int *old_id) { int bytes; unsigned pps_id = ue_bits(bs); // max = 255 unsigned sps_id = ue_bits(bs); // max = 31 *old_id = pps_id; sps_id = h->map_sps[sps_id]; pps_id = new_pps_id; assert(sps_id <= 31); assert(pps_id <= 255); h264e_bs_put_golomb(bd, pps_id); h264e_bs_put_golomb(bd, sps_id); copy_bits(bs, bd); bytes = h264e_bs_byte_align(bd) / 8; h264e_bs_flush(bd); return bytes; } static void patch_slice_header(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd) { unsigned first_mb_in_slice = ue_bits(bs); unsigned slice_type = ue_bits(bs); unsigned pps_id = ue_bits(bs); pps_id = h->map_pps[pps_id]; assert(pps_id <= 255); h264e_bs_put_golomb(bd, first_mb_in_slice); h264e_bs_put_golomb(bd, slice_type); h264e_bs_put_golomb(bd, pps_id); copy_bits(bs, bd); } static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, int nalu_bytes, unsigned char *dst) { int old_id; bit_reader_t bst[1]; bs_t bdt[1]; bit_reader_t bs[1]; bs_t bd[1]; int payload_type = src[0] & 31; *dst = *src; h264e_bs_init_bits(bd, dst + 1); init_bits(bs, src + 1, nalu_bytes - 1); h264e_bs_init_bits(bdt, dst + 1); init_bits(bst, src + 1, nalu_bytes - 1); switch(payload_type) { case 7: { int cb = change_sps_id(bst, bdt, 0, &old_id); int id = find_mem_cache(h->sps_cache, h->sps_bytes, MINIMP4_MAX_SPS, dst + 1, cb); if (id == -1) return 0; h->map_sps[old_id] = id; change_sps_id(bs, bd, id, &old_id); } break; case 8: { int cb = patch_pps(h, bst, bdt, 0, &old_id); int id = find_mem_cache(h->pps_cache, h->pps_bytes, MINIMP4_MAX_PPS, dst + 1, cb); if (id == -1) return 0; h->map_pps[old_id] = id; patch_pps(h, bs, bd, id, &old_id); } break; case 1: case 2: case 5: patch_slice_header(h, bs, bd); break; default: memcpy(dst, src, nalu_bytes); return nalu_bytes; } nalu_bytes = 1 + h264e_bs_byte_align(bd) / 8; h264e_bs_flush(bd); return nalu_bytes; } #endif /** * Set pointer just after start code (00 .. 00 01), or to EOF if not found: * * NZ NZ ... NZ 00 00 00 00 01 xx xx ... xx (EOF) * ^ ^ * non-zero head.............. here ....... or here if no start code found * */ static const uint8_t *find_start_code(const uint8_t *h264_data, int h264_data_bytes, int *zcount) { const uint8_t *eof = h264_data + h264_data_bytes; const uint8_t *p = h264_data; do { int zero_cnt = 1; const uint8_t* found = (uint8_t*)memchr(p, 0, eof - p); p = found ? found : eof; while (p + zero_cnt < eof && !p[zero_cnt]) zero_cnt++; if (zero_cnt >= 2 && p[zero_cnt] == 1) { *zcount = zero_cnt + 1; return p + zero_cnt + 1; } p += zero_cnt; } while (p < eof); *zcount = 0; return eof; } /** * Locate NAL unit in given buffer, and calculate it's length */ static const uint8_t *find_nal_unit(const uint8_t *h264_data, int h264_data_bytes, int *pnal_unit_bytes) { const uint8_t *eof = h264_data + h264_data_bytes; int zcount; const uint8_t *start = find_start_code(h264_data, h264_data_bytes, &zcount); const uint8_t *stop = start; if (start) { stop = find_start_code(start, (int)(eof - start), &zcount); while (stop > start && !stop[-1]) { stop--; } } *pnal_unit_bytes = (int)(stop - start - zcount); return start; } int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc) { MP4E_track_t tr; tr.track_media_kind = e_video; tr.language[0] = 'u'; tr.language[1] = 'n'; tr.language[2] = 'd'; tr.language[3] = 0; tr.object_type_indication = is_hevc ? MP4_OBJECT_TYPE_HEVC : MP4_OBJECT_TYPE_AVC; tr.time_scale = 90000; tr.default_duration = 0; tr.u.v.width = width; tr.u.v.height = height; h->mux_track_id = MP4E_add_track(mux, &tr); h->mux = mux; h->is_hevc = is_hevc; h->need_vps = is_hevc; h->need_sps = 1; h->need_pps = 1; h->need_idr = 1; #if MINIMP4_TRANSCODE_SPS_ID memset(&h->sps_patcher, 0, sizeof(h264_sps_id_patcher_t)); #endif return MP4E_STATUS_OK; } void mp4_h26x_write_close(mp4_h26x_writer_t *h) { #if MINIMP4_TRANSCODE_SPS_ID h264_sps_id_patcher_t *p = &h->sps_patcher; int i; for (i = 0; i < MINIMP4_MAX_SPS; i++) { if (p->sps_cache[i]) free(p->sps_cache[i]); } for (i = 0; i < MINIMP4_MAX_PPS; i++) { if (p->pps_cache[i]) free(p->pps_cache[i]); } #endif memset(h, 0, sizeof(*h)); } static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int sizeof_nal, unsigned timeStamp90kHz_next) { int payload_type = (nal[0] >> 1) & 0x3f; int is_intra = payload_type >= HEVC_NAL_BLA_W_LP && payload_type <= HEVC_NAL_CRA_NUT; int err = MP4E_STATUS_OK; //printf("payload_type=%d, intra=%d\n", payload_type, is_intra); if (is_intra && !h->need_sps && !h->need_pps && !h->need_vps) h->need_idr = 0; switch (payload_type) { case HEVC_NAL_VPS: MP4E_set_vps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_vps = 0; break; case HEVC_NAL_SPS: MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_sps = 0; break; case HEVC_NAL_PPS: MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_pps = 0; break; default: if (h->need_vps || h->need_sps || h->need_pps || h->need_idr) return MP4E_STATUS_BAD_ARGUMENTS; { unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal); if (!tmp) return MP4E_STATUS_NO_MEMORY; int sample_kind = MP4E_SAMPLE_DEFAULT; tmp[0] = (unsigned char)(sizeof_nal >> 24); tmp[1] = (unsigned char)(sizeof_nal >> 16); tmp[2] = (unsigned char)(sizeof_nal >> 8); tmp[3] = (unsigned char)(sizeof_nal); memcpy(tmp + 4, nal, sizeof_nal); if (is_intra) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } break; } return err; } int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next) { const unsigned char *eof = nal + length; int payload_type, sizeof_nal, err = MP4E_STATUS_OK; for (;;nal++) { #if MINIMP4_TRANSCODE_SPS_ID unsigned char *nal1, *nal2; #endif nal = find_nal_unit(nal, (int)(eof - nal), &sizeof_nal); if (!sizeof_nal) break; if (h->is_hevc) { ERR(mp4_h265_write_nal(h, nal, sizeof_nal, timeStamp90kHz_next)); continue; } payload_type = nal[0] & 31; if (9 == payload_type) continue; // access unit delimiter, nothing to be done #if MINIMP4_TRANSCODE_SPS_ID // Transcode SPS, PPS and slice headers, reassigning ID's for SPS and PPS: // - assign unique ID's to different SPS and PPS // - assign same ID's to equal (except ID) SPS and PPS // - save all different SPS and PPS nal1 = (unsigned char *)malloc(sizeof_nal*17/16 + 32); if (!nal1) return MP4E_STATUS_NO_MEMORY; nal2 = (unsigned char *)malloc(sizeof_nal*17/16 + 32); if (!nal2) { free(nal1); return MP4E_STATUS_NO_MEMORY; } sizeof_nal = remove_nal_escapes(nal2, nal, sizeof_nal); if (!sizeof_nal) { exit_with_free: free(nal1); free(nal2); return MP4E_STATUS_BAD_ARGUMENTS; } sizeof_nal = transcode_nalu(&h->sps_patcher, nal2, sizeof_nal, nal1); sizeof_nal = nal_put_esc(nal2, nal1, sizeof_nal); switch (payload_type) { case 7: MP4E_set_sps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_sps = 0; break; case 8: if (h->need_sps) goto exit_with_free; MP4E_set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_pps = 0; break; case 5: if (h->need_sps) goto exit_with_free; h->need_idr = 0; // flow through /* FALLTHROUGH */ default: if (h->need_sps) goto exit_with_free; if (!h->need_pps && !h->need_idr) { bit_reader_t bs[1]; init_bits(bs, nal + 1, sizeof_nal - 4 - 1); unsigned first_mb_in_slice = ue_bits(bs); //unsigned slice_type = ue_bits(bs); int sample_kind = MP4E_SAMPLE_DEFAULT; nal2[0] = (unsigned char)((sizeof_nal - 4) >> 24); nal2[1] = (unsigned char)((sizeof_nal - 4) >> 16); nal2[2] = (unsigned char)((sizeof_nal - 4) >> 8); nal2[3] = (unsigned char)((sizeof_nal - 4)); if (first_mb_in_slice) sample_kind = MP4E_SAMPLE_CONTINUATION; else if (payload_type == 5) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; err = MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind); } break; } free(nal1); free(nal2); #else // No SPS/PPS transcoding // This branch assumes that encoder use correct SPS/PPS ID's switch (payload_type) { case 7: MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_sps = 0; break; case 8: MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_pps = 0; break; case 5: if (h->need_sps) return MP4E_STATUS_BAD_ARGUMENTS; h->need_idr = 0; // flow through default: if (h->need_sps) return MP4E_STATUS_BAD_ARGUMENTS; if (!h->need_pps && !h->need_idr) { bit_reader_t bs[1]; unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal); if (!tmp) return MP4E_STATUS_NO_MEMORY; init_bits(bs, nal + 1, sizeof_nal - 1); unsigned first_mb_in_slice = ue_bits(bs); int sample_kind = MP4E_SAMPLE_DEFAULT; tmp[0] = (unsigned char)(sizeof_nal >> 24); tmp[1] = (unsigned char)(sizeof_nal >> 16); tmp[2] = (unsigned char)(sizeof_nal >> 8); tmp[3] = (unsigned char)(sizeof_nal); memcpy(tmp + 4, nal, sizeof_nal); if (first_mb_in_slice) sample_kind = MP4E_SAMPLE_CONTINUATION; else if (payload_type == 5) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } break; } #endif if (err) break; } return err; } #if MP4D_TRACE_SUPPORTED # define TRACE(x) printf x #else # define TRACE(x) #endif #define NELEM(x) (sizeof(x) / sizeof((x)[0])) static int minimp4_fgets(MP4D_demux_t *mp4) { uint8_t c; if (mp4->read_callback(mp4->read_pos, &c, 1, mp4->token)) return -1; mp4->read_pos++; return c; } /** * Read given number of bytes from input stream * Used to read box headers */ static unsigned minimp4_read(MP4D_demux_t *mp4, int nb, int *eof_flag) { uint32_t v = 0; int last_byte; switch (nb) { case 4: v = (v << 8) | minimp4_fgets(mp4); /* FALLTHROUGH */ case 3: v = (v << 8) | minimp4_fgets(mp4); /* FALLTHROUGH */ case 2: v = (v << 8) | minimp4_fgets(mp4); /* FALLTHROUGH */ default: case 1: v = (v << 8) | (last_byte = minimp4_fgets(mp4)); } if (last_byte < 0) { *eof_flag = 1; } return v; } /** * Read given number of bytes, but no more than *payload_bytes specifies... * Used to read box payload */ static uint32_t read_payload(MP4D_demux_t *mp4, unsigned nb, boxsize_t *payload_bytes, int *eof_flag) { if (*payload_bytes < nb) { *eof_flag = 1; nb = (int)*payload_bytes; } *payload_bytes -= nb; return minimp4_read(mp4, nb, eof_flag); } /** * Skips given number of bytes. * Avoid math operations with fpos_t */ static void my_fseek(MP4D_demux_t *mp4, boxsize_t pos, int *eof_flag) { mp4->read_pos += pos; if (mp4->read_pos >= mp4->read_size) *eof_flag = 1; } #define READ(n) read_payload(mp4, n, &payload_bytes, &eof_flag) #define SKIP(n) { boxsize_t t = MINIMP4_MIN(payload_bytes, n); my_fseek(mp4, t, &eof_flag); payload_bytes -= t; } #define MALLOC(t, p, size) p = (t)malloc(size); if (!(p)) { ERROR("out of memory"); } /* * On error: release resources. */ #define RETURN_ERROR(mess) { \ TRACE(("\nMP4 ERROR: " mess)); \ MP4D_close(mp4); \ return 0; \ } /* * Any errors, occurred on top-level hierarchy is passed to exit check: 'if (!mp4->track_count) ... ' */ #define ERROR(mess) \ if (!depth) \ break; \ else \ RETURN_ERROR(mess); typedef enum { BOX_ATOM, BOX_OD } boxtype_t; int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size) { // box stack size int depth = 0; struct { // remaining bytes for box in the stack boxsize_t bytes; // kind of box children's: OD chunks handled in the same manner as name chunks boxtype_t format; } stack[MAX_CHUNKS_DEPTH]; #if MP4D_TRACE_SUPPORTED // path of current element: List0/List1/... etc uint32_t box_path[MAX_CHUNKS_DEPTH]; #endif int eof_flag = 0; unsigned i; MP4D_track_t *tr = NULL; if (!mp4 || !read_callback) { TRACE(("\nERROR: invlaid arguments!")); return 0; } memset(mp4, 0, sizeof(MP4D_demux_t)); mp4->read_callback = read_callback; mp4->token = token; mp4->read_size = file_size; stack[0].format = BOX_ATOM; // start with atom box stack[0].bytes = 0; // never accessed do { // List of boxes, derived from 'FullBox' // ~~~~~~~~~~~~~~~~~~~~~ // need read version field and check version for these boxes static const struct { uint32_t name; unsigned max_version; unsigned use_track_flag; } g_fullbox[] = { #if MP4D_INFO_SUPPORTED {BOX_mdhd, 1, 1}, {BOX_mvhd, 1, 0}, {BOX_hdlr, 0, 0}, {BOX_meta, 0, 0}, // Android can produce meta box without 'FullBox' field, comment this line to simulate the bug #endif #if MP4D_TRACE_TIMESTAMPS {BOX_stts, 0, 0}, {BOX_ctts, 0, 0}, #endif {BOX_stz2, 0, 1}, {BOX_stsz, 0, 1}, {BOX_stsc, 0, 1}, {BOX_stco, 0, 1}, {BOX_co64, 0, 1}, {BOX_stsd, 0, 0}, {BOX_esds, 0, 1} // esds does not use track, but switches to OD mode. Check here, to avoid OD check }; // List of boxes, which contains other boxes ('envelopes') // Parser will descend down for boxes in this list, otherwise parsing will proceed to // the next sibling box // OD boxes handled in the same way as atom boxes... static const struct { uint32_t name; boxtype_t type; } g_envelope_box[] = { {BOX_esds, BOX_OD}, // TODO: BOX_esds can be used for both audio and video, but this code supports audio only! {OD_ESD, BOX_OD}, {OD_DCD, BOX_OD}, {OD_DSI, BOX_OD}, {BOX_trak, BOX_ATOM}, {BOX_moov, BOX_ATOM}, //{BOX_moof, BOX_ATOM}, {BOX_mdia, BOX_ATOM}, {BOX_tref, BOX_ATOM}, {BOX_minf, BOX_ATOM}, {BOX_dinf, BOX_ATOM}, {BOX_stbl, BOX_ATOM}, {BOX_stsd, BOX_ATOM}, {BOX_mp4a, BOX_ATOM}, {BOX_mp4s, BOX_ATOM}, #if MP4D_AVC_SUPPORTED {BOX_mp4v, BOX_ATOM}, {BOX_avc1, BOX_ATOM}, //{BOX_avc2, BOX_ATOM}, //{BOX_svc1, BOX_ATOM}, #endif #if MP4D_HEVC_SUPPORTED {BOX_hvc1, BOX_ATOM}, #endif {BOX_udta, BOX_ATOM}, {BOX_meta, BOX_ATOM}, {BOX_ilst, BOX_ATOM} }; uint32_t FullAtomVersionAndFlags = 0; boxsize_t payload_bytes; boxsize_t box_bytes; uint32_t box_name; #if MP4D_INFO_SUPPORTED unsigned char **ptag = NULL; #endif int read_bytes = 0; // Read header box type and it's length if (stack[depth].format == BOX_ATOM) { box_bytes = minimp4_read(mp4, 4, &eof_flag); #if FIX_BAD_ANDROID_META_BOX broken_android_meta_hack: #endif if (eof_flag) break; // normal exit if (box_bytes >= 2 && box_bytes < 8) { ERROR("invalid box size (broken file?)"); } box_name = minimp4_read(mp4, 4, &eof_flag); read_bytes = 8; // Decode box size if (box_bytes == 0 || // standard indication of 'till eof' size box_bytes == (boxsize_t)0xFFFFFFFFU // some files uses non-standard 'till eof' signaling ) { box_bytes = ~(boxsize_t)0; } payload_bytes = box_bytes - 8; if (box_bytes == 1) // 64-bit sizes { TRACE(("\n64-bit chunk encountered")); box_bytes = minimp4_read(mp4, 4, &eof_flag); #if MP4D_64BIT_SUPPORTED box_bytes <<= 32; box_bytes |= minimp4_read(mp4, 4, &eof_flag); #else if (box_bytes) { ERROR("UNSUPPORTED FEATURE: MP4BoxHeader(): 64-bit boxes not supported!"); } box_bytes = minimp4_read(mp4, 4, &eof_flag); #endif if (box_bytes < 16) { ERROR("invalid box size (broken file?)"); } payload_bytes = box_bytes - 16; } // Read and check box version for some boxes for (i = 0; i < NELEM(g_fullbox); i++) { if (box_name == g_fullbox[i].name) { FullAtomVersionAndFlags = READ(4); read_bytes += 4; #if FIX_BAD_ANDROID_META_BOX // Fix invalid BOX_meta, found in some Android-produced MP4 // This branch is optional: bad box would be skipped if (box_name == BOX_meta) { if (FullAtomVersionAndFlags >= 8 && FullAtomVersionAndFlags < payload_bytes) { if (box_bytes > stack[depth].bytes) { ERROR("broken file structure!"); } stack[depth].bytes -= box_bytes;; depth++; stack[depth].bytes = payload_bytes + 4; // +4 need for missing header stack[depth].format = BOX_ATOM; box_bytes = FullAtomVersionAndFlags; TRACE(("Bad metadata box detected (Android bug?)!\n")); goto broken_android_meta_hack; } } #endif // FIX_BAD_ANDROID_META_BOX if ((FullAtomVersionAndFlags >> 24) > g_fullbox[i].max_version) { ERROR("unsupported box version!"); } if (g_fullbox[i].use_track_flag && !tr) { ERROR("broken file structure!"); } } } } else // stack[depth].format == BOX_OD { int val; box_name = OD_BASE + minimp4_read(mp4, 1, &eof_flag); // 1-byte box type read_bytes += 1; if (eof_flag) break; payload_bytes = 0; box_bytes = 1; do { val = minimp4_read(mp4, 1, &eof_flag); read_bytes += 1; if (eof_flag) { ERROR("premature EOF!"); } payload_bytes = (payload_bytes << 7) | (val & 0x7F); box_bytes++; } while (val & 0x80); box_bytes += payload_bytes; } #if MP4D_TRACE_SUPPORTED box_path[depth] = (box_name >> 24) | (box_name << 24) | ((box_name >> 8) & 0x0000FF00) | ((box_name << 8) & 0x00FF0000); TRACE(("%2d %8d %.*s (%d bytes remains for sibilings) \n", depth, (int)box_bytes, depth*4, (char*)box_path, (int)stack[depth].bytes)); #endif // Check that box size <= parent size if (depth) { // Skip box with bad size assert(box_bytes > 0); if (box_bytes > stack[depth].bytes) { TRACE(("Wrong %c%c%c%c box size: broken file?\n", (box_name >> 24)&255, (box_name >> 16)&255, (box_name >> 8)&255, box_name&255)); box_bytes = stack[depth].bytes; box_name = 0; payload_bytes = box_bytes - read_bytes; } stack[depth].bytes -= box_bytes; } // Read box header switch(box_name) { case BOX_stz2: //ISO/IEC 14496-1 Page 38. Section 8.17.2 - Sample Size Box. case BOX_stsz: { int size = 0; uint32_t sample_size = READ(4); tr->sample_count = READ(4); MALLOC(unsigned int*, tr->entry_size, tr->sample_count*4); for (i = 0; i < tr->sample_count; i++) { if (box_name == BOX_stsz) { tr->entry_size[i] = (sample_size?sample_size:READ(4)); } else { switch (sample_size & 0xFF) { case 16: tr->entry_size[i] = READ(2); break; case 8: tr->entry_size[i] = READ(1); break; case 4: if (i & 1) { tr->entry_size[i] = size & 15; } else { size = READ(1); tr->entry_size[i] = (size >> 4); } break; } } } } break; case BOX_stsc: //ISO/IEC 14496-12 Page 38. Section 8.18 - Sample To Chunk Box. tr->sample_to_chunk_count = READ(4); MALLOC(MP4D_sample_to_chunk_t*, tr->sample_to_chunk, tr->sample_to_chunk_count*sizeof(tr->sample_to_chunk[0])); for (i = 0; i < tr->sample_to_chunk_count; i++) { tr->sample_to_chunk[i].first_chunk = READ(4); tr->sample_to_chunk[i].samples_per_chunk = READ(4); SKIP(4); // sample_description_index } break; #if MP4D_TRACE_TIMESTAMPS || MP4D_TIMESTAMPS_SUPPORTED case BOX_stts: { unsigned count = READ(4); unsigned j, k = 0, ts = 0, ts_count = count; #if MP4D_TIMESTAMPS_SUPPORTED MALLOC(unsigned int*, tr->timestamp, ts_count*4); MALLOC(unsigned int*, tr->duration, ts_count*4); #endif for (i = 0; i < count; i++) { unsigned sc = READ(4); int d = READ(4); TRACE(("sample %8d count %8d duration %8d\n", i, sc, d)); #if MP4D_TIMESTAMPS_SUPPORTED if (k + sc > ts_count) { ts_count = k + sc; tr->timestamp = (unsigned int*)realloc(tr->timestamp, ts_count * sizeof(unsigned)); tr->duration = (unsigned int*)realloc(tr->duration, ts_count * sizeof(unsigned)); } for (j = 0; j < sc; j++) { tr->duration[k] = d; tr->timestamp[k++] = ts; ts += d; } #endif } } break; case BOX_ctts: { unsigned count = READ(4); for (i = 0; i < count; i++) { int sc = READ(4); int d = READ(4); (void)sc; (void)d; TRACE(("sample %8d count %8d decoding to composition offset %8d\n", i, sc, d)); } } break; #endif case BOX_stco: //ISO/IEC 14496-12 Page 39. Section 8.19 - Chunk Offset Box. case BOX_co64: tr->chunk_count = READ(4); MALLOC(MP4D_file_offset_t*, tr->chunk_offset, tr->chunk_count*sizeof(MP4D_file_offset_t)); for (i = 0; i < tr->chunk_count; i++) { tr->chunk_offset[i] = READ(4); if (box_name == BOX_co64) { #if !MP4D_64BIT_SUPPORTED if (tr->chunk_offset[i]) { ERROR("UNSUPPORTED FEATURE: 64-bit chunk_offset not supported!"); } #endif tr->chunk_offset[i] <<= 32; tr->chunk_offset[i] |= READ(4); } } break; #if MP4D_INFO_SUPPORTED case BOX_mvhd: SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); mp4->timescale = READ(4); mp4->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0; mp4->duration_lo = READ(4); SKIP(4 + 2 + 2 + 4*2 + 4*9 + 4*6 + 4); break; case BOX_mdhd: SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); tr->timescale = READ(4); tr->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0; tr->duration_lo = READ(4); { int ISO_639_2_T = READ(2); tr->language[2] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5; tr->language[1] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5; tr->language[0] = (ISO_639_2_T & 31) + 0x60; } // the rest of this box is skipped by default ... break; case BOX_hdlr: if (tr) // When this box is within 'meta' box, the track may not be avaialable { SKIP(4); // pre_defined tr->handler_type = READ(4); } // typically hdlr box does not contain any useful info. // the rest of this box is skipped by default ... break; case BOX_btrt: if (!tr) { ERROR("broken file structure!"); } SKIP(4 + 4); tr->avg_bitrate_bps = READ(4); break; // Set pointer to tag to be read... case BOX_calb: ptag = &mp4->tag.album; break; case BOX_cART: ptag = &mp4->tag.artist; break; case BOX_cnam: ptag = &mp4->tag.title; break; case BOX_cday: ptag = &mp4->tag.year; break; case BOX_ccmt: ptag = &mp4->tag.comment; break; case BOX_cgen: ptag = &mp4->tag.genre; break; #endif case BOX_stsd: SKIP(4); // entry_count, BOX_mp4a & BOX_mp4v boxes follows immediately break; case BOX_mp4s: // private stream if (!tr) { ERROR("broken file structure!"); } SKIP(6*1 + 2/*Base SampleEntry*/); break; case BOX_mp4a: if (!tr) { ERROR("broken file structure!"); } #if MP4D_INFO_SUPPORTED SKIP(6*1+2/*Base SampleEntry*/ + 4*2); tr->SampleDescription.audio.channelcount = READ(2); SKIP(2/*samplesize*/ + 2 + 2); tr->SampleDescription.audio.samplerate_hz = READ(4) >> 16; #else SKIP(28); #endif break; #if MP4D_AVC_SUPPORTED case BOX_avc1: // AVCSampleEntry extends VisualSampleEntry // case BOX_avc2: - no test // case BOX_svc1: - no test case BOX_mp4v: if (!tr) { ERROR("broken file structure!"); } #if MP4D_INFO_SUPPORTED SKIP(6*1 + 2/*Base SampleEntry*/ + 2 + 2 + 4*3); tr->SampleDescription.video.width = READ(2); tr->SampleDescription.video.height = READ(2); // frame_count is always 1 // compressorname is rarely set.. SKIP(4 + 4 + 4 + 2/*frame_count*/ + 32/*compressorname*/ + 2 + 2); #else SKIP(78); #endif // ^^^ end of VisualSampleEntry // now follows for BOX_avc1: // BOX_avcC // BOX_btrt (optional) // BOX_m4ds (optional) // for BOX_mp4v: // BOX_esds break; case BOX_avcC: // AVCDecoderConfigurationRecord() // hack: AAC-specific DSI field reused (for it have same purpoose as sps/pps) // TODO: check this hack if BOX_esds co-exist with BOX_avcC tr->object_type_indication = MP4_OBJECT_TYPE_AVC; tr->dsi = (unsigned char*)malloc((size_t)box_bytes); tr->dsi_bytes = (unsigned)box_bytes; { int spspps; unsigned char *p = tr->dsi; unsigned int configurationVersion = READ(1); unsigned int AVCProfileIndication = READ(1); unsigned int profile_compatibility = READ(1); unsigned int AVCLevelIndication = READ(1); //bit(6) reserved = unsigned int lengthSizeMinusOne = READ(1) & 3; (void)configurationVersion; (void)AVCProfileIndication; (void)profile_compatibility; (void)AVCLevelIndication; (void)lengthSizeMinusOne; for (spspps = 0; spspps < 2; spspps++) { unsigned int numOfSequenceParameterSets= READ(1); if (!spspps) { numOfSequenceParameterSets &= 31; // clears 3 msb for SPS } *p++ = numOfSequenceParameterSets; for (i = 0; i < numOfSequenceParameterSets; i++) { unsigned k, sequenceParameterSetLength = READ(2); *p++ = sequenceParameterSetLength >> 8; *p++ = sequenceParameterSetLength ; for (k = 0; k < sequenceParameterSetLength; k++) { *p++ = READ(1); } } } } break; #endif // MP4D_AVC_SUPPORTED case OD_ESD: { unsigned flags = READ(3); // ES_ID(2) + flags(1) if (flags & 0x80) // steamdependflag { SKIP(2); // dependsOnESID } if (flags & 0x40) // urlflag { unsigned bytecount = READ(1); SKIP(bytecount); // skip URL } if (flags & 0x20) // ocrflag (was reserved in MPEG-4 v.1) { SKIP(2); // OCRESID } break; } case OD_DCD: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. assert(tr); // ensured by g_fullbox[] check tr->object_type_indication = READ(1); #if MP4D_INFO_SUPPORTED tr->stream_type = READ(1) >> 2; SKIP(3/*bufferSizeDB*/ + 4/*maxBitrate*/); tr->avg_bitrate_bps = READ(4); #else SKIP(1+3+4+4); #endif break; case OD_DSI: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. assert(tr); // ensured by g_fullbox[] check if (!tr->dsi && payload_bytes) { MALLOC(unsigned char*, tr->dsi, (int)payload_bytes); for (i = 0; i < payload_bytes; i++) { tr->dsi[i] = minimp4_read(mp4, 1, &eof_flag); // These bytes available due to check above } tr->dsi_bytes = i; payload_bytes -= i; break; } default: TRACE(("[%c%c%c%c] %d\n", box_name >> 24, box_name >> 16, box_name >> 8, box_name, (int)payload_bytes)); } #if MP4D_INFO_SUPPORTED // Read tag is tag pointer is set if (ptag && !*ptag && payload_bytes > 16) { #if 0 uint32_t size = READ(4); uint32_t data = READ(4); uint32_t class = READ(4); uint32_t x1 = READ(4); TRACE(("%2d %2d %2d ", size, class, x1)); #else SKIP(4 + 4 + 4 + 4); #endif MALLOC(unsigned char*, *ptag, (unsigned)payload_bytes + 1); for (i = 0; payload_bytes != 0; i++) { (*ptag)[i] = READ(1); } (*ptag)[i] = 0; // zero-terminated string } #endif if (box_name == BOX_trak) { // New track found: allocate memory using realloc() // Typically there are 1 audio track for AAC audio file, // 4 tracks for movie file, // 3-5 tracks for scalable audio (CELP+AAC) // and up to 50 tracks for BSAC scalable audio void *mem = realloc(mp4->track, (mp4->track_count + 1)*sizeof(MP4D_track_t)); if (!mem) { // if realloc fails, it does not deallocate old pointer! ERROR("out of memory"); } mp4->track = (MP4D_track_t*)mem; tr = mp4->track + mp4->track_count++; memset(tr, 0, sizeof(MP4D_track_t)); } else if (box_name == BOX_meta) { tr = NULL; // Avoid update of 'hdlr' box, which may contains in the 'meta' box } // If this box is envelope, save it's size in box stack for (i = 0; i < NELEM(g_envelope_box); i++) { if (box_name == g_envelope_box[i].name) { if (++depth >= MAX_CHUNKS_DEPTH) { ERROR("too deep atoms nesting!"); } stack[depth].bytes = payload_bytes; stack[depth].format = g_envelope_box[i].type; break; } } // if box is not envelope, just skip it if (i == NELEM(g_envelope_box)) { if (payload_bytes > (boxsize_t)file_size) { eof_flag = 1; } else { SKIP(payload_bytes); } } // remove empty boxes from stack // don't touch box with index 0 (which indicates whole file) while (depth > 0 && !stack[depth].bytes) { depth--; } } while(!eof_flag); if (!mp4->track_count) { RETURN_ERROR("no tracks found"); } return 1; } /** * Find chunk, containing given sample. * Returns chunk number, and first sample in this chunk. */ static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_sample_in_chunk) { unsigned chunk_group = 0, nc; unsigned sum = 0; *nfirst_sample_in_chunk = 0; if (tr->chunk_count <= 1) { return 0; } for (nc = 0; nc < tr->chunk_count; nc++) { if (chunk_group + 1 < tr->sample_to_chunk_count // stuck at last entry till EOF && nc + 1 == // Chunks counted starting with '1' tr->sample_to_chunk[chunk_group + 1].first_chunk) // next group? { chunk_group++; } sum += tr->sample_to_chunk[chunk_group].samples_per_chunk; if (nsample < sum) return nc; // TODO: this can be calculated once per file *nfirst_sample_in_chunk = sum; } return -1; } // Exported API function MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, unsigned *frame_bytes, unsigned *timestamp, unsigned *duration) { MP4D_track_t *tr = mp4->track + ntrack; unsigned ns; int nchunk = sample_to_chunk(tr, nsample, &ns); MP4D_file_offset_t offset; if (nchunk < 0) { *frame_bytes = 0; return 0; } offset = tr->chunk_offset[nchunk]; for (; ns < nsample; ns++) { offset += tr->entry_size[ns]; } *frame_bytes = tr->entry_size[ns]; if (timestamp) { #if MP4D_TIMESTAMPS_SUPPORTED *timestamp = tr->timestamp[ns]; #else *timestamp = 0; #endif } if (duration) { #if MP4D_TIMESTAMPS_SUPPORTED *duration = tr->duration[ns]; #else *duration = 0; #endif } return offset; } #define FREE(x) if (x) {free(x); x = NULL;} // Exported API function void MP4D_close(MP4D_demux_t *mp4) { while (mp4->track_count) { MP4D_track_t *tr = mp4->track + --mp4->track_count; FREE(tr->entry_size); #if MP4D_TIMESTAMPS_SUPPORTED FREE(tr->timestamp); FREE(tr->duration); #endif FREE(tr->sample_to_chunk); FREE(tr->chunk_offset); FREE(tr->dsi); } FREE(mp4->track); #if MP4D_INFO_SUPPORTED FREE(mp4->tag.title); FREE(mp4->tag.artist); FREE(mp4->tag.album); FREE(mp4->tag.year); FREE(mp4->tag.comment); FREE(mp4->tag.genre); #endif } static int skip_spspps(const unsigned char *p, int nbytes, int nskip) { int i, k = 0; for (i = 0; i < nskip; i++) { unsigned segmbytes; if (k > nbytes - 2) return -1; segmbytes = p[k]*256 + p[k+1]; k += 2 + segmbytes; } return k; } static const void *MP4D_read_spspps(const MP4D_demux_t *mp4, unsigned int ntrack, int pps_flag, int nsps, int *sps_bytes) { int sps_count, skip_bytes; int bytepos = 0; unsigned char *p = mp4->track[ntrack].dsi; if (ntrack >= mp4->track_count) return NULL; if (mp4->track[ntrack].object_type_indication != MP4_OBJECT_TYPE_AVC) return NULL; // SPS/PPS are specific for AVC format only if (pps_flag) { // Skip all SPS sps_count = p[bytepos++]; skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, sps_count); if (skip_bytes < 0) return NULL; bytepos += skip_bytes; } // Skip sps/pps before the given target sps_count = p[bytepos++]; if (nsps >= sps_count) return NULL; skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, nsps); if (skip_bytes < 0) return NULL; bytepos += skip_bytes; *sps_bytes = p[bytepos]*256 + p[bytepos+1]; return p + bytepos + 2; } const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes) { return MP4D_read_spspps(mp4, ntrack, 0, nsps, sps_bytes); } const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes) { return MP4D_read_spspps(mp4, ntrack, 1, npps, pps_bytes); } #if MP4D_PRINT_INFO_SUPPORTED /************************************************************************/ /* Purely informational part, may be removed for embedded applications */ /************************************************************************/ // // Decodes ISO/IEC 14496 MP4 stream type to ASCII string // static const char *GetMP4StreamTypeName(int streamType) { switch (streamType) { case 0x00: return "Forbidden"; case 0x01: return "ObjectDescriptorStream"; case 0x02: return "ClockReferenceStream"; case 0x03: return "SceneDescriptionStream"; case 0x04: return "VisualStream"; case 0x05: return "AudioStream"; case 0x06: return "MPEG7Stream"; case 0x07: return "IPMPStream"; case 0x08: return "ObjectContentInfoStream"; case 0x09: return "MPEGJStream"; default: if (streamType >= 0x20 && streamType <= 0x3F) { return "User private"; } else { return "Reserved for ISO use"; } } } // // Decodes ISO/IEC 14496 MP4 object type to ASCII string // static const char *GetMP4ObjectTypeName(int objectTypeIndication) { switch (objectTypeIndication) { case 0x00: return "Forbidden"; case 0x01: return "Systems ISO/IEC 14496-1"; case 0x02: return "Systems ISO/IEC 14496-1"; case 0x20: return "Visual ISO/IEC 14496-2"; case 0x40: return "Audio ISO/IEC 14496-3"; case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; case 0x69: return "Audio ISO/IEC 13818-3"; case 0x6A: return "Visual ISO/IEC 11172-2"; case 0x6B: return "Audio ISO/IEC 11172-3"; case 0x6C: return "Visual ISO/IEC 10918-1"; case 0xFF: return "no object type specified"; default: if (objectTypeIndication >= 0xC0 && objectTypeIndication <= 0xFE) return "User private"; else return "Reserved for ISO use"; } } /** * Print MP4 information to stdout. * Subject for customization to particular application Output Example #1: movie file MP4 FILE: 7 tracks found. Movie time 104.12 sec No|type|lng| duration | bitrate| Stream type | Object type 0|odsm|fre| 0.00 s 1 frm| 0| Forbidden | Forbidden 1|sdsm|fre| 0.00 s 1 frm| 0| Forbidden | Forbidden 2|vide|```| 104.12 s 2603 frm| 1960559| VisualStream | Visual ISO/IEC 14496-2 - 720x304 3|soun|ger| 104.06 s 2439 frm| 191242| AudioStream | Audio ISO/IEC 14496-3 - 6 ch 24000 hz 4|soun|eng| 104.06 s 2439 frm| 194171| AudioStream | Audio ISO/IEC 14496-3 - 6 ch 24000 hz 5|subp|ger| 71.08 s 25 frm| 0| Forbidden | Forbidden 6|subp|eng| 71.08 s 25 frm| 0| Forbidden | Forbidden Output Example #2: audio file with tags MP4 FILE: 1 tracks found. Movie time 92.42 sec title = 86-Second Blowout artist = Yo La Tengo album = May I Sing With Me year = 1992 No|type|lng| duration | bitrate| Stream type | Object type 0|mdir|und| 92.42 s 3980 frm| 128000| AudioStream | Audio ISO/IEC 14496-3MP4 FILE: 1 tracks found. Movie time 92.42 sec */ void MP4D_printf_info(const MP4D_demux_t *mp4) { unsigned i; printf("\nMP4 FILE: %d tracks found. Movie time %.2f sec\n", mp4->track_count, (4294967296.0*mp4->duration_hi + mp4->duration_lo) / mp4->timescale); #define STR_TAG(name) if (mp4->tag.name) printf("%10s = %s\n", #name, mp4->tag.name) STR_TAG(title); STR_TAG(artist); STR_TAG(album); STR_TAG(year); STR_TAG(comment); STR_TAG(genre); printf("\nNo|type|lng| duration | bitrate| %-23s| Object type", "Stream type"); for (i = 0; i < mp4->track_count; i++) { MP4D_track_t *tr = mp4->track + i; printf("\n%2d|%c%c%c%c|%c%c%c|%7.2f s %6d frm| %7d|", i, (tr->handler_type >> 24), (tr->handler_type >> 16), (tr->handler_type >> 8), (tr->handler_type >> 0), tr->language[0], tr->language[1], tr->language[2], (65536.0*65536.0*tr->duration_hi + tr->duration_lo) / tr->timescale, tr->sample_count, tr->avg_bitrate_bps); printf(" %-23s|", GetMP4StreamTypeName(tr->stream_type)); printf(" %-23s", GetMP4ObjectTypeName(tr->object_type_indication)); if (tr->handler_type == MP4D_HANDLER_TYPE_SOUN) { printf(" - %d ch %d hz", tr->SampleDescription.audio.channelcount, tr->SampleDescription.audio.samplerate_hz); } else if (tr->handler_type == MP4D_HANDLER_TYPE_VIDE) { printf(" - %dx%d", tr->SampleDescription.video.width, tr->SampleDescription.video.height); } } printf("\n"); } #endif // MP4D_PRINT_INFO_SUPPORTED #endif kew/include/nestegg/000077500000000000000000000000001512074754200147325ustar00rootroot00000000000000kew/include/nestegg/LICENSE000066400000000000000000000013341512074754200157400ustar00rootroot00000000000000Copyright © 2010 Mozilla Foundation Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. kew/include/nestegg/nestegg.c000066400000000000000000002403651512074754200165440ustar00rootroot00000000000000/* * Copyright © 2010 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ #include #include #include #include #include "nestegg.h" /* EBML Elements */ #define ID_EBML 0x1a45dfa3 #define ID_EBML_VERSION 0x4286 #define ID_EBML_READ_VERSION 0x42f7 #define ID_EBML_MAX_ID_LENGTH 0x42f2 #define ID_EBML_MAX_SIZE_LENGTH 0x42f3 #define ID_DOCTYPE 0x4282 #define ID_DOCTYPE_VERSION 0x4287 #define ID_DOCTYPE_READ_VERSION 0x4285 /* Global Elements */ #define ID_VOID 0xec #define ID_CRC32 0xbf /* WebM Elements */ #define ID_SEGMENT 0x18538067 /* Seek Head Elements */ #define ID_SEEK_HEAD 0x114d9b74 #define ID_SEEK 0x4dbb #define ID_SEEK_ID 0x53ab #define ID_SEEK_POSITION 0x53ac /* Info Elements */ #define ID_INFO 0x1549a966 #define ID_TIMECODE_SCALE 0x2ad7b1 #define ID_DURATION 0x4489 /* Cluster Elements */ #define ID_CLUSTER 0x1f43b675 #define ID_TIMECODE 0xe7 #define ID_BLOCK_GROUP 0xa0 #define ID_SIMPLE_BLOCK 0xa3 /* BlockGroup Elements */ #define ID_BLOCK 0xa1 #define ID_BLOCK_ADDITIONS 0x75a1 #define ID_BLOCK_DURATION 0x9b #define ID_REFERENCE_BLOCK 0xfb #define ID_DISCARD_PADDING 0x75a2 /* BlockAdditions Elements */ #define ID_BLOCK_MORE 0xa6 /* BlockMore Elements */ #define ID_BLOCK_ADD_ID 0xee #define ID_BLOCK_ADDITIONAL 0xa5 /* Tracks Elements */ #define ID_TRACKS 0x1654ae6b #define ID_TRACK_ENTRY 0xae #define ID_TRACK_NUMBER 0xd7 #define ID_TRACK_UID 0x73c5 #define ID_TRACK_TYPE 0x83 #define ID_FLAG_ENABLED 0xb9 #define ID_FLAG_DEFAULT 0x88 #define ID_FLAG_LACING 0x9c #define ID_TRACK_TIMECODE_SCALE 0x23314f #define ID_LANGUAGE 0x22b59c #define ID_CODEC_ID 0x86 #define ID_CODEC_PRIVATE 0x63a2 #define ID_CODEC_DELAY 0x56aa #define ID_SEEK_PREROLL 0x56bb #define ID_DEFAULT_DURATION 0x23e383 /* Video Elements */ #define ID_VIDEO 0xe0 #define ID_STEREO_MODE 0x53b8 #define ID_ALPHA_MODE 0x53c0 #define ID_PIXEL_WIDTH 0xb0 #define ID_PIXEL_HEIGHT 0xba #define ID_PIXEL_CROP_BOTTOM 0x54aa #define ID_PIXEL_CROP_TOP 0x54bb #define ID_PIXEL_CROP_LEFT 0x54cc #define ID_PIXEL_CROP_RIGHT 0x54dd #define ID_DISPLAY_WIDTH 0x54b0 #define ID_DISPLAY_HEIGHT 0x54ba #define ID_COLOUR 0x55b0 /* Audio Elements */ #define ID_AUDIO 0xe1 #define ID_SAMPLING_FREQUENCY 0xb5 #define ID_CHANNELS 0x9f #define ID_BIT_DEPTH 0x6264 /* Cues Elements */ #define ID_CUES 0x1c53bb6b #define ID_CUE_POINT 0xbb #define ID_CUE_TIME 0xb3 #define ID_CUE_TRACK_POSITIONS 0xb7 #define ID_CUE_TRACK 0xf7 #define ID_CUE_CLUSTER_POSITION 0xf1 #define ID_CUE_BLOCK_NUMBER 0x5378 /* Encoding Elements */ #define ID_CONTENT_ENCODINGS 0x6d80 #define ID_CONTENT_ENCODING 0x6240 #define ID_CONTENT_ENCODING_TYPE 0x5033 /* Encryption Elements */ #define ID_CONTENT_ENCRYPTION 0x5035 #define ID_CONTENT_ENC_ALGO 0x47e1 #define ID_CONTENT_ENC_KEY_ID 0x47e2 #define ID_CONTENT_ENC_AES_SETTINGS 0x47e7 #define ID_AES_SETTINGS_CIPHER_MODE 0x47e8 /* Colour Elements */ #define ID_MATRIX_COEFFICIENTS 0x55b1 #define ID_RANGE 0x55b9 #define ID_TRANSFER_CHARACTERISTICS 0x55ba #define ID_PRIMARIES 0x55bb #define ID_MASTERING_METADATA 0x55d0 /* MasteringMetadata Elements */ #define ID_PRIMARY_R_CHROMATICITY_X 0x55d1 #define ID_PRIMARY_R_CHROMATICITY_Y 0x55d2 #define ID_PRIMARY_G_CHROMATICITY_X 0x55d3 #define ID_PRIMARY_G_CHROMATICITY_Y 0x55d4 #define ID_PRIMARY_B_CHROMATICITY_X 0x55d5 #define ID_PRIMARY_B_CHROMATICITY_Y 0x55d6 #define ID_WHITE_POINT_CHROMATICITY_X 0x55d7 #define ID_WHITE_POINT_CHROMATICITY_Y 0x55d8 #define ID_LUMINANCE_MAX 0x55d9 #define ID_LUMINANCE_MIN 0x55da /* EBML Types */ enum ebml_type_enum { TYPE_UNKNOWN, TYPE_MASTER, TYPE_UINT, TYPE_FLOAT, TYPE_STRING, TYPE_BINARY }; #define LIMIT_STRING (1 << 20) #define LIMIT_BINARY (1 << 24) #define LIMIT_BLOCK (1 << 30) #define LIMIT_FRAME (1 << 28) /* Field Flags */ #define DESC_FLAG_NONE 0 #define DESC_FLAG_MULTI (1 << 0) #define DESC_FLAG_SUSPEND (1 << 1) #define DESC_FLAG_OFFSET (1 << 2) /* Block Header Flags */ #define SIMPLE_BLOCK_FLAGS_KEYFRAME (1 << 7) #define BLOCK_FLAGS_LACING 6 /* Lacing Constants */ #define LACING_NONE 0 #define LACING_XIPH 1 #define LACING_FIXED 2 #define LACING_EBML 3 /* Track Types */ #define TRACK_TYPE_VIDEO 1 #define TRACK_TYPE_AUDIO 2 /* Track IDs */ #define TRACK_ID_VP8 "V_VP8" #define TRACK_ID_VP9 "V_VP9" #define TRACK_ID_AV1 "V_AV1" #define TRACK_ID_VORBIS "A_VORBIS" #define TRACK_ID_OPUS "A_OPUS" /* Track Encryption */ #define CONTENT_ENC_ALGO_AES 5 #define AES_SETTINGS_CIPHER_CTR 1 /* Packet Encryption */ #define SIGNAL_BYTE_SIZE 1 #define IV_SIZE 8 #define NUM_PACKETS_SIZE 1 #define PACKET_OFFSET_SIZE 4 /* Signal Byte */ #define PACKET_ENCRYPTED 1 #define ENCRYPTED_BIT_MASK (1 << 0) #define PACKET_PARTITIONED 2 #define PARTITIONED_BIT_MASK (1 << 1) enum vint_mask { MASK_NONE, MASK_FIRST_BIT }; struct ebml_binary { unsigned char * data; size_t length; }; struct ebml_list_node { struct ebml_list_node * next; uint64_t id; void * data; }; struct ebml_list { struct ebml_list_node * head; struct ebml_list_node * tail; }; struct ebml_type { union ebml_value { uint64_t u; double f; int64_t i; char * s; struct ebml_binary b; } v; enum ebml_type_enum type; int read; }; /* EBML Definitions */ struct ebml { struct ebml_type ebml_version; struct ebml_type ebml_read_version; struct ebml_type ebml_max_id_length; struct ebml_type ebml_max_size_length; struct ebml_type doctype; struct ebml_type doctype_version; struct ebml_type doctype_read_version; }; /* Matroksa Definitions */ struct seek { struct ebml_type id; struct ebml_type position; }; struct seek_head { struct ebml_list seek; }; struct info { struct ebml_type timecode_scale; struct ebml_type duration; }; struct mastering_metadata { struct ebml_type primary_r_chromacity_x; struct ebml_type primary_r_chromacity_y; struct ebml_type primary_g_chromacity_x; struct ebml_type primary_g_chromacity_y; struct ebml_type primary_b_chromacity_x; struct ebml_type primary_b_chromacity_y; struct ebml_type white_point_chromaticity_x; struct ebml_type white_point_chromaticity_y; struct ebml_type luminance_max; struct ebml_type luminance_min; }; struct colour { struct ebml_type matrix_coefficients; struct ebml_type range; struct ebml_type transfer_characteristics; struct ebml_type primaries; struct mastering_metadata mastering_metadata; }; struct video { struct ebml_type stereo_mode; struct ebml_type alpha_mode; struct ebml_type pixel_width; struct ebml_type pixel_height; struct ebml_type pixel_crop_bottom; struct ebml_type pixel_crop_top; struct ebml_type pixel_crop_left; struct ebml_type pixel_crop_right; struct ebml_type display_width; struct ebml_type display_height; struct colour colour; }; struct audio { struct ebml_type sampling_frequency; struct ebml_type channels; struct ebml_type bit_depth; }; struct content_enc_aes_settings { struct ebml_type aes_settings_cipher_mode; }; struct content_encryption { struct ebml_type content_enc_algo; struct ebml_type content_enc_key_id; struct ebml_list content_enc_aes_settings; }; struct content_encoding { struct ebml_type content_encoding_type; struct ebml_list content_encryption; }; struct content_encodings { struct ebml_list content_encoding; }; struct track_entry { struct ebml_type number; struct ebml_type uid; struct ebml_type type; struct ebml_type flag_enabled; struct ebml_type flag_default; struct ebml_type flag_lacing; struct ebml_type track_timecode_scale; struct ebml_type language; struct ebml_type codec_id; struct ebml_type codec_private; struct ebml_type codec_delay; struct ebml_type seek_preroll; struct ebml_type default_duration; struct video video; struct audio audio; struct content_encodings content_encodings; }; struct tracks { struct ebml_list track_entry; }; struct cue_track_positions { struct ebml_type track; struct ebml_type cluster_position; struct ebml_type block_number; }; struct cue_point { struct ebml_type time; struct ebml_list cue_track_positions; }; struct cues { struct ebml_list cue_point; }; struct segment { struct ebml_list seek_head; struct info info; struct tracks tracks; struct cues cues; }; /* Misc. */ struct pool_node { struct pool_node * next; void * data; }; struct pool_ctx { struct pool_node * head; }; struct list_node { struct list_node * previous; struct ebml_element_desc * node; unsigned char * data; }; struct saved_state { int64_t stream_offset; uint64_t last_id; uint64_t last_size; int last_valid; }; struct frame_encryption { unsigned char * iv; size_t length; uint8_t signal_byte; uint8_t num_partitions; uint32_t * partition_offsets; }; struct frame { unsigned char * data; size_t length; struct frame_encryption * frame_encryption; struct frame * next; }; struct block_additional { unsigned int id; unsigned char * data; size_t length; struct block_additional * next; }; /* Public (opaque) Structures */ struct nestegg { nestegg_io * io; nestegg_log log; struct pool_ctx * alloc_pool; uint64_t last_id; uint64_t last_size; int last_valid; struct list_node * ancestor; struct ebml ebml; struct segment segment; int64_t segment_offset; unsigned int track_count; /* Last read cluster. */ uint64_t cluster_timecode; int read_cluster_timecode; struct saved_state saved; }; struct nestegg_packet { uint64_t track; uint64_t timecode; uint64_t duration; int read_duration; struct frame * frame; struct block_additional * block_additional; int64_t discard_padding; int read_discard_padding; int64_t reference_block; int read_reference_block; uint8_t keyframe; }; /* Element Descriptor */ struct ebml_element_desc { char const * name; uint64_t id; enum ebml_type_enum type; size_t offset; unsigned int flags; struct ebml_element_desc * children; size_t size; size_t data_offset; }; #define E_FIELD(ID, TYPE, STRUCT, FIELD) \ { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, NULL, 0, 0 } #define E_MASTER(ID, TYPE, STRUCT, FIELD) \ { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_MULTI, ne_ ## FIELD ## _elements, \ sizeof(struct FIELD), 0 } #define E_SINGLE_MASTER_O(ID, TYPE, STRUCT, FIELD) \ { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_OFFSET, ne_ ## FIELD ## _elements, 0, \ offsetof(STRUCT, FIELD ## _offset) } #define E_SINGLE_MASTER(ID, TYPE, STRUCT, FIELD) \ { #ID, ID, TYPE, offsetof(STRUCT, FIELD), DESC_FLAG_NONE, ne_ ## FIELD ## _elements, 0, 0 } #define E_SUSPEND(ID, TYPE) \ { #ID, ID, TYPE, 0, DESC_FLAG_SUSPEND, NULL, 0, 0 } #define E_LAST \ { NULL, 0, 0, 0, DESC_FLAG_NONE, NULL, 0, 0 } /* EBML Element Lists */ static struct ebml_element_desc ne_ebml_elements[] = { E_FIELD(ID_EBML_VERSION, TYPE_UINT, struct ebml, ebml_version), E_FIELD(ID_EBML_READ_VERSION, TYPE_UINT, struct ebml, ebml_read_version), E_FIELD(ID_EBML_MAX_ID_LENGTH, TYPE_UINT, struct ebml, ebml_max_id_length), E_FIELD(ID_EBML_MAX_SIZE_LENGTH, TYPE_UINT, struct ebml, ebml_max_size_length), E_FIELD(ID_DOCTYPE, TYPE_STRING, struct ebml, doctype), E_FIELD(ID_DOCTYPE_VERSION, TYPE_UINT, struct ebml, doctype_version), E_FIELD(ID_DOCTYPE_READ_VERSION, TYPE_UINT, struct ebml, doctype_read_version), E_LAST }; /* WebM Element Lists */ static struct ebml_element_desc ne_seek_elements[] = { E_FIELD(ID_SEEK_ID, TYPE_BINARY, struct seek, id), E_FIELD(ID_SEEK_POSITION, TYPE_UINT, struct seek, position), E_LAST }; static struct ebml_element_desc ne_seek_head_elements[] = { E_MASTER(ID_SEEK, TYPE_MASTER, struct seek_head, seek), E_LAST }; static struct ebml_element_desc ne_info_elements[] = { E_FIELD(ID_TIMECODE_SCALE, TYPE_UINT, struct info, timecode_scale), E_FIELD(ID_DURATION, TYPE_FLOAT, struct info, duration), E_LAST }; static struct ebml_element_desc ne_mastering_metadata_elements[] = { E_FIELD(ID_PRIMARY_R_CHROMATICITY_X, TYPE_FLOAT, struct mastering_metadata, primary_r_chromacity_x), E_FIELD(ID_PRIMARY_R_CHROMATICITY_Y, TYPE_FLOAT, struct mastering_metadata, primary_r_chromacity_y), E_FIELD(ID_PRIMARY_G_CHROMATICITY_X, TYPE_FLOAT, struct mastering_metadata, primary_g_chromacity_x), E_FIELD(ID_PRIMARY_G_CHROMATICITY_Y, TYPE_FLOAT, struct mastering_metadata, primary_g_chromacity_y), E_FIELD(ID_PRIMARY_B_CHROMATICITY_X, TYPE_FLOAT, struct mastering_metadata, primary_b_chromacity_x), E_FIELD(ID_PRIMARY_B_CHROMATICITY_Y, TYPE_FLOAT, struct mastering_metadata, primary_b_chromacity_y), E_FIELD(ID_WHITE_POINT_CHROMATICITY_X, TYPE_FLOAT, struct mastering_metadata, white_point_chromaticity_x), E_FIELD(ID_WHITE_POINT_CHROMATICITY_Y, TYPE_FLOAT, struct mastering_metadata, white_point_chromaticity_y), E_FIELD(ID_LUMINANCE_MAX, TYPE_FLOAT, struct mastering_metadata, luminance_max), E_FIELD(ID_LUMINANCE_MIN, TYPE_FLOAT, struct mastering_metadata, luminance_min), E_LAST }; static struct ebml_element_desc ne_colour_elements[] = { E_FIELD(ID_MATRIX_COEFFICIENTS, TYPE_UINT, struct colour, matrix_coefficients), E_FIELD(ID_RANGE, TYPE_UINT, struct colour, range), E_FIELD(ID_TRANSFER_CHARACTERISTICS, TYPE_UINT, struct colour, transfer_characteristics), E_FIELD(ID_PRIMARIES, TYPE_UINT, struct colour, primaries), E_SINGLE_MASTER(ID_MASTERING_METADATA, TYPE_MASTER, struct colour, mastering_metadata), E_LAST }; static struct ebml_element_desc ne_video_elements[] = { E_FIELD(ID_STEREO_MODE, TYPE_UINT, struct video, stereo_mode), E_FIELD(ID_ALPHA_MODE, TYPE_UINT, struct video, alpha_mode), E_FIELD(ID_PIXEL_WIDTH, TYPE_UINT, struct video, pixel_width), E_FIELD(ID_PIXEL_HEIGHT, TYPE_UINT, struct video, pixel_height), E_FIELD(ID_PIXEL_CROP_BOTTOM, TYPE_UINT, struct video, pixel_crop_bottom), E_FIELD(ID_PIXEL_CROP_TOP, TYPE_UINT, struct video, pixel_crop_top), E_FIELD(ID_PIXEL_CROP_LEFT, TYPE_UINT, struct video, pixel_crop_left), E_FIELD(ID_PIXEL_CROP_RIGHT, TYPE_UINT, struct video, pixel_crop_right), E_FIELD(ID_DISPLAY_WIDTH, TYPE_UINT, struct video, display_width), E_FIELD(ID_DISPLAY_HEIGHT, TYPE_UINT, struct video, display_height), E_SINGLE_MASTER(ID_COLOUR, TYPE_MASTER, struct video, colour), E_LAST }; static struct ebml_element_desc ne_audio_elements[] = { E_FIELD(ID_SAMPLING_FREQUENCY, TYPE_FLOAT, struct audio, sampling_frequency), E_FIELD(ID_CHANNELS, TYPE_UINT, struct audio, channels), E_FIELD(ID_BIT_DEPTH, TYPE_UINT, struct audio, bit_depth), E_LAST }; static struct ebml_element_desc ne_content_enc_aes_settings_elements[] = { E_FIELD(ID_AES_SETTINGS_CIPHER_MODE, TYPE_UINT, struct content_enc_aes_settings, aes_settings_cipher_mode), E_LAST }; static struct ebml_element_desc ne_content_encryption_elements[] = { E_FIELD(ID_CONTENT_ENC_ALGO, TYPE_UINT, struct content_encryption, content_enc_algo), E_FIELD(ID_CONTENT_ENC_KEY_ID, TYPE_BINARY, struct content_encryption, content_enc_key_id), E_MASTER(ID_CONTENT_ENC_AES_SETTINGS, TYPE_MASTER, struct content_encryption, content_enc_aes_settings), E_LAST }; static struct ebml_element_desc ne_content_encoding_elements[] = { E_FIELD(ID_CONTENT_ENCODING_TYPE, TYPE_UINT, struct content_encoding, content_encoding_type), E_MASTER(ID_CONTENT_ENCRYPTION, TYPE_MASTER, struct content_encoding, content_encryption), E_LAST }; static struct ebml_element_desc ne_content_encodings_elements[] = { E_MASTER(ID_CONTENT_ENCODING, TYPE_MASTER, struct content_encodings, content_encoding), E_LAST }; static struct ebml_element_desc ne_track_entry_elements[] = { E_FIELD(ID_TRACK_NUMBER, TYPE_UINT, struct track_entry, number), E_FIELD(ID_TRACK_UID, TYPE_UINT, struct track_entry, uid), E_FIELD(ID_TRACK_TYPE, TYPE_UINT, struct track_entry, type), E_FIELD(ID_FLAG_ENABLED, TYPE_UINT, struct track_entry, flag_enabled), E_FIELD(ID_FLAG_DEFAULT, TYPE_UINT, struct track_entry, flag_default), E_FIELD(ID_FLAG_LACING, TYPE_UINT, struct track_entry, flag_lacing), E_FIELD(ID_TRACK_TIMECODE_SCALE, TYPE_FLOAT, struct track_entry, track_timecode_scale), E_FIELD(ID_LANGUAGE, TYPE_STRING, struct track_entry, language), E_FIELD(ID_CODEC_ID, TYPE_STRING, struct track_entry, codec_id), E_FIELD(ID_CODEC_PRIVATE, TYPE_BINARY, struct track_entry, codec_private), E_FIELD(ID_CODEC_DELAY, TYPE_UINT, struct track_entry, codec_delay), E_FIELD(ID_SEEK_PREROLL, TYPE_UINT, struct track_entry, seek_preroll), E_FIELD(ID_DEFAULT_DURATION, TYPE_UINT, struct track_entry, default_duration), E_SINGLE_MASTER(ID_VIDEO, TYPE_MASTER, struct track_entry, video), E_SINGLE_MASTER(ID_AUDIO, TYPE_MASTER, struct track_entry, audio), E_SINGLE_MASTER(ID_CONTENT_ENCODINGS, TYPE_MASTER, struct track_entry, content_encodings), E_LAST }; static struct ebml_element_desc ne_tracks_elements[] = { E_MASTER(ID_TRACK_ENTRY, TYPE_MASTER, struct tracks, track_entry), E_LAST }; static struct ebml_element_desc ne_cue_track_positions_elements[] = { E_FIELD(ID_CUE_TRACK, TYPE_UINT, struct cue_track_positions, track), E_FIELD(ID_CUE_CLUSTER_POSITION, TYPE_UINT, struct cue_track_positions, cluster_position), E_FIELD(ID_CUE_BLOCK_NUMBER, TYPE_UINT, struct cue_track_positions, block_number), E_LAST }; static struct ebml_element_desc ne_cue_point_elements[] = { E_FIELD(ID_CUE_TIME, TYPE_UINT, struct cue_point, time), E_MASTER(ID_CUE_TRACK_POSITIONS, TYPE_MASTER, struct cue_point, cue_track_positions), E_LAST }; static struct ebml_element_desc ne_cues_elements[] = { E_MASTER(ID_CUE_POINT, TYPE_MASTER, struct cues, cue_point), E_LAST }; static struct ebml_element_desc ne_segment_elements[] = { E_MASTER(ID_SEEK_HEAD, TYPE_MASTER, struct segment, seek_head), E_SINGLE_MASTER(ID_INFO, TYPE_MASTER, struct segment, info), E_SUSPEND(ID_CLUSTER, TYPE_MASTER), E_SINGLE_MASTER(ID_TRACKS, TYPE_MASTER, struct segment, tracks), E_SINGLE_MASTER(ID_CUES, TYPE_MASTER, struct segment, cues), E_LAST }; static struct ebml_element_desc ne_top_level_elements[] = { E_SINGLE_MASTER(ID_EBML, TYPE_MASTER, nestegg, ebml), E_SINGLE_MASTER_O(ID_SEGMENT, TYPE_MASTER, nestegg, segment), E_LAST }; #undef E_FIELD #undef E_MASTER #undef E_SINGLE_MASTER_O #undef E_SINGLE_MASTER #undef E_SUSPEND #undef E_LAST static struct pool_ctx * ne_pool_init(void) { return calloc(1, sizeof(struct pool_ctx)); } static void ne_pool_destroy(struct pool_ctx * pool) { struct pool_node * node = pool->head; while (node) { struct pool_node * old = node; node = node->next; free(old->data); free(old); } free(pool); } static void * ne_pool_alloc(size_t size, struct pool_ctx * pool) { struct pool_node * node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->data = calloc(1, size); if (!node->data) { free(node); return NULL; } node->next = pool->head; pool->head = node; return node->data; } static void * ne_alloc(size_t size) { return calloc(1, size); } static int ne_io_read(nestegg_io * io, void * buffer, size_t length) { return io->read(buffer, length, io->userdata); } static int ne_io_seek(nestegg_io * io, int64_t offset, int whence) { return io->seek(offset, whence, io->userdata); } static int ne_io_read_skip(nestegg_io * io, size_t length) { size_t get; unsigned char buf[8192]; int r = 1; while (length > 0) { get = length < sizeof(buf) ? length : sizeof(buf); r = ne_io_read(io, buf, get); if (r != 1) break; length -= get; } return r; } static int64_t ne_io_tell(nestegg_io * io) { return io->tell(io->userdata); } static int ne_bare_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length, enum vint_mask maskflag) { int r; unsigned char b; size_t maxlen = 8; unsigned int count = 1, mask = 1 << 7; r = ne_io_read(io, &b, 1); if (r != 1) return r; while (count < maxlen) { if ((b & mask) != 0) break; mask >>= 1; count += 1; } if (length) *length = count; *value = b; if (maskflag == MASK_FIRST_BIT) *value = b & ~mask; while (--count) { r = ne_io_read(io, &b, 1); if (r != 1) return r; *value <<= 8; *value |= b; } return 1; } static int ne_read_id(nestegg_io * io, uint64_t * value, uint64_t * length) { return ne_bare_read_vint(io, value, length, MASK_NONE); } static int ne_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length) { return ne_bare_read_vint(io, value, length, MASK_FIRST_BIT); } static int ne_read_svint(nestegg_io * io, int64_t * value, uint64_t * length) { int r; uint64_t uvalue; uint64_t ulength; int64_t svint_subtr[] = { 0x3f, 0x1fff, 0xfffff, 0x7ffffff, 0x3ffffffffLL, 0x1ffffffffffLL, 0xffffffffffffLL, 0x7fffffffffffffLL }; r = ne_bare_read_vint(io, &uvalue, &ulength, MASK_FIRST_BIT); if (r != 1) return r; *value = uvalue - svint_subtr[ulength - 1]; if (length) *length = ulength; return r; } static int ne_read_uint(nestegg_io * io, uint64_t * val, uint64_t length) { unsigned char b; int r; if (length == 0 || length > 8) return -1; r = ne_io_read(io, &b, 1); if (r != 1) return r; *val = b; while (--length) { r = ne_io_read(io, &b, 1); if (r != 1) return r; *val <<= 8; *val |= b; } return 1; } static int ne_read_int(nestegg_io * io, int64_t * val, uint64_t length) { int r; uint64_t uval, base; r = ne_read_uint(io, &uval, length); if (r != 1) return r; if (length < sizeof(int64_t)) { base = 1; base <<= length * 8 - 1; if (uval >= base) { base = 1; base <<= length * 8; } else { base = 0; } *val = uval - base; } else { *val = (int64_t) uval; } return 1; } static int ne_read_float(nestegg_io * io, double * val, uint64_t length) { union { uint64_t u; struct { #if defined(__FLOAT_WORD_ORDER__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__ uint32_t _pad; float f; #else float f; uint32_t _pad; #endif } f; double d; } value; int r; /* Length == 10 not implemented. */ if (length != 4 && length != 8) return -1; r = ne_read_uint(io, &value.u, length); if (r != 1) return r; if (length == 4) *val = value.f.f; else *val = value.d; return 1; } static int ne_read_string(nestegg * ctx, char ** val, uint64_t length) { char * str; int r; if (length > LIMIT_STRING) return -1; str = ne_pool_alloc(length + 1, ctx->alloc_pool); if (!str) return -1; if (length) { r = ne_io_read(ctx->io, (unsigned char *) str, length); if (r != 1) return r; } str[length] = '\0'; *val = str; return 1; } static int ne_read_binary(nestegg * ctx, struct ebml_binary * val, uint64_t length) { if (length == 0 || length > LIMIT_BINARY) return -1; val->data = ne_pool_alloc(length, ctx->alloc_pool); if (!val->data) return -1; val->length = length; return ne_io_read(ctx->io, val->data, length); } static int ne_get_uint(struct ebml_type type, uint64_t * value) { if (!type.read) return -1; assert(type.type == TYPE_UINT); *value = type.v.u; return 0; } static int ne_get_float(struct ebml_type type, double * value) { if (!type.read) return -1; assert(type.type == TYPE_FLOAT); *value = type.v.f; return 0; } static int ne_get_string(struct ebml_type type, char ** value) { if (!type.read) return -1; assert(type.type == TYPE_STRING); *value = type.v.s; return 0; } static int ne_get_binary(struct ebml_type type, struct ebml_binary * value) { if (!type.read) return -1; assert(type.type == TYPE_BINARY); *value = type.v.b; return 0; } static int ne_is_ancestor_element(uint64_t id, struct list_node * ancestor) { struct ebml_element_desc * element; for (; ancestor; ancestor = ancestor->previous) for (element = ancestor->node; element->id; ++element) if (element->id == id) return 1; return 0; } static struct ebml_element_desc * ne_find_element(uint64_t id, struct ebml_element_desc * elements) { struct ebml_element_desc * element; for (element = elements; element->id; ++element) if (element->id == id) return element; return NULL; } static int ne_ctx_push(nestegg * ctx, struct ebml_element_desc * ancestor, void * data) { struct list_node * item; item = ne_alloc(sizeof(*item)); if (!item) return -1; item->previous = ctx->ancestor; item->node = ancestor; item->data = data; ctx->ancestor = item; return 0; } static void ne_ctx_pop(nestegg * ctx) { struct list_node * item; item = ctx->ancestor; ctx->ancestor = item->previous; free(item); } static int ne_ctx_save(nestegg * ctx, struct saved_state * s) { s->stream_offset = ne_io_tell(ctx->io); if (s->stream_offset < 0) return -1; s->last_id = ctx->last_id; s->last_size = ctx->last_size; s->last_valid = ctx->last_valid; return 0; } static int ne_ctx_restore(nestegg * ctx, struct saved_state * s) { int r; if (s->stream_offset < 0) return -1; r = ne_io_seek(ctx->io, s->stream_offset, NESTEGG_SEEK_SET); if (r != 0) return -1; ctx->last_id = s->last_id; ctx->last_size = s->last_size; ctx->last_valid = s->last_valid; return 0; } static int ne_peek_element(nestegg * ctx, uint64_t * id, uint64_t * size) { int r; if (ctx->last_valid) { if (id) *id = ctx->last_id; if (size) *size = ctx->last_size; return 1; } r = ne_read_id(ctx->io, &ctx->last_id, NULL); if (r != 1) return r; r = ne_read_vint(ctx->io, &ctx->last_size, NULL); if (r != 1) return r; if (id) *id = ctx->last_id; if (size) *size = ctx->last_size; ctx->last_valid = 1; return 1; } static int ne_read_element(nestegg * ctx, uint64_t * id, uint64_t * size) { int r; r = ne_peek_element(ctx, id, size); if (r != 1) return r; ctx->last_valid = 0; return 1; } static int ne_read_master(nestegg * ctx, struct ebml_element_desc * desc) { struct ebml_list * list; struct ebml_list_node * node, * oldtail; assert(desc->type == TYPE_MASTER && desc->flags & DESC_FLAG_MULTI); ctx->log(ctx, NESTEGG_LOG_DEBUG, "multi master element %llx (%s)", desc->id, desc->name); list = (struct ebml_list *) (ctx->ancestor->data + desc->offset); node = ne_pool_alloc(sizeof(*node), ctx->alloc_pool); if (!node) return -1; node->id = desc->id; node->data = ne_pool_alloc(desc->size, ctx->alloc_pool); if (!node->data) return -1; oldtail = list->tail; if (oldtail) oldtail->next = node; list->tail = node; if (!list->head) list->head = node; ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p", node->data); if (ne_ctx_push(ctx, desc->children, node->data) < 0) return -1; return 0; } static int ne_read_single_master(nestegg * ctx, struct ebml_element_desc * desc) { assert(desc->type == TYPE_MASTER && !(desc->flags & DESC_FLAG_MULTI)); ctx->log(ctx, NESTEGG_LOG_DEBUG, "single master element %llx (%s)", desc->id, desc->name); ctx->log(ctx, NESTEGG_LOG_DEBUG, " -> using data %p (%u)", ctx->ancestor->data + desc->offset, desc->offset); return ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset); } static int ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length) { struct ebml_type * storage; int r = -1; storage = (struct ebml_type *) (ctx->ancestor->data + desc->offset); if (storage->read) { ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) already read, skipping %u", desc->id, desc->name, length); return ne_io_read_skip(ctx->io, length); } storage->type = desc->type; ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) -> %p (%u)", desc->id, desc->name, storage, desc->offset); switch (desc->type) { case TYPE_UINT: r = ne_read_uint(ctx->io, &storage->v.u, length); break; case TYPE_FLOAT: r = ne_read_float(ctx->io, &storage->v.f, length); break; case TYPE_STRING: r = ne_read_string(ctx, &storage->v.s, length); break; case TYPE_BINARY: r = ne_read_binary(ctx, &storage->v.b, length); break; case TYPE_MASTER: case TYPE_UNKNOWN: default: assert(0); break; } if (r == 1) storage->read = 1; return r; } static int ne_parse(nestegg * ctx, struct ebml_element_desc * top_level, int64_t max_offset) { int r; int64_t * data_offset; uint64_t id, size, peeked_id; struct ebml_element_desc * element; assert(ctx->ancestor); for (;;) { if (max_offset > 0 && ne_io_tell(ctx->io) >= max_offset) { /* Reached end of offset allowed for parsing - return gracefully */ r = 1; break; } r = ne_peek_element(ctx, &id, &size); if (r != 1) break; peeked_id = id; element = ne_find_element(id, ctx->ancestor->node); if (element) { if (element->flags & DESC_FLAG_SUSPEND) { assert(element->id == ID_CLUSTER && element->type == TYPE_MASTER); ctx->log(ctx, NESTEGG_LOG_DEBUG, "suspend parse at %llx", id); r = 1; break; } r = ne_read_element(ctx, &id, &size); if (r != 1) break; assert(id == peeked_id); if (element->flags & DESC_FLAG_OFFSET) { data_offset = (int64_t *) (ctx->ancestor->data + element->data_offset); *data_offset = ne_io_tell(ctx->io); if (*data_offset < 0) { r = -1; break; } } if (element->type == TYPE_MASTER) { if (element->flags & DESC_FLAG_MULTI) { if (ne_read_master(ctx, element) < 0) break; } else { if (ne_read_single_master(ctx, element) < 0) break; } continue; } else { r = ne_read_simple(ctx, element, size); if (r < 0) break; } } else if (ne_is_ancestor_element(id, ctx->ancestor->previous)) { ctx->log(ctx, NESTEGG_LOG_DEBUG, "parent element %llx", id); if (top_level && ctx->ancestor->node == top_level) { ctx->log(ctx, NESTEGG_LOG_DEBUG, "*** parse about to back up past top_level"); r = 1; break; } ne_ctx_pop(ctx); } else { r = ne_read_element(ctx, &id, &size); if (r != 1) break; if (id != ID_VOID && id != ID_CRC32) ctx->log(ctx, NESTEGG_LOG_DEBUG, "unknown element %llx", id); r = ne_io_read_skip(ctx->io, size); if (r != 1) break; } } if (r != 1) while (ctx->ancestor) ne_ctx_pop(ctx); return r; } static int ne_read_block_encryption(nestegg * ctx, struct track_entry const * entry, uint64_t * encoding_type, uint64_t * encryption_algo, uint64_t * encryption_mode) { struct content_encoding * encoding; struct content_encryption * encryption; struct content_enc_aes_settings * aes_settings; *encoding_type = 0; if (entry->content_encodings.content_encoding.head) { encoding = entry->content_encodings.content_encoding.head->data; if (ne_get_uint(encoding->content_encoding_type, encoding_type) != 0) return -1; if (*encoding_type == NESTEGG_ENCODING_ENCRYPTION) { /* Metadata states content is encrypted */ if (!encoding->content_encryption.head) return -1; encryption = encoding->content_encryption.head->data; if (ne_get_uint(encryption->content_enc_algo, encryption_algo) != 0) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No ContentEncAlgo element found"); return -1; } if (*encryption_algo != CONTENT_ENC_ALGO_AES) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed ContentEncAlgo used"); return -1; } if (!encryption->content_enc_aes_settings.head) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No ContentEncAESSettings element found"); return -1; } aes_settings = encryption->content_enc_aes_settings.head->data; *encryption_mode = AES_SETTINGS_CIPHER_CTR; ne_get_uint(aes_settings->aes_settings_cipher_mode, encryption_mode); if (*encryption_mode != AES_SETTINGS_CIPHER_CTR) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed AESSettingsCipherMode used"); return -1; } } } return 1; } static int ne_read_xiph_lace_value(nestegg_io * io, uint64_t * value, size_t * consumed) { int r; uint64_t lace; r = ne_read_uint(io, &lace, 1); if (r != 1) return r; *consumed += 1; *value = lace; while (lace == 255) { r = ne_read_uint(io, &lace, 1); if (r != 1) return r; *consumed += 1; *value += lace; } return 1; } static int ne_read_xiph_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) { int r; size_t i = 0; uint64_t sum = 0; while (--n) { r = ne_read_xiph_lace_value(io, &sizes[i], read); if (r != 1) return r; sum += sizes[i]; i += 1; } if (*read + sum > block) return -1; /* Last frame is the remainder of the block. */ sizes[i] = block - *read - sum; return 1; } static int ne_read_ebml_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes) { int r; uint64_t lace, sum, length; int64_t slace; size_t i = 0; r = ne_read_vint(io, &lace, &length); if (r != 1) return r; *read += length; sizes[i] = lace; sum = sizes[i]; i += 1; n -= 1; while (--n) { r = ne_read_svint(io, &slace, &length); if (r != 1) return r; *read += length; sizes[i] = sizes[i - 1] + slace; sum += sizes[i]; i += 1; } if (*read + sum > block) return -1; /* Last frame is the remainder of the block. */ sizes[i] = block - *read - sum; return 1; } static uint64_t ne_get_timecode_scale(nestegg * ctx) { uint64_t scale; if (ne_get_uint(ctx->segment.info.timecode_scale, &scale) != 0) scale = 1000000; return scale; } static int ne_map_track_number_to_index(nestegg * ctx, unsigned int track_number, unsigned int * track_index) { struct ebml_list_node * node; struct track_entry * t_entry; uint64_t t_number = 0; if (!track_index) return -1; *track_index = 0; if (track_number == 0) return -1; node = ctx->segment.tracks.track_entry.head; while (node) { assert(node->id == ID_TRACK_ENTRY); t_entry = node->data; if (ne_get_uint(t_entry->number, &t_number) != 0) return -1; if (t_number == track_number) return 0; *track_index += 1; node = node->next; } return -1; } static struct track_entry * ne_find_track_entry(nestegg * ctx, unsigned int track) { struct ebml_list_node * node; unsigned int tracks = 0; node = ctx->segment.tracks.track_entry.head; while (node) { assert(node->id == ID_TRACK_ENTRY); if (track == tracks) return node->data; tracks += 1; node = node->next; } return NULL; } static struct frame * ne_alloc_frame(void) { struct frame * f = ne_alloc(sizeof(*f)); if (!f) return NULL; f->data = NULL; f->length = 0; f->frame_encryption = NULL; f->next = NULL; return f; } static struct frame_encryption * ne_alloc_frame_encryption(void) { struct frame_encryption * f = ne_alloc(sizeof(*f)); if (!f) return NULL; f->iv = NULL; f->length = 0; f->signal_byte = 0; f->num_partitions = 0; f->partition_offsets = NULL; return f; } static void ne_free_frame(struct frame * f) { if (f->frame_encryption) { free(f->frame_encryption->iv); free(f->frame_encryption->partition_offsets); } free(f->frame_encryption); free(f->data); free(f); } static int ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_packet ** data) { int r; int64_t timecode, abs_timecode; nestegg_packet * pkt; struct frame * f, * last; struct track_entry * entry; double track_scale; uint64_t track_number, length, frame_sizes[256], cluster_tc, flags, frames, tc_scale, total, encoding_type, encryption_algo, encryption_mode; unsigned int i, lacing, track; uint8_t signal_byte, keyframe = NESTEGG_PACKET_HAS_KEYFRAME_UNKNOWN, j = 0; size_t consumed = 0, data_size, encryption_size; *data = NULL; if (block_size > LIMIT_BLOCK) return -1; r = ne_read_vint(ctx->io, &track_number, &length); if (r != 1) return r; if (track_number == 0) return -1; consumed += length; r = ne_read_int(ctx->io, &timecode, 2); if (r != 1) return r; consumed += 2; r = ne_read_uint(ctx->io, &flags, 1); if (r != 1) return r; consumed += 1; frames = 0; /* Simple blocks have an explicit flag for if the contents a keyframes*/ if (block_id == ID_SIMPLE_BLOCK) keyframe = (flags & SIMPLE_BLOCK_FLAGS_KEYFRAME) == SIMPLE_BLOCK_FLAGS_KEYFRAME ? NESTEGG_PACKET_HAS_KEYFRAME_TRUE : NESTEGG_PACKET_HAS_KEYFRAME_FALSE; /* Flags are different between Block and SimpleBlock, but lacing is encoded the same way. */ lacing = (flags & BLOCK_FLAGS_LACING) >> 1; switch (lacing) { case LACING_NONE: frames = 1; break; case LACING_XIPH: case LACING_FIXED: case LACING_EBML: r = ne_read_uint(ctx->io, &frames, 1); if (r != 1) return r; consumed += 1; frames += 1; break; default: assert(0); return -1; } if (frames > 256) return -1; switch (lacing) { case LACING_NONE: frame_sizes[0] = block_size - consumed; break; case LACING_XIPH: if (frames == 1) return -1; r = ne_read_xiph_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); if (r != 1) return r; break; case LACING_FIXED: if ((block_size - consumed) % frames) return -1; for (i = 0; i < frames; ++i) frame_sizes[i] = (block_size - consumed) / frames; break; case LACING_EBML: if (frames == 1) return -1; r = ne_read_ebml_lacing(ctx->io, block_size, &consumed, frames, frame_sizes); if (r != 1) return r; break; default: assert(0); return -1; } /* Sanity check unlaced frame sizes against total block size. */ total = consumed; for (i = 0; i < frames; ++i) total += frame_sizes[i]; if (total > block_size) return -1; if (ne_map_track_number_to_index(ctx, track_number, &track) != 0) return -1; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; r = ne_read_block_encryption(ctx, entry, &encoding_type, &encryption_algo, &encryption_mode); if (r != 1) return r; /* Encryption does not support lacing */ if (lacing != LACING_NONE && encoding_type == NESTEGG_ENCODING_ENCRYPTION) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Encrypted blocks may not also be laced"); return -1; } track_scale = 1.0; tc_scale = ne_get_timecode_scale(ctx); if (tc_scale == 0) return -1; if (!ctx->read_cluster_timecode) return -1; cluster_tc = ctx->cluster_timecode; abs_timecode = timecode + cluster_tc; if (abs_timecode < 0) { /* Ignore the spec and negative timestamps */ ctx->log(ctx, NESTEGG_LOG_WARNING, "ignoring negative timecode: %lld", abs_timecode); abs_timecode = 0; } pkt = ne_alloc(sizeof(*pkt)); if (!pkt) return -1; pkt->track = track; pkt->timecode = abs_timecode * tc_scale * track_scale; pkt->keyframe = keyframe; ctx->log(ctx, NESTEGG_LOG_DEBUG, "%sblock t %lld pts %f f %llx frames: %llu", block_id == ID_BLOCK ? "" : "simple", pkt->track, pkt->timecode / 1e9, flags, frames); last = NULL; for (i = 0; i < frames; ++i) { if (frame_sizes[i] > LIMIT_FRAME) { nestegg_free_packet(pkt); return -1; } f = ne_alloc_frame(); if (!f) { nestegg_free_packet(pkt); return -1; } /* Parse encryption */ if (encoding_type == NESTEGG_ENCODING_ENCRYPTION) { r = ne_io_read(ctx->io, &signal_byte, SIGNAL_BYTE_SIZE); if (r != 1) { ne_free_frame(f); nestegg_free_packet(pkt); return r; } f->frame_encryption = ne_alloc_frame_encryption(); if (!f->frame_encryption) { ne_free_frame(f); nestegg_free_packet(pkt); return -1; } f->frame_encryption->signal_byte = signal_byte; if ((signal_byte & ENCRYPTED_BIT_MASK) == PACKET_ENCRYPTED) { f->frame_encryption->iv = ne_alloc(IV_SIZE); if (!f->frame_encryption->iv) { ne_free_frame(f); nestegg_free_packet(pkt); return -1; } r = ne_io_read(ctx->io, f->frame_encryption->iv, IV_SIZE); if (r != 1) { ne_free_frame(f); nestegg_free_packet(pkt); return r; } f->frame_encryption->length = IV_SIZE; encryption_size = SIGNAL_BYTE_SIZE + IV_SIZE; if ((signal_byte & PARTITIONED_BIT_MASK) == PACKET_PARTITIONED) { r = ne_io_read(ctx->io, &f->frame_encryption->num_partitions, NUM_PACKETS_SIZE); if (r != 1) { ne_free_frame(f); nestegg_free_packet(pkt); return r; } encryption_size += NUM_PACKETS_SIZE + f->frame_encryption->num_partitions * PACKET_OFFSET_SIZE; f->frame_encryption->partition_offsets = ne_alloc(f->frame_encryption->num_partitions * PACKET_OFFSET_SIZE); for (j = 0; j < f->frame_encryption->num_partitions; ++j) { uint64_t value = 0; r = ne_read_uint(ctx->io, &value, PACKET_OFFSET_SIZE); if (r != 1) { break; } f->frame_encryption->partition_offsets[j] = (uint32_t) value; } /* If any of the partition offsets did not return 1, then fail. */ if (j != f->frame_encryption->num_partitions) { ne_free_frame(f); nestegg_free_packet(pkt); return r; } } } else { encryption_size = SIGNAL_BYTE_SIZE; } } else { encryption_size = 0; } if (encryption_size > frame_sizes[i]) { ne_free_frame(f); nestegg_free_packet(pkt); return -1; } data_size = frame_sizes[i] - encryption_size; /* Encryption parsed */ f->data = ne_alloc(data_size); if (!f->data) { ne_free_frame(f); nestegg_free_packet(pkt); return -1; } f->length = data_size; r = ne_io_read(ctx->io, f->data, data_size); if (r != 1) { ne_free_frame(f); nestegg_free_packet(pkt); return r; } if (!last) pkt->frame = f; else last->next = f; last = f; } *data = pkt; return 1; } static int ne_read_block_additions(nestegg * ctx, uint64_t block_size, struct block_additional ** pkt_block_additional) { int r; uint64_t id, size, data_size; int64_t block_additions_end, block_more_end; void * data; int has_data; struct block_additional * block_additional; uint64_t add_id; assert(*pkt_block_additional == NULL); block_additions_end = ne_io_tell(ctx->io) + block_size; while (ne_io_tell(ctx->io) < block_additions_end) { add_id = 1; data = NULL; has_data = 0; data_size = 0; r = ne_read_element(ctx, &id, &size); if (r != 1) return r; if (id != ID_BLOCK_MORE) { /* We don't know what this element is, so skip over it */ if (id != ID_VOID && id != ID_CRC32) ctx->log(ctx, NESTEGG_LOG_DEBUG, "unknown element %llx in BlockAdditions", id); r = ne_io_read_skip(ctx->io, size); if (r != 1) return r; continue; } block_more_end = ne_io_tell(ctx->io) + size; while (ne_io_tell(ctx->io) < block_more_end) { r = ne_read_element(ctx, &id, &size); if (r != 1) { free(data); return r; } if (id == ID_BLOCK_ADD_ID) { r = ne_read_uint(ctx->io, &add_id, size); if (r != 1) { free(data); return r; } if (add_id == 0) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed BlockAddId 0 used"); free(data); return -1; } } else if (id == ID_BLOCK_ADDITIONAL) { if (has_data) { /* BlockAdditional is supposed to only occur once in a BlockMore. */ ctx->log(ctx, NESTEGG_LOG_ERROR, "Multiple BlockAdditional elements in a BlockMore"); free(data); return -1; } has_data = 1; data_size = size; if (data_size != 0 && data_size < LIMIT_FRAME) { data = ne_alloc(data_size); if (!data) return -1; r = ne_io_read(ctx->io, data, data_size); if (r != 1) { free(data); return r; } } } else { /* We don't know what this element is, so skip over it */ if (id != ID_VOID && id != ID_CRC32) ctx->log(ctx, NESTEGG_LOG_DEBUG, "unknown element %llx in BlockMore", id); r = ne_io_read_skip(ctx->io, size); if (r != 1) { free(data); return r; } } } if (has_data == 0) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No BlockAdditional element in a BlockMore"); return -1; } block_additional = ne_alloc(sizeof(*block_additional)); block_additional->next = *pkt_block_additional; block_additional->id = add_id; block_additional->data = data; block_additional->length = data_size; *pkt_block_additional = block_additional; } return 1; } static uint64_t ne_buf_read_id(unsigned char const * p, size_t length) { uint64_t id = 0; while (length--) { id <<= 8; id |= *p++; } return id; } static struct seek * ne_find_seek_for_id(struct ebml_list_node * seek_head, uint64_t id) { struct ebml_list * head; struct ebml_list_node * seek; struct ebml_binary binary_id; struct seek * s; while (seek_head) { assert(seek_head->id == ID_SEEK_HEAD); head = seek_head->data; seek = head->head; while (seek) { assert(seek->id == ID_SEEK); s = seek->data; if (ne_get_binary(s->id, &binary_id) == 0 && ne_buf_read_id(binary_id.data, binary_id.length) == id) return s; seek = seek->next; } seek_head = seek_head->next; } return NULL; } static struct cue_track_positions * ne_find_cue_position_for_track(nestegg * ctx, struct ebml_list_node * node, unsigned int track) { struct cue_track_positions * pos = NULL; uint64_t track_number; unsigned int t; while (node) { assert(node->id == ID_CUE_TRACK_POSITIONS); pos = node->data; if (ne_get_uint(pos->track, &track_number) != 0) return NULL; if (ne_map_track_number_to_index(ctx, track_number, &t) != 0) return NULL; if (t == track) return pos; node = node->next; } return NULL; } static struct cue_point * ne_find_cue_point_for_tstamp(nestegg * ctx, struct ebml_list_node * cue_point, unsigned int track, uint64_t scale, uint64_t tstamp) { uint64_t time; struct cue_point * c, * prev = NULL; while (cue_point) { assert(cue_point->id == ID_CUE_POINT); c = cue_point->data; if (!prev) prev = c; if (ne_get_uint(c->time, &time) == 0 && time * scale > tstamp) break; if (ne_find_cue_position_for_track(ctx, c->cue_track_positions.head, track) != NULL) prev = c; cue_point = cue_point->next; } return prev; } static void ne_null_log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ...) { if (ctx && severity && fmt) return; } static int ne_init_cue_points(nestegg * ctx, int64_t max_offset) { int r; struct ebml_list_node * node = ctx->segment.cues.cue_point.head; struct seek * found; uint64_t seek_pos, id; struct saved_state state; /* If there are no cues loaded, check for cues element in the seek head and load it. */ if (!node) { found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); if (!found) return -1; if (ne_get_uint(found->position, &seek_pos) != 0) return -1; /* Save old parser state. */ r = ne_ctx_save(ctx, &state); if (r != 0) return -1; /* Seek and set up parser state for segment-level element (Cues). */ r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET); if (r != 0) return -1; ctx->last_valid = 0; r = ne_read_element(ctx, &id, NULL); if (r != 1) return -1; if (id != ID_CUES) return -1; assert(ctx->ancestor == NULL); if (ne_ctx_push(ctx, ne_top_level_elements, ctx) < 0) return -1; if (ne_ctx_push(ctx, ne_segment_elements, &ctx->segment) < 0) return -1; if (ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues) < 0) return -1; /* parser will run until end of cues element. */ ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements"); r = ne_parse(ctx, ne_cues_elements, max_offset); while (ctx->ancestor) ne_ctx_pop(ctx); /* Reset parser state to original state and seek back to old position. */ if (ne_ctx_restore(ctx, &state) != 0) return -1; if (r < 0) return -1; node = ctx->segment.cues.cue_point.head; if (!node) return -1; } return 0; } /* Three functions that implement the nestegg_io interface, operating on a io_buffer. */ struct io_buffer { unsigned char const * buffer; size_t length; int64_t offset; }; static int ne_buffer_read(void * buffer, size_t length, void * userdata) { struct io_buffer * iob = userdata; size_t available = iob->length - iob->offset; if (available == 0) return 0; if (available < length) return -1; memcpy(buffer, iob->buffer + iob->offset, length); iob->offset += length; return 1; } static int ne_buffer_seek(int64_t offset, int whence, void * userdata) { struct io_buffer * iob = userdata; int64_t o = iob->offset; switch(whence) { case NESTEGG_SEEK_SET: o = offset; break; case NESTEGG_SEEK_CUR: o += offset; break; case NESTEGG_SEEK_END: o = iob->length + offset; break; } if (o < 0 || o > (int64_t) iob->length) return -1; iob->offset = o; return 0; } static int64_t ne_buffer_tell(void * userdata) { struct io_buffer * iob = userdata; return iob->offset; } static int ne_context_new(nestegg ** context, nestegg_io io, nestegg_log callback) { nestegg * ctx; if (!(io.read && io.seek && io.tell)) return -1; ctx = ne_alloc(sizeof(*ctx)); if (!ctx) return -1; ctx->io = ne_alloc(sizeof(*ctx->io)); if (!ctx->io) { nestegg_destroy(ctx); return -1; } *ctx->io = io; ctx->log = callback; ctx->alloc_pool = ne_pool_init(); if (!ctx->alloc_pool) { nestegg_destroy(ctx); return -1; } if (!ctx->log) ctx->log = ne_null_log_callback; *context = ctx; return 0; } static int ne_match_webm(nestegg_io io, int64_t max_offset) { int r; uint64_t id; char * doctype; nestegg * ctx; if (ne_context_new(&ctx, io, NULL) != 0) return -1; r = ne_peek_element(ctx, &id, NULL); if (r != 1) { nestegg_destroy(ctx); return 0; } if (id != ID_EBML) { nestegg_destroy(ctx); return 0; } if (ne_ctx_push(ctx, ne_top_level_elements, ctx) < 0) { nestegg_destroy(ctx); return -1; } /* we don't check the return value of ne_parse, that might fail because max_offset is not on a valid element end point. We only want to check the EBML ID and that the doctype is "webm". */ ne_parse(ctx, NULL, max_offset); while (ctx->ancestor) ne_ctx_pop(ctx); if (ne_get_string(ctx->ebml.doctype, &doctype) != 0 || strcmp(doctype, "webm") != 0) { nestegg_destroy(ctx); return 0; } nestegg_destroy(ctx); return 1; } static void ne_free_block_additions(struct block_additional * block_additional) { while (block_additional) { struct block_additional * tmp = block_additional; block_additional = block_additional->next; free(tmp->data); free(tmp); } } int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset) { int r; uint64_t id, version, docversion; struct ebml_list_node * track; char * doctype; nestegg * ctx; if (ne_context_new(&ctx, io, callback) != 0) return -1; r = ne_peek_element(ctx, &id, NULL); if (r != 1) { nestegg_destroy(ctx); return -1; } if (id != ID_EBML) { nestegg_destroy(ctx); return -1; } ctx->log(ctx, NESTEGG_LOG_DEBUG, "ctx %p", ctx); if (ne_ctx_push(ctx, ne_top_level_elements, ctx) < 0) { nestegg_destroy(ctx); return -1; } r = ne_parse(ctx, NULL, max_offset); while (ctx->ancestor) ne_ctx_pop(ctx); if (r != 1) { nestegg_destroy(ctx); return -1; } if (ne_get_uint(ctx->ebml.ebml_read_version, &version) != 0) version = 1; if (version != 1) { nestegg_destroy(ctx); return -1; } if (ne_get_string(ctx->ebml.doctype, &doctype) != 0) doctype = "matroska"; if (!!strcmp(doctype, "webm") && !!strcmp(doctype, "matroska")) { nestegg_destroy(ctx); return -1; } if (ne_get_uint(ctx->ebml.doctype_read_version, &docversion) != 0) docversion = 1; if (docversion < 1 || docversion > 2) { nestegg_destroy(ctx); return -1; } if (!ctx->segment.tracks.track_entry.head) { nestegg_destroy(ctx); return -1; } track = ctx->segment.tracks.track_entry.head; ctx->track_count = 0; while (track) { ctx->track_count += 1; track = track->next; } r = ne_ctx_save(ctx, &ctx->saved); if (r != 0) { nestegg_destroy(ctx); return -1; } *context = ctx; return 0; } void nestegg_destroy(nestegg * ctx) { assert(ctx->ancestor == NULL); if (ctx->alloc_pool) ne_pool_destroy(ctx->alloc_pool); free(ctx->io); free(ctx); } int nestegg_duration(nestegg * ctx, uint64_t * duration) { uint64_t tc_scale; double unscaled_duration; if (ne_get_float(ctx->segment.info.duration, &unscaled_duration) != 0) return -1; tc_scale = ne_get_timecode_scale(ctx); if (tc_scale == 0) return -1; if (unscaled_duration != unscaled_duration || unscaled_duration < 0 || unscaled_duration >= (double) UINT64_MAX || (uint64_t) unscaled_duration > UINT64_MAX / tc_scale) return -1; *duration = (uint64_t) (unscaled_duration * tc_scale); return 0; } int nestegg_tstamp_scale(nestegg * ctx, uint64_t * scale) { *scale = ne_get_timecode_scale(ctx); if (*scale == 0) return -1; return 0; } int nestegg_track_count(nestegg * ctx, unsigned int * tracks) { *tracks = ctx->track_count; return 0; } int nestegg_get_cue_point(nestegg * ctx, unsigned int cluster_num, int64_t max_offset, int64_t * start_pos, int64_t * end_pos, uint64_t * tstamp) { int range_obtained = 0; unsigned int cluster_count = 0; struct cue_point * cue_point; struct cue_track_positions * pos; uint64_t seek_pos, track_number, tc_scale, time; struct ebml_list_node * cues_node = ctx->segment.cues.cue_point.head; struct ebml_list_node * cue_pos_node = NULL; unsigned int track = 0, track_count = 0, track_index; if (!start_pos || !end_pos || !tstamp) return -1; /* Initialise return values */ *start_pos = -1; *end_pos = -1; *tstamp = 0; if (!cues_node) { ne_init_cue_points(ctx, max_offset); cues_node = ctx->segment.cues.cue_point.head; /* Verify cues have been added to context. */ if (!cues_node) return -1; } nestegg_track_count(ctx, &track_count); tc_scale = ne_get_timecode_scale(ctx); if (tc_scale == 0) return -1; while (cues_node && !range_obtained) { assert(cues_node->id == ID_CUE_POINT); cue_point = cues_node->data; cue_pos_node = cue_point->cue_track_positions.head; while (cue_pos_node) { assert(cue_pos_node->id == ID_CUE_TRACK_POSITIONS); pos = cue_pos_node->data; for (track = 0; track < track_count; ++track) { if (ne_get_uint(pos->track, &track_number) != 0) return -1; if (ne_map_track_number_to_index(ctx, track_number, &track_index) != 0) return -1; if (track_index == track) { if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) return -1; if (cluster_count == cluster_num) { *start_pos = ctx->segment_offset + seek_pos; if (ne_get_uint(cue_point->time, &time) != 0) return -1; *tstamp = time * tc_scale; } else if (cluster_count == cluster_num + 1) { *end_pos = ctx->segment_offset + seek_pos - 1; range_obtained = 1; break; } cluster_count++; } } cue_pos_node = cue_pos_node->next; } cues_node = cues_node->next; } return 0; } int nestegg_offset_seek(nestegg * ctx, uint64_t offset) { int r; if (offset > INT64_MAX) return -1; /* Seek and set up parser state for segment-level element (Cluster). */ r = ne_io_seek(ctx->io, offset, NESTEGG_SEEK_SET); if (r != 0) return -1; ctx->last_valid = 0; assert(ctx->ancestor == NULL); return 0; } int nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp) { int r; struct cue_point * cue_point; struct cue_track_positions * pos; uint64_t seek_pos, tc_scale; /* If there are no cues loaded, check for cues element in the seek head and load it. */ if (!ctx->segment.cues.cue_point.head) { r = ne_init_cue_points(ctx, -1); if (r != 0) return -1; } tc_scale = ne_get_timecode_scale(ctx); if (tc_scale == 0) return -1; cue_point = ne_find_cue_point_for_tstamp(ctx, ctx->segment.cues.cue_point.head, track, tc_scale, tstamp); if (!cue_point) return -1; pos = ne_find_cue_position_for_track(ctx, cue_point->cue_track_positions.head, track); if (pos == NULL) return -1; if (ne_get_uint(pos->cluster_position, &seek_pos) != 0) return -1; /* Seek to (we assume) the start of a Cluster element. */ r = nestegg_offset_seek(ctx, ctx->segment_offset + seek_pos); if (r != 0) return -1; return 0; } int nestegg_track_type(nestegg * ctx, unsigned int track) { struct track_entry * entry; uint64_t type; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (ne_get_uint(entry->type, &type) != 0) return -1; if (type == TRACK_TYPE_VIDEO) return NESTEGG_TRACK_VIDEO; if (type == TRACK_TYPE_AUDIO) return NESTEGG_TRACK_AUDIO; return NESTEGG_TRACK_UNKNOWN; } int nestegg_track_codec_id(nestegg * ctx, unsigned int track) { char * codec_id; struct track_entry * entry; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (ne_get_string(entry->codec_id, &codec_id) != 0) return -1; if (strcmp(codec_id, TRACK_ID_VP8) == 0) return NESTEGG_CODEC_VP8; if (strcmp(codec_id, TRACK_ID_VP9) == 0) return NESTEGG_CODEC_VP9; if (strcmp(codec_id, TRACK_ID_AV1) == 0) return NESTEGG_CODEC_AV1; if (strcmp(codec_id, TRACK_ID_VORBIS) == 0) return NESTEGG_CODEC_VORBIS; if (strcmp(codec_id, TRACK_ID_OPUS) == 0) return NESTEGG_CODEC_OPUS; return NESTEGG_CODEC_UNKNOWN; } int nestegg_track_codec_data_count(nestegg * ctx, unsigned int track, unsigned int * count) { struct track_entry * entry; struct ebml_binary codec_private; int codec_id; unsigned char * p; *count = 0; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; codec_id = nestegg_track_codec_id(ctx, track); if (codec_id == NESTEGG_CODEC_OPUS) { *count = 1; return 0; } if (codec_id != NESTEGG_CODEC_VORBIS) return -1; if (ne_get_binary(entry->codec_private, &codec_private) != 0) return -1; if (codec_private.length < 1) return -1; p = codec_private.data; *count = *p + 1; if (*count > 3) return -1; return 0; } int nestegg_track_codec_data(nestegg * ctx, unsigned int track, unsigned int item, unsigned char ** data, size_t * length) { struct track_entry * entry; struct ebml_binary codec_private; *data = NULL; *length = 0; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS) return -1; if (ne_get_binary(entry->codec_private, &codec_private) != 0) return -1; if (nestegg_track_codec_id(ctx, track) == NESTEGG_CODEC_VORBIS) { uint64_t count; uint64_t sizes[3]; size_t total; unsigned char * p; unsigned int i; int r; nestegg_io io; struct io_buffer userdata; userdata.buffer = codec_private.data; userdata.length = codec_private.length; userdata.offset = 0; io.read = ne_buffer_read; io.seek = ne_buffer_seek; io.tell = ne_buffer_tell; io.userdata = &userdata; total = 0; r = ne_read_uint(&io, &count, 1); if (r != 1) return r; total += 1; count += 1; if (count > 3) return -1; r = ne_read_xiph_lacing(&io, codec_private.length, &total, count, sizes); if (r != 1) return r; if (item >= count) return -1; p = codec_private.data + total; for (i = 0; i < item; ++i) { p += sizes[i]; } assert((size_t) (p - codec_private.data) <= codec_private.length && codec_private.length - (p - codec_private.data) >= sizes[item]); *data = p; *length = sizes[item]; } else { if (item >= 1) return -1; *data = codec_private.data; *length = codec_private.length; } return 0; } int nestegg_track_video_params(nestegg * ctx, unsigned int track, nestegg_video_params * params) { struct track_entry * entry; uint64_t value; double fvalue; memset(params, 0, sizeof(*params)); entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_VIDEO) return -1; value = 0; ne_get_uint(entry->video.stereo_mode, &value); if (value <= NESTEGG_VIDEO_STEREO_TOP_BOTTOM || value == NESTEGG_VIDEO_STEREO_RIGHT_LEFT) params->stereo_mode = value; value = 0; ne_get_uint(entry->video.alpha_mode, &value); params->alpha_mode = value; if (ne_get_uint(entry->video.pixel_width, &value) != 0) return -1; params->width = value; if (ne_get_uint(entry->video.pixel_height, &value) != 0) return -1; params->height = value; value = 0; ne_get_uint(entry->video.pixel_crop_bottom, &value); params->crop_bottom = value; value = 0; ne_get_uint(entry->video.pixel_crop_top, &value); params->crop_top = value; value = 0; ne_get_uint(entry->video.pixel_crop_left, &value); params->crop_left = value; value = 0; ne_get_uint(entry->video.pixel_crop_right, &value); params->crop_right = value; value = params->width; ne_get_uint(entry->video.display_width, &value); params->display_width = value; value = params->height; ne_get_uint(entry->video.display_height, &value); params->display_height = value; value = 2; ne_get_uint(entry->video.colour.matrix_coefficients, &value); params->matrix_coefficients = value; value = 0; ne_get_uint(entry->video.colour.range, &value); params->range = value; value = 2; ne_get_uint(entry->video.colour.transfer_characteristics, &value); params->transfer_characteristics = value; value = 2; ne_get_uint(entry->video.colour.primaries, &value); params->primaries = value; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_r_chromacity_x, &fvalue); params->primary_r_chromacity_x = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_r_chromacity_y, &fvalue); params->primary_r_chromacity_y = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_g_chromacity_x, &fvalue); params->primary_g_chromacity_x = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_g_chromacity_y, &fvalue); params->primary_g_chromacity_y = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_b_chromacity_x, &fvalue); params->primary_b_chromacity_x = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.primary_b_chromacity_y, &fvalue); params->primary_b_chromacity_y = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.white_point_chromaticity_x, &fvalue); params->white_point_chromaticity_x = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.white_point_chromaticity_y, &fvalue); params->white_point_chromaticity_y = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.luminance_max, &fvalue); params->luminance_max = fvalue; fvalue = strtod("NaN", NULL); ne_get_float(entry->video.colour.mastering_metadata.luminance_min, &fvalue); params->luminance_min = fvalue; return 0; } int nestegg_track_audio_params(nestegg * ctx, unsigned int track, nestegg_audio_params * params) { struct track_entry * entry; uint64_t value; memset(params, 0, sizeof(*params)); entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (nestegg_track_type(ctx, track) != NESTEGG_TRACK_AUDIO) return -1; params->rate = 8000; ne_get_float(entry->audio.sampling_frequency, ¶ms->rate); value = 1; ne_get_uint(entry->audio.channels, &value); params->channels = value; value = 16; ne_get_uint(entry->audio.bit_depth, &value); params->depth = value; value = 0; ne_get_uint(entry->codec_delay, &value); params->codec_delay = value; value = 0; ne_get_uint(entry->seek_preroll, &value); params->seek_preroll = value; return 0; } int nestegg_track_encoding(nestegg * ctx, unsigned int track) { struct track_entry * entry; struct content_encoding * encoding; uint64_t encoding_value; entry = ne_find_track_entry(ctx, track); if (!entry) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No track entry found"); return -1; } if (!entry->content_encodings.content_encoding.head) { /* Default encoding is compression */ return NESTEGG_ENCODING_COMPRESSION; } encoding = entry->content_encodings.content_encoding.head->data; encoding_value = NESTEGG_ENCODING_COMPRESSION; ne_get_uint(encoding->content_encoding_type, &encoding_value); if (encoding_value != NESTEGG_ENCODING_COMPRESSION && encoding_value != NESTEGG_ENCODING_ENCRYPTION) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Invalid ContentEncoding element found"); return -1; } return encoding_value; } int nestegg_track_content_enc_key_id(nestegg * ctx, unsigned int track, unsigned char const ** content_enc_key_id, size_t * content_enc_key_id_length) { struct track_entry * entry; struct content_encoding * encoding; struct content_encryption * encryption; struct content_enc_aes_settings * aes_settings; struct nestegg_encryption_params; uint64_t value; struct ebml_binary enc_key_id; entry = ne_find_track_entry(ctx, track); if (!entry) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No track entry found"); return -1; } if (!entry->content_encodings.content_encoding.head) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No ContentEncoding element found"); return -1; } encoding = entry->content_encodings.content_encoding.head->data; value = 0; ne_get_uint(encoding->content_encoding_type, &value); if (value != NESTEGG_ENCODING_ENCRYPTION) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed ContentEncodingType found"); return -1; } if (!encoding->content_encryption.head) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No ContentEncryption element found"); return -1; } encryption = encoding->content_encryption.head->data; value = 0; ne_get_uint(encryption->content_enc_algo, &value); if (value != CONTENT_ENC_ALGO_AES) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed ContentEncAlgo found"); return -1; } if (!encryption->content_enc_aes_settings.head) { ctx->log(ctx, NESTEGG_LOG_ERROR, "No ContentEncAesSettings element found"); return -1; } aes_settings = encryption->content_enc_aes_settings.head->data; value = AES_SETTINGS_CIPHER_CTR; ne_get_uint(aes_settings->aes_settings_cipher_mode, &value); if (value != AES_SETTINGS_CIPHER_CTR) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Disallowed AESSettingCipherMode used"); return -1; } if (ne_get_binary(encryption->content_enc_key_id, &enc_key_id) != 0) { ctx->log(ctx, NESTEGG_LOG_ERROR, "Could not retrieve track ContentEncKeyId"); return -1; } *content_enc_key_id = enc_key_id.data; *content_enc_key_id_length = enc_key_id.length; return 0; } int nestegg_track_default_duration(nestegg * ctx, unsigned int track, uint64_t * duration) { struct track_entry * entry; uint64_t value; entry = ne_find_track_entry(ctx, track); if (!entry) return -1; if (ne_get_uint(entry->default_duration, &value) != 0) return -1; *duration = value; return 0; } int nestegg_read_reset(nestegg * ctx) { assert(ctx->ancestor == NULL); return ne_ctx_restore(ctx, &ctx->saved); } int nestegg_read_packet(nestegg * ctx, nestegg_packet ** pkt) { int r, read_block = 0; uint64_t id, size; *pkt = NULL; assert(ctx->ancestor == NULL); /* Prepare for read_reset to resume parsing from this point upon error. */ r = ne_ctx_save(ctx, &ctx->saved); if (r != 0) return -1; while (!read_block) { r = ne_read_element(ctx, &id, &size); if (r != 1) return r; switch (id) { case ID_CLUSTER: { r = ne_read_element(ctx, &id, &size); if (r != 1) return r; /* Matroska may place a CRC32 before the Timecode. Skip and continue parsing. */ if (id == ID_CRC32) { r = ne_io_read_skip(ctx->io, size); if (r != 1) return r; r = ne_read_element(ctx, &id, &size); if (r != 1) return r; } /* Timecode must be the first element in a Cluster, per WebM spec. */ if (id != ID_TIMECODE) return -1; r = ne_read_uint(ctx->io, &ctx->cluster_timecode, size); if (r != 1) return r; ctx->read_cluster_timecode = 1; break; } case ID_SIMPLE_BLOCK: r = ne_read_block(ctx, id, size, pkt); if (r != 1) return r; read_block = 1; break; case ID_BLOCK_GROUP: { int64_t block_group_end; uint64_t block_duration = 0; int read_block_duration = 0; int64_t discard_padding = 0; int read_discard_padding = 0; int64_t reference_block = 0; int read_reference_block = 0; struct block_additional * block_additional = NULL; uint64_t tc_scale; block_group_end = ne_io_tell(ctx->io) + size; /* Read the entire BlockGroup manually. */ while (ne_io_tell(ctx->io) < block_group_end) { r = ne_read_element(ctx, &id, &size); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } switch (id) { case ID_BLOCK: { if (*pkt) { ctx->log(ctx, NESTEGG_LOG_DEBUG, "read_packet: multiple Blocks in BlockGroup, dropping previously read Block"); nestegg_free_packet(*pkt); } r = ne_read_block(ctx, id, size, pkt); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } read_block = 1; break; } case ID_BLOCK_DURATION: { r = ne_read_uint(ctx->io, &block_duration, size); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } tc_scale = ne_get_timecode_scale(ctx); if (tc_scale == 0) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return -1; } block_duration *= tc_scale; read_block_duration = 1; break; } case ID_DISCARD_PADDING: { r = ne_read_int(ctx->io, &discard_padding, size); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } read_discard_padding = 1; break; } case ID_BLOCK_ADDITIONS: { /* There should only be one BlockAdditions; treat multiple as an error. */ if (block_additional) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return -1; } r = ne_read_block_additions(ctx, size, &block_additional); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } break; } case ID_REFERENCE_BLOCK: { r = ne_read_int(ctx->io, &reference_block, size); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } read_reference_block = 1; break; } default: /* We don't know what this element is, so skip over it */ if (id != ID_VOID && id != ID_CRC32) ctx->log(ctx, NESTEGG_LOG_DEBUG, "read_packet: unknown element %llx in BlockGroup", id); r = ne_io_read_skip(ctx->io, size); if (r != 1) { ne_free_block_additions(block_additional); if (*pkt) { nestegg_free_packet(*pkt); *pkt = NULL; } return r; } } } assert(read_block == (*pkt != NULL)); if (*pkt) { (*pkt)->duration = block_duration; (*pkt)->read_duration = read_block_duration; (*pkt)->discard_padding = discard_padding; (*pkt)->read_discard_padding = read_discard_padding; (*pkt)->reference_block = reference_block; (*pkt)->read_reference_block = read_reference_block; (*pkt)->block_additional = block_additional; if ((*pkt)->read_reference_block) /* If a packet has a reference block it contains predictive frames and no keyframes */ (*pkt)->keyframe = NESTEGG_PACKET_HAS_KEYFRAME_FALSE; } else { ne_free_block_additions(block_additional); } break; } default: ctx->log(ctx, NESTEGG_LOG_DEBUG, "read_packet: unknown element %llx", id); r = ne_io_read_skip(ctx->io, size); if (r != 1) return r; } } return 1; } void nestegg_free_packet(nestegg_packet * pkt) { struct frame * frame; while (pkt->frame) { frame = pkt->frame; pkt->frame = frame->next; ne_free_frame(frame); } ne_free_block_additions(pkt->block_additional); free(pkt); } int nestegg_packet_has_keyframe(nestegg_packet * pkt) { return pkt->keyframe; } int nestegg_packet_track(nestegg_packet * pkt, unsigned int * track) { *track = pkt->track; return 0; } int nestegg_packet_tstamp(nestegg_packet * pkt, uint64_t * tstamp) { *tstamp = pkt->timecode; return 0; } int nestegg_packet_duration(nestegg_packet * pkt, uint64_t * duration) { if (!pkt->read_duration) return -1; *duration = pkt->duration; return 0; } int nestegg_packet_discard_padding(nestegg_packet * pkt, int64_t * discard_padding) { if (!pkt->read_discard_padding) return -1; *discard_padding = pkt->discard_padding; return 0; } int nestegg_packet_reference_block(nestegg_packet * pkt, int64_t * reference_block) { if (!pkt->read_reference_block) return -1; *reference_block = pkt->reference_block; return 0; } int nestegg_packet_count(nestegg_packet * pkt, unsigned int * count) { struct frame * f = pkt->frame; *count = 0; while (f) { *count += 1; f = f->next; } return 0; } int nestegg_packet_data(nestegg_packet * pkt, unsigned int item, unsigned char ** data, size_t * length) { struct frame * f = pkt->frame; unsigned int count = 0; *data = NULL; *length = 0; while (f) { if (count == item) { *data = f->data; *length = f->length; return 0; } count += 1; f = f->next; } return -1; } int nestegg_packet_additional_data(nestegg_packet * pkt, unsigned int id, unsigned char ** data, size_t * length) { struct block_additional * a = pkt->block_additional; *data = NULL; *length = 0; while (a) { if (a->id == id) { *data = a->data; *length = a->length; return 0; } a = a->next; } return -1; } int nestegg_packet_encryption(nestegg_packet * pkt) { struct frame * f = pkt->frame; unsigned char encrypted_bit; unsigned char partitioned_bit; if (!f->frame_encryption) return NESTEGG_PACKET_HAS_SIGNAL_BYTE_FALSE; /* Should never have parsed blocks with both encryption and lacing */ assert(f->next == NULL); encrypted_bit = f->frame_encryption->signal_byte & ENCRYPTED_BIT_MASK; partitioned_bit = f->frame_encryption->signal_byte & PARTITIONED_BIT_MASK; if (encrypted_bit != PACKET_ENCRYPTED) return NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED; if (partitioned_bit == PACKET_PARTITIONED) return NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED; return NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED; } int nestegg_packet_iv(nestegg_packet * pkt, unsigned char const ** iv, size_t * length) { struct frame * f = pkt->frame; unsigned char encrypted_bit; *iv = NULL; *length = 0; if (!f->frame_encryption) return -1; /* Should never have parsed blocks with both encryption and lacing */ assert(f->next == NULL); encrypted_bit = f->frame_encryption->signal_byte & ENCRYPTED_BIT_MASK; if (encrypted_bit != PACKET_ENCRYPTED) return 0; *iv = f->frame_encryption->iv; *length = f->frame_encryption->length; return 0; } int nestegg_packet_offsets(nestegg_packet * pkt, uint32_t const ** partition_offsets, uint8_t * num_partitions) { struct frame * f = pkt->frame; unsigned char encrypted_bit; unsigned char partitioned_bit; *partition_offsets = NULL; *num_partitions = 0; if (!f->frame_encryption) return -1; /* Should never have parsed blocks with both encryption and lacing */ assert(f->next == NULL); encrypted_bit = f->frame_encryption->signal_byte & ENCRYPTED_BIT_MASK; partitioned_bit = f->frame_encryption->signal_byte & PARTITIONED_BIT_MASK; if (encrypted_bit != PACKET_ENCRYPTED || partitioned_bit != PACKET_PARTITIONED) return -1; *num_partitions = f->frame_encryption->num_partitions; *partition_offsets = f->frame_encryption->partition_offsets; return 0; } int nestegg_has_cues(nestegg * ctx) { return ctx->segment.cues.cue_point.head || ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES); } int nestegg_sniff(unsigned char const * buffer, size_t length) { nestegg_io io; struct io_buffer userdata; userdata.buffer = buffer; userdata.length = length; userdata.offset = 0; io.read = ne_buffer_read; io.seek = ne_buffer_seek; io.tell = ne_buffer_tell; io.userdata = &userdata; return ne_match_webm(io, length); } kew/include/nestegg/nestegg.h000066400000000000000000000573461512074754200165560ustar00rootroot00000000000000/* * Copyright © 2010 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ #if !defined(NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79) #define NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 #include #include #include #if defined(__cplusplus) extern "C" { #endif /** @mainpage @section intro Introduction This is the documentation for the libnestegg C API. libnestegg is a demultiplexing library for WebM media files. @section example Example code @code nestegg * demux_ctx; nestegg_init(&demux_ctx, io, NULL, -1); nestegg_packet * pkt; while ((r = nestegg_read_packet(demux_ctx, &pkt)) > 0) { unsigned int track; nestegg_packet_track(pkt, &track); // This example decodes the first track only. if (track == 0) { unsigned int chunk, chunks; nestegg_packet_count(pkt, &chunks); // Decode each chunk of data. for (chunk = 0; chunk < chunks; ++chunk) { unsigned char * data; size_t data_size; nestegg_packet_data(pkt, chunk, &data, &data_size); example_codec_decode(codec_ctx, data, data_size); } } nestegg_free_packet(pkt); } nestegg_destroy(demux_ctx); @endcode */ /** @file The libnestegg C API. */ #define NESTEGG_TRACK_VIDEO 0 /**< Track is of type video. */ #define NESTEGG_TRACK_AUDIO 1 /**< Track is of type audio. */ #define NESTEGG_TRACK_UNKNOWN INT_MAX /**< Track is of type unknown. */ #define NESTEGG_CODEC_VP8 0 /**< Track uses Google On2 VP8 codec. */ #define NESTEGG_CODEC_VORBIS 1 /**< Track uses Xiph Vorbis codec. */ #define NESTEGG_CODEC_VP9 2 /**< Track uses Google On2 VP9 codec. */ #define NESTEGG_CODEC_OPUS 3 /**< Track uses Xiph Opus codec. */ #define NESTEGG_CODEC_AV1 4 /**< Track uses AOMedia AV1 codec. */ #define NESTEGG_CODEC_UNKNOWN INT_MAX /**< Track uses unknown codec. */ #define NESTEGG_VIDEO_MONO 0 /**< Track is mono video. */ #define NESTEGG_VIDEO_STEREO_LEFT_RIGHT 1 /**< Track is side-by-side stereo video. Left first. */ #define NESTEGG_VIDEO_STEREO_BOTTOM_TOP 2 /**< Track is top-bottom stereo video. Right first. */ #define NESTEGG_VIDEO_STEREO_TOP_BOTTOM 3 /**< Track is top-bottom stereo video. Left first. */ #define NESTEGG_VIDEO_STEREO_RIGHT_LEFT 11 /**< Track is side-by-side stereo video. Right first. */ #define NESTEGG_SEEK_SET 0 /**< Seek offset relative to beginning of stream. */ #define NESTEGG_SEEK_CUR 1 /**< Seek offset relative to current position in stream. */ #define NESTEGG_SEEK_END 2 /**< Seek offset relative to end of stream. */ #define NESTEGG_LOG_DEBUG 1 /**< Debug level log message. */ #define NESTEGG_LOG_INFO 10 /**< Informational level log message. */ #define NESTEGG_LOG_WARNING 100 /**< Warning level log message. */ #define NESTEGG_LOG_ERROR 1000 /**< Error level log message. */ #define NESTEGG_LOG_CRITICAL 10000 /**< Critical level log message. */ #define NESTEGG_ENCODING_COMPRESSION 0 /**< Content encoding type is compression. */ #define NESTEGG_ENCODING_ENCRYPTION 1 /**< Content encoding type is encryption. */ #define NESTEGG_PACKET_HAS_SIGNAL_BYTE_FALSE 0 /**< Packet does not have signal byte */ #define NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED 1 /**< Packet has signal byte and is unencrypted */ #define NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED 2 /**< Packet has signal byte and is encrypted */ #define NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED 4 /**< Packet has signal byte and is partitioned */ #define NESTEGG_PACKET_HAS_KEYFRAME_FALSE 0 /**< Packet contains only keyframes. */ #define NESTEGG_PACKET_HAS_KEYFRAME_TRUE 1 /**< Packet does not contain any keyframes */ #define NESTEGG_PACKET_HAS_KEYFRAME_UNKNOWN 2 /**< Packet may or may not contain keyframes */ typedef struct nestegg nestegg; /**< Opaque handle referencing the stream state. */ typedef struct nestegg_packet nestegg_packet; /**< Opaque handle referencing a packet of data. */ /** User supplied IO context. */ typedef struct { /** User supplied read callback. @param buffer Buffer to read data into. @param length Length of supplied buffer in bytes. @param userdata The #userdata supplied by the user. @retval 1 Read succeeded. @retval 0 End of stream. @retval -1 Error. */ int (* read)(void * buffer, size_t length, void * userdata); /** User supplied seek callback. @param offset Offset within the stream to seek to. @param whence Seek direction. One of #NESTEGG_SEEK_SET, #NESTEGG_SEEK_CUR, or #NESTEGG_SEEK_END. @param userdata The #userdata supplied by the user. @retval 0 Seek succeeded. @retval -1 Error. */ int (* seek)(int64_t offset, int whence, void * userdata); /** User supplied tell callback. @param userdata The #userdata supplied by the user. @returns Current position within the stream. @retval -1 Error. */ int64_t (* tell)(void * userdata); /** User supplied pointer to be passed to the IO callbacks. */ void * userdata; } nestegg_io; /** Parameters specific to a video track. */ typedef struct { unsigned int stereo_mode; /**< Video mode. One of #NESTEGG_VIDEO_MONO, #NESTEGG_VIDEO_STEREO_LEFT_RIGHT, #NESTEGG_VIDEO_STEREO_BOTTOM_TOP, or #NESTEGG_VIDEO_STEREO_TOP_BOTTOM. */ unsigned int width; /**< Width of the video frame in pixels. */ unsigned int height; /**< Height of the video frame in pixels. */ unsigned int display_width; /**< Display width of the video frame in pixels. */ unsigned int display_height; /**< Display height of the video frame in pixels. */ unsigned int crop_bottom; /**< Pixels to crop from the bottom of the frame. */ unsigned int crop_top; /**< Pixels to crop from the top of the frame. */ unsigned int crop_left; /**< Pixels to crop from the left of the frame. */ unsigned int crop_right; /**< Pixels to crop from the right of the frame. */ unsigned int alpha_mode; /**< 1 if an additional opacity stream is available, otherwise 0. */ unsigned int matrix_coefficients; /**< See Table 4 of ISO/IEC 23001-8:2016. */ unsigned int range; /**< Clipping of color ranges. */ unsigned int transfer_characteristics; /**< See Table 3 of ISO/IEC 23091-4. */ unsigned int primaries; /**< See Table 2 of ISO/IEC 23091-4. */ double primary_r_chromacity_x; /**< Red X chromaticity coordinate per CIE 1931. NaN means element not present. */ double primary_r_chromacity_y; /**< Red Y chromaticity coordinate per CIE 1931. NaN means element not present. */ double primary_g_chromacity_x; /**< Green X chromaticity coordinate per CIE 1931. NaN means element not present. */ double primary_g_chromacity_y; /**< Green Y chromaticity coordinate per CIE 1931. NaN means element not present. */ double primary_b_chromacity_x; /**< Blue X chromaticity coordinate per CIE 1931. NaN means element not present. */ double primary_b_chromacity_y; /**< Blue Y chromaticity coordinate per CIE 1931. NaN means element not present. */ double white_point_chromaticity_x; /**< White X chromaticity coordinate per CIE 1931. NaN means element not present. */ double white_point_chromaticity_y; /**< White Y chromaticity coordinate per CIE 1931. NaN means element not present. */ double luminance_max; /**< Maximum luminance in cd/m2. NaN means element not present. */ double luminance_min; /**< Minimum luminance in cd/m2. NaN means element not present. */ } nestegg_video_params; /** Parameters specific to an audio track. */ typedef struct { double rate; /**< Sampling rate in Hz. */ unsigned int channels; /**< Number of audio channels. */ unsigned int depth; /**< Bits per sample. */ uint64_t codec_delay; /**< Nanoseconds that must be discarded from the start. */ uint64_t seek_preroll;/**< Nanoseconds that must be discarded after a seek. */ } nestegg_audio_params; /** Logging callback function pointer. */ typedef void (* nestegg_log)(nestegg * context, unsigned int severity, char const * format, ...); /** Initialize a nestegg context. During initialization the parser will read forward in the stream processing all elements until the first block of media is reached. All track metadata has been processed at this point. @param context Storage for the new nestegg context. @see nestegg_destroy @param io User supplied IO context. @param callback Optional logging callback function pointer. May be NULL. @param max_offset Optional maximum offset to be read. Set -1 to ignore. @retval 0 Success. @retval -1 Error. */ int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset); /** Destroy a nestegg context and free associated memory. @param context #nestegg context to be freed. @see nestegg_init */ void nestegg_destroy(nestegg * context); /** Query the duration of the media stream in nanoseconds. @param context Stream context initialized by #nestegg_init. @param duration Storage for the queried duration. @retval 0 Success. @retval -1 Error. */ int nestegg_duration(nestegg * context, uint64_t * duration); /** Query the tstamp scale of the media stream in nanoseconds. @note Timestamps presented by nestegg have been scaled by this value before presentation to the caller. @param context Stream context initialized by #nestegg_init. @param scale Storage for the queried scale factor. @retval 0 Success. @retval -1 Error. */ int nestegg_tstamp_scale(nestegg * context, uint64_t * scale); /** Query the number of tracks in the media stream. @param context Stream context initialized by #nestegg_init. @param tracks Storage for the queried track count. @retval 0 Success. @retval -1 Error. */ int nestegg_track_count(nestegg * context, unsigned int * tracks); /** Query the start and end offset for a particular cluster. @param context Stream context initialized by #nestegg_init. @param cluster_num Zero-based cluster number; order they appear in cues. @param max_offset Optional maximum offset to be read. Set -1 to ignore. @param start_pos Starting offset of the cluster. -1 means non-existant. @param end_pos Starting offset of the cluster. -1 means non-existant or final cluster. @param tstamp Starting timestamp of the cluster. @retval 0 Success. @retval -1 Error. */ int nestegg_get_cue_point(nestegg * context, unsigned int cluster_num, int64_t max_offset, int64_t * start_pos, int64_t * end_pos, uint64_t * tstamp); /** Seek to @a offset. Stream will seek directly to offset. Must be used to seek to the start of a cluster; the parser will not be able to understand other offsets. @param context Stream context initialized by #nestegg_init. @param offset Absolute offset in bytes. @retval 0 Success. @retval -1 Error. */ int nestegg_offset_seek(nestegg * context, uint64_t offset); /** Seek @a track to @a tstamp. Stream seek will terminate at the earliest key point in the stream at or before @a tstamp. Other tracks in the stream will output packets with unspecified but nearby timestamps. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param tstamp Absolute timestamp in nanoseconds. @retval 0 Success. @retval -1 Error. */ int nestegg_track_seek(nestegg * context, unsigned int track, uint64_t tstamp); /** Query the type specified by @a track. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @retval #NESTEGG_TRACK_VIDEO Track type is video. @retval #NESTEGG_TRACK_AUDIO Track type is audio. @retval #NESTEGG_TRACK_UNKNOWN Track type is unknown. @retval -1 Error. */ int nestegg_track_type(nestegg * context, unsigned int track); /** Query the codec ID specified by @a track. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @retval #NESTEGG_CODEC_VP8 Track codec is VP8. @retval #NESTEGG_CODEC_VP9 Track codec is VP9. @retval #NESTEGG_CODEC_AV1 Track codec is AV1. @retval #NESTEGG_CODEC_VORBIS Track codec is Vorbis. @retval #NESTEGG_CODEC_OPUS Track codec is Opus. @retval #NESTEGG_CODEC_UNKNOWN Track codec is unknown. @retval -1 Error. */ int nestegg_track_codec_id(nestegg * context, unsigned int track); /** Query the number of codec initialization chunks for @a track. Each chunk of data should be passed to the codec initialization functions in the order returned. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param count Storage for the queried chunk count. @retval 0 Success. @retval -1 Error. */ int nestegg_track_codec_data_count(nestegg * context, unsigned int track, unsigned int * count); /** Get a pointer to chunk number @a item of codec initialization data for @a track. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param item Zero based chunk item number. @param data Storage for the queried data pointer. The data is owned by the #nestegg context. @param length Storage for the queried data size. @retval 0 Success. @retval -1 Error. */ int nestegg_track_codec_data(nestegg * context, unsigned int track, unsigned int item, unsigned char ** data, size_t * length); /** Query the video parameters specified by @a track. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param params Storage for the queried video parameters. @retval 0 Success. @retval -1 Error. */ int nestegg_track_video_params(nestegg * context, unsigned int track, nestegg_video_params * params); /** Query the audio parameters specified by @a track. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param params Storage for the queried audio parameters. @retval 0 Success. @retval -1 Error. */ int nestegg_track_audio_params(nestegg * context, unsigned int track, nestegg_audio_params * params); /** Query the encoding status for @a track. If a track has multiple encodings the first will be returned. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @retval #NESTEGG_ENCODING_COMPRESSION The track is compressed, but not encrypted. @retval #NESTEGG_ENCODING_ENCRYPTION The track is encrypted and compressed. @retval -1 Error. */ int nestegg_track_encoding(nestegg * context, unsigned int track); /** Query the ContentEncKeyId for @a track. Will return an error if the track in not encrypted, or is not recognized. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param content_enc_key_id Storage for queried id. The content encryption key used. Owned by nestegg and will be freed separately. @param content_enc_key_id_length Length of the queried ContentEncKeyId in bytes. @retval 0 Success. @retval -1 Error. */ int nestegg_track_content_enc_key_id(nestegg * context, unsigned int track, unsigned char const ** content_enc_key_id, size_t * content_enc_key_id_length); /** Query the default frame duration for @a track. For a video track, this is typically the inverse of the video frame rate. @param context Stream context initialized by #nestegg_init. @param track Zero based track number. @param duration Storage for the default duration in nanoseconds. @retval 0 Success. @retval -1 Error. */ int nestegg_track_default_duration(nestegg * context, unsigned int track, uint64_t * duration); /** Reset parser state to the last valid state before nestegg_read_packet failed. @param context Stream context initialized by #nestegg_init. @retval 0 Success. @retval -1 Error. */ int nestegg_read_reset(nestegg * context); /** Read a packet of media data. A packet consists of one or more chunks of data associated with a single track. nestegg_read_packet should be called in a loop while the return value is 1 to drive the stream parser forward. @see nestegg_free_packet @param context Context returned by #nestegg_init. @param packet Storage for the returned nestegg_packet. @retval 1 Additional packets may be read in subsequent calls. @retval 0 End of stream. @retval -1 Error. */ int nestegg_read_packet(nestegg * context, nestegg_packet ** packet); /** Destroy a nestegg_packet and free associated memory. @param packet #nestegg_packet to be freed. @see nestegg_read_packet */ void nestegg_free_packet(nestegg_packet * packet); /** Query the keyframe status for a given packet. @param packet Packet initialized by #nestegg_read_packet. @retval #NESTEGG_PACKET_HAS_KEYFRAME_FALSE Packet contains no keyframes. @retval #NESTEGG_PACKET_HAS_KEYFRAME_TRUE Packet contains keyframes. @retval #NESTEGG_PACKET_HAS_KEYFRAME_UNKNOWN Unknown packet keyframe content. @retval -1 Error. */ int nestegg_packet_has_keyframe(nestegg_packet * packet); /** Query the track number of @a packet. @param packet Packet initialized by #nestegg_read_packet. @param track Storage for the queried zero based track index. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_track(nestegg_packet * packet, unsigned int * track); /** Query the timestamp in nanoseconds of @a packet. @param packet Packet initialized by #nestegg_read_packet. @param tstamp Storage for the queried timestamp in nanoseconds. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_tstamp(nestegg_packet * packet, uint64_t * tstamp); /** Query the duration in nanoseconds of @a packet. @param packet Packet initialized by #nestegg_read_packet. @param duration Storage for the queried duration in nanoseconds. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_duration(nestegg_packet * packet, uint64_t * duration); /** Query the number of data chunks contained in @a packet. @param packet Packet initialized by #nestegg_read_packet. @param count Storage for the queried chunk count. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_count(nestegg_packet * packet, unsigned int * count); /** Get a pointer to chunk number @a item of packet data. @param packet Packet initialized by #nestegg_read_packet. @param item Zero based chunk item number. @param data Storage for the queried data pointer. The data is owned by the #nestegg_packet packet. @param length Storage for the queried data size. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_data(nestegg_packet * packet, unsigned int item, unsigned char ** data, size_t * length); /** Get a pointer to additional data with identifier @a id of additional packet data. If @a id isn't present in the packet, returns -1. @param packet Packet initialized by #nestegg_read_packet. @param id Codec specific identifer. For VP8, use 1 to get a VP8 encoded frame containing an alpha channel in its Y plane. @param data Storage for the queried data pointer. The data is owned by the #nestegg_packet packet. @param length Storage for the queried data size. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_additional_data(nestegg_packet * packet, unsigned int id, unsigned char ** data, size_t * length); /** Returns discard_padding for given packet @param packet Packet initialized by #nestegg_read_packet. @param discard_padding pointer to store discard padding in. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_discard_padding(nestegg_packet * packet, int64_t * discard_padding); /** Query if a packet is encrypted. @param packet Packet initialized by #nestegg_read_packet. @retval #NESTEGG_PACKET_HAS_SIGNAL_BYTE_FALSE No signal byte, encryption information not read from packet. @retval #NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED Encrypted bit not set, encryption information not read from packet. @retval #NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED Encrypted bit set, encryption infomation read from packet. @retval #NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED Partitioned bit set, encryption and parition information read from packet. @retval -1 Error.*/ int nestegg_packet_encryption(nestegg_packet * packet); /** Query the IV for an encrypted packet. Expects a packet from an encrypted track, and will return error if given a packet that has no signal btye. @param packet Packet initialized by #nestegg_read_packet. @param iv Storage for queried iv. @param length Length of returned iv, may be 0. The data is owned by the #nestegg_packet packet. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_iv(nestegg_packet * packet, unsigned char const ** iv, size_t * length); /** Query the packet for offsets. @param packet Packet initialized by #nestegg_read_packet. @param partition_offsets Storage for queried offsets. @param num_offsets Length of returned offsets, may be 0. The data is owned by the #nestegg_packet packet. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_offsets(nestegg_packet * packet, uint32_t const ** partition_offsets, uint8_t * num_offsets); /** Returns reference_block given packet @param packet Packet initialized by #nestegg_read_packet. @param reference_block pointer to store reference block in. @retval 0 Success. @retval -1 Error. */ int nestegg_packet_reference_block(nestegg_packet * packet, int64_t * reference_block); /** Query the presence of cues. @param context Stream context initialized by #nestegg_init. @retval 0 The media has no cues. @retval 1 The media has cues. */ int nestegg_has_cues(nestegg * context); /** Try to determine if the buffer looks like the beginning of a WebM file. @param buffer A buffer containing the beginning of a media file. @param length The size of the buffer. @retval 0 The file is not a WebM file. @retval 1 The file is a WebM file. */ int nestegg_sniff(unsigned char const * buffer, size_t length); #if defined(__cplusplus) } #endif #endif /* NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 */ kew/kew.pot000066400000000000000000000161161512074754200131720ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-12-17 17:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/data/playlist.c:201 #, c-format msgid "Playlist too large to allocate.\n" msgstr "" #: src/data/playlist.c:209 #, c-format msgid "Memory allocation error.\n" msgstr "" #: src/data/playlist.c:287 #, c-format msgid "Failed to allocate memory." msgstr "" #: src/data/playlist.c:339 #, c-format msgid "Failed to open directory: %s\n" msgstr "" #: src/data/playlist.c:346 #, c-format msgid "Failed to compile regular expression\n" msgstr "" #: src/data/playlist.c:357 #, c-format msgid "Failed to scan directory: %s\n" msgstr "" #: src/data/playlist.c:641 #, c-format msgid "Music not found\n" msgstr "" #: src/ops/playlist_ops.c:921 #, c-format msgid "" "Couldn't find any songs in the special playlist. Add a song by pressing '.' " "while it's playing. \n" msgstr "" #: src/sound/sound.c:575 #, c-format msgid "" "\n" "\n" "Failed to initialize opus file.\n" msgstr "" #: src/sound/sound.c:599 #, c-format msgid "" "\n" "\n" "Failed to initialize webm file.\n" msgstr "" #: src/sys/mpris.c:1047 #, c-format msgid "Failed to own D-Bus name: %s\n" msgstr "" #: src/ui/cli.c:141 #, c-format msgid "Music Library: " msgstr "" #: src/ui/cli.c:146 #, c-format msgid "" "Is this correct? Press Enter.\n" "\n" msgstr "" #: src/ui/cli.c:148 #, c-format msgid "" "Or type a path:\n" "\n" msgstr "" #: src/ui/cli.c:155 src/ui/cli.c:179 #, c-format msgid "Error reading input.\n" msgstr "" #: src/ui/cli.c:173 #, c-format msgid "" "Type a path:\n" "\n" msgstr "" #: src/ui/search_ui.c:297 #, c-format msgid " Search: " msgstr "" #: src/ui/player_ui.c:300 #, c-format msgid "Run man kew for help.\n" msgstr "" #: src/ui/player_ui.c:523 src/ui/player_ui.c:1033 #, c-format msgid " kew version: " msgstr "" #: src/ui/player_ui.c:901 #, c-format msgid "%s Playlist|%s Library|%s Track|%s Search|%s Help" msgstr "" #: src/ui/player_ui.c:1073 #, c-format msgid " Theme: " msgstr "" #: src/ui/player_ui.c:1077 #, c-format msgid "Using " msgstr "" #: src/ui/player_ui.c:1079 #, c-format msgid "Colors " msgstr "" #: src/ui/player_ui.c:1081 #, c-format msgid "From Track Covers" msgstr "" #: src/ui/player_ui.c:1089 #, c-format msgid " Author: " msgstr "" #: src/ui/player_ui.c:1096 #, c-format msgid " · Play/Pause: %s" msgstr "" #: src/ui/player_ui.c:1099 #, c-format msgid " · Enqueue/Dequeue: %s" msgstr "" #: src/ui/player_ui.c:1102 #, c-format msgid " · Enqueue and Play: %s" msgstr "" #: src/ui/player_ui.c:1105 #, c-format msgid " · Quit: %s" msgstr "" #: src/ui/player_ui.c:1108 #, c-format msgid " · Switch tracks: %s" msgstr "" #: src/ui/player_ui.c:1110 #, c-format msgid " and %s" msgstr "" #: src/ui/player_ui.c:1114 #, c-format msgid " · Volume: %s " msgstr "" #: src/ui/player_ui.c:1115 #, c-format msgid "and %s" msgstr "" #: src/ui/player_ui.c:1118 #, c-format msgid " · Scroll: %s" msgstr "" #: src/ui/player_ui.c:1119 #, c-format msgid ", %s" msgstr "" #: src/ui/player_ui.c:1122 #, c-format msgid " · Clear List: %s" msgstr "" #: src/ui/player_ui.c:1125 #, c-format msgid " · Remove from playlist: %s" msgstr "" #: src/ui/player_ui.c:1128 #, c-format msgid " · Move songs: %s" msgstr "" #: src/ui/player_ui.c:1129 #, c-format msgid "/%s" msgstr "" #: src/ui/player_ui.c:1132 #, c-format msgid " · Change View: %s or " msgstr "" #: src/ui/player_ui.c:1140 #, c-format msgid " or click the footer" msgstr "" #: src/ui/player_ui.c:1144 #, c-format msgid " · Cycle Color Mode: %s (default theme, theme or cover colors)" msgstr "" #: src/ui/player_ui.c:1148 #, c-format msgid " · Cycle Themes: %s" msgstr "" #: src/ui/player_ui.c:1156 #, c-format msgid " · Stop: %s" msgstr "" #: src/ui/player_ui.c:1159 #, c-format msgid " · Update Library: %s" msgstr "" #: src/ui/player_ui.c:1162 #, c-format msgid " · Sort Library: %s" msgstr "" #: src/ui/player_ui.c:1165 #, c-format msgid " · Toggle Visualizer: %s" msgstr "" #: src/ui/player_ui.c:1170 #, c-format msgid " · Toggle ASCII Cover: %s" msgstr "" #: src/ui/player_ui.c:1173 #, c-format msgid " · Toggle Lyrics Page on Track View: %s" msgstr "" #: src/ui/player_ui.c:1176 #, c-format msgid " · Toggle Notifications: %s" msgstr "" #: src/ui/player_ui.c:1179 #, c-format msgid " · Cycle Repeat: %s (repeat/repeat list/off)" msgstr "" #: src/ui/player_ui.c:1183 #, c-format msgid " · Shuffle: %s" msgstr "" #: src/ui/player_ui.c:1186 #, c-format msgid " · Seek: %s and" msgstr "" #: src/ui/player_ui.c:1187 #, c-format msgid " %s" msgstr "" #: src/ui/player_ui.c:1190 #, c-format msgid " · Export Playlist: %s (named after the first song)" msgstr "" #: src/ui/player_ui.c:1194 #, c-format msgid " · Add Song To 'kew favorites.m3u': %s (run with 'kew .')" msgstr "" #: src/ui/player_ui.c:1200 #, c-format msgid " Project URL: " msgstr "" #: src/ui/player_ui.c:1206 #, c-format msgid " Please Donate: " msgstr "" #: src/ui/player_ui.c:1419 #, c-format msgid " Select:↑/↓ or k/j." msgstr "" #: src/ui/player_ui.c:1420 #, c-format msgid " Accept:%s." msgstr "" #: src/ui/player_ui.c:1421 #, c-format msgid " Clear:%s." msgstr "" #: src/ui/player_ui.c:1426 src/ui/player_ui.c:2103 #, c-format msgid " Scroll:PgUp/PgDn." msgstr "" #: src/ui/player_ui.c:1428 src/ui/player_ui.c:2105 #, c-format msgid " Scroll:Fn+↑/↓." msgstr "" #: src/ui/player_ui.c:1430 #, c-format msgid " Remove:%s." msgstr "" #: src/ui/player_ui.c:1431 #, c-format msgid " Move songs:%s" msgstr "" #: src/ui/player_ui.c:1432 #, c-format msgid "/%s." msgstr "" #: src/ui/player_ui.c:1469 src/ui/player_ui.c:2096 #, c-format msgid " Select:↑/↓." msgstr "" #: src/ui/player_ui.c:1470 #, c-format msgid " Enqueue:%s." msgstr "" #: src/ui/player_ui.c:1471 src/ui/player_ui.c:2098 #, c-format msgid " Play:%s." msgstr "" #: src/ui/player_ui.c:1521 #, c-format msgid " ─ PLAYLIST ─" msgstr "" #: src/ui/player_ui.c:1845 msgid "─ MUSIC LIBRARY ─" msgstr "" #: src/ui/player_ui.c:2097 #, c-format msgid " Enqueue/Dequeue:%s." msgstr "" #: src/ui/player_ui.c:2107 #, c-format msgid " Update:%s." msgstr "" #: src/ui/player_ui.c:2108 #, c-format msgid " Sort:%s." msgstr "" #: src/ui/player_ui.c:2206 #, c-format msgid " No lyrics available. Press %s to go back." msgstr "" #: src/kew.c:389 msgid " [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help]" msgstr "" #: src/kew.c:563 #, c-format msgid "No Music found.\n" msgstr "" #: src/kew.c:564 #, c-format msgid "Please make sure the path is set correctly. \n" msgstr "" #: src/kew.c:565 #, c-format msgid "To set it type: kew path \"/path/to/Music\". \n" msgstr "" #: src/kew.c:567 #, c-format msgid "Music not found.\n" msgstr "" #: src/kew.c:571 #, c-format msgid "%s\n" msgstr "" kew/locale/000077500000000000000000000000001512074754200131125ustar00rootroot00000000000000kew/locale/ja/000077500000000000000000000000001512074754200135045ustar00rootroot00000000000000kew/locale/ja/LC_MESSAGES/000077500000000000000000000000001512074754200152715ustar00rootroot00000000000000kew/locale/ja/LC_MESSAGES/kew.mo000066400000000000000000000154621512074754200164240ustar00rootroot00000000000000Rm<"" 7CTh l x  *  3F^ oy 4: 3 ?F -    4 / B U b       ( + H b y  1     a 7 M %h       4 D U g x -  , ,3:DP88 .L P ^i+x: 2M ^  Rc%jD 'ZMy0+N='''Fn!~';LOP=;0y43-<A!~D"!"A"d/CL@HP%  F5Q9#8(  $0+"C /=@-IJ%1N:>.?4&E* L3'<7O R2,HDMGPA 6B!)K; Failed to initialize opus file. Failed to initialize webm file. Search: kew version: ─ PLAYLIST ─ %s Accept:%s. Author: Clear:%s. Enqueue/Dequeue:%s. Enqueue:%s. Move songs:%s No lyrics available. Press %s to go back. Play:%s. Please Donate: Project URL: Remove:%s. Scroll:Fn+↑/↓. Scroll:PgUp/PgDn. Select:↑/↓ or k/j. Select:↑/↓. Sort:%s. Theme: Update:%s. [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help] and %s or click the footer · Add Song To 'kew favorites.m3u': %s (run with 'kew .') · Change View: %s or · Clear List: %s · Cycle Color Mode: %s (default theme, theme or cover colors) · Cycle Repeat: %s (repeat/repeat list/off) · Cycle Themes: %s · Enqueue and Play: %s · Enqueue/Dequeue: %s · Export Playlist: %s (named after the first song) · Move songs: %s · Play/Pause: %s · Quit: %s · Remove from playlist: %s · Scroll: %s · Seek: %s and · Shuffle: %s · Sort Library: %s · Stop: %s · Switch tracks: %s · Toggle ASCII Cover: %s · Toggle Lyrics Page on Track View: %s · Toggle Notifications: %s · Toggle Visualizer: %s · Update Library: %s · Volume: %s %s %s Playlist|%s Library|%s Track|%s Search|%s Help, %s/%s/%s.Colors Couldn't find any songs in the special playlist. Add a song by pressing '.' while it's playing. Error reading input. Failed to allocate memory.Failed to compile regular expression Failed to open directory: %s Failed to own D-Bus name: %s Failed to scan directory: %s From Track CoversIs this correct? Press Enter. Memory allocation error. Music Library: Music not found Music not found. No Music found. Or type a path: Playlist too large to allocate. Please make sure the path is set correctly. Run man kew for help. To set it type: kew path "/path/to/Music". Type a path: Using and %s─ MUSIC LIBRARY ─Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: 2025-11-05 15:05+0100 Last-Translator: FULL NAME Language-Team: Japanese Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; Opus ファイルの初期化に失敗しました。 WebM ファイルの初期化に失敗しました。 検索: kew バージョン: ─ プレイリスト ─ %s 承認:%s。 作者:クリア:%s · プレイリストに追加/削除:%s 曲を追加:%s。 曲を移動する:%s 歌詞はありません。%s を押して戻ります。 遊ぶ:%s 寄付のお願い: プロジェクト URL: 取り除く: スクロール:Fn+↑/↓。 スクロール:PgUp/PgDn。 選択:↑/↓またはk/j。 選択:↑/↓。 並べ替え:%s。 テーマ:アップデート:%s。 [F2 プレイリスト|F3 ライブラリ|F4 トラック|F5 検索|F6 ヘルプ]と %s またはフッターをクリック · 曲を 'kew favorites.m3u' に追加:%s('kew .' で実行) · 表示切替:%s または · プレイリストをクリア:%s · カラーモード切替:%s(デフォルトテーマ、テーマ、カバー色) · リピートモード:%s(繰り返し/リスト繰り返し/オフ) · テーマ切替:%s · プレイリストに追加して再生:%s · プレイリストに追加/削除:%s · プレイリストをエクスポート:%s(最初の曲名を使用) · 曲を移動:%s · 再生/一時停止:%s · 終了:%s · プレイリストから削除:%s · スクロール:%s · シーク:%s と · シャッフル再生:%s · ライブラリを並べ替え:%s · 停止:%s · トラック切り替え:%s · ASCII カバー切替:%s · 歌詞ページ切替:%s · 通知切替:%s · ビジュアライザー切替:%s · ライブラリ更新:%s · 音量:%s %s %s プレイリスト|%s ライブラリ|%s トラック|%s 検索|%s ヘルプ、%s/%s/%s.色:特殊なプレイリストに曲が見つかりませんでした。再生中に '.' を押して曲を追加してください。 入力の読み取り中にエラーが発生しました。 メモリの割り当てに失敗しました。正規表現のコンパイルに失敗しました ディレクトリを開けませんでした:%s D-Bus 名の取得に失敗しました:%s ディレクトリのスキャンに失敗しました:%s (トラックカバーから)これでよろしいですか?Enter を押してください。 メモリ割り当てエラー。 音楽ライブラリ:音楽が見つかりません 音楽が見つかりません。 音楽が見つかりません。 またはパスを入力してください: プレイリストが大きすぎて割り当てできません。 パスが正しく設定されていることを確認してください。 ヘルプを見るには man kew を実行してください。 設定するには次を入力してください:kew path "/path/to/Music"。 パスを入力してください: 使用中:と %s─ 音楽ライブラリ ─kew/locale/zh_CN/000077500000000000000000000000001512074754200141135ustar00rootroot00000000000000kew/locale/zh_CN/LC_MESSAGES/000077500000000000000000000000001512074754200157005ustar00rootroot00000000000000kew/locale/zh_CN/LC_MESSAGES/kew.mo000066400000000000000000000135021512074754200170240ustar00rootroot00000000000000Qm,"" '3DX \ h r} *  #6N _i r4~: # ?6 -v    4  2 E R o ~     (  8 R i y 1}     a ' = %X ~      $ 4 E W h z -  ,  01 b     #C&V }    &=3qxGE5N$"6/F!Vx (>]v:Q.H^{#  !.?Sg+5!)0F5P9#8(  $0+"C /=@-IJ%1N:>.?4&E* L3'<7 Q2,HDMGOA 6B!)K; Failed to initialize opus file. Failed to initialize webm file. Search: kew version: ─ PLAYLIST ─ %s Accept:%s. Author: Clear:%s. Enqueue/Dequeue:%s. Enqueue:%s. Move songs:%s No lyrics available. Press %s to go back. Play:%s. Please Donate: Project URL: Remove:%s. Scroll:Fn+↑/↓. Scroll:PgUp/PgDn. Select:↑/↓ or k/j. Select:↑/↓. Sort:%s. Theme: Update:%s. [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help] and %s or click the footer · Add Song To 'kew favorites.m3u': %s (run with 'kew .') · Change View: %s or · Clear List: %s · Cycle Color Mode: %s (default theme, theme or cover colors) · Cycle Repeat: %s (repeat/repeat list/off) · Cycle Themes: %s · Enqueue and Play: %s · Enqueue/Dequeue: %s · Export Playlist: %s (named after the first song) · Move songs: %s · Play/Pause: %s · Quit: %s · Remove from playlist: %s · Scroll: %s · Seek: %s and · Shuffle: %s · Sort Library: %s · Stop: %s · Switch tracks: %s · Toggle ASCII Cover: %s · Toggle Lyrics Page on Track View: %s · Toggle Notifications: %s · Toggle Visualizer: %s · Update Library: %s · Volume: %s %s %s Playlist|%s Library|%s Track|%s Search|%s Help, %s/%s/%s.Colors Couldn't find any songs in the special playlist. Add a song by pressing '.' while it's playing. Error reading input. Failed to allocate memory.Failed to compile regular expression Failed to open directory: %s Failed to own D-Bus name: %s Failed to scan directory: %s From Track CoversIs this correct? Press Enter. Memory allocation error. Music Library: Music not found Music not found. No Music found. Or type a path: Playlist too large to allocate. Please make sure the path is set correctly. Run man kew for help. To set it type: kew path "/path/to/Music". Using and %s─ MUSIC LIBRARY ─Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: Simplified Chinese Language: zh_CN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opus 文件初始化失败。 WebM 文件初始化失败。 搜索: kew 版本: ─ 播放列表 ─ %s 接受:%s 作者: 清除:%s 加入/移出播放列表:%s 加入/移出播放列表:%s 移动歌曲:%s 没有歌词可用。按 %s 返回。 清除:%s 捐助支持: 项目网址: 移除:%s 滚动:Fn+↑/↓ 滚动:PgUp/PgDn 选择:↑/↓ 或 k/j 选择:↑/↓ 排序:%s 主题: 更新:%s [F2 播放列表|F3 音乐库|F4 曲目|F5 搜索|F6 帮助]和 %s 或点击页脚 · 添加歌曲到 'kew favorites.m3u':%s(使用 'kew .' 运行) · 切换视图:%s 或 · 清空播放列表:%s · 切换颜色模式:%s(默认主题、主题或封面颜色) · 循环模式:%s(循环/循环列表/关闭) · 切换主题:%s · 加入播放列表并播放:%s · 加入/移出播放列表:%s · 导出播放列表:%s(以第一首歌命名) · 移动歌曲:%s · 播放/暂停:%s · 退出:%s · 从播放列表中移除:%s · 卷轴:%s · 快进/快退:%s 和 · 随机播放:%s · 排序:%s · 停止:%s · 切换曲目:%s · 切换 ASCII 封面:%s · 切换歌词页面:%s · 开关通知:%s · 开关可视化效果:%s · 更新音乐库:%s · 音量:%s %s %s 播放列表|%s 音乐库|%s 曲目|%s 搜索|%s 帮助,%s/%s/%s颜色 在特殊播放列表中未找到任何歌曲。播放时按 '.' 添加歌曲。 读取输入时出错。 内存分配失败。正则表达式编译失败 打开目录失败:%s 获取 D-Bus 名称失败:%s 扫描目录失败:%s (来自专辑封面)是否正确?按回车确认。 内存分配错误。 音乐库:未找到音乐 未找到音乐。 未找到音乐。 或者输入路径: 播放列表太大,无法分配内存。 请确保路径设置正确。 运行 man kew 获取帮助。 设置方法:键入:kew path "/path/to/Music"。 使用 和 %s─ 音乐库 ─kew/play000066400000000000000000000013201512074754200125370ustar00rootroot00000000000000#!/usr/bin/env bash # play — wrapper that installs itself on first run and calls kew # Run chmod +x play # Then ./play # You can now run commands like: play nirvana and it will automatically play all your nirvana, # which was the original intent of kew set -euo pipefail INSTALL_DIR="$HOME/.local/bin" INSTALL_PATH="$INSTALL_DIR/play" # Self-install if not already in PATH if ! command -v play >/dev/null 2>&1 || [ "$(command -v play)" != "$INSTALL_PATH" ]; then echo "Installing play to $INSTALL_PATH..." mkdir -p "$INSTALL_DIR" cp "$0" "$INSTALL_PATH" chmod +x "$INSTALL_PATH" echo "✅ Installed play. Run it again from anywhere next time." fi # kew should already be in PATH exec kew "$@" kew/po/000077500000000000000000000000001512074754200122715ustar00rootroot00000000000000kew/po/ja.po000066400000000000000000000251521512074754200132300ustar00rootroot00000000000000# kew - Music For The Shell # Copyright (C) 2022- Ravachol # This file is distributed under GPLv2, the same license as the kew package. # FIRST AUTHOR , 2025. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-12-17 17:57+0100\n" "PO-Revision-Date: 2025-11-05 15:05+0100\n" "Last-Translator: FULL NAME \n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: src/data/playlist.c:201 #, c-format msgid "Playlist too large to allocate.\n" msgstr "プレイリストが大きすぎて割り当てできません。\n" #: src/data/playlist.c:209 #, c-format msgid "Memory allocation error.\n" msgstr "メモリ割り当てエラー。\n" #: src/data/playlist.c:287 #, c-format msgid "Failed to allocate memory." msgstr "メモリの割り当てに失敗しました。" #: src/data/playlist.c:339 #, c-format msgid "Failed to open directory: %s\n" msgstr "ディレクトリを開けませんでした:%s\n" #: src/data/playlist.c:346 #, c-format msgid "Failed to compile regular expression\n" msgstr "正規表現のコンパイルに失敗しました\n" #: src/data/playlist.c:357 #, c-format msgid "Failed to scan directory: %s\n" msgstr "ディレクトリのスキャンに失敗しました:%s\n" #: src/data/playlist.c:641 #, c-format msgid "Music not found\n" msgstr "音楽が見つかりません\n" #: src/ops/playlist_ops.c:921 #, c-format msgid "" "Couldn't find any songs in the special playlist. Add a song by pressing '.' " "while it's playing. \n" msgstr "" "特殊なプレイリストに曲が見つかりませんでした。再生中に '.' を押して曲を追加し" "てください。\n" #: src/sound/sound.c:575 #, c-format msgid "" "\n" "\n" "Failed to initialize opus file.\n" msgstr "" "\n" "\n" "Opus ファイルの初期化に失敗しました。\n" #: src/sound/sound.c:599 #, c-format msgid "" "\n" "\n" "Failed to initialize webm file.\n" msgstr "" "\n" "\n" "WebM ファイルの初期化に失敗しました。\n" #: src/sys/mpris.c:1047 #, c-format msgid "Failed to own D-Bus name: %s\n" msgstr "D-Bus 名の取得に失敗しました:%s\n" #: src/ui/cli.c:141 #, c-format msgid "Music Library: " msgstr "音楽ライブラリ:" #: src/ui/cli.c:146 #, c-format msgid "" "Is this correct? Press Enter.\n" "\n" msgstr "" "これでよろしいですか?Enter を押してください。\n" "\n" #: src/ui/cli.c:148 #, c-format msgid "" "Or type a path:\n" "\n" msgstr "" "またはパスを入力してください:\n" "\n" #: src/ui/cli.c:155 src/ui/cli.c:179 #, c-format msgid "Error reading input.\n" msgstr "入力の読み取り中にエラーが発生しました。\n" #: src/ui/cli.c:173 #, c-format msgid "" "Type a path:\n" "\n" msgstr "パスを入力してください:\n" #: src/ui/search_ui.c:297 #, c-format msgid " Search: " msgstr " 検索:" #: src/ui/player_ui.c:300 #, c-format msgid "Run man kew for help.\n" msgstr "ヘルプを見るには man kew を実行してください。\n" #: src/ui/player_ui.c:523 src/ui/player_ui.c:1033 #, c-format msgid " kew version: " msgstr " kew バージョン:" #: src/ui/player_ui.c:901 #, c-format msgid "%s Playlist|%s Library|%s Track|%s Search|%s Help" msgstr "%s プレイリスト|%s ライブラリ|%s トラック|%s 検索|%s ヘルプ" #: src/ui/player_ui.c:1073 #, c-format msgid " Theme: " msgstr " テーマ:" #: src/ui/player_ui.c:1077 #, c-format msgid "Using " msgstr "使用中:" #: src/ui/player_ui.c:1079 #, c-format msgid "Colors " msgstr "色:" #: src/ui/player_ui.c:1081 #, c-format msgid "From Track Covers" msgstr "(トラックカバーから)" #: src/ui/player_ui.c:1089 #, c-format msgid " Author: " msgstr " 作者:" #: src/ui/player_ui.c:1096 #, c-format msgid " · Play/Pause: %s" msgstr " · 再生/一時停止:%s" #: src/ui/player_ui.c:1099 #, c-format msgid " · Enqueue/Dequeue: %s" msgstr " · プレイリストに追加/削除:%s" #: src/ui/player_ui.c:1102 #, c-format msgid " · Enqueue and Play: %s" msgstr " · プレイリストに追加して再生:%s" #: src/ui/player_ui.c:1105 #, c-format msgid " · Quit: %s" msgstr " · 終了:%s" #: src/ui/player_ui.c:1108 #, c-format msgid " · Switch tracks: %s" msgstr " · トラック切り替え:%s" #: src/ui/player_ui.c:1110 #, c-format msgid " and %s" msgstr "と %s" #: src/ui/player_ui.c:1114 #, c-format msgid " · Volume: %s " msgstr " · 音量:%s " #: src/ui/player_ui.c:1115 #, c-format msgid "and %s" msgstr "と %s" #: src/ui/player_ui.c:1118 #, c-format msgid " · Scroll: %s" msgstr " · スクロール:%s" #: src/ui/player_ui.c:1119 #, c-format msgid ", %s" msgstr "、%s" #: src/ui/player_ui.c:1122 #, c-format msgid " · Clear List: %s" msgstr " · プレイリストをクリア:%s" #: src/ui/player_ui.c:1125 #, c-format msgid " · Remove from playlist: %s" msgstr " · プレイリストから削除:%s" #: src/ui/player_ui.c:1128 #, c-format msgid " · Move songs: %s" msgstr " · 曲を移動:%s" #: src/ui/player_ui.c:1129 #, c-format msgid "/%s" msgstr "/%s" #: src/ui/player_ui.c:1132 #, c-format msgid " · Change View: %s or " msgstr " · 表示切替:%s または " #: src/ui/player_ui.c:1140 #, c-format msgid " or click the footer" msgstr " またはフッターをクリック" #: src/ui/player_ui.c:1144 #, c-format msgid " · Cycle Color Mode: %s (default theme, theme or cover colors)" msgstr " · カラーモード切替:%s(デフォルトテーマ、テーマ、カバー色)" #: src/ui/player_ui.c:1148 #, c-format msgid " · Cycle Themes: %s" msgstr " · テーマ切替:%s" # FIXME: Enable Chroma # msgid " · Cycle Chroma Visualization: %s (requires Chroma)\n" # msgstr " · クロマ可視化切替:%s(Chroma 必須)\n" #: src/ui/player_ui.c:1156 #, c-format msgid " · Stop: %s" msgstr " · 停止:%s" #: src/ui/player_ui.c:1159 #, c-format msgid " · Update Library: %s" msgstr " · ライブラリ更新:%s" #: src/ui/player_ui.c:1162 #, c-format msgid " · Sort Library: %s" msgstr " · ライブラリを並べ替え:%s" #: src/ui/player_ui.c:1165 #, c-format msgid " · Toggle Visualizer: %s" msgstr " · ビジュアライザー切替:%s" #: src/ui/player_ui.c:1170 #, c-format msgid " · Toggle ASCII Cover: %s" msgstr " · ASCII カバー切替:%s" # FIXME: Enable Chroma # msgstr " · ASCII カバー切替:%s(Chroma 無効)\n" #: src/ui/player_ui.c:1173 #, c-format msgid " · Toggle Lyrics Page on Track View: %s" msgstr " · 歌詞ページ切替:%s" #: src/ui/player_ui.c:1176 #, c-format msgid " · Toggle Notifications: %s" msgstr " · 通知切替:%s" #: src/ui/player_ui.c:1179 #, c-format msgid " · Cycle Repeat: %s (repeat/repeat list/off)" msgstr " · リピートモード:%s(繰り返し/リスト繰り返し/オフ)" #: src/ui/player_ui.c:1183 #, c-format msgid " · Shuffle: %s" msgstr " · シャッフル再生:%s" #: src/ui/player_ui.c:1186 #, c-format msgid " · Seek: %s and" msgstr " · シーク:%s と" #: src/ui/player_ui.c:1187 #, c-format msgid " %s" msgstr " %s" #: src/ui/player_ui.c:1190 #, c-format msgid " · Export Playlist: %s (named after the first song)" msgstr " · プレイリストをエクスポート:%s(最初の曲名を使用)" #: src/ui/player_ui.c:1194 #, c-format msgid " · Add Song To 'kew favorites.m3u': %s (run with 'kew .')" msgstr " · 曲を 'kew favorites.m3u' に追加:%s('kew .' で実行)" #: src/ui/player_ui.c:1200 #, c-format msgid " Project URL: " msgstr " プロジェクト URL:" #: src/ui/player_ui.c:1206 #, c-format msgid " Please Donate: " msgstr " 寄付のお願い:" #: src/ui/player_ui.c:1419 #, c-format msgid " Select:↑/↓ or k/j." msgstr " 選択:↑/↓またはk/j。" #: src/ui/player_ui.c:1420 #, c-format msgid " Accept:%s." msgstr " 承認:%s。" #: src/ui/player_ui.c:1421 #, c-format msgid " Clear:%s." msgstr "クリア:%s" #: src/ui/player_ui.c:1426 src/ui/player_ui.c:2103 #, c-format msgid " Scroll:PgUp/PgDn." msgstr " スクロール:PgUp/PgDn。" #: src/ui/player_ui.c:1428 src/ui/player_ui.c:2105 #, c-format msgid " Scroll:Fn+↑/↓." msgstr " スクロール:Fn+↑/↓。" #: src/ui/player_ui.c:1430 #, c-format msgid " Remove:%s." msgstr " 取り除く:" #: src/ui/player_ui.c:1431 #, c-format msgid " Move songs:%s" msgstr " 曲を移動する:%s" #: src/ui/player_ui.c:1432 #, c-format msgid "/%s." msgstr "/%s." #: src/ui/player_ui.c:1469 src/ui/player_ui.c:2096 #, c-format msgid " Select:↑/↓." msgstr " 選択:↑/↓。" #: src/ui/player_ui.c:1470 #, c-format msgid " Enqueue:%s." msgstr " 曲を追加:%s。" #: src/ui/player_ui.c:1471 src/ui/player_ui.c:2098 #, c-format msgid " Play:%s." msgstr " 遊ぶ:%s" #: src/ui/player_ui.c:1521 #, c-format msgid " ─ PLAYLIST ─" msgstr " ─ プレイリスト ─" #: src/ui/player_ui.c:1845 msgid "─ MUSIC LIBRARY ─" msgstr "─ 音楽ライブラリ ─" #: src/ui/player_ui.c:2097 #, c-format msgid " Enqueue/Dequeue:%s." msgstr " · プレイリストに追加/削除:%s" #: src/ui/player_ui.c:2107 #, c-format msgid " Update:%s." msgstr "アップデート:%s。" #: src/ui/player_ui.c:2108 #, c-format msgid " Sort:%s." msgstr " 並べ替え:%s。" #: src/ui/player_ui.c:2206 #, c-format msgid " No lyrics available. Press %s to go back." msgstr " 歌詞はありません。%s を押して戻ります。" #: src/kew.c:389 msgid " [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help]" msgstr " [F2 プレイリスト|F3 ライブラリ|F4 トラック|F5 検索|F6 ヘルプ]" #: src/kew.c:563 #, c-format msgid "No Music found.\n" msgstr "音楽が見つかりません。\n" #: src/kew.c:564 #, c-format msgid "Please make sure the path is set correctly. \n" msgstr "パスが正しく設定されていることを確認してください。\n" #: src/kew.c:565 #, c-format msgid "To set it type: kew path \"/path/to/Music\". \n" msgstr "設定するには次を入力してください:kew path \"/path/to/Music\"。\n" #: src/kew.c:567 #, c-format msgid "Music not found.\n" msgstr "音楽が見つかりません。\n" #: src/kew.c:571 #, c-format msgid "%s\n" msgstr "%s\n" #, c-format #~ msgid " Manual: See" #~ msgstr " マニュアル:参照" #, c-format #~ msgid " README" #~ msgstr " README ファイル" #, c-format #~ msgid "" #~ " Or man kew\n" #~ "\n" #~ msgstr "" #~ " または man kew\n" #~ "\n" #, c-format #~ msgid " · Cycle Chroma Visualization: %s (requires Chroma)\n" #~ msgstr " · クロマ可視化切替:%s(Chroma 必須)\n" kew/po/zh_CN.po000066400000000000000000000231571512074754200136420ustar00rootroot00000000000000# kew - Music For The Shell # Copyright (C) 2022- Ravachol # This file is distributed under GPLv2, the same license as the kew package. # FIRST AUTHOR , 2025. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-12-17 17:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: Simplified Chinese \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/data/playlist.c:201 #, c-format msgid "Playlist too large to allocate.\n" msgstr "播放列表太大,无法分配内存。\n" #: src/data/playlist.c:209 #, c-format msgid "Memory allocation error.\n" msgstr "内存分配错误。\n" #: src/data/playlist.c:287 #, c-format msgid "Failed to allocate memory." msgstr "内存分配失败。" #: src/data/playlist.c:339 #, c-format msgid "Failed to open directory: %s\n" msgstr "打开目录失败:%s\n" #: src/data/playlist.c:346 #, c-format msgid "Failed to compile regular expression\n" msgstr "正则表达式编译失败\n" #: src/data/playlist.c:357 #, c-format msgid "Failed to scan directory: %s\n" msgstr "扫描目录失败:%s\n" #: src/data/playlist.c:641 #, c-format msgid "Music not found\n" msgstr "未找到音乐\n" #: src/ops/playlist_ops.c:921 #, c-format msgid "" "Couldn't find any songs in the special playlist. Add a song by pressing '.' " "while it's playing. \n" msgstr "在特殊播放列表中未找到任何歌曲。播放时按 '.' 添加歌曲。\n" #: src/sound/sound.c:575 #, c-format msgid "" "\n" "\n" "Failed to initialize opus file.\n" msgstr "" "\n" "\n" "Opus 文件初始化失败。\n" #: src/sound/sound.c:599 #, c-format msgid "" "\n" "\n" "Failed to initialize webm file.\n" msgstr "" "\n" "\n" "WebM 文件初始化失败。\n" #: src/sys/mpris.c:1047 #, c-format msgid "Failed to own D-Bus name: %s\n" msgstr "获取 D-Bus 名称失败:%s\n" #: src/ui/cli.c:141 #, c-format msgid "Music Library: " msgstr "音乐库:" #: src/ui/cli.c:146 #, c-format msgid "" "Is this correct? Press Enter.\n" "\n" msgstr "" "是否正确?按回车确认。\n" "\n" #: src/ui/cli.c:148 #, c-format msgid "" "Or type a path:\n" "\n" msgstr "" "或者输入路径:\n" "\n" #: src/ui/cli.c:155 src/ui/cli.c:179 #, c-format msgid "Error reading input.\n" msgstr "读取输入时出错。\n" #: src/ui/cli.c:173 #, c-format msgid "" "Type a path:\n" "\n" msgstr "" #: src/ui/search_ui.c:297 #, c-format msgid " Search: " msgstr " 搜索:" #: src/ui/player_ui.c:300 #, c-format msgid "Run man kew for help.\n" msgstr "运行 man kew 获取帮助。\n" #: src/ui/player_ui.c:523 src/ui/player_ui.c:1033 #, c-format msgid " kew version: " msgstr " kew 版本:" #: src/ui/player_ui.c:901 #, c-format msgid "%s Playlist|%s Library|%s Track|%s Search|%s Help" msgstr "%s 播放列表|%s 音乐库|%s 曲目|%s 搜索|%s 帮助" #: src/ui/player_ui.c:1073 #, c-format msgid " Theme: " msgstr " 主题:" #: src/ui/player_ui.c:1077 #, c-format msgid "Using " msgstr "使用 " #: src/ui/player_ui.c:1079 #, c-format msgid "Colors " msgstr "颜色 " #: src/ui/player_ui.c:1081 #, c-format msgid "From Track Covers" msgstr "(来自专辑封面)" #: src/ui/player_ui.c:1089 #, c-format msgid " Author: " msgstr " 作者:" #: src/ui/player_ui.c:1096 #, c-format msgid " · Play/Pause: %s" msgstr " · 播放/暂停:%s" #: src/ui/player_ui.c:1099 #, c-format msgid " · Enqueue/Dequeue: %s" msgstr " · 加入/移出播放列表:%s" #: src/ui/player_ui.c:1102 #, c-format msgid " · Enqueue and Play: %s" msgstr " · 加入播放列表并播放:%s" #: src/ui/player_ui.c:1105 #, c-format msgid " · Quit: %s" msgstr " · 退出:%s" #: src/ui/player_ui.c:1108 #, c-format msgid " · Switch tracks: %s" msgstr " · 切换曲目:%s" #: src/ui/player_ui.c:1110 #, c-format msgid " and %s" msgstr "和 %s" #: src/ui/player_ui.c:1114 #, c-format msgid " · Volume: %s " msgstr " · 音量:%s " #: src/ui/player_ui.c:1115 #, c-format msgid "and %s" msgstr "和 %s" #: src/ui/player_ui.c:1118 #, c-format msgid " · Scroll: %s" msgstr " · 卷轴:%s" #: src/ui/player_ui.c:1119 #, c-format msgid ", %s" msgstr ",%s" #: src/ui/player_ui.c:1122 #, c-format msgid " · Clear List: %s" msgstr " · 清空播放列表:%s" #: src/ui/player_ui.c:1125 #, c-format msgid " · Remove from playlist: %s" msgstr " · 从播放列表中移除:%s" #: src/ui/player_ui.c:1128 #, c-format msgid " · Move songs: %s" msgstr " · 移动歌曲:%s" #: src/ui/player_ui.c:1129 #, c-format msgid "/%s" msgstr "/%s" #: src/ui/player_ui.c:1132 #, c-format msgid " · Change View: %s or " msgstr " · 切换视图:%s 或 " #: src/ui/player_ui.c:1140 #, c-format msgid " or click the footer" msgstr " 或点击页脚" #: src/ui/player_ui.c:1144 #, c-format msgid " · Cycle Color Mode: %s (default theme, theme or cover colors)" msgstr " · 切换颜色模式:%s(默认主题、主题或封面颜色)" #: src/ui/player_ui.c:1148 #, c-format msgid " · Cycle Themes: %s" msgstr " · 切换主题:%s" # FIXME: Enable Chroma # msgid " · Cycle Chroma Visualization: %s (requires Chroma)\n" # msgstr " · 循环色度可视化:%s(需要 chroma)\n" #: src/ui/player_ui.c:1156 #, c-format msgid " · Stop: %s" msgstr " · 停止:%s" #: src/ui/player_ui.c:1159 #, c-format msgid " · Update Library: %s" msgstr " · 更新音乐库:%s" #: src/ui/player_ui.c:1162 #, c-format msgid " · Sort Library: %s" msgstr " · 排序:%s" #: src/ui/player_ui.c:1165 #, c-format msgid " · Toggle Visualizer: %s" msgstr " · 开关可视化效果:%s" #: src/ui/player_ui.c:1170 #, c-format msgid " · Toggle ASCII Cover: %s" msgstr " · 切换 ASCII 封面:%s" # FIXME: Enable Chroma # msgstr " · 切换 ASCII 封面:%s (禁用 Chroma) \n" #: src/ui/player_ui.c:1173 #, c-format msgid " · Toggle Lyrics Page on Track View: %s" msgstr " · 切换歌词页面:%s" #: src/ui/player_ui.c:1176 #, c-format msgid " · Toggle Notifications: %s" msgstr " · 开关通知:%s" #: src/ui/player_ui.c:1179 #, c-format msgid " · Cycle Repeat: %s (repeat/repeat list/off)" msgstr " · 循环模式:%s(循环/循环列表/关闭)" #: src/ui/player_ui.c:1183 #, c-format msgid " · Shuffle: %s" msgstr " · 随机播放:%s" #: src/ui/player_ui.c:1186 #, c-format msgid " · Seek: %s and" msgstr " · 快进/快退:%s 和" #: src/ui/player_ui.c:1187 #, c-format msgid " %s" msgstr " %s" #: src/ui/player_ui.c:1190 #, c-format msgid " · Export Playlist: %s (named after the first song)" msgstr " · 导出播放列表:%s(以第一首歌命名)" #: src/ui/player_ui.c:1194 #, c-format msgid " · Add Song To 'kew favorites.m3u': %s (run with 'kew .')" msgstr " · 添加歌曲到 'kew favorites.m3u':%s(使用 'kew .' 运行)" #: src/ui/player_ui.c:1200 #, c-format msgid " Project URL: " msgstr " 项目网址:" #: src/ui/player_ui.c:1206 #, c-format msgid " Please Donate: " msgstr " 捐助支持:" #: src/ui/player_ui.c:1419 #, c-format msgid " Select:↑/↓ or k/j." msgstr " 选择:↑/↓ 或 k/j" #: src/ui/player_ui.c:1420 #, c-format msgid " Accept:%s." msgstr " 接受:%s" #: src/ui/player_ui.c:1421 #, c-format msgid " Clear:%s." msgstr " 清除:%s" #: src/ui/player_ui.c:1426 src/ui/player_ui.c:2103 #, c-format msgid " Scroll:PgUp/PgDn." msgstr " 滚动:PgUp/PgDn" #: src/ui/player_ui.c:1428 src/ui/player_ui.c:2105 #, c-format msgid " Scroll:Fn+↑/↓." msgstr " 滚动:Fn+↑/↓" #: src/ui/player_ui.c:1430 #, c-format msgid " Remove:%s." msgstr " 移除:%s" #: src/ui/player_ui.c:1431 #, c-format msgid " Move songs:%s" msgstr " 移动歌曲:%s" #: src/ui/player_ui.c:1432 #, c-format msgid "/%s." msgstr "/%s" #: src/ui/player_ui.c:1469 src/ui/player_ui.c:2096 #, c-format msgid " Select:↑/↓." msgstr " 选择:↑/↓" #: src/ui/player_ui.c:1470 #, c-format msgid " Enqueue:%s." msgstr " 加入/移出播放列表:%s" #: src/ui/player_ui.c:1471 src/ui/player_ui.c:2098 #, c-format msgid " Play:%s." msgstr " 清除:%s" #: src/ui/player_ui.c:1521 #, c-format msgid " ─ PLAYLIST ─" msgstr " ─ 播放列表 ─" #: src/ui/player_ui.c:1845 msgid "─ MUSIC LIBRARY ─" msgstr "─ 音乐库 ─" #: src/ui/player_ui.c:2097 #, c-format msgid " Enqueue/Dequeue:%s." msgstr " 加入/移出播放列表:%s" #: src/ui/player_ui.c:2107 #, c-format msgid " Update:%s." msgstr " 更新:%s" #: src/ui/player_ui.c:2108 #, c-format msgid " Sort:%s." msgstr " 排序:%s" #: src/ui/player_ui.c:2206 #, c-format msgid " No lyrics available. Press %s to go back." msgstr " 没有歌词可用。按 %s 返回。" #: src/kew.c:389 msgid " [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help]" msgstr " [F2 播放列表|F3 音乐库|F4 曲目|F5 搜索|F6 帮助]" #: src/kew.c:563 #, c-format msgid "No Music found.\n" msgstr "未找到音乐。\n" #: src/kew.c:564 #, c-format msgid "Please make sure the path is set correctly. \n" msgstr "请确保路径设置正确。\n" #: src/kew.c:565 #, c-format msgid "To set it type: kew path \"/path/to/Music\". \n" msgstr "设置方法:键入:kew path \"/path/to/Music\"。\n" #: src/kew.c:567 #, c-format msgid "Music not found.\n" msgstr "未找到音乐。\n" #: src/kew.c:571 #, c-format msgid "%s\n" msgstr "%s\n" #, c-format #~ msgid " Manual: See" #~ msgstr " 使用手册:见" #, c-format #~ msgid " README" #~ msgstr " README 文件" #, c-format #~ msgid "" #~ " Or man kew\n" #~ "\n" #~ msgstr "" #~ " 或 man kew\n" #~ "\n" #, c-format #~ msgid " · Cycle Chroma Visualization: %s (requires Chroma)\n" #~ msgstr " · 循环色度可视化:%s(需要 chroma)\n" kew/src/000077500000000000000000000000001512074754200124425ustar00rootroot00000000000000kew/src/common/000077500000000000000000000000001512074754200137325ustar00rootroot00000000000000kew/src/common/appstate.c000066400000000000000000000102721512074754200157210ustar00rootroot00000000000000/** * @file appstate.c * @brief Provides globally accessible state structs, getters and setters. * */ #include "common/appstate.h" #include "utils/utils.h" AppState app_state; FileSystemEntry *library = NULL; PlaybackState playback_state; AudioData audio_data; AppSettings settings; // The (sometimes shuffled) sequence of songs that will be played PlayList playlist = {NULL, NULL, 0, PTHREAD_MUTEX_INITIALIZER}; // The playlist unshuffled as it appears in playlist view PlayList *unshuffled_playlist = NULL; // The playlist from kew favorites .m3u PlayList *favorites_playlist = NULL; static const char LIBRARY_FILE[] = "library.dat"; double pause_seconds = 0.0; double total_pause_seconds = 0.0; Node *next_song = NULL; Node *try_next_song = NULL; Node *song_to_start_from = NULL; ProgressBar progress_bar; void free_playlists(void) { empty_playlist(&playlist); pthread_mutex_destroy(&playlist.mutex); PlayList *unshuffled = get_unshuffled_playlist(); PlayList *favorites = get_favorites_playlist(); if (unshuffled != NULL) { empty_playlist(unshuffled); pthread_mutex_destroy(&unshuffled->mutex); free(unshuffled); unshuffled_playlist = NULL; } if (favorites != NULL) { empty_playlist(favorites); pthread_mutex_destroy(&favorites->mutex); free(favorites); favorites_playlist = NULL; } } void create_playlist(PlayList **playlist) { if (*playlist == NULL) { *playlist = malloc(sizeof(PlayList)); if (*playlist == NULL) { return; } (*playlist)->count = 0; (*playlist)->head = NULL; (*playlist)->tail = NULL; pthread_mutex_init(&(*playlist)->mutex, NULL); } } // --- Getters --- AudioData *get_audio_data(void) { return &audio_data; } AppState *get_app_state() { return &app_state; } AppSettings *get_app_settings() { return &settings; } FileSystemEntry *get_library() { return library; } PlaybackState *get_playback_state() { return &playback_state; } char *get_library_file_path(void) { return get_file_path(LIBRARY_FILE); } double get_pause_seconds(void) { return pause_seconds; } double get_total_pause_seconds(void) { return total_pause_seconds; } Node *get_next_song(void) { return next_song; } Node *get_song_to_start_from(void) { return song_to_start_from; } Node *get_try_next_song(void) { return try_next_song; } ProgressBar *get_progress_bar(void) { return &progress_bar; } PlayList *get_playlist(void) { return &playlist; } PlayList *get_unshuffled_playlist(void) { return unshuffled_playlist; } PlayList *get_favorites_playlist(void) { return favorites_playlist; } // --- Setters --- void set_audio_data(AudioData *ad) { if (ad) audio_data = *ad; } void set_library(FileSystemEntry *root) { library = root; } void set_unshuffled_playlist(PlayList *pl) { if (pl == unshuffled_playlist) { return; } if (unshuffled_playlist != NULL) { empty_playlist(unshuffled_playlist); pthread_mutex_destroy(&unshuffled_playlist->mutex); free(unshuffled_playlist); } unshuffled_playlist = pl; } void set_favorites_playlist(PlayList *pl) { if (pl == favorites_playlist) { return; } if (favorites_playlist != NULL) { empty_playlist(favorites_playlist); pthread_mutex_destroy(&favorites_playlist->mutex); free(favorites_playlist); } favorites_playlist = pl; } void set_pause_seconds(double seconds) { pause_seconds = seconds; } void set_total_pause_seconds(double seconds) { total_pause_seconds = seconds; } void set_next_song(Node *node) { next_song = node; } void set_song_to_start_from(Node *node) { song_to_start_from = node; } void set_try_next_song(Node *node) { try_next_song = node; } kew/src/common/appstate.h000066400000000000000000000363661512074754200157420ustar00rootroot00000000000000/** * @file appstate.h * @brief Provides globally accessible state structs, getters and setters. * */ #ifndef APPSTATE_H #define APPSTATE_H #include "data/lyrics.h" #include "data/playlist.h" #include "stdio.h" #include "utils/cache.h" #include #include #include #include #include #include #define _(STRING) gettext(STRING) #ifndef PATH_MAX #define PATH_MAX 4096 #endif #ifndef G_USEC_PER_SEC #define G_USEC_PER_SEC 1000000 #endif #define SONG_MAGIC 0x534F4E47 // "SONG" #define METADATA_MAX_LENGTH 256 typedef struct { unsigned char r; unsigned char g; unsigned char b; } PixelData; typedef enum { COLOR_TYPE_RGB, COLOR_TYPE_ANSI } ColorType; typedef struct { ColorType type; union { PixelData rgb; int8_t ansiIndex; // -1 to 15 for 16 colors + -1 = foreground }; } ColorValue; typedef struct { char theme_name[NAME_MAX]; char theme_author[NAME_MAX]; ColorValue accent; ColorValue text; ColorValue textDim; ColorValue textMuted; ColorValue logo; ColorValue header; ColorValue footer; ColorValue help; ColorValue link; ColorValue nowplaying; ColorValue playlist_rownum; ColorValue playlist_title; ColorValue playlist_playing; ColorValue trackview_title; ColorValue trackview_artist; ColorValue trackview_album; ColorValue trackview_year; ColorValue trackview_time; ColorValue trackview_visualizer; ColorValue trackview_lyrics; ColorValue library_artist; ColorValue library_album; ColorValue library_track; ColorValue library_enqueued; ColorValue library_playing; ColorValue search_label; ColorValue search_query; ColorValue search_result; ColorValue search_enqueued; ColorValue search_playing; ColorValue progress_filled; ColorValue progress_empty; ColorValue progress_elapsed; ColorValue progress_duration; ColorValue status_info; ColorValue status_warning; ColorValue status_error; ColorValue status_success; } Theme; typedef enum { TRACK_VIEW, HELP_VIEW, PLAYLIST_VIEW, LIBRARY_VIEW, SEARCH_VIEW } ViewState; typedef enum { COLOR_MODE_DEFAULT = 0, // Colors from ANSI 16-color palette theme COLOR_MODE_ALBUM = 1, // Colors derived from album art COLOR_MODE_THEME = 2 // Colors from truecolor theme } ColorMode; typedef struct { bool mouseEnabled; // Accept mouse input or not int mouseLeftClickAction; // Left mouse action int mouseMiddleClickAction; // Middle mouse action int mouseRightClickAction; // Right mouse action int mouseScrollUpAction; // Mouse scroll up action int mouseScrollDownAction; // Mouse scroll down action int mouseAltScrollUpAction; // Mouse scroll up + alt action int mouseAltScrollDownAction; // Mouse scroll down + alt action PixelData color; // The current color, when using album derived colors bool coverEnabled; // Show covers or not bool uiEnabled; // Show ui or not bool coverAnsi; // Show chafa cover (picture perfect in the right // terminal), or ascii/ansi typ cover bool visualizerEnabled; // Show spectrum visualizer bool hideLogo; // No kew text at top bool hideHelp; // No help text at top bool hideSideCover; bool allowNotifications; // Send desktop notifications or not int visualizer_height; // Height in characters of the spectrum visualizer int visualizer_color_type; // How colors are laid out in the spectrum // visualizer bool visualizerBrailleMode; // Display the visualizer using braille // characteres int titleDelay; // Delay when drawing title in track view int cacheLibrary; // Cache the library or not bool quitAfterStopping; // Exit kew when the music stops or not bool hideGlimmeringText; // Glimmering text on the bottom row time_t last_time_app_ran; // When did this app run last, used for updating // the cached library if it has been modified // since that time int visualizer_bar_mode; // 0=Thin bars, 1=Bars twice the width or 2=Auto // (Depends on window size, default) int replayGainCheckFirst; // Prioritize track or album replay gain // setting bool saveRepeatShuffleSettings; // Save repeat and shuffle settings // between sessions. Default on. int repeatState; // 0=disabled,1=repeat track ,2=repeat list bool shuffle_enabled; bool trackTitleAsWindowTitle; // Set the window title to the title of // the currently playing track Theme theme; // The color theme. bool themeIsSet; // Whether a theme has been loaded; char theme_name[NAME_MAX]; // the name part of .theme, usually // lowercase first character, unlike theme.name // which is taken from within the file. char themeAuthor[NAME_MAX]; ColorMode colorMode; // Which color mode to use. const char *VERSION; char *LAST_ROW; unsigned char default_color; PixelData defaultColorRGB; PixelData kewColorRGB; int chromaPreset; bool visualizations_instead_of_cover; } UISettings; typedef struct { volatile bool refresh; // Trigger a full screen refresh next update (ie // redraw cover) int chosen_node_id; // The id of the tree node that is chosen in library // view bool allowChooseSongs; // In library view, has the user entered a folder // that contains songs bool openedSubDir; // Opening a directory in an open directory. int numSongsAboveSubDir; // How many rows do we need to jump up if we // close the parent directory and open one // within int numDirectoryTreeEntries; // The number of entries in directory tree // in library view int num_progress_bars; // The number of progress dots at the bottom of // track view volatile sig_atomic_t resizeFlag; // Is the user resizing the terminal window bool resetPlaylistDisplay; // Should the playlist be reset, ie drawn // starting from playing song bool collapseView; // Signal that ui needs to collapse the view bool isFastForwarding; bool isRewinding; bool songWasRemoved; bool startFromTop; int lastNotifiedId; bool noPlaylist; struct winsize windowSize; bool showLyricsPage; FILE *logFile; FileSystemEntry *currentLibEntry; } UIState; typedef struct { Cache *tmpCache; // Cache for temporary files ViewState currentView; // The current view (playlist, library, track) // that kew is on UIState uiState; UISettings uiSettings; pthread_mutex_t data_source_mutex; pthread_mutex_t switch_mutex; } AppState; typedef struct { char *key; char *value; } KeyValuePair; typedef struct { char path[PATH_MAX]; char original_music_path[PATH_MAX]; char theme[NAME_MAX]; char ansiTheme[NAME_MAX]; char colorMode[6]; char coverEnabled[2]; char coverAnsi[2]; char useConfigColors[2]; char visualizerEnabled[2]; char visualizer_height[6]; char visualizer_color_type[2]; char titleDelay[6]; char togglePlaylist[6]; char toggleBindings[6]; char volumeUp[6]; char volumeUpAlt[6]; char volumeDown[6]; char previousTrackAlt[6]; char nextTrackAlt[6]; char scrollUpAlt[6]; char scrollDownAlt[6]; char switchNumberedSong[6]; char switchNumberedSongAlt[6]; char switchNumberedSongAlt2[6]; char toggle_pause[6]; char toggle_notifications[6]; char cycleColorsDerivedFrom[6]; char cycle_themes[6]; char toggle_visualizer[6]; char toggle_ascii[6]; char toggle_repeat[6]; char toggle_shuffle[6]; char seekBackward[6]; char seek_forward[6]; char save_playlist[6]; char add_to_favorites_playlist[6]; char update_library[6]; char quit[6]; char altQuit[6]; char hardSwitchNumberedSong[6]; char hardPlayPause[6]; char hardPrev[6]; char hardNext[6]; char hardScrollUp[6]; char hardScrollDown[6]; char hardShowPlaylist[6]; char hardShowPlaylistAlt[6]; char showPlaylistAlt[6]; char hardShowKeys[6]; char hardShowKeysAlt[6]; char showKeysAlt[6]; char hardEndOfPlaylist[6]; char hardShowLibrary[6]; char hardShowLibraryAlt[6]; char showLibraryAlt[6]; char hardShowSearch[6]; char hardShowSearchAlt[6]; char showSearchAlt[6]; char hardShowTrack[6]; char hardShowTrackAlt[6]; char showTrackAlt[6]; char nextPage[6]; char prevPage[6]; char hardRemove[6]; char hardRemove2[6]; char mouseLeftClick[12]; char mouseMiddleClick[12]; char mouseRightClick[12]; char mouseScrollUp[12]; char mouseScrollDown[12]; char mouseAltScrollUp[12]; char mouseAltScrollDown[12]; char lastVolume[12]; char allowNotifications[2]; char color[2]; char artistColor[2]; char enqueued_color[2]; char titleColor[2]; char mouseEnabled[2]; char mouseLeftClickAction[3]; char mouseMiddleClickAction[3]; char mouseRightClickAction[3]; char mouseScrollUpAction[3]; char mouseScrollDownAction[3]; char mouseAltScrollUpAction[3]; char mouseAltScrollDownAction[3]; char hideLogo[2]; char hideHelp[2]; char hideSideCover[2]; char quitAfterStopping[2]; char hideGlimmeringText[2]; char nextView[6]; char prevView[6]; char hardClearPlaylist[6]; char move_song_up[6]; char move_song_down[6]; char enqueueAndPlay[6]; char hardStop[6]; char sort_library[6]; char visualizerBrailleMode[2]; char progressBarElapsedEvenChar[12]; char progressBarElapsedOddChar[12]; char progressBarApproachingEvenChar[12]; char progressBarApproachingOddChar[12]; char progressBarCurrentEvenChar[12]; char progressBarCurrentOddChar[12]; char visualizer_bar_width[2]; char replayGainCheckFirst[2]; char saveRepeatShuffleSettings[2]; char repeatState[2]; char shuffle_enabled[2]; char trackTitleAsWindowTitle[2]; char showLyricsPage[6]; char chromaPreset[6]; } AppSettings; typedef struct { int row; int col; int length; } ProgressBar; typedef struct { char title[METADATA_MAX_LENGTH]; char artist[METADATA_MAX_LENGTH]; char album_artist[METADATA_MAX_LENGTH]; char album[METADATA_MAX_LENGTH]; char date[METADATA_MAX_LENGTH]; double replaygainTrack; double replaygainAlbum; } TagSettings; typedef struct { int magic; gchar *track_id; char file_path[PATH_MAX]; char cover_art_path[PATH_MAX]; unsigned char red; unsigned char green; unsigned char blue; TagSettings *metadata; unsigned char *cover; int avg_bit_rate; int coverWidth; int coverHeight; double duration; bool hasErrors; Lyrics *lyrics; } SongData; typedef struct { char file_path[PATH_MAX]; SongData *songdataA; SongData *songdataB; bool loadA; bool loadingFirstDecoder; pthread_mutex_t mutex; AppState *state; } LoadingThreadData; typedef struct { LoadingThreadData loadingdata; int lastPlayedId; bool skipping; bool songLoading; bool forceSkip; bool nextSongNeedsRebuilding; bool skipOutOfOrder; bool hasSilentlySwitched; bool usingSongDataA; bool clearingErrors; bool songHasErrors; bool skipFromStopped; bool waitingForNext; // Playlist has songs but playback is stopped. bool waitingForPlaylist; // Playlist is empty. bool notifySwitch; // Emit mpris song switched signal bool notifyPlaying; // Emit mpris music is playing signal volatile bool loadedNextSong; } PlaybackState; typedef struct { SongData *songdataA; SongData *songdataB; bool songdataADeleted; bool songdataBDeleted; int replayGainCheckFirst; SongData *current_song_data; ma_uint64 currentPCMFrame; } UserData; typedef struct { ma_data_source_base base; UserData *pUserData; ma_format format; ma_uint32 channels; ma_uint32 sample_rate; ma_uint64 currentPCMFrame; ma_uint32 avg_bit_rate; bool switchFiles; int currentFileIndex; ma_uint64 totalFrames; bool end_of_list_reached; bool restart; } AudioData; extern AudioData audio_data; // --- Getters --- PlaybackState *get_playback_state(void); AudioData *get_audio_data(void); double get_pause_seconds(void); double get_total_pause_seconds(void); void create_playlist(PlayList **playlist); Node *get_next_song(void); Node *get_song_to_start_from(void); Node *get_try_next_song(void); PlayList *get_playlist(void); PlayList *get_unshuffled_playlist(void); PlayList *get_favorites_playlist(void); ProgressBar *get_progress_bar(void); AppState *get_app_state(void); AppSettings *get_app_settings(void); FileSystemEntry *get_library(void); char *get_library_file_path(void); // --- Setters --- void set_pause_seconds(double seconds); void set_total_pause_seconds(double seconds); void set_next_song(Node *node); void set_song_to_start_from(Node *node); void set_try_next_song(Node *node); void set_audio_data(AudioData *audio_data); void set_library(FileSystemEntry *root); void free_playlists(void); void set_unshuffled_playlist(PlayList *pl); void set_favorites_playlist(PlayList *pl); #endif kew/src/common/common.c000066400000000000000000000023351512074754200153710ustar00rootroot00000000000000/** * @file common.h * @brief Provides common function such as errorr message handling. * */ #include "common.h" #include #include #define ERROR_MESSAGE_LENGTH 256 static char current_error_message[ERROR_MESSAGE_LENGTH]; static bool has_printed_error = true; static volatile bool refresh_triggered = true; void trigger_refresh(void) { refresh_triggered = true; } void cancel_refresh(void) { refresh_triggered = false; } bool is_refresh_triggered(void) { return (refresh_triggered == true); } void set_error_message(const char *message) { if (message == NULL) return; strncpy(current_error_message, message, ERROR_MESSAGE_LENGTH - 1); current_error_message[ERROR_MESSAGE_LENGTH - 1] = '\0'; has_printed_error = false; trigger_refresh(); } bool has_printed_error_message(void) { return has_printed_error; } bool has_error_message(void) { return (current_error_message[0] != '\0'); } void mark_error_message_as_printed(void) { has_printed_error = true; } char *get_error_message(void) { return current_error_message; } void clear_error_message(void) { current_error_message[0] = '\0'; } kew/src/common/common.h000066400000000000000000000013031512074754200153700ustar00rootroot00000000000000/** * @file common.h * @brief Provides common function such as errorr message handling. * */ #ifndef COMMON_H #define COMMON_H #include typedef enum { k_unknown = 0, k_aac = 1, k_rawAAC = 2, // Raw aac (.aac file) decoding is included here for convenience although they are not .m4a files k_ALAC = 3, k_FLAC = 4 } k_m4adec_filetype; void set_error_message(const char *message); bool has_error_message(void); void clear_error_message(void); void mark_error_message_as_printed(void); void trigger_refresh(void); void cancel_refresh(void); bool is_refresh_triggered(void); bool has_printed_error_message(void); char *get_error_message(void); #endif kew/src/common/events.h000066400000000000000000000035461512074754200154170ustar00rootroot00000000000000#ifndef EVENTS_H #define EVENTS_H #include #define MAX_SEQ_LEN 1024 // Maximum length of sequence buffer enum EventType { EVENT_NONE, EVENT_PLAY_PAUSE, EVENT_VOLUME_UP, EVENT_VOLUME_DOWN, EVENT_NEXT, EVENT_PREV, EVENT_QUIT, EVENT_TOGGLEREPEAT, EVENT_TOGGLEVISUALIZER, EVENT_TOGGLEASCII, EVENT_ADDTOFAVORITESPLAYLIST, EVENT_DELETEFROMMAINPLAYLIST, EVENT_EXPORTPLAYLIST, EVENT_UPDATELIBRARY, EVENT_SHUFFLE, EVENT_KEY_PRESS, EVENT_SHOWHELP, EVENT_SHOWPLAYLIST, EVENT_SHOWSEARCH, EVENT_ENQUEUE, EVENT_GOTOBEGINNINGOFPLAYLIST, EVENT_GOTOENDOFPLAYLIST, EVENT_CYCLECOLORMODE, EVENT_SCROLLDOWN, EVENT_SCROLLUP, EVENT_SEEKBACK, EVENT_SEEKFORWARD, EVENT_SHOWLIBRARY, EVENT_SHOWTRACK, EVENT_NEXTPAGE, EVENT_PREVPAGE, EVENT_REMOVE, EVENT_SEARCH, EVENT_NEXTVIEW, EVENT_PREVVIEW, EVENT_CLEARPLAYLIST, EVENT_MOVESONGUP, EVENT_MOVESONGDOWN, EVENT_ENQUEUEANDPLAY, EVENT_STOP, EVENT_SORTLIBRARY, EVENT_CYCLETHEMES, EVENT_CYCLEVISUALIZATION, EVENT_TOGGLENOTIFICATIONS, EVENT_SHOWLYRICSPAGE }; struct Event { enum EventType type; char key[MAX_SEQ_LEN]; // To store multi-byte characters char args[32]; }; typedef struct { char *seq; enum EventType eventType; } EventMapping; typedef struct { uint16_t key; // TB_KEY_* constants, 0 if printable uint32_t ch; // Unicode character for printable keys, 0 otherwise uint8_t mods; // MOD_CTRL | MOD_ALT | MOD_SHIFT enum EventType eventType; char args[32]; // Optional arguments like "+5%" } TBKeyBinding; #endif kew/src/data/000077500000000000000000000000001512074754200133535ustar00rootroot00000000000000kew/src/data/directorytree.c000066400000000000000000001056011512074754200164060ustar00rootroot00000000000000/** * @file directorytree.c * @brief Filesystem scanning and in-memory tree construction. * * Provides operations for recursively traversing directories and * constructing an in-memory linked tree representation of the music * library. Used by library and playlist modules to index songs efficiently. */ #include "directorytree.h" #include "common/appstate.h" #include "utils/file.h" #include "utils/utils.h" #include #include #include #include #include #include #include #include #include #include #define FSDB_MAGIC 0x46534442 // "FSDB" static int last_used_id = 0; static uint32_t DB_VERSION = 2; // Header for the DB typedef struct { uint32_t magic; uint32_t version; uint32_t entry_count; uint32_t string_table_size; uint32_t max_id; uint32_t root_full_path_offset; uint32_t root_full_path_length; } FileSystemHeader; // Per-entry on disk (fixed-size fields + offsets into string table) typedef struct { int32_t id; int32_t parent_id; int32_t is_directory; int32_t is_enqueued; uint32_t name_offset; } FileSystemEntryDisk; typedef struct { FileSystemEntry **data; size_t size; size_t capacity; } EntryArray; static void entry_array_init(EntryArray *arr) { arr->size = 0; arr->capacity = 1024; arr->data = malloc(sizeof(FileSystemEntry *) * arr->capacity); } static void entry_array_push(EntryArray *arr, FileSystemEntry *entry) { if (arr->size >= arr->capacity) { arr->capacity *= 2; arr->data = realloc(arr->data, sizeof(FileSystemEntry *) * arr->capacity); } arr->data[arr->size++] = entry; } static void collect_entries(FileSystemEntry *node, EntryArray *arr) { if (!node) return; entry_array_push(arr, node); for (FileSystemEntry *child = node->children; child; child = child->next) collect_entries(child, arr); } int write_tree_to_binary(FileSystemEntry *root, const char *filename) { if (!root || !filename) return -1; // Flatten tree EntryArray arr; entry_array_init(&arr); collect_entries(root, &arr); if (arr.size == 0) { free(arr.data); return -1; // nothing to write } // Compute max ID (for loader safety) uint32_t max_id = 0; for (size_t i = 0; i < arr.size; i++) { if ((uint32_t)arr.data[i]->id > max_id) max_id = arr.data[i]->id; } // Build string table (names only) size_t total_name_size = 0; for (size_t i = 0; i < arr.size; i++) { if (!arr.data[i]->name) continue; total_name_size += strlen(arr.data[i]->name) + 1; } char *string_table = malloc(total_name_size); if (!string_table) { free(arr.data); return -1; } FileSystemEntryDisk *disk_entries = malloc(sizeof(FileSystemEntryDisk) * arr.size); if (!disk_entries) { free(arr.data); free(string_table); return -1; } // Populate disk entries and string table size_t offset = 0; for (size_t i = 0; i < arr.size; i++) { FileSystemEntry *n = arr.data[i]; FileSystemEntryDisk *d = &disk_entries[i]; d->id = n->id; d->parent_id = n->parent_id; d->is_directory = n->is_directory; d->is_enqueued = n->is_enqueued; if (n->name) { d->name_offset = (uint32_t)offset; strcpy(&string_table[offset], n->name); offset += strlen(n->name) + 1; } else { d->name_offset = UINT32_MAX; // fallback if name is NULL } } FileSystemHeader header = { .magic = FSDB_MAGIC, .version = DB_VERSION, .entry_count = (uint32_t)arr.size, .max_id = max_id, .string_table_size = (uint32_t)total_name_size, .root_full_path_offset = 0, .root_full_path_length = 0}; header.root_full_path_offset = offset; // Store the root path size_t root_len = strlen(root->full_path) + 1; header.root_full_path_length = (uint32_t)root_len; // Increase the string table in memory string_table = realloc(string_table, offset + root_len); memcpy(string_table + offset, root->full_path, root_len); // Update final string table size header.string_table_size = (uint32_t)(offset + root_len); FILE *f = fopen(filename, "wb"); if (!f) { free(arr.data); free(disk_entries); free(string_table); return -1; } if (fwrite(&header, sizeof(header), 1, f) != 1 || fwrite(disk_entries, sizeof(FileSystemEntryDisk), arr.size, f) != arr.size || fwrite(string_table, 1, header.string_table_size, f) != header.string_table_size) { fclose(f); free(arr.data); free(disk_entries); free(string_table); return -1; } fclose(f); free(arr.data); free(disk_entries); free(string_table); return 0; } FileSystemEntry *create_entry(const char *name, int is_directory, FileSystemEntry *parent) { if (last_used_id == INT_MAX) return NULL; FileSystemEntry *new_entry = malloc(sizeof(FileSystemEntry)); if (new_entry != NULL) { new_entry->name = strdup(name); if (new_entry->name == NULL) { fprintf(stderr, "create_entry: name is null\n"); free(new_entry); return NULL; } new_entry->is_directory = is_directory; new_entry->is_enqueued = 0; new_entry->parent = parent; new_entry->children = NULL; new_entry->next = NULL; new_entry->id = ++last_used_id; if (parent != NULL) { new_entry->parent_id = parent->id; } else { new_entry->parent_id = -1; } } return new_entry; } void add_child(FileSystemEntry *parent, FileSystemEntry *child) { if (parent != NULL) { child->next = parent->children; parent->children = child; } } int is_valid_entry_name(const char *name) { if (name == NULL) return 0; size_t len = strnlen(name, PATH_MAX + 1); if (len > NAME_MAX || len > PATH_MAX) return 0; if (len == 0) return 1; // Reject "." and ".." exactly if (len == 1 && name[0] == '.') return 0; if (len == 2 && name[0] == '.' && name[1] == '.') return 0; for (size_t i = 0; i < len; ++i) { unsigned char c = (unsigned char)name[i]; // Reject path separator if (c == '/') return 0; // Reject ASCII control chars and DEL if (c <= 0x1F || c == 0x7F) return 0; } return 1; } void set_full_path(FileSystemEntry *entry, const char *parent_path, const char *entry_name) { if (entry == NULL || parent_path == NULL || entry_name == NULL) return; if (!is_valid_entry_name(entry_name)) { return; } size_t parentLen = strnlen(parent_path, PATH_MAX + 1); size_t nameLen = strnlen(entry_name, PATH_MAX + 1); if (parentLen > PATH_MAX || nameLen > PATH_MAX) { fprintf( stderr, "Parent or entry name too long or not null-terminated.\n"); return; } if (parentLen > 0 && parent_path[parentLen - 1] == '/') parentLen--; // Normalize parent path (remove trailing slash) size_t needed = parentLen + 1 + nameLen + 1; // slash + null if (needed > PATH_MAX) { fprintf(stderr, "Path too long, rejecting.\n"); return; } entry->full_path = malloc(needed); if (entry->full_path == NULL) return; if (nameLen == 0) snprintf(entry->full_path, needed, "%s", parent_path); else snprintf(entry->full_path, needed, "%.*s/%s", (int)parentLen, parent_path, entry_name); entry->full_path[needed - 1] = '\0'; // Explicit null-termination // Post-check for directory traversal patterns if (strstr(entry->full_path, "/../") != NULL || strstr(entry->full_path, "/..") == entry->full_path + strlen(entry->full_path) - 3 || strncmp(entry->full_path, "../", 3) == 0) { fprintf(stderr, "Path traversal attempt detected in full_path: '%s'\n", entry->full_path); free(entry->full_path); entry->full_path = NULL; return; } } void free_tree(FileSystemEntry *root) { if (root == NULL) return; size_t cap = 128, top = 0; FileSystemEntry **stack = malloc(cap * sizeof(*stack)); if (!stack) return; stack[top++] = root; while (top > 0) { FileSystemEntry *node = stack[--top]; // Push siblings first (to handle next pointers) if (node->next) { if (top + 1 >= cap) { cap *= 2; FileSystemEntry **tmp = realloc(stack, cap * sizeof(*stack)); if (!tmp) break; stack = tmp; } stack[top++] = node->next; } // Push children (so they get freed before the parent) if (node->children) { if (top + 1 >= cap) { cap *= 2; FileSystemEntry **tmp = realloc(stack, cap * sizeof(*stack)); if (!tmp) break; stack = tmp; } stack[top++] = node->children; } // Now free this node itself free(node->name); free(node->full_path); free(node); } free(stack); } int natural_compare(const char *a, const char *b) { while (*a && *b) { if (*a >= '0' && *a <= '9' && *b >= '0' && *b <= '9') { // Parse number sequences char *end_a, *endB; errno = 0; unsigned long long num_a = strtoull(a, &end_a, 10); int overflow_a = (errno == ERANGE); errno = 0; unsigned long long num_b = strtoull(b, &endB, 10); int overflow_b = (errno == ERANGE); if (!overflow_a && !overflow_b) { if (num_a < num_b) return -1; if (num_a > num_b) return 1; } else { // Fallback: compare digit length, then // lexicographically size_t lenA = end_a - a; size_t lenB = endB - b; if (lenA < lenB) return -1; if (lenA > lenB) return 1; int cmp = strncmp(a, b, lenA); if (cmp != 0) return cmp; } // Numbers equal, advance a = end_a; b = endB; } else { if (*a != *b) return (unsigned char)*a - (unsigned char)*b; a++; b++; } } if (*a == 0 && *b == 0) return 0; if (*a == 0) return -1; return 1; } int compare_lib_entries(const struct dirent **a, const struct dirent **b) { // All strings need to be uppercased or already uppercased characters // will come before all lower-case ones char *name_a = string_to_upper((*a)->d_name); char *name_b = string_to_upper((*b)->d_name); if (name_a[0] == '_' && name_b[0] != '_') { free(name_a); free(name_b); return 1; } else if (name_a[0] != '_' && name_b[0] == '_') { free(name_a); free(name_b); return -1; } int result = natural_compare(name_a, name_b); free(name_a); free(name_b); return result; } int compare_lib_entries_reversed(const struct dirent **a, const struct dirent **b) { int result = compare_lib_entries(a, b); return -result; } int compare_entry_natural(const void *a, const void *b) { const FileSystemEntry *entry_a = *(const FileSystemEntry **)a; const FileSystemEntry *entry_b = *(const FileSystemEntry **)b; char *name_a = string_to_upper(entry_a->name); char *name_b = string_to_upper(entry_b->name); if (name_a[0] == '_' && name_b[0] != '_') { free(name_a); free(name_b); return 1; } else if (name_a[0] != '_' && name_b[0] == '_') { free(name_a); free(name_b); return -1; } int result = natural_compare(name_a, name_b); free(name_a); free(name_b); return result; } int compare_entry_natural_reversed(const void *a, const void *b) { return -compare_entry_natural(a, b); } #define MAX_RECURSION_DEPTH 1024 int remove_empty_directories(FileSystemEntry *node, int depth) { if (node == NULL || depth > MAX_RECURSION_DEPTH) return 0; FileSystemEntry *current_child = node->children; FileSystemEntry *prev_child = NULL; int num_entries = 0; while (current_child != NULL) { if (current_child->is_directory) { num_entries += remove_empty_directories(current_child, depth + 1); if (current_child->children == NULL) { if (prev_child == NULL) { node->children = current_child->next; } else { prev_child->next = current_child->next; } FileSystemEntry *to_free = current_child; current_child = current_child->next; free(to_free->name); free(to_free->full_path); free(to_free); num_entries++; continue; } } prev_child = current_child; current_child = current_child->next; } return num_entries; } int read_directory(const char *path, FileSystemEntry *parent) { struct dirent **entries; int dir_entries = scandir(path, &entries, NULL, compare_lib_entries_reversed); if (dir_entries < 0) { return 0; } regex_t regex; regcomp(®ex, AUDIO_EXTENSIONS, REG_EXTENDED | REG_ICASE); int num_entries = 0; for (int i = 0; i < dir_entries; ++i) { struct dirent *entry = entries[i]; if (entry == NULL) { continue; } if (entry->d_name[0] != '.' && strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { char child_path[PATH_MAX]; snprintf(child_path, sizeof(child_path), "%s/%s", path, entry->d_name); struct stat file_stats; if (stat(child_path, &file_stats) == -1) { continue; } int is_dir = true; if (S_ISREG(file_stats.st_mode)) { is_dir = false; } char exto[100]; extract_extension(entry->d_name, sizeof(exto) - 1, exto); int is_audio = match_regex(®ex, exto); if (is_audio == 0 || is_dir) { FileSystemEntry *child = create_entry(entry->d_name, is_dir, parent); if (child == NULL) continue; set_full_path(child, path, entry->d_name); if (child->full_path == NULL) { free(child); continue; } add_child(parent, child); if (is_dir) { num_entries++; num_entries += read_directory(child_path, child); } } } free(entry); } free(entries); regfree(®ex); return num_entries; } FileSystemEntry *create_directory_tree(const char *start_path, int *num_entries) { FileSystemEntry *root = create_entry("root", 1, NULL); set_library(root); set_full_path(root, start_path, ""); *num_entries = read_directory(start_path, root); *num_entries -= remove_empty_directories(root, 0); last_used_id = 0; return root; } FileSystemEntry **resize_nodes_array(FileSystemEntry **nodes, int old_size, int new_size) { FileSystemEntry **new_nodes = realloc(nodes, new_size * sizeof(FileSystemEntry *)); if (new_nodes) { for (int i = old_size; i < new_size; i++) { new_nodes[i] = NULL; } } return new_nodes; } int count_lines_and_max_id(const char *filename, int *max_id_out) { FILE *file = fopen(filename, "r"); if (!file) return -1; char line[1024]; int lines = 0, max_id = -1; while (fgets(line, sizeof(line), file)) { int id; if (sscanf(line, "%d", &id) == 1) { if (id > max_id) max_id = id; } lines++; } fclose(file); if (max_id_out) *max_id_out = max_id; return lines; } FileSystemEntry *read_tree_from_binary( const char *filename, const char *start_music_path, int *num_directory_entries, bool set_enqueued_status) { if (!filename || !start_music_path) return NULL; if (num_directory_entries) *num_directory_entries = 0; FILE *f = fopen(filename, "rb"); if (!f) return NULL; FileSystemHeader header; if (fread(&header, sizeof(header), 1, f) != 1 || header.magic != FSDB_MAGIC || header.version != DB_VERSION) { fclose(f); return NULL; } // Read disk entries FileSystemEntryDisk *disk_entries = malloc(sizeof(FileSystemEntryDisk) * header.entry_count); if (!disk_entries) { fclose(f); return NULL; } if (fread(disk_entries, sizeof(FileSystemEntryDisk), header.entry_count, f) != header.entry_count) { free(disk_entries); fclose(f); return NULL; } // Read string table char *string_table = malloc(header.string_table_size); if (!string_table) { free(disk_entries); fclose(f); return NULL; } if (fread(string_table, 1, header.string_table_size, f) != header.string_table_size) { free(disk_entries); free(string_table); fclose(f); return NULL; } fclose(f); // Determine max ID to safely allocate array int max_id = -1; for (uint32_t i = 0; i < header.entry_count; i++) if (disk_entries[i].id > max_id) max_id = disk_entries[i].id; FileSystemEntry **nodes = calloc((size_t)max_id + 1, sizeof(FileSystemEntry *)); if (!nodes) { free(disk_entries); free(string_table); return NULL; } const char *stored_root_path = string_table + header.root_full_path_offset; FileSystemEntry *root = NULL; for (uint32_t i = 0; i < header.entry_count; i++) { FileSystemEntryDisk *d = &disk_entries[i]; if (d->id < 0 || d->id > max_id) continue; FileSystemEntry *n = malloc(sizeof(FileSystemEntry)); if (!n) continue; n->id = d->id; n->parent_id = d->parent_id; n->is_directory = d->is_directory; if (set_enqueued_status) n->is_enqueued = d->is_enqueued; else n->is_enqueued = 0; if (d->name_offset == UINT32_MAX) n->name = strdup(""); // empty name else n->name = strdup(string_table + d->name_offset); n->parent = n->children = n->next = n->lastChild = NULL; n->full_path = NULL; nodes[n->id] = n; // Link to parent if exists if (n->parent_id >= 0 && n->parent_id <= max_id && nodes[n->parent_id]) { FileSystemEntry *p = nodes[n->parent_id]; n->parent = p; if (!p->children) p->children = p->lastChild = n; else { p->lastChild->next = n; p->lastChild = n; } // full_path = parent/full_name size_t plen = strlen(p->full_path); size_t nlen = strlen(n->name); n->full_path = malloc(plen + 1 + nlen + 1); if (n->full_path) { memcpy(n->full_path, p->full_path, plen); n->full_path[plen] = '/'; memcpy(n->full_path + plen + 1, n->name, nlen); n->full_path[plen + 1 + nlen] = '\0'; } else { n->full_path = strdup(n->name); } if (n->is_directory && num_directory_entries) (*num_directory_entries)++; } else { // Root node root = n; n->parent = NULL; n->full_path = strdup(stored_root_path); } } free(nodes); free(disk_entries); free(string_table); return root; } // Calculates the Levenshtein distance. // The Levenshtein distance between two strings is the minimum number of // single-character edits (insertions, deletions, or substitutions) required to // change one string into the other. int utf8_levenshteinDistance(const char *s1, const char *s2) { // Get the length of s1 and s2 in terms of characters, not bytes int len1 = g_utf8_strlen(s1, -1); int len2 = g_utf8_strlen(s2, -1); // Allocate a 2D matrix (only two rows at a time are needed) int *prev_row = malloc((len2 + 1) * sizeof(int)); int *curr_row = malloc((len2 + 1) * sizeof(int)); if (prev_row == NULL) { if (curr_row != NULL) free(curr_row); perror("malloc"); return 0; } if (curr_row == NULL) { free(prev_row); perror("malloc"); return 0; } // Initialize the first row (for empty s1) for (int j = 0; j <= len2; j++) { prev_row[j] = j; } // Iterate over the characters of both strings const char *p1 = s1; for (int i = 1; i <= len1; i++, p1 = g_utf8_next_char(p1)) { curr_row[0] = i; const char *p2 = s2; for (int j = 1; j <= len2; j++, p2 = g_utf8_next_char(p2)) { // Compare Unicode characters using g_utf8_get_char gunichar c1 = g_utf8_get_char(p1); gunichar c2 = g_utf8_get_char(p2); int cost = (c1 == c2) ? 0 : 1; // Fill the current row with the minimum of deletion, // insertion, or substitution curr_row[j] = MIN(prev_row[j] + 1, // Deletion MIN(curr_row[j - 1] + 1, // Insertion prev_row[j - 1] + cost)); // Substitution } // Swap rows (current becomes previous for the next iteration) int *tmp = prev_row; prev_row = curr_row; curr_row = tmp; } // The last value in prev_row contains the Levenshtein distance int distance = prev_row[len2]; // Free the allocated memory free(prev_row); free(curr_row); return distance; } // Helper function to normalize and remove accents char *normalize_string(const char *str) { // First normalize to NFD (decomposed form) which separates base chars from accents char *normalized = g_utf8_normalize(str, -1, G_NORMALIZE_NFD); if (!normalized) return g_utf8_strdown(str, -1); // Then remove combining diacritical marks (accents) GString *result = g_string_new(""); for (const char *p = normalized; *p; p = g_utf8_next_char(p)) { gunichar c = g_utf8_get_char(p); GUnicodeType type = g_unichar_type(c); // Skip combining marks (accents, diacritics) if (type != G_UNICODE_NON_SPACING_MARK && type != G_UNICODE_SPACING_MARK && type != G_UNICODE_ENCLOSING_MARK) { g_string_append_unichar(result, g_unichar_tolower(c)); } } g_free(normalized); return g_string_free(result, FALSE); } int calculate_search_distance(const char *needle, const char *haystack, int is_directory) { // Convert to lowercase for case-insensitive matching char *needle_lower = normalize_string(needle); char *haystack_lower = normalize_string(haystack); int distance; // Check for exact match (distance 0) if (strcmp(haystack_lower, needle_lower) == 0) { distance = 0; } // Check for substring match (low distance based on extra chars) else if (strstr(haystack_lower, needle_lower) != NULL) { // Substring match: distance = extra characters int needle_len = g_utf8_strlen(needle_lower, -1); int haystack_len = g_utf8_strlen(haystack_lower, -1); distance = haystack_len - needle_len; } // Check if haystack starts with needle (prefix match) else if (g_str_has_prefix(haystack_lower, needle_lower)) { int needle_len = g_utf8_strlen(needle_lower, -1); int haystack_len = g_utf8_strlen(haystack_lower, -1); distance = haystack_len - needle_len; } // No substring match: use Levenshtein but add penalty else { int levenshtein = utf8_levenshteinDistance(needle_lower, haystack_lower); // Add large penalty to ensure substring matches rank higher int needle_len = g_utf8_strlen(needle_lower, -1); distance = needle_len + levenshtein + 100; } // Add penalty for files (non-directories) to prioritize albums if (!is_directory) { distance += 25; } g_free(needle_lower); g_free(haystack_lower); return distance; } char *strip_file_extension(const char *filename) { if (filename == NULL) return NULL; const char *dot = strrchr(filename, '.'); // Don't treat a leading '.' as an extension (e.g. ".bashrc") if (dot == NULL || dot == filename) dot = filename + strlen(filename); size_t length = (size_t)(dot - filename); char *result = malloc(length + 1); if (!result) { perror("malloc"); return NULL; } memcpy(result, filename, length); result[length] = '\0'; return result; } // Traverses the tree and applies fuzzy search on each node void fuzzy_search_recursive(FileSystemEntry *node, const char *search_term, int threshold, void (*callback)(FileSystemEntry *, int)) { if (node == NULL) { return; } // Convert search term, name, and full_path to lowercase char *lower_search_term = g_utf8_casefold(search_term, -1); char *lower_name = g_utf8_casefold(node->name, -1); int name_distance = calculate_search_distance(lower_search_term, lower_name, node->is_directory); // Partial matching with lowercase strings if (strstr(lower_name, lower_search_term) != NULL) { callback(node, name_distance); } else if (name_distance <= threshold) { callback(node, name_distance); } // Free the allocated memory for lowercase strings g_free(lower_search_term); g_free(lower_name); fuzzy_search_recursive(node->children, search_term, threshold, callback); fuzzy_search_recursive(node->next, search_term, threshold, callback); } FileSystemEntry *find_corresponding_entry(FileSystemEntry *tmp, const char *full_path) { if (tmp == NULL) return NULL; if (strcmp(tmp->full_path, full_path) == 0) return tmp; FileSystemEntry *found = find_corresponding_entry(tmp->children, full_path); if (found != NULL) return found; return find_corresponding_entry(tmp->next, full_path); } void copy_is_enqueued(FileSystemEntry *library, FileSystemEntry *tmp) { if (library == NULL) return; if (library->is_enqueued) { FileSystemEntry *tmp_entry = find_corresponding_entry(tmp, library->full_path); if (tmp_entry != NULL) { tmp_entry->is_enqueued = library->is_enqueued; } } copy_is_enqueued(library->children, tmp); copy_is_enqueued(library->next, tmp); } int compare_folders_by_age_files_alphabetically(const void *a, const void *b) { const FileSystemEntry *entry_a = *(const FileSystemEntry **)a; const FileSystemEntry *entry_b = *(const FileSystemEntry **)b; // Both are directories → sort by mtime descending if (entry_a->is_directory && entry_b->is_directory) { struct stat stat_a, statB; if (stat(entry_a->full_path, &stat_a) != 0 || stat(entry_b->full_path, &statB) != 0) return 0; return (int)(statB.st_mtime - stat_a.st_mtime); // newer first } // Both are files → sort alphabetically if (!entry_a->is_directory && !entry_b->is_directory) { return strcasecmp(entry_a->name, entry_b->name); } // Put directories before files return entry_b->is_directory - entry_a->is_directory; } void sort_file_system_entry_children(FileSystemEntry *parent, int (*comparator)(const void *, const void *)) { int count = 0; FileSystemEntry *curr = parent->children; while (curr) { count++; curr = curr->next; } if (count < 2) return; FileSystemEntry **entry_array = malloc(count * sizeof(FileSystemEntry *)); if (entry_array == NULL) { perror("malloc"); return; } curr = parent->children; for (int i = 0; i < count; i++) { entry_array[i] = curr; curr = curr->next; } qsort(entry_array, count, sizeof(FileSystemEntry *), comparator); for (int i = 0; i < count - 1; i++) { entry_array[i]->next = entry_array[i + 1]; } entry_array[count - 1]->next = NULL; parent->children = entry_array[0]; free(entry_array); } void sort_file_system_tree(FileSystemEntry *root, int (*comparator)(const void *, const void *)) { if (!root) return; sort_file_system_entry_children(root, comparator); FileSystemEntry *child = root->children; while (child) { if (child->is_directory) { sort_file_system_tree(child, comparator); } child = child->next; } } kew/src/data/directorytree.h000066400000000000000000000042571512074754200164200ustar00rootroot00000000000000/** * @file directorytree.h * @brief Filesystem scanning and in-memory tree construction. * * Provides operations for recursively traversing directories and * constructing an in-memory linked tree representation of the music * library. Used by library and playlist modules to index songs efficiently. */ #ifndef DIRECTORYTREE_H #define DIRECTORYTREE_H #include #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif #ifndef FILE_SYSTEM_ENTRY #define FILE_SYSTEM_ENTRY typedef struct FileSystemEntry { int id; char *name; char *full_path; int is_directory; int is_enqueued; int parent_id; struct FileSystemEntry *parent; struct FileSystemEntry *children; struct FileSystemEntry *next; // For siblings (next node in the same directory) struct FileSystemEntry *lastChild; // TEMP: only for construction } FileSystemEntry; #endif #ifndef SLOWLOADING_CALLBACK #define SLOWLOADING_CALLBACK typedef void (*SlowloadingCallback)(void); #endif FileSystemEntry *create_directory_tree(const char *start_path, int *num_entries); void free_tree(FileSystemEntry *root); FileSystemEntry *read_tree_from_binary( const char *filename, const char *start_music_path, int *num_directory_entries, bool set_enqueued_status); int write_tree_to_binary(FileSystemEntry *root, const char *filename); void fuzzy_search_recursive(FileSystemEntry *node, const char *search_term, int threshold, void (*callback)(FileSystemEntry *, int)); void copy_is_enqueued(FileSystemEntry *library, FileSystemEntry *tmp); void sort_file_system_tree(FileSystemEntry *root, int (*comparator)(const void *, const void *)); int compare_folders_by_age_files_alphabetically(const void *a, const void *b); int compare_lib_entries(const struct dirent **a, const struct dirent **b); int compare_lib_entries_reversed(const struct dirent **a, const struct dirent **b); int compare_entry_natural_reversed(const void *a, const void *b); int compare_entry_natural(const void *a, const void *b); FileSystemEntry *find_corresponding_entry(FileSystemEntry *tmp, const char *full_path); #endif kew/src/data/img_func.c000066400000000000000000000645451512074754200153240ustar00rootroot00000000000000/** * @file img_func.c * @brief Image rendering and conversion helpers using Chafa. * * Handles loading and displaying album art or other images in the terminal * using the Chafa library. Supports scaling, aspect-ratio preservation, * and different rendering modes (truecolor, ASCII, etc.). */ #include "common/appstate.h" #include "common/common.h" #include "img_func.h" #include #include #include #include #include // Disable some warnings for stb headers. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #pragma GCC diagnostic ignored "-Wstrict-overflow" #define STB_IMAGE_IMPLEMENTATION #include #define STB_IMAGE_RESIZE_IMPLEMENTATION #include #pragma GCC diagnostic pop /* Include after chafa.h for G_OS_WIN32 */ #ifdef G_OS_WIN32 #ifdef HAVE_WINDOWS_H #include #endif #include #else #include /* ioctl */ #endif #define MACRO_STRLEN(s) (sizeof(s) / sizeof(s[0])) char scale[] = "$@&B%8WM#ZO0QoahkbdpqwmLCJUYXIjft/\\|()1{}[]l?zcvunxr!<>i;:*-+~_,\"^`'. "; unsigned int brightness_levels = MACRO_STRLEN(scale) - 2; #if CHAFA_VERSION_CUR_STABLE >= G_ENCODE_VERSION(1, 16) 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 **argv = NULL; gint wait_status = -1; gchar *mode = NULL; /* Use g_spawn_sync with explicit argv to avoid shell injection */ argv = g_new0(gchar *, 4); argv[0] = g_strdup("tmux"); argv[1] = g_strdup("show"); argv[2] = g_strdup("allow-passthrough"); argv[3] = NULL; if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &standard_output, &standard_error, &wait_status, NULL)) { g_strfreev(argv); goto out; } g_strfreev(argv); /* Parse output safely */ if (standard_output && *standard_output) { gchar **lines = g_strsplit(standard_output, "\n", 2); if (lines[0]) { gchar **parts = g_strsplit(lines[0], " ", 3); if (parts[0] && parts[1]) { mode = g_ascii_strdown(parts[1], -1); g_strstrip(mode); } g_strfreev(parts); } g_strfreev(lines); } if (!mode || (strcmp(mode, "on") && strcmp(mode, "all"))) { argv = g_new0(gchar *, 4); argv[0] = g_strdup("tmux"); argv[1] = g_strdup("set-option"); argv[2] = g_strdup("allow-passthrough on"); argv[3] = NULL; result = g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &standard_output, &standard_error, &wait_status, NULL); g_strfreev(argv); 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); g_free(mode); return result; } gboolean retirePassthroughWorkarounds_tmux(void) { gboolean result = FALSE; gchar *standard_output = NULL; gchar *standard_error = NULL; gint wait_status = -1; gchar **argv = NULL; if (!tmux_allow_passthrough_is_changed) return TRUE; if (tmux_allow_passthrough_original) { // Use argument array to avoid shell injection argv = g_new0(gchar *, 5); argv[0] = g_strdup("tmux"); argv[1] = g_strdup("set-option"); argv[2] = g_strdup("allow-passthrough"); argv[3] = g_strdup(tmux_allow_passthrough_original); argv[4] = NULL; } else { // Use argument array for unsetting the option argv = g_new0(gchar *, 4); argv[0] = g_strdup("tmux"); argv[1] = g_strdup("set-option"); argv[2] = g_strdup("-u"); argv[3] = g_strdup("allow-passthrough"); } result = g_spawn_sync( NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &standard_output, &standard_error, &wait_status, NULL); // Free the argument array for (int i = 0; argv[i] != NULL; i++) g_free(argv[i]); g_free(argv); 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 void detect_terminal(ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, ChafaPixelMode *pixel_mode_out, ChafaPassthrough *passthrough_out, ChafaSymbolMap **symbol_map_out) { ChafaCanvasMode mode; ChafaPixelMode pixel_mode; ChafaPassthrough passthrough; ChafaTermInfo *term_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); /* Pick the most high-quality rendering possible */ mode = chafa_term_info_get_best_canvas_mode(term_info); pixel_mode = chafa_term_info_get_best_pixel_mode(term_info); passthrough = chafa_term_info_get_is_pixel_passthrough_needed(term_info, pixel_mode) ? chafa_term_info_get_passthrough_type(term_info) : CHAFA_PASSTHROUGH_NONE; const gchar *term_name = chafa_term_info_get_name(term_info); if (strstr(term_name, "tmux") != NULL && pixel_mode != CHAFA_PIXEL_MODE_KITTY) { /* Always use sixels in tmux */ pixel_mode = CHAFA_PIXEL_MODE_SIXELS; mode = CHAFA_CANVAS_MODE_TRUECOLOR; } *symbol_map_out = chafa_symbol_map_new(); chafa_symbol_map_add_by_tags(*symbol_map_out, chafa_term_info_get_safe_symbol_tags(term_info)); /* Hand over the information to caller */ *term_info_out = term_info; *mode_out = mode; *pixel_mode_out = pixel_mode; *passthrough_out = passthrough; /* Cleanup */ g_strfreev(envp); } #else static void detect_terminal(ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, ChafaPixelMode *pixel_mode_out) { ChafaCanvasMode mode; ChafaPixelMode pixel_mode; ChafaTermInfo *term_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); } #endif 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; } 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; #if CHAFA_VERSION_CUR_STABLE >= G_ENCODE_VERSION(1, 16) ChafaPassthrough passthrough; ChafaFrame *frame; ChafaImage *image; ChafaPlacement *placement; detect_terminal(&term_info, &mode, &pixel_mode, &passthrough, &symbol_map); if (passthrough == CHAFA_PASSTHROUGH_TMUX) apply_passthrough_workarounds_tmux(); 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); 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); } chafa_canvas_config_set_passthrough(config, passthrough); chafa_canvas_config_set_symbol_map(config, symbol_map); canvas = chafa_canvas_new(config); frame = chafa_frame_new_borrow((gpointer)pixels, pixel_type, pix_width, pix_height, pix_rowstride); image = chafa_image_new(); chafa_image_set_frame(image, frame); placement = chafa_placement_new(image, 1); chafa_placement_set_tuck(placement, CHAFA_TUCK_STRETCH); chafa_placement_set_halign(placement, CHAFA_ALIGN_START); chafa_placement_set_valign(placement, CHAFA_ALIGN_START); chafa_canvas_set_placement(canvas, placement); printable = chafa_canvas_print(canvas, NULL); /* Clean up and return */ chafa_placement_unref(placement); chafa_image_unref(image); chafa_frame_unref(frame); chafa_canvas_unref(canvas); chafa_canvas_config_unref(config); chafa_symbol_map_unref(symbol_map); chafa_term_info_unref(term_info); canvas = NULL; config = NULL; symbol_map = NULL; term_info = NULL; return printable; #else 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); canvas = NULL; config = NULL; symbol_map = NULL; term_info = NULL; return printable; #endif } // The function to load and return image data unsigned char *get_bitmap(const char *image_path, int *width, int *height) { if (image_path == NULL) return NULL; int channels; unsigned char *image = stbi_load(image_path, width, height, &channels, 4); // Force 4 channels (RGBA) if (!image) { fprintf(stderr, "Failed to load image: %s\n", image_path); return NULL; } return image; } float calc_aspect_ratio(void) { TermSize term_size; gint cell_width = -1, cell_height = -1; tty_init(); 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; } // Set default for some terminals if (cell_width == -1 && cell_height == -1) { cell_width = 8; cell_height = 16; } return (float)cell_height / (float)cell_width; } float get_aspect_ratio() { TermSize term_size; gint cell_width = -1, cell_height = -1; tty_init(); 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; } // Set default cell size for some terminals if (cell_width == -1 || cell_height == -1) { cell_width = 8; cell_height = 16; } // Calculate corrected width based on aspect ratio correction return (float)cell_height / (float)cell_width; } void print_square_bitmap(int row, int col, unsigned char *pixels, int width, int height, int base_height, bool centered) { if (pixels == NULL) { set_error_message("Invalid pixel data.\n"); return; } // Use the provided width and height int pix_width = width; int pix_height = height; int n_channels = 4; // Assuming RGBA format // Validate the image dimensions if (pix_width == 0 || pix_height == 0) { set_error_message("Invalid image dimensions.\n"); return; } TermSize term_size; GString *printable; gint cell_width = -1, cell_height = -1; tty_init(); 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; } // Set default cell size for some terminals if (cell_width <= -1 || cell_height <= -1) { cell_width = 8; cell_height = 16; } if (cell_width == 0 || cell_height == 0) { set_error_message("Invalid image cell width dimensions.\n"); return; } // Calculate corrected width based on aspect ratio correction float aspect_ratio_correction = (float)cell_height / (float)cell_width; int corrected_width = (int)(base_height * aspect_ratio_correction); if (term_size.width_cells > 0 && corrected_width > term_size.width_cells) { set_error_message("Invalid terminal dimensions.\n"); return; } if (term_size.height_cells > 0 && base_height > term_size.height_cells) { set_error_message("Invalid terminal dimensions.\n"); return; } // Convert image to a printable string using Chafa printable = convert_image( pixels, pix_width, pix_height, pix_width * n_channels, // Row stride CHAFA_PIXEL_RGBA8_UNASSOCIATED, // Correct pixel format corrected_width, base_height, cell_width, cell_height); // Ensure the string is null-terminated g_string_append_c(printable, '\0'); // Split the printable string into lines const gchar *delimiters = "\n"; gchar **lines = g_strsplit(printable->str, delimiters, -1); if (centered) col = ((term_size.width_cells - corrected_width) / 2) + 1; // Print each line with indentation for (int i = 0; lines[i] != NULL; i++) { printf("\033[%d;%dH", row + i, col); printf("%s", lines[i]); fflush(stdout); } // Free allocated memory g_strfreev(lines); g_string_free(printable, TRUE); } unsigned char luminance_from_r_g_b(unsigned char r, unsigned char g, unsigned char b) { return (unsigned char)(0.2126 * r + 0.7152 * g + 0.0722 * b); } void check_if_bright_pixel(unsigned char r, unsigned char g, unsigned char b, bool *found) { // Calc luminace and use to find Ascii char. unsigned char ch = luminance_from_r_g_b(r, g, b); if (ch > 80 && !(r < g + 20 && r > g - 20 && g < b + 20 && g > b - 20) && !(r > 150 && g > 150 && b > 150)) { *found = true; } } int get_cover_color(unsigned char *pixels, int width, int height, unsigned char *r, unsigned char *g, unsigned char *b) { if (pixels == NULL || width <= 0 || height <= 0) { return -1; } int channels = 4; // RGBA format bool found = false; int num_pixels = width * height; for (int i = 0; i < num_pixels; i++) { int index = i * channels; unsigned char red = pixels[index + 0]; unsigned char green = pixels[index + 1]; unsigned char blue = pixels[index + 2]; check_if_bright_pixel(red, green, blue, &found); if (found) { *r = red; *g = green; *b = blue; break; } } return found ? 0 : -1; } unsigned char calc_ascii_char(PixelData *p) { unsigned char ch = luminance_from_r_g_b(p->r, p->g, p->b); int rescaled = ch * brightness_levels / 256; return scale[brightness_levels - rescaled]; } int convert_to_ascii(int row, int col, const char *filepath, unsigned int height, bool centered) { /* Modified, originally by Danny Burrows: https://github.com/danny-burrows/img_to_txt MIT License Copyright (c) 2021 Danny Burrows 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. */ TermSize term_size; gint cell_width = -1, cell_height = -1; tty_init(); 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; } // Set default cell size for some terminals if (cell_width == -1 || cell_height == -1) { cell_width = 8; cell_height = 16; } float aspect_ratio_correction = (float)cell_height / (float)cell_width; unsigned int corrected_width = (int)(height * aspect_ratio_correction); int rwidth, rheight, rchannels; unsigned char *read_data = stbi_load(filepath, &rwidth, &rheight, &rchannels, 3); if (read_data == NULL) { return -1; } PixelData *data; if (corrected_width != (unsigned)rwidth || height != (unsigned)rheight) { // 3 * uint8 for RGB! unsigned char *new_data = malloc(3 * sizeof(unsigned char) * corrected_width * height); stbir_resize_uint8_srgb( read_data, rwidth, rheight, 0, new_data, corrected_width, height, 0, 3); stbi_image_free(read_data); data = (PixelData *)new_data; } else { data = (PixelData *)read_data; } // Calculate indentation to center the image if (centered) col = ((term_size.width_cells - corrected_width) / 2) + 1; printf("\033[%d;%dH", row, col); for (unsigned int d = 0; d < corrected_width * height; d++) { if (d % corrected_width == 0 && d != 0) { row++; printf("\033[%d;%dH", row, col); } PixelData *c = data + d; printf("\033[1;38;2;%03u;%03u;%03um%c", c->r, c->g, c->b, calc_ascii_char(c)); } stbi_image_free(data); return 0; } int print_in_ascii(int row, int col, const char *path_to_img_file, int height, bool centered) { int ret = convert_to_ascii(row, col, path_to_img_file, (unsigned)height, centered); if (ret == -1) printf("\033[0m"); return 0; } kew/src/data/img_func.h000066400000000000000000000021421512074754200153120ustar00rootroot00000000000000/** * @file img_func.h * @brief Image rendering and conversion helpers using Chafa. * * Handles loading and displaying album art or other images in the terminal * using the Chafa library. Supports scaling, aspect-ratio preservation, * and different rendering modes (truecolor, ASCII, etc.). */ #ifndef IMG_FUNC_H #define IMG_FUNC_H #include #include typedef struct { gint width_cells, height_cells; gint width_pixels, height_pixels; } TermSize; void tty_init(void); int print_in_ascii(int row, int col, const char *path_to_img_file, int height, bool centered); int get_cover_color(unsigned char *pixels, int width, int height, unsigned char *r, unsigned char *g, unsigned char *b); float get_aspect_ratio(); float calc_aspect_ratio(void); unsigned char *get_bitmap(const char *image_path, int *width, int *height); void print_square_bitmap(int row, int col, unsigned char *pixels, int width, int height, int base_height, bool centered); void get_tty_size(TermSize *term_size_out); #ifdef CHAFA_VERSION_1_16 gboolean retirePassthroughWorkarounds_tmux(void); #endif #endif kew/src/data/lyrics.cpp000066400000000000000000000126161512074754200153720ustar00rootroot00000000000000/** * @file lyrics.cpp * @brief Lyrics fetching and parsing. * * Provides functions to load, cache lyrics from local files * or remote sources. Supports synchronized lyric display and fallback modes * when metadata or network access is unavailable. */ #include "lyrics.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // LRC Loader static int loadTimedLyrics(FILE *file, Lyrics *lyrics) { size_t capacity = 64; lyrics->lines = (LyricsLine *)malloc(sizeof(LyricsLine) * capacity); if (!lyrics->lines) return 0; char lineBuffer[1024]; while (fgets(lineBuffer, sizeof(lineBuffer), file)) { if (lineBuffer[0] != '[' || !isdigit((unsigned char)lineBuffer[1])) continue; int min = 0, sec = 0, cs = 0; char text[512] = {0}; if (sscanf(lineBuffer, "[%d:%d.%d]%511[^\r\n]", &min, &sec, &cs, text) == 4) { if (lyrics->count == capacity) { capacity *= 2; LyricsLine *newLines = (LyricsLine *)realloc(lyrics->lines, sizeof(LyricsLine) * capacity); if (!newLines) return 0; lyrics->lines = newLines; } char *start = text; while (isspace((unsigned char)*start)) start++; char *end = start + strlen(start); while (end > start && isspace((unsigned char)*(end - 1))) *(--end) = '\0'; lyrics->lines[lyrics->count].timestamp = min * 60.0 + sec + cs / 100.0; lyrics->lines[lyrics->count].text = strdup(start); if (!lyrics->lines[lyrics->count].text) return 0; lyrics->count++; } } lyrics->isTimed = 1; return 1; } static int loadUntimedLyrics(FILE *file, Lyrics *lyrics) { size_t capacity = 64; lyrics->lines = (LyricsLine *)malloc(sizeof(LyricsLine) * capacity); if (!lyrics->lines) return 0; char lineBuffer[1024]; lyrics->count = 0; while (fgets(lineBuffer, sizeof(lineBuffer), file)) { char *newline = strpbrk(lineBuffer, "\r\n"); if (newline) *newline = '\0'; if (lineBuffer[0] == '\0') continue; if (lyrics->count == capacity) { capacity *= 2; LyricsLine *newLines = (LyricsLine *)realloc(lyrics->lines, sizeof(LyricsLine) * capacity); if (!newLines) return 0; lyrics->lines = newLines; } lyrics->lines[lyrics->count].timestamp = 0.0; lyrics->lines[lyrics->count].text = strdup(lineBuffer); if (!lyrics->lines[lyrics->count].text) return 0; lyrics->count++; } lyrics->isTimed = 0; return 1; } Lyrics *loadLyricsFromLRC(const char *path) { char lrcPath[PATH_MAX]; if (snprintf(lrcPath, sizeof(lrcPath), "%s", path) >= (int)sizeof(lrcPath)) return nullptr; char *dot = strrchr(lrcPath, '.'); if (!dot || dot == lrcPath) return nullptr; if (snprintf(dot, sizeof(lrcPath) - (dot - lrcPath), ".lrc") >= (int)(sizeof(lrcPath) - (dot - lrcPath))) return nullptr; FILE *file = fopen(lrcPath, "r"); if (!file) return nullptr; Lyrics *lyrics = (Lyrics *)calloc(1, sizeof(Lyrics)); if (!lyrics) { fclose(file); return nullptr; } lyrics->max_length = 1024; // Detect if there are timestamps char lineBuffer[1024]; int foundTimestamp = 0; while (fgets(lineBuffer, sizeof(lineBuffer), file)) { if (lineBuffer[0] == '[' && isdigit((unsigned char)lineBuffer[1])) { foundTimestamp = 1; break; } } rewind(file); int ok = foundTimestamp ? loadTimedLyrics(file, lyrics) : loadUntimedLyrics(file, lyrics); fclose(file); if (!ok) { freeLyrics(lyrics); return nullptr; } return lyrics; } // Free & Access void freeLyrics(Lyrics *lyrics) { if (!lyrics) return; for (size_t i = 0; i < lyrics->count; i++) free(lyrics->lines[i].text); free(lyrics->lines); free(lyrics); } kew/src/data/lyrics.h000066400000000000000000000012771512074754200150400ustar00rootroot00000000000000 /** * @file lyrics.h * @brief Lyrics fetching and parsing. * * Provides functions to load, cache lyrics from local files * or remote sources. Supports synchronized lyric display and fallback modes * when metadata or network access is unavailable. */ #ifndef LYRICS_H #define LYRICS_H #include #include #ifdef __cplusplus extern "C" { #endif typedef struct { double timestamp; char *text; } LyricsLine; typedef struct { LyricsLine *lines; size_t count; int max_length; int isTimed; } Lyrics; Lyrics *loadLyricsFromLRC(const char *path); void freeLyrics(Lyrics *lyrics); #ifdef __cplusplus } #endif #endif // LYRICS_H kew/src/data/playlist.c000066400000000000000000001014251512074754200153630ustar00rootroot00000000000000#define _XOPEN_SOURCE 700 #define __USE_XOPEN_EXTENDED 1 /** * @file playlist.c * @brief Playlist data structures and operations. * * Defines the core playlist data types and provides functions for managing * track lists, iterating songs, and maintaining play order. Used by both * playback and UI modules to coordinate current and upcoming tracks. */ #include "playlist.h" #include "common/appstate.h" #include "utils/file.h" #include "utils/utils.h" #include #include #include #include #include #include #define MAX_SEARCH_SIZE 256 const char PLAYLIST_EXTENSIONS[] = "(m3u8?)$"; const char favorites_playlist_name[] = "kew favorites.m3u"; static char search[MAX_SEARCH_SIZE]; static char playlist_name[MAX_SEARCH_SIZE]; static bool shuffle = false; static int num_dirs = 0; static Node *current_song = NULL; static int node_id_counter = 0; void clear_current_song(void) { current_song = NULL; } void set_current_song(Node *node) { current_song = node; } Node *get_current_song(void) { return current_song; } Node *get_list_next(Node *node) { return (node == NULL) ? NULL : node->next; } Node *get_list_prev(Node *node) { return (node == NULL) ? NULL : node->prev; } int add_to_list(PlayList *list, Node *new_node) { if (new_node == NULL) return 0; if (list == NULL || list->count >= MAX_FILES) return -1; list->count++; if (list->head == NULL) { new_node->prev = NULL; list->head = new_node; list->tail = new_node; } else { new_node->prev = list->tail; list->tail->next = new_node; list->tail = new_node; } return 0; } void move_up_list(PlayList *list, Node *node) { if (node == list->head || node == NULL || node->prev == NULL) return; Node *prev_node = node->prev; Node *next_node = node->next; if (prev_node->prev) prev_node->prev->next = node; else list->head = node; node->prev = prev_node->prev; node->next = prev_node; prev_node->prev = node; prev_node->next = next_node; if (next_node) next_node->prev = prev_node; else list->tail = prev_node; } void move_down_list(PlayList *list, Node *node) { if (node == list->tail || node == NULL || node->next == NULL) return; Node *next_node = node->next; Node *prev_node = node->prev; Node *next_next_node = next_node->next; if (prev_node) prev_node->next = next_node; else list->head = next_node; next_node->prev = prev_node; next_node->next = node; node->prev = next_node; node->next = next_next_node; if (next_next_node) next_next_node->prev = node; else list->tail = node; } Node *delete_from_list(PlayList *list, Node *node) { if (list == NULL || node == NULL || list->head == NULL) return NULL; Node *next_node = node->next; // Adjust head and tail if (list->head == node) list->head = next_node; if (list->tail == node) list->tail = node->prev; // Fix neighbors if (node->prev != NULL) node->prev->next = next_node; if (next_node != NULL) next_node->prev = node->prev; // Free song file path string if allocated if (node->song.file_path != NULL) { free(node->song.file_path); node->song.file_path = NULL; } free(node); node = NULL; list->count--; return next_node; } void empty_playlist(PlayList *list) { if (list == NULL) return; pthread_mutex_lock(&list->mutex); Node *current = list->head; while (current != NULL) { Node *next = current->next; free(current->song.file_path); free(current); current = next; } list->head = NULL; list->tail = NULL; list->count = 0; pthread_mutex_unlock(&list->mutex); } void shuffle_playlist(PlayList *playlist) { if (playlist == NULL || playlist->count <= 1) { return; // No need to shuffle } // Check for overflow before malloc if ((size_t)playlist->count > SIZE_MAX / sizeof(Node *)) { printf(_("Playlist too large to allocate.\n")); // atexit() will free up resources properly exit(1); } // Convert the linked list to an array Node **nodes = (Node **)malloc(playlist->count * sizeof(Node *)); if (nodes == NULL) { printf(_("Memory allocation error.\n")); // atexit() will free up resources properly exit(1); } Node *current = playlist->head; int i = 0; while (current != NULL) { nodes[i++] = current; current = current->next; } // Shuffle the array using Fisher-Yates algorithm for (int j = playlist->count - 1; j >= 1; --j) { int k = rand() % (j + 1); Node *tmp = nodes[j]; nodes[j] = nodes[k]; nodes[k] = tmp; } playlist->head = nodes[0]; playlist->tail = nodes[playlist->count - 1]; for (int j = 0; j < playlist->count; ++j) { nodes[j]->next = (j < playlist->count - 1) ? nodes[j + 1] : NULL; nodes[j]->prev = (j > 0) ? nodes[j - 1] : NULL; } free(nodes); } void insert_as_first(Node *current_song, PlayList *playlist) { if (current_song == NULL || playlist == NULL) { return; } if (playlist->head == NULL) { current_song->next = NULL; current_song->prev = NULL; playlist->head = current_song; playlist->tail = current_song; } else { if (current_song != playlist->head) { if (current_song->next != NULL) { current_song->next->prev = current_song->prev; } else { playlist->tail = current_song->prev; } if (current_song->prev != NULL) { current_song->prev->next = current_song->next; } // Add the current_song as the new head current_song->next = playlist->head; current_song->prev = NULL; playlist->head->prev = current_song; playlist->head = current_song; } } } void shuffle_playlist_starting_from_song(PlayList *playlist, Node *song) { shuffle_playlist(playlist); if (song != NULL && playlist->count > 1) { insert_as_first(song, playlist); } } void create_node(Node **node, const char *directory_path, int id) { SongInfo song; song.file_path = strdup(directory_path); song.duration = 0.0; *node = (Node *)malloc(sizeof(Node)); if (*node == NULL) { printf(_("Failed to allocate memory.")); free(song.file_path); exit(0); return; } (*node)->song = song; (*node)->next = NULL; (*node)->prev = NULL; (*node)->id = id; } void destroy_node(Node *node) { if (node == NULL) return; if (node->song.file_path != NULL) free(node->song.file_path); free(node); } void exit_if_overflow(int counter) { if (counter == INT_MAX) { fprintf(stderr, "Error: Node ID overflow. Max node limit reached.\n"); exit(1); } } void build_playlist_recursive(const char *directory_path, const char *allowed_extensions, PlayList *playlist) { char expanded_path[PATH_MAX - NAME_MAX - 1]; expand_path(directory_path, expanded_path); int res = is_directory(expanded_path); if (res != 1 && res != -1 && directory_path != NULL) { Node *node = NULL; exit_if_overflow(node_id_counter); create_node(&node, expanded_path, node_id_counter++); if (add_to_list(playlist, node) == -1) destroy_node(node); return; } DIR *dir = opendir(expanded_path); if (dir == NULL) { printf(_("Failed to open directory: %s\n"), expanded_path); return; } regex_t regex; int ret = regcomp(®ex, allowed_extensions, REG_EXTENDED | REG_ICASE); if (ret != 0) { printf(_("Failed to compile regular expression\n")); closedir(dir); return; } char exto[100]; struct dirent **entries; int num_entries = scandir(expanded_path, &entries, NULL, compare_lib_entries); if (num_entries < 0) { printf(_("Failed to scan directory: %s\n"), expanded_path); return; } for (int i = 0; i < num_entries && playlist->count < MAX_FILES; i++) { struct dirent *entry = entries[i]; if (entry->d_name[0] == '.' || strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } char file_path[PATH_MAX]; snprintf(file_path, sizeof(file_path), "%s/%s", expanded_path, entry->d_name); size_t pathLen = strnlen(expanded_path, sizeof(expanded_path)); size_t nameLen = strnlen(entry->d_name, NAME_MAX); if (pathLen + 1 + nameLen >= PATH_MAX) { fprintf(stderr, "Path too long: %s/%s\n", expanded_path, entry->d_name); return; // or skip this entry } snprintf(file_path, sizeof(file_path), "%s/%s", expanded_path, entry->d_name); if (is_directory(file_path) == 1) { int song_count = playlist->count; build_playlist_recursive(file_path, allowed_extensions, playlist); if (playlist->count > song_count) num_dirs++; } else { extract_extension(entry->d_name, sizeof(exto) - 1, exto); if (match_regex(®ex, exto) == 0) { snprintf(file_path, sizeof(file_path), "%s/%s", directory_path, entry->d_name); Node *node = NULL; exit_if_overflow(node_id_counter); create_node(&node, file_path, node_id_counter++); if (add_to_list(playlist, node) == -1) destroy_node(node); } } } for (int i = 0; i < num_entries; i++) { free(entries[i]); } free(entries); closedir(dir); regfree(®ex); } int join_playlist(PlayList *dest, PlayList *src) { if (src->count == 0) { return 0; } if (dest->count == 0) { dest->head = src->head; dest->tail = src->tail; } else { dest->tail->next = src->head; src->head->prev = dest->tail; dest->tail = src->tail; } dest->count += src->count; src->head = NULL; src->tail = NULL; src->count = 0; return 1; } void make_playlist_name(const char *search, int max_size) { playlist_name[0] = '\0'; snprintf(playlist_name, max_size, "%s", search); snprintf(playlist_name + strnlen(playlist_name, max_size), max_size - strnlen(playlist_name, max_size), ".m3u"); for (int i = 0; playlist_name[i] != '\0'; i++) { if (playlist_name[i] == ':') { playlist_name[i] = '-'; } } } Node *read_m3u_file(const char *filepath, PlayList *playlist) { GError *error = NULL; gchar *contents; gchar **lines; Node *first_in_list = NULL; char filename[PATH_MAX]; expand_path(filepath, filename); if (!g_file_get_contents(filename, &contents, NULL, &error)) { g_clear_error(&error); return NULL; } gchar *directory = g_path_get_dirname(filename); lines = g_strsplit(contents, "\n", -1); for (gint i = 0; lines[i] != NULL; i++) { gchar *line = lines[i]; line = g_strdelimit(line, "\r", '\0'); gchar *trimmed_line = g_strstrip(line); if (trimmed_line[0] != '#' && trimmed_line[0] != '\0') { gchar *songPath; if (g_path_is_absolute(trimmed_line)) { songPath = g_strdup(trimmed_line); } else { songPath = g_build_filename(directory, trimmed_line, NULL); } if (songPath == NULL) continue; if (exists_file(songPath) < 0) { g_free(songPath); continue; } // Don't add songs that are already enqueued Node *found = find_path_in_playlist(songPath, playlist); if (first_in_list == NULL) first_in_list = found; if (found != NULL) { g_free(songPath); continue; } Node *new_node = NULL; node_id_counter++; create_node(&new_node, songPath, node_id_counter); if (playlist->head == NULL) { playlist->head = new_node; playlist->tail = new_node; } else { playlist->tail->next = new_node; new_node->prev = playlist->tail; playlist->tail = new_node; } if (first_in_list == NULL) first_in_list = playlist->tail; playlist->count++; g_free(songPath); } } g_free(directory); g_strfreev(lines); g_free(contents); return first_in_list; } int make_playlist(PlayList **playlist, int argc, char *argv[], bool exact_search, const char *path) { const char *delimiter = ":"; const char *allowed_extensions = MUSIC_FILE_EXTENSIONS; enum SearchType search_type = SearchAny; int search_type_index = 1; PlayList partial_playlist = {NULL, NULL, 0, PTHREAD_MUTEX_INITIALIZER}; char expanded_path[PATH_MAX]; expand_path(path, expanded_path); if (strcmp(argv[1], "all") == 0) { search_type = ReturnAllSongs; shuffle = true; } if (argc > 1) { if (strcmp(argv[1], "list") == 0 && argc > 2) { allowed_extensions = PLAYLIST_EXTENSIONS; search_type = SearchPlayList; } if (strcmp(argv[1], "random") == 0 || strcmp(argv[1], "rand") == 0 || strcmp(argv[1], "shuffle") == 0) { int count = 0; while (argv[count] != NULL) { count++; } if (count > 2) { search_type_index = 2; shuffle = true; } } if (strcmp(argv[search_type_index], "dir") == 0) search_type = DirOnly; else if (strcmp(argv[search_type_index], "song") == 0) search_type = FileOnly; } int start = search_type_index + 1; if (search_type == FileOnly || search_type == DirOnly || search_type == SearchPlayList) start = search_type_index + 2; search[0] = '\0'; for (int i = start - 1; i < argc; i++) { size_t len = strnlen(search, MAX_SEARCH_SIZE); snprintf(search + len, MAX_SEARCH_SIZE - len, " %s", argv[i]); } make_playlist_name(search, MAX_SEARCH_SIZE); if (strstr(search, delimiter)) { shuffle = true; } if (search_type == ReturnAllSongs) { pthread_mutex_lock(&((*playlist)->mutex)); build_playlist_recursive(expanded_path, allowed_extensions, *playlist); pthread_mutex_unlock(&((*playlist)->mutex)); } else { char *token = strtok(search, delimiter); while (token != NULL) { char buf[PATH_MAX] = {0}; if (strncmp(token, "song", 4) == 0) { memmove(token, token + 4, strnlen(token + 4, PATH_MAX) + 1); search_type = FileOnly; } trim(token, PATH_MAX); char *searching = g_utf8_casefold(token, -1); if (walker(expanded_path, searching, buf, allowed_extensions, search_type, exact_search, 0) == 0) { if (strcmp(argv[1], "list") == 0) { read_m3u_file(buf, *playlist); } else { pthread_mutex_lock(&((*playlist)->mutex)); build_playlist_recursive(buf, allowed_extensions, &partial_playlist); join_playlist(*playlist, &partial_playlist); pthread_mutex_unlock( &((*playlist)->mutex)); } } free(searching); token = strtok(NULL, delimiter); } } if (num_dirs > 1) shuffle = true; if (shuffle) shuffle_playlist(*playlist); if ((*playlist)->head == NULL) printf(_("Music not found\n")); return 0; } void generate_m3_u_filename(const char *base_path, const char *file_path, char *m3u_filename, size_t size) { const char *base_name = strrchr(file_path, '/'); if (base_name == NULL) { base_name = file_path; // No '/' found, use the entire filename } else { base_name++; // Skip the '/' character } const char *dot = strrchr(base_name, '.'); if (dot == NULL) { // No '.' found, copy the base name and append ".m3u" if (base_path[strnlen(base_path, PATH_MAX) - 1] == '/') { snprintf(m3u_filename, size, "%s%s.m3u", base_path, base_name); } else { snprintf(m3u_filename, size, "%s/%s.m3u", base_path, base_name); } } else { // Copy the base name up to the dot and append ".m3u" size_t baseNameLen = dot - base_name; if (base_path[strnlen(base_path, PATH_MAX) - 1] == '/') { snprintf(m3u_filename, size, "%s%.*s.m3u", base_path, (int)baseNameLen, base_name); } else { snprintf(m3u_filename, size, "%s/%.*s.m3u", base_path, (int)baseNameLen, base_name); } } } void write_m3u_file(const char *filename, const PlayList *playlist) { FILE *file = fopen(filename, "w"); if (file == NULL) { return; } Node *current_node = playlist->head; while (current_node != NULL) { fprintf(file, "%s\n", current_node->song.file_path); current_node = current_node->next; } fclose(file); } void load_playlist(const char *directory, const char *playlist_name, PlayList **playlist) { char playlist_path[PATH_MAX]; if (!directory || !playlist_name || !playlist) return; size_t len = strnlen(directory, PATH_MAX); if (len == 0) return; snprintf(playlist_path, sizeof(playlist_path), "%s%s%s", directory, (directory[len - 1] == '/' ? "" : "/"), playlist_name); if (*playlist == NULL) { create_playlist(playlist); } if (*playlist) read_m3u_file(playlist_path, *playlist); } void load_favorites_playlist(const char *directory, PlayList **favorites_playlist) { char expanded_path[PATH_MAX]; expand_path(directory, expanded_path); load_playlist(directory, favorites_playlist_name, favorites_playlist); set_favorites_playlist(*favorites_playlist); } void add_enqueued_songs_to_playlist(FileSystemEntry *root, PlayList *playlist) { if (!root) return; if (root->is_enqueued && root->is_directory == 0) { Node *node = malloc(sizeof(Node)); node->song.file_path = strdup(root->full_path); node->prev = playlist->tail; node->next = NULL; node->id = root->id; node->song.duration = 0.0; if (playlist->tail) playlist->tail->next = node; else playlist->head = node; playlist->tail = node; playlist->count++; } for (FileSystemEntry *child = root->children; child; child = child->next) add_enqueued_songs_to_playlist(child, playlist); } void save_named_playlist(const char *directory, const char *playlist_name, const PlayList *playlist) { if (directory == NULL) { return; } char playlist_path[PATH_MAX]; int length = snprintf(playlist_path, sizeof(playlist_path), "%s", directory); if (length <= 0 || length >= (int)sizeof(playlist_path) || playlist_path[0] == '\0') { return; } if (playlist_path[length - 1] != '/') { snprintf(playlist_path + length, sizeof(playlist_path) - length, "/%s", playlist_name); } else { snprintf(playlist_path + length, sizeof(playlist_path) - length, "%s", playlist_name); } if (playlist != NULL) { write_m3u_file(playlist_path, playlist); } } void save_favorites_playlist(const char *directory, PlayList *favorites_playlist) { char expanded_path[PATH_MAX]; expand_path(directory, expanded_path); if (favorites_playlist != NULL && favorites_playlist->count > 0) { save_named_playlist(expanded_path, favorites_playlist_name, favorites_playlist); } } void save_playlist(const char *path, const PlayList *playlist) { if (path == NULL) { return; } char expanded_path[PATH_MAX]; expand_path(path, expanded_path); if (playlist->head == NULL || playlist->head->song.file_path == NULL) return; write_m3u_file(expanded_path, playlist); } void export_current_playlist(const char *path, const PlayList *playlist) { char m3u_filename[PATH_MAX]; char expanded_path[PATH_MAX]; expand_path(path, expanded_path); if (path == NULL || playlist->head == NULL) return; generate_m3_u_filename(expanded_path, playlist->head->song.file_path, m3u_filename, sizeof(m3u_filename)); save_playlist(m3u_filename, playlist); } Node *deep_copy_node(Node *original_node) { if (original_node == NULL) { return NULL; } Node *new_node = malloc(sizeof(Node)); if (new_node == NULL) { return NULL; } new_node->song.file_path = strdup(original_node->song.file_path); new_node->song.duration = original_node->song.duration; new_node->prev = NULL; new_node->id = original_node->id; new_node->next = deep_copy_node(original_node->next); if (new_node->next != NULL) { new_node->next->prev = new_node; } return new_node; } Node *find_tail(Node *head) { if (head == NULL) return NULL; Node *current = head; while (current->next != NULL) { current = current->next; } return current; } PlayList *deep_copy_playlist(const PlayList *original_list) { if (original_list == NULL) return NULL; PlayList *new_list = NULL; create_playlist(&new_list); if (new_list == NULL) return NULL; deep_copy_play_list_onto_list(original_list, &new_list); return new_list; } void deep_copy_play_list_onto_list(const PlayList *original_list, PlayList **new_list) { if (original_list == NULL || new_list == NULL) return; if (*new_list == NULL) { *new_list = malloc(sizeof(PlayList)); if (*new_list == NULL) { perror("malloc failed in deep_copy_play_list_onto_list"); return; } memset(*new_list, 0, sizeof(PlayList)); pthread_mutex_init(&(*new_list)->mutex, NULL); } else if ((*new_list)->count > 0) { empty_playlist(*new_list); } (*new_list)->head = deep_copy_node(original_list->head); (*new_list)->tail = find_tail((*new_list)->head); (*new_list)->count = original_list->count; } Node *find_path_in_playlist(const char *path, PlayList *playlist) { if (!playlist) return NULL; Node *current_node = playlist->head; while (current_node != NULL) { if (strcmp(current_node->song.file_path, path) == 0) { return current_node; } current_node = current_node->next; } return NULL; } Node *find_last_path_in_playlist(const char *path, PlayList *playlist) { if (!playlist) return NULL; Node *current_node = playlist->tail; while (current_node != NULL) { if (strcmp(current_node->song.file_path, path) == 0) { return current_node; } current_node = current_node->prev; } return NULL; } int find_node_in_list(PlayList *list, int id, Node **found_node) { Node *node = list->head; int row = 0; while (node != NULL) { if (id == node->id) { *found_node = node; return row; } node = node->next; row++; } *found_node = NULL; return -1; } void add_song_to_play_list(PlayList *list, const char *file_path, int playlist_max) { if (list->count >= playlist_max) return; Node *new_node = NULL; create_node(&new_node, file_path, list->count); if (add_to_list(list, new_node) == -1) destroy_node(new_node); } void traverse_file_system_entry(FileSystemEntry *entry, PlayList *list, int playlist_max) { if (entry == NULL || list->count >= playlist_max) return; if (entry->is_directory == 0) { add_song_to_play_list(list, entry->full_path, playlist_max); } if (entry->is_directory == 1 && entry->children != NULL) { traverse_file_system_entry(entry->children, list, playlist_max); } if (entry->next != NULL) { traverse_file_system_entry(entry->next, list, playlist_max); } } void create_play_list_from_file_system_entry(FileSystemEntry *root, PlayList *list, int playlist_max) { traverse_file_system_entry(root, list, playlist_max); } int is_music_file(const char *filename) { if (filename == NULL) return 0; const char *extensions[] = {".m4a", ".aac", ".mp3", ".ogg", ".flac", ".wav", ".opus", ".webm"}; size_t numExtensions = sizeof(extensions) / sizeof(extensions[0]); for (size_t i = 0; i < numExtensions; i++) { if (strstr(filename, extensions[i]) != NULL) { return 1; } } return 0; } int contains_music_files(FileSystemEntry *entry) { if (entry == NULL) return 0; FileSystemEntry *child = entry->children; while (child != NULL) { if (!child->is_directory && is_music_file(child->name)) { return 1; } child = child->next; } return 0; } void add_album_to_play_list(PlayList *list, FileSystemEntry *album, int playlist_max) { FileSystemEntry *entry = album->children; while (entry != NULL && list->count < playlist_max) { if (!entry->is_directory && is_music_file(entry->name)) { add_song_to_play_list(list, entry->full_path, playlist_max); } entry = entry->next; } } void add_albums_to_play_list(FileSystemEntry *entry, PlayList *list, int playlist_max) { if (entry == NULL || list->count >= playlist_max) return; if (entry->is_directory && contains_music_files(entry)) { add_album_to_play_list(list, entry, playlist_max); } if (entry->is_directory && entry->children != NULL) { add_albums_to_play_list(entry->children, list, playlist_max); } if (entry->next != NULL) { add_albums_to_play_list(entry->next, list, playlist_max); } } void shuffle_entries(FileSystemEntry **array, size_t n) { if (n > 1) { for (size_t i = 0; i < n - 1; i++) { size_t j = get_random_number(i, n - 1); // Swap entries at i and j FileSystemEntry *tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } } void collect_albums(FileSystemEntry *entry, FileSystemEntry **albums, size_t *count) { if (entry == NULL) return; if (entry->is_directory && contains_music_files(entry)) { albums[*count] = entry; (*count)++; } if (entry->is_directory && entry->children != NULL) { collect_albums(entry->children, albums, count); } if (entry->next != NULL) { collect_albums(entry->next, albums, count); } } void add_shuffled_albums_to_play_list(FileSystemEntry *root, PlayList *list, int playlist_max) { size_t maxAlbums = 2000; FileSystemEntry *albums[maxAlbums]; size_t albumCount = 0; collect_albums(root, albums, &albumCount); shuffle_entries(albums, albumCount); for (size_t i = 0; i < albumCount && list->count < playlist_max; i++) { add_album_to_play_list(list, albums[i], playlist_max); } } int increment_node_id() { return ++node_id_counter; } kew/src/data/playlist.h000066400000000000000000000053471512074754200153760ustar00rootroot00000000000000/** * @file playlist.h * @brief Playlist data structures and operations. * * Defines the core playlist data types and provides functions for managing * track lists, iterating songs, and maintaining play order. Used by both * playback and UI modules to coordinate current and upcoming tracks. */ #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #ifndef __USE_XOPEN_EXTENDED #define __USE_XOPEN_EXTENDED #endif #include "directorytree.h" #include #include #define MAX_FILES 50000 #ifndef PLAYLIST_STRUCT #define PLAYLIST_STRUCT typedef struct { char *file_path; double duration; } SongInfo; typedef struct Node { int id; SongInfo song; struct Node *next; struct Node *prev; } Node; typedef struct { Node *head; Node *tail; int count; pthread_mutex_t mutex; } PlayList; #endif Node *get_current_song(void); Node *get_list_next(Node *node); Node *get_list_prev(Node *node); Node *delete_from_list(PlayList *list, Node *node); Node *find_path_in_playlist(const char *path, PlayList *playlist); Node *find_last_path_in_playlist(const char *path, PlayList *playlist); PlayList *deep_copy_playlist(const PlayList *original_list); int increment_node_id(void); int add_to_list(PlayList *list, Node *new_node); int make_playlist(PlayList **playlist, int argc, char *argv[], bool exact_search, const char *path); int find_node_in_list(PlayList *list, int id, Node **found_node); int is_music_file(const char *filename); void clear_current_song(void); void set_current_song(Node *node); void create_node(Node **node, const char *directory_path, int id); void empty_playlist(PlayList *list); void destroy_node(Node *node); void shuffle_playlist(PlayList *playlist); void shuffle_playlist_starting_from_song(PlayList *playlist, Node *song); void write_m3u_file(const char *filename, const PlayList *playlist); void export_current_playlist(const char *path, const PlayList *playlist); void load_favorites_playlist(const char *directory, PlayList **favorites_playlist); void add_enqueued_songs_to_playlist(FileSystemEntry *root, PlayList *playlist); void save_named_playlist(const char *directory, const char *playlist_name, const PlayList *playlist); void save_favorites_playlist(const char *directory, PlayList *favorites_playlist); void deep_copy_play_list_onto_list(const PlayList *original_list, PlayList **new_list); void create_play_list_from_file_system_entry(FileSystemEntry *root, PlayList *list, int playlist_max); void add_shuffled_albums_to_play_list(FileSystemEntry *root, PlayList *list, int playlist_max); void move_up_list(PlayList *list, Node *node); void move_down_list(PlayList *list, Node *node); Node *read_m3u_file(const char *filename, PlayList *playlist); kew/src/data/song_loader.c000066400000000000000000000336631512074754200160260ustar00rootroot00000000000000/** * @file song_loader.c * @brief Song loading and preparation routines. * * Responsible for loading song data from file */ #include "song_loader.h" #include "img_func.h" #include "lyrics.h" #include "utils/cache.h" #include "utils/file.h" #include "utils/utils.h" #include "stb_image.h" #include "tagLibWrapper.h" #include #include #include #include #include #include #include #define MAX_RECURSION_DEPTH 10 static guint track_counter = 0; void make_file_path(const char *dir_path, char *file_path, size_t file_path_size, const struct dirent *entry) { if (dir_path == NULL || file_path == NULL || entry == NULL || file_path_size == 0) return; size_t dirLen = strnlen(dir_path, file_path_size); size_t nameLen = strnlen(entry->d_name, file_path_size); if (dirLen == file_path_size) { file_path[0] = '\0'; return; } size_t neededSize = dirLen + nameLen + 1; // +1 for '\0' if (dir_path[dirLen - 1] != '/') { neededSize += 1; // for the added '/' } if (neededSize > file_path_size) { file_path[0] = '\0'; return; } // Compose the path safely if (dir_path[dirLen - 1] == '/') { snprintf(file_path, file_path_size, "%s%s", dir_path, entry->d_name); } else { snprintf(file_path, file_path_size, "%s/%s", dir_path, entry->d_name); } // snprintf guarantees null termination if file_path_size > 0 } char *choose_album_art(const char *dir_path, char **custom_file_name_arr, int size, int depth) { if (!dir_path || !custom_file_name_arr || size <= 0 || depth > MAX_RECURSION_DEPTH) { return NULL; } DIR *directory = opendir(dir_path); if (!directory) { return NULL; } struct dirent *entry; struct stat file_stat; char file_path[PATH_MAX]; char resolved_path[PATH_MAX]; char *result = NULL; for (int i = 0; i < size && !result; i++) { rewinddir(directory); while ((entry = readdir(directory)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; int written = snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, entry->d_name); if (written < 0 || written >= (int)sizeof(file_path)) { continue; // path too long } if (realpath(file_path, resolved_path) == NULL) { continue; } if (strncmp(resolved_path, dir_path, strlen(dir_path)) != 0) { continue; // outside allowed directory } if (stat(resolved_path, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) { if (strcmp(entry->d_name, custom_file_name_arr[i]) == 0) { result = strdup(resolved_path); break; } } } } // Recursive search for directories if (!result) { rewinddir(directory); while ((entry = readdir(directory)) != NULL && !result) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; int written = snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, entry->d_name); if (written < 0 || written >= (int)sizeof(file_path)) { continue; } if (realpath(file_path, resolved_path) == NULL) { continue; } if (strncmp(resolved_path, dir_path, strlen(dir_path)) != 0) { continue; } struct stat link_stat; if (lstat(resolved_path, &link_stat) == 0) { if (S_ISLNK(link_stat.st_mode)) { continue; // skip symlink } if (S_ISDIR(link_stat.st_mode)) { result = choose_album_art( resolved_path, custom_file_name_arr, size, depth + 1); } } } } closedir(directory); return result; } char *find_largest_image_file(const char *directory_path, char *largest_image_file, off_t *largest_file_size) { DIR *directory = opendir(directory_path); if (directory == NULL) { fprintf(stderr, "Failed to open directory: %s\n", directory_path); return largest_image_file; } struct dirent *entry; struct stat file_stats; char file_path[PATH_MAX]; char resolved_path[PATH_MAX]; while ((entry = readdir(directory)) != NULL) { // Skip "." and ".." if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // Construct file path safely int len = snprintf(file_path, sizeof(file_path), "%s/%s", directory_path, entry->d_name); if (len < 0 || len >= (int)sizeof(file_path)) { // Path too long, skip continue; } // Resolve the real path if (realpath(file_path, resolved_path) == NULL) { // Could not resolve, skip continue; } // Use lstat to avoid following symlinks if (lstat(resolved_path, &file_stats) == -1) { continue; } if (S_ISLNK(file_stats.st_mode)) { // Ignore symlinks continue; } if (S_ISREG(file_stats.st_mode)) { // Validate extension char *extension = strrchr(entry->d_name, '.'); if (extension != NULL && (strcasecmp(extension, ".jpg") == 0 || strcasecmp(extension, ".jpeg") == 0 || strcasecmp(extension, ".png") == 0 || strcasecmp(extension, ".gif") == 0)) { // Ensure non-negative file size and prevent // integer overflow if (file_stats.st_size < 0) continue; // Optional: impose max file size limit, e.g., // 100 MB const off_t MAX_FILE_SIZE = 100 * 1024 * 1024; if (file_stats.st_size > MAX_FILE_SIZE) continue; if (file_stats.st_size > *largest_file_size) { *largest_file_size = file_stats.st_size; // Free previous allocation if owned if (largest_image_file != NULL) { free(largest_image_file); largest_image_file = NULL; } largest_image_file = strdup(resolved_path); if (largest_image_file == NULL) { fprintf(stderr, "Memory allocation " "failure\n"); // Return early or continue // depending on desired behavior break; } } } } } closedir(directory); return largest_image_file; } gchar *generate_track_id(void) { gchar *track_id = g_strdup_printf("/org/kew/tracklist/track%d", track_counter); track_counter++; return track_id; } int load_color(SongData *songdata) { return get_cover_color(songdata->cover, songdata->coverWidth, songdata->coverHeight, &(songdata->red), &(songdata->green), &(songdata->blue)); } void load_meta_data(SongData *songdata) { AppState *state = get_app_state(); char path[PATH_MAX]; songdata->metadata = malloc(sizeof(TagSettings)); if (songdata->metadata == NULL) { songdata->hasErrors = true; return; } songdata->metadata->replaygainTrack = 0.0; songdata->metadata->replaygainAlbum = 0.0; generate_temp_file_path(songdata->cover_art_path, "cover", ".jpg"); int res = extractTags(songdata->file_path, songdata->metadata, &(songdata->duration), songdata->cover_art_path, &(songdata->lyrics)); if (res == -2) { songdata->hasErrors = true; return; } else if (res == -1) { get_directory_from_path(songdata->file_path, path); char *tmp = NULL; off_t size = 0; char *file_arr[12] = { "front.png", "front.jpg", "front.jpeg", "folder.png", "folder.jpg", "folder.jpeg", "cover.png", "cover.jpg", "cover.jpeg", "f.png", "f.jpg", "f.jpeg", }; tmp = choose_album_art(path, file_arr, 12, 0); if (tmp == NULL) { tmp = find_largest_image_file(path, tmp, &size); } if (tmp != NULL) { c_strcpy(songdata->cover_art_path, tmp, sizeof(songdata->cover_art_path)); free(tmp); tmp = NULL; } else c_strcpy(songdata->cover_art_path, "", sizeof(songdata->cover_art_path)); } else { add_to_cache(state->tmpCache, songdata->cover_art_path); } songdata->cover = get_bitmap(songdata->cover_art_path, &(songdata->coverWidth), &(songdata->coverHeight)); } void unload_lyrics(SongData *songdata) { if (songdata->lyrics) { freeLyrics(songdata->lyrics); songdata->lyrics = NULL; } } SongData *load_song_data(char *file_path) { AppState *state = get_app_state(); SongData *songdata = NULL; songdata = malloc(sizeof(SongData)); songdata->magic = SONG_MAGIC; songdata->track_id = generate_track_id(); songdata->hasErrors = false; c_strcpy(songdata->file_path, "", sizeof(songdata->file_path)); c_strcpy(songdata->cover_art_path, "", sizeof(songdata->cover_art_path)); songdata->red = state->uiSettings.defaultColorRGB.r; songdata->green = state->uiSettings.defaultColorRGB.g; songdata->blue = state->uiSettings.defaultColorRGB.b; songdata->metadata = NULL; songdata->cover = NULL; songdata->duration = 0.0; songdata->avg_bit_rate = 0; songdata->lyrics = NULL; c_strcpy(songdata->file_path, file_path, sizeof(songdata->file_path)); songdata->lyrics = loadLyricsFromLRC(songdata->file_path); load_meta_data(songdata); int res = load_color(songdata); if (songdata->cover && res != 0) { songdata->red = state->uiSettings.defaultColorRGB.r; songdata->green = state->uiSettings.defaultColorRGB.g; songdata->blue = state->uiSettings.defaultColorRGB.b; } return songdata; } void unload_song_data(SongData **songdata) { AppState *state = get_app_state(); if (*songdata == NULL) return; SongData *data = *songdata; if (data->cover != NULL) { stbi_image_free(data->cover); data->cover = NULL; } if (exists_in_cache(state->tmpCache, data->cover_art_path) && is_in_temp_dir(data->cover_art_path)) { delete_file(data->cover_art_path); } unload_lyrics(data); data->magic = 0; free(data->metadata); free(data->track_id); data->cover = NULL; data->metadata = NULL; data->track_id = NULL; free(*songdata); *songdata = NULL; } kew/src/data/song_loader.h000066400000000000000000000004161512074754200160210ustar00rootroot00000000000000/** * @file song_loader.h * @brief Song loading and preparation routines. * * Responsible for loading song data from file */ #include "common/appstate.h" #include SongData *load_song_data(char *file_path); void unload_song_data(SongData **songdata); kew/src/data/tagLibWrapper.cpp000066400000000000000000001665371512074754200166440ustar00rootroot00000000000000/** * @file taglibwrapper.cpp * @brief C++ wrapper around TagLib for metadata extraction. * * Provides a simplified interface for reading song metadata such as * title, artist, album and embedded artwork using the TagLib library. * Exposes a C-compatible API for integration with the main C codebase. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) #include #else #include #endif #include "tagLibWrapper.h" // Base64 character map for decoding static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; const uint32_t MAX_REASONABLE_SIZE = 100 * 1024 * 1024; // 100MB limit #if defined(TAGLIB_MAJOR_VERSION) && TAGLIB_MAJOR_VERSION >= 2 #define HAVE_COMPLEXPROPERTIES 1 #else #define HAVE_COMPLEXPROPERTIES 0 #endif std::vector decodeBase64(const std::string &encoded_string) { const size_t MAX_DECODED_SIZE = 100 * 1024 * 1024; // 100 MB const size_t MAX_ENCODED_SIZE = (MAX_DECODED_SIZE * 4) / 3 + 4; // Max base64 size + padding const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; auto is_base64 = [&](unsigned char c) -> bool { return base64_chars.find(c) != std::string::npos; }; auto base64_index = [&](unsigned char c) -> unsigned char { auto pos = base64_chars.find(c); return pos != std::string::npos ? static_cast(pos) : 0; }; size_t in_len = encoded_string.size(); size_t i = 0; size_t in_ = 0; unsigned char char_array_4[4], char_array_3[3]; // Early check to prevent any overflow issues if (in_len > MAX_ENCODED_SIZE) { throw std::runtime_error( "Base64 input too large: exceeds reasonable limit"); } // Rough estimate of decoded size size_t decoded_size = (in_len * 3) / 4; if (decoded_size > MAX_DECODED_SIZE) throw std::runtime_error( "Base64 input too large: exceeds 100 MB limit"); std::vector decoded_data; try { decoded_data.reserve(decoded_size); } catch (const std::bad_alloc &) { throw std::runtime_error( "Cannot allocate memory for base64 decoding"); } while (in_len-- && encoded_string[in_] != '=' && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_++]; if (i == 4) { for (i = 0; i < 4; i++) char_array_4[i] = base64_index(char_array_4[i]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; if (decoded_data.size() + 3 > MAX_DECODED_SIZE) { throw std::runtime_error( "Decoded data exceeds size limit during " "processing"); } for (i = 0; i < 3; i++) decoded_data.push_back(char_array_3[i]); i = 0; } } // Process remaining characters if (i > 0) { for (size_t j = i; j < 4; j++) char_array_4[j] = 0; for (size_t j = 0; j < 4; j++) char_array_4[j] = is_base64(char_array_4[j]) ? base64_index(char_array_4[j]) : 0; char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; if (i > 1) { size_t remaining_bytes = i - 1; if (decoded_data.size() + remaining_bytes > MAX_DECODED_SIZE) { throw std::runtime_error( "Decoded data exceeds size limit during " "final processing"); } for (size_t j = 0; j < i - 1; j++) decoded_data.push_back(char_array_3[j]); } } return decoded_data; } TagLib::StringList getOggFieldListCaseInsensitive(const TagLib::Ogg::XiphComment *comment, const std::string &fieldName) { if (!comment) { return TagLib::StringList(); } // Use TagLib::String for safer Unicode handling TagLib::String targetField(fieldName, TagLib::String::UTF8); TagLib::String lowerTargetField = targetField.upper(); // TagLib handles case conversion safely TagLib::Ogg::FieldListMap fieldListMap = comment->fieldListMap(); for (auto it = fieldListMap.begin(); it != fieldListMap.end(); ++it) { TagLib::String currentKey = it->first; if (currentKey.upper() == lowerTargetField) { return it->second; } } return TagLib::StringList(); } extern "C" { #include "utils/utils.h" // Function to read a 32-bit unsigned integer from buffer in big-endian // format unsigned int read_uint32_be(const unsigned char *buffer, size_t buffer_size, size_t offset) { if (buffer == nullptr || offset + 4 > buffer_size) { // Handle error - throw exception, return error code, // etc. throw std::runtime_error( "Buffer overflow in read_uint32_be"); } return (static_cast(buffer[offset]) << 24) | (static_cast(buffer[offset + 1]) << 16) | (static_cast(buffer[offset + 2]) << 8) | static_cast(buffer[offset + 3]); } void parseFlacPictureBlock(const std::vector &data, std::string &mimeType, std::vector &imageData) { const unsigned char *ptr = data.data(); size_t offset = 0; size_t dataSize = data.size(); auto readUInt32 = [&](uint32_t &value) -> bool { if (offset + 4 > dataSize) return false; value = (ptr[offset] << 24) | (ptr[offset + 1] << 16) | (ptr[offset + 2] << 8) | ptr[offset + 3]; offset += 4; return true; }; auto safeAdd = [](size_t a, uint32_t b) -> bool { return b <= SIZE_MAX - a; // Check if a + b would overflow }; uint32_t pictureType, mimeLength, descLength, width, height, depth, colors, dataLength; if (!readUInt32(pictureType) || !readUInt32(mimeLength)) return; if (mimeLength > MAX_REASONABLE_SIZE || offset + mimeLength > dataSize) return; // Check for overflow before adding if (!safeAdd(offset, mimeLength) || offset + mimeLength > dataSize) return; mimeType = std::string( reinterpret_cast(&ptr[offset]), mimeLength); offset += mimeLength; if (!readUInt32(descLength)) return; if (!safeAdd(offset, descLength) || offset + descLength > dataSize) return; offset += descLength; if (!readUInt32(width) || !readUInt32(height) || !readUInt32(depth) || !readUInt32(colors) || !readUInt32(dataLength)) return; if (!safeAdd(offset, dataLength) || offset + dataLength > dataSize) return; imageData.assign(&ptr[offset], &ptr[offset + dataLength]); } #if HAVE_COMPLEXPROPERTIES bool extractCoverArtFromOgg(const std::string &audioFilePath, const std::string &outputFileName) { TagLib::FileRef f(audioFilePath.c_str(), true); if (!f.file() || !f.file()->isOpen()) { std::cerr << "Error: Could not open file: " << audioFilePath << std::endl; return false; } auto pictures = f.file()->complexProperties("PICTURE"); if (pictures.isEmpty()) { std::cerr << "No cover art found in the file (via " "complexProperties).\n"; return false; } // Use the first picture found (usually the front cover) for (const auto &pic : pictures) { auto it_data = pic.find("data"); if (it_data == pic.end()) continue; // Skip if no data // Write the image data to a file std::ofstream outFile(outputFileName, std::ios::binary); if (!outFile) { std::cerr << "Error: Could not write to output " "file.\n"; return false; } TagLib::ByteVector bv = it_data->second.toByteVector(); outFile.write(bv.data(), bv.size()); outFile.close(); return true; } std::cerr << "No usable cover image found in complexProperties().\n"; return false; } #else bool extractCoverArtFromOgg(const std::string &audioFilePath, const std::string &outputFileName) { TagLib::File *file = nullptr; TagLib::Tag *tag = nullptr; // Try to open as Ogg Vorbis file = new TagLib::Vorbis::File(audioFilePath.c_str()); if (!file->isValid()) { delete file; // Try to open as Opus file = new TagLib::Ogg::Opus::File(audioFilePath.c_str()); if (!file->isValid()) { delete file; std::cerr << "Error: File not found or not a " "valid Ogg Vorbis or Opus file." << std::endl; return false; // File not found or invalid } } tag = file->tag(); const TagLib::Ogg::XiphComment *xiphComment = dynamic_cast(tag); if (!xiphComment) { std::cerr << "Error: No XiphComment found in the file." << std::endl; delete file; return false; // No cover art found } // Check METADATA_BLOCK_PICTURE TagLib::StringList pictureList = getOggFieldListCaseInsensitive( xiphComment, "METADATA_BLOCK_PICTURE"); if (!pictureList.isEmpty()) { std::string base64Data = pictureList.front().to8Bit(true); std::vector decodedData = decodeBase64(base64Data); std::string mimeType; std::vector imageData; parseFlacPictureBlock(decodedData, mimeType, imageData); std::ofstream outFile(outputFileName, std::ios::binary); if (!outFile) { std::cerr << "Error: Could not write to output file." << std::endl; delete file; return false; // Could not write to output file } outFile.write( reinterpret_cast(imageData.data()), imageData.size()); outFile.close(); delete file; return true; // Success } // Check COVERART and COVERARTMIME TagLib::StringList coverArtList = getOggFieldListCaseInsensitive(xiphComment, "COVERART"); TagLib::StringList coverArtMimeList = getOggFieldListCaseInsensitive(xiphComment, "COVERARTMIME"); if (!coverArtList.isEmpty() && !coverArtMimeList.isEmpty()) { std::string base64Data = coverArtList.front().to8Bit(true); std::vector imageData = decodeBase64(base64Data); std::ofstream outFile(outputFileName, std::ios::binary); if (!outFile) { std::cerr << "Error: Could not write to output file." << std::endl; delete file; return false; // Could not write to output file } outFile.write( reinterpret_cast(imageData.data()), imageData.size()); outFile.close(); delete file; return true; // Success } std::cerr << "No cover art found in the file." << std::endl; delete file; return false; // No cover art found } #endif bool looksLikeJpeg(const std::vector &data) { return data.size() > 4 && data[0] == 0xFF && data[1] == 0xD8 && data[data.size() - 2] == 0xFF && data[data.size() - 1] == 0xD9; } bool looksLikePng(const std::vector &data) { static const unsigned char pngHeader[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; if (data.size() < 12) return false; if (memcmp(data.data(), pngHeader, 8) != 0) return false; // Look for IEND within last 16 bytes for (size_t i = data.size() >= 16 ? data.size() - 16 : 0; i + 3 < data.size(); ++i) { if (memcmp(&data[i], "IEND", 4) == 0) return true; } return false; } bool looksLikeWebp(const std::vector &data) { return data.size() > 12 && memcmp(&data[0], "RIFF", 4) == 0 && memcmp(&data[8], "WEBP", 4) == 0; } bool isAudioOggStreamHeader(const ogg_packet &headerPacket) { if (headerPacket.bytes >= 7 && headerPacket.packet[0] == 0x01 && memcmp(headerPacket.packet + 1, "vorbis", 6) == 0) return true; if (headerPacket.bytes >= 8 && memcmp(headerPacket.packet, "OpusHead", 8) == 0) return true; if (headerPacket.bytes >= 4 && memcmp(headerPacket.packet, "fLaC", 4) == 0) return true; return false; } bool extractCoverArtFromOggVideo(const std::string &audioFilePath, const std::string &outputFileName) { FILE *oggFile = fopen(audioFilePath.c_str(), "rb"); if (!oggFile) { std::cerr << "Error: Could not open file: " << audioFilePath << std::endl; return false; } ogg_sync_state oy; ogg_sync_init(&oy); ogg_page og; ogg_packet op; std::map streams; std::map isHeaderParsed; std::map isAudioStream; std::map> streamPackets; char *buffer; int bytes = 0; while (true) { buffer = ogg_sync_buffer(&oy, 4096); bytes = fread(buffer, 1, 4096, oggFile); ogg_sync_wrote(&oy, bytes); while (ogg_sync_pageout(&oy, &og) == 1) { int serialNo = ogg_page_serialno(&og); // Lazily create state for new streams if (streams.find(serialNo) == streams.end()) { ogg_stream_state os; ogg_stream_init(&os, serialNo); streams[serialNo] = os; isHeaderParsed[serialNo] = false; isAudioStream[serialNo] = false; } ogg_stream_state &os = streams[serialNo]; ogg_stream_pagein(&os, &og); while (ogg_stream_packetout(&os, &op) == 1) { // Only decide on audio-ness for first // packet if (!isHeaderParsed[serialNo]) { isAudioStream[serialNo] = isAudioOggStreamHeader(op); isHeaderParsed[serialNo] = true; if (isAudioStream[serialNo]) continue; } if (!isAudioStream[serialNo]) { // For image/video: collect all // packets from this stream streamPackets[serialNo].insert( streamPackets[serialNo] .end(), op.packet, op.packet + op.bytes); } } } if (bytes == 0) break; } // Clean up for (auto &entry : streams) ogg_stream_clear(&(entry.second)); ogg_sync_clear(&oy); fclose(oggFile); // Write out a valid image stream for (auto &kv : streamPackets) { const auto &data = kv.second; if (looksLikeJpeg(data) || looksLikePng(data) || looksLikeWebp(data)) { std::ofstream outFile(outputFileName, std::ios::binary); if (!outFile) { std::cerr << "Error: Could not write " "to output file." << std::endl; return false; } outFile.write( reinterpret_cast(data.data()), data.size()); outFile.close(); return true; } } std::cerr << "No embedded image stream found in file." << std::endl; return false; } bool extractCoverArtFromMp3(const std::string &inputFile, const std::string &coverFilePath) { TagLib::MPEG::File file(inputFile.c_str()); if (!file.isValid()) { return false; } const TagLib::ID3v2::Tag *id3v2tag = file.ID3v2Tag(); if (id3v2tag) { // Collect all attached picture frames TagLib::ID3v2::FrameList frames; frames.append(id3v2tag->frameListMap()["APIC"]); frames.append(id3v2tag->frameListMap()["PIC"]); if (!frames.isEmpty()) { for (auto it = frames.begin(); it != frames.end(); ++it) { const TagLib::ID3v2:: AttachedPictureFrame *picFrame = dynamic_cast< TagLib::ID3v2:: AttachedPictureFrame *>( *it); if (picFrame) { // Access picture data and MIME // type TagLib::ByteVector pictureData = picFrame->picture(); TagLib::String mimeType = picFrame->mimeType(); // Construct the output file // path std::string outputFilePath = coverFilePath; // Write the image data to a // file FILE *outFile = fopen( outputFilePath.c_str(), "wb"); if (outFile) { fwrite( pictureData.data(), 1, pictureData.size(), outFile); fclose(outFile); return true; } else { return false; // Failed // to open // output // file } // Break if only the first image // is needed break; } } } else { return false; // No picture frames found } } else { return false; // No ID3v2 tag found } return true; // Success } bool extractCoverArtFromFlac(const std::string &inputFile, const std::string &coverFilePath) { TagLib::FLAC::File file(inputFile.c_str()); if (file.pictureList().size() > 0) { const TagLib::FLAC::Picture *picture = file.pictureList().front(); if (picture) { FILE *coverFile = fopen(coverFilePath.c_str(), "wb"); if (coverFile) { fwrite(picture->data().data(), 1, picture->data().size(), coverFile); fclose(coverFile); return true; } else { return false; } } } return false; } bool extractCoverArtFromWav(const std::string &inputFile, const std::string &coverFilePath) { TagLib::RIFF::WAV::File file(inputFile.c_str()); if (!file.isValid()) { return false; } const TagLib::ID3v2::Tag *id3v2tag = file.ID3v2Tag(); if (id3v2tag) { // Collect all attached picture frames TagLib::ID3v2::FrameList frames; frames.append(id3v2tag->frameListMap()["APIC"]); frames.append(id3v2tag->frameListMap()["PIC"]); if (!frames.isEmpty()) { for (auto it = frames.begin(); it != frames.end(); ++it) { const TagLib::ID3v2:: AttachedPictureFrame *picFrame = dynamic_cast< TagLib::ID3v2:: AttachedPictureFrame *>( *it); if (picFrame) { // Access picture data and MIME // type TagLib::ByteVector pictureData = picFrame->picture(); TagLib::String mimeType = picFrame->mimeType(); // Construct the output file // path std::string outputFilePath = coverFilePath; // Write the image data to a // file FILE *outFile = fopen( outputFilePath.c_str(), "wb"); if (outFile) { fwrite( pictureData.data(), 1, pictureData.size(), outFile); fclose(outFile); return true; } else { return false; // Failed // to open // output // file } // Break if only the first image // is needed break; } } } else { return false; // No picture frames found } } else { return false; // No ID3v2 tag found } return true; // Success } bool extractCoverArtFromOpus(const std::string &audioFilePath, const std::string &outputFileName) { int error; OggOpusFile *of = op_open_file(audioFilePath.c_str(), &error); if (error != OPUS_OK || of == nullptr) { std::cerr << "Error: Failed to open Opus file." << std::endl; return false; } const OpusTags *tags = op_tags(of, -1); if (!tags) { std::cerr << "Error: No tags found in Opus file." << std::endl; op_free(of); return false; } // Search through the metadata for an attached picture (if // present) for (int i = 0; i < tags->comments; ++i) { // Check for METADATA_BLOCK_PICTURE const char *comment = tags->user_comments[i]; if (strncasecmp(comment, "METADATA_BLOCK_PICTURE=", 23) == 0) { // Extract the value after // "METADATA_BLOCK_PICTURE=" std::string metadataBlockPicture(comment + 23); // Base64-decode this value to get the binary // PICTURE block std::vector pictureBlock = decodeBase64(metadataBlockPicture); if (pictureBlock.empty()) { std::cerr << "Failed to decode Base64 data." << std::endl; op_free(of); return false; } // Now parse the binary pictureBlock to extract // the image data size_t offset = 0; if (pictureBlock.size() < 32) { std::cerr << "Picture block too small." << std::endl; op_free(of); return false; } // Read PICTURE TYPE read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read MIME TYPE LENGTH unsigned int mimeTypeLength = read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read MIME TYPE if (offset + mimeTypeLength > pictureBlock.size()) { op_free(of); return false; } offset += mimeTypeLength; // Read DESCRIPTION LENGTH unsigned int descriptionLength = read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read DESCRIPTION if (offset + descriptionLength > pictureBlock.size()) { op_free(of); return false; } offset += descriptionLength; // Optionally print or ignore description // Read WIDTH read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read HEIGHT read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; ; // Read COLOR DEPTH read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read NUMBER OF COLORS read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; // Read DATA LENGTH unsigned int dataLength = read_uint32_be(pictureBlock.data(), pictureBlock.size(), offset); offset += 4; if (offset + dataLength > pictureBlock.size()) { std::cerr << "Invalid image data length." << std::endl; op_free(of); return false; } // Extract image data std::vector imageData( pictureBlock.begin() + offset, pictureBlock.begin() + offset + dataLength); // Save image data to file std::ofstream outFile(outputFileName, std::ios::binary); if (!outFile) { std::cerr << "Error: Could not write " "to output file." << std::endl; op_free(of); return false; } outFile.write(reinterpret_cast( imageData.data()), imageData.size()); outFile.close(); op_free(of); return true; } } std::cerr << "No cover art found in the metadata." << std::endl; op_free(of); return false; } bool extractCoverArtFromMp4(const std::string &inputFile, const std::string &coverFilePath) { TagLib::MP4::File file(inputFile.c_str()); if (!file.isValid()) { return false; } const TagLib::MP4::Item coverItem = file.tag()->item("covr"); if (coverItem.isValid()) { TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList(); if (!coverArtList.isEmpty()) { const TagLib::MP4::CoverArt &coverArt = coverArtList.front(); FILE *coverFile = fopen(coverFilePath.c_str(), "wb"); if (coverFile) { fwrite(coverArt.data().data(), 1, coverArt.data().size(), coverFile); fclose(coverFile); return true; // Success } else { fprintf( stderr, "Could not open output file '%s'\n", coverFilePath.c_str()); return false; // Failed to open the // output file } } } return false; // No valid cover item or cover art found } void trimcpp(std::string &str) { // Remove leading spaces str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) { return !std::isspace(ch); })); // Remove trailing spaces str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) { return !std::isspace(ch); }) .base(), str.end()); } void turnFilePathIntoTitle(const char *filePath, char *title, size_t titleMaxLength) { std::string filePathStr( filePath); // Convert the C-style string to std::string size_t lastSlashPos = filePathStr.find_last_of( "/\\"); // Find the last '/' or '\\' size_t lastDotPos = filePathStr.find_last_of('.'); // Find the last '.' // Validate that both positions exist and the dot is after the // last slash if (lastSlashPos != std::string::npos && lastDotPos != std::string::npos && lastDotPos > lastSlashPos) { // Extract the substring between the last slash and the // last dot std::string extractedTitle = filePathStr.substr( lastSlashPos + 1, lastDotPos - lastSlashPos - 1); // Trim any unwanted spaces trimcpp(extractedTitle); // Ensure title is not longer than titleMaxLength, // including the null terminator if (extractedTitle.length() >= titleMaxLength) { extractedTitle = extractedTitle.substr( 0, titleMaxLength - 1); } // Copy the result into the output char* title, ensuring // no overflow c_strcpy( title, extractedTitle.c_str(), titleMaxLength - 1); // Copy up to titleMaxLength - 1 characters title[titleMaxLength - 1] = '\0'; // Null-terminate the string } else { // If no valid title is found, ensure title is an empty // string title[0] = '\0'; } } double parseDecibelValue(const TagLib::String &dbString) { double val = 0.0; try { std::string valStr = dbString.to8Bit(true); std::string filtered; for (char c : valStr) { if (std::isdigit((unsigned char)c) || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E') { filtered.push_back(c); } } val = std::stod(filtered); } catch (...) { } return val; } static bool detectLrcFormat(const TagLib::StringList &lines) { for (const auto &line : lines) { std::string text = line.toCString(true); if (text.size() > 3 && text[0] == '[' && std::isdigit((unsigned char)text[1])) return true; } return false; } static bool parseTimedLyricsFromTagLines(const TagLib::StringList &lines, Lyrics *lyrics) { size_t capacity = 64; lyrics->lines = (LyricsLine *)malloc(sizeof(LyricsLine) * capacity); if (!lyrics->lines) return false; for (const auto &line : lines) { std::string text = line.toCString(true); if (text.empty() || text[0] != '[') continue; int min = 0, sec = 0, cs = 0; char lyricText[512] = {0}; if (sscanf(text.c_str(), "[%d:%d.%d]%511[^\r\n]", &min, &sec, &cs, lyricText) == 4) { if (lyrics->count == capacity) { capacity *= 2; LyricsLine *newLines = (LyricsLine *)realloc(lyrics->lines, sizeof(LyricsLine) * capacity); if (!newLines) return false; lyrics->lines = newLines; } char *start = lyricText; while (isspace((unsigned char)*start)) start++; char *end = start + strlen(start); while (end > start && isspace((unsigned char)*(end - 1))) *(--end) = '\0'; lyrics->lines[lyrics->count].timestamp = min * 60.0 + sec + cs / 100.0; lyrics->lines[lyrics->count].text = strdup(start); if (!lyrics->lines[lyrics->count].text) return false; lyrics->count++; } } lyrics->isTimed = 1; return (lyrics->count > 0); } static bool parseUntimedLyricsFromTagLines(const TagLib::StringList &lines, Lyrics *lyrics) { size_t capacity = lines.size() > 64 ? lines.size() : 64; lyrics->lines = (LyricsLine *)malloc(sizeof(LyricsLine) * capacity); if (!lyrics->lines) return false; lyrics->count = 0; for (size_t i = 0; i < lines.size(); ++i) { std::string text = lines[i].toCString(true); // Trim leading/trailing whitespace size_t start = 0; while (start < text.size() && std::isspace((unsigned char)text[start])) start++; size_t end = text.size(); while (end > start && std::isspace((unsigned char)text[end - 1])) end--; text = text.substr(start, end - start); if (text.empty()) continue; if (lyrics->count == capacity) { capacity *= 2; LyricsLine *newLines = (LyricsLine *)realloc(lyrics->lines, sizeof(LyricsLine) * capacity); if (!newLines) return false; lyrics->lines = newLines; } lyrics->lines[lyrics->count].timestamp = 0.0; lyrics->lines[lyrics->count].text = strdup(text.c_str()); if (!lyrics->lines[lyrics->count].text) return false; lyrics->count++; } lyrics->isTimed = 0; return (lyrics->count > 0); } static bool loadLyricsVorbisFromTag(TagLib::Ogg::XiphComment *tag, Lyrics **lyricsOut) { if (!tag || !lyricsOut) return false; auto fields = tag->fieldListMap(); const char *keys[] = {"LYRICS", "UNSYNCEDLYRICS", "lyrics"}; const TagLib::StringList *entry = nullptr; for (auto key : keys) { if (fields.contains(key)) { entry = &fields[key]; break; } } if (!entry || entry->isEmpty()) return false; // Build final line list TagLib::StringList lines; for (const auto &val : *entry) { TagLib::StringList split = val.split("\n"); for (const auto &ln : split) lines.append(ln); } // Remove trailing blanks while (!lines.isEmpty() && lines.back().stripWhiteSpace().isEmpty()) { auto it = lines.end(); --it; lines.erase(it); } if (lines.isEmpty()) return false; Lyrics *lyrics = (Lyrics *)calloc(1, sizeof(Lyrics)); if (!lyrics) return false; lyrics->max_length = 1024; bool looksLikeLrc = detectLrcFormat(lines); bool ok = looksLikeLrc ? parseTimedLyricsFromTagLines(lines, lyrics) : parseUntimedLyricsFromTagLines(lines, lyrics); if (!ok) { freeLyrics(lyrics); return false; } *lyricsOut = lyrics; return true; } static bool loadLyricsVorbisFLAC(TagLib::FLAC::File *file, Lyrics **lyricsOut) { return loadLyricsVorbisFromTag(file->xiphComment(), lyricsOut); } static bool loadLyricsVorbisOgg(TagLib::Ogg::Vorbis::File *file, Lyrics **lyricsOut) { return loadLyricsVorbisFromTag(file->tag(), lyricsOut); } static bool loadLyricsVorbisOpus(TagLib::Ogg::Opus::File *file, Lyrics **lyricsOut) { return loadLyricsVorbisFromTag(file->tag(), lyricsOut); } static bool loadLyricsFromSYLTTag(TagLib::ID3v2::Tag *id3v2Tag, Lyrics **lyricsOut) { if (!id3v2Tag || !lyricsOut) return false; auto frames = id3v2Tag->frameList("SYLT"); if (frames.isEmpty()) return false; // Count total lines across all SYLT frames size_t totalLines = 0; for (auto frame : frames) { auto sylt = dynamic_cast(frame); if (!sylt) continue; totalLines += sylt->synchedText().size(); } if (totalLines == 0) return false; Lyrics *lyrics = (Lyrics *)calloc(1, sizeof(Lyrics)); if (!lyrics) return false; lyrics->max_length = 1024; lyrics->lines = (LyricsLine *)malloc(sizeof(LyricsLine) * totalLines); if (!lyrics->lines) { free(lyrics); return false; } lyrics->count = 0; for (auto frame : frames) { auto sylt = dynamic_cast(frame); if (!sylt) continue; for (auto lyric : sylt->synchedText()) { char *text = strdup(lyric.text.toCString(true)); if (!text) continue; // Trim leading/trailing whitespace char *start = text; while (isspace((unsigned char)*start)) start++; char *end = start + strlen(start); while (end > start && isspace((unsigned char)*(end - 1))) *(--end) = '\0'; lyrics->lines[lyrics->count].timestamp = lyric.time / 1000.0; // ms → s lyrics->lines[lyrics->count].text = strdup(start); free(text); if (!lyrics->lines[lyrics->count].text) { freeLyrics(lyrics); return false; } lyrics->count++; } } lyrics->isTimed = 1; *lyricsOut = lyrics; return true; } static bool loadLyricsFromUSLTTag(TagLib::ID3v2::Tag *id3v2Tag, Lyrics **lyricsOut) { if (!id3v2Tag || !lyricsOut) return false; auto frames = id3v2Tag->frameList("USLT"); if (frames.isEmpty()) return false; TagLib::ID3v2::Frame *best = nullptr; size_t bestLen = 0; // Pick the largest USLT payload for (auto frame : frames) { TagLib::String text = frame->toString(); if (text.isEmpty()) continue; size_t len = text.size(); if (len > bestLen) { best = frame; bestLen = len; } } if (!best) return false; // Convert once to UTF-8 std::string utf8 = best->toString().to8Bit(true); if (utf8.empty()) return false; // Normalize line endings std::string normalized; normalized.reserve(utf8.size()); for (size_t i = 0; i < utf8.size(); ++i) { if (utf8[i] == '\r') { if (i + 1 < utf8.size() && utf8[i + 1] == '\n') ++i; normalized.push_back('\n'); } else { normalized.push_back(utf8[i]); } } // Split into TagLib::StringList TagLib::StringList lines; size_t start = 0; while (start < normalized.size()) { size_t end = normalized.find('\n', start); if (end == std::string::npos) end = normalized.size(); lines.append(TagLib::String( normalized.substr(start, end - start), TagLib::String::UTF8)); start = end + 1; } // Trim trailing blanks while (!lines.isEmpty() && lines.back().stripWhiteSpace().isEmpty()) lines.erase(--lines.end()); if (lines.isEmpty()) return false; Lyrics *lyrics = (Lyrics *)calloc(1, sizeof(Lyrics)); if (!lyrics) return false; lyrics->max_length = 1024; bool looksLikeLrc = detectLrcFormat(lines); bool ok = looksLikeLrc ? parseTimedLyricsFromTagLines(lines, lyrics) : parseUntimedLyricsFromTagLines(lines, lyrics); if (!ok) { freeLyrics(lyrics); return false; } *lyricsOut = lyrics; return true; } int extractTags(const char *input_file, TagSettings *tag_settings, double *duration, const char *coverFilePath, Lyrics **lyrics) { memset(tag_settings, 0, sizeof(TagSettings)); // Initialize tag settings tag_settings->replaygainTrack = 0.0; tag_settings->replaygainAlbum = 0.0; // Use TagLib's FileRef for generic file parsing. TagLib::FileRef f(input_file); if (f.isNull() || !f.file()) { fprintf(stderr, "FileRef is null or file could not be opened: " "'%s'\n", input_file); char title[4096]; turnFilePathIntoTitle(input_file, title, 4096); c_strcpy(tag_settings->title, title, sizeof(tag_settings->title) - 1); tag_settings->title[sizeof(tag_settings->title) - 1] = '\0'; return -1; } // Extract tags using the stable method that worked before. const TagLib::Tag *tag = f.tag(); if (!tag) { fprintf(stderr, "Tag is null for file '%s'\n", input_file); return -2; } // Copy the title c_strcpy(tag_settings->title, tag->title().toCString(true), sizeof(tag_settings->title) - 1); tag_settings->title[sizeof(tag_settings->title) - 1] = '\0'; // Check if the title is empty, and if so, use the file path to // generate a title if (strnlen(tag_settings->title, 10) == 0) { char title[4096]; turnFilePathIntoTitle(input_file, title, 4096); c_strcpy(tag_settings->title, title, sizeof(tag_settings->title) - 1); tag_settings->title[sizeof(tag_settings->title) - 1] = '\0'; } else { // Copy the artist c_strcpy(tag_settings->artist, tag->artist().toCString(true), sizeof(tag_settings->artist) - 1); tag_settings->artist[sizeof(tag_settings->artist) - 1] = '\0'; // Copy the album c_strcpy(tag_settings->album, tag->album().toCString(true), sizeof(tag_settings->album) - 1); tag_settings->album[sizeof(tag_settings->album) - 1] = '\0'; // Copy the year as date snprintf(tag_settings->date, sizeof(tag_settings->date), "%d", (int)tag->year()); if (tag_settings->date[0] == '0') { tag_settings->date[0] = '\0'; } } if (*lyrics == nullptr) { if (auto mpegFile = dynamic_cast(f.file())) { // 1) True synchronized lyrics (SYLT) loadLyricsFromSYLTTag(mpegFile->ID3v2Tag(), lyrics); // 2) USLT fallback (may contain LRC timestamps) if (*lyrics == nullptr) loadLyricsFromUSLTTag(mpegFile->ID3v2Tag(), lyrics); } if (auto flacFile = dynamic_cast(f.file())) loadLyricsVorbisFLAC(flacFile, lyrics); else if (auto oggFile = dynamic_cast(f.file())) loadLyricsVorbisOgg(oggFile, lyrics); else if (auto opusFile = dynamic_cast(f.file())) loadLyricsVorbisOpus(opusFile, lyrics); } // Extract audio properties for duration. if (f.audioProperties()) { *duration = f.audioProperties()->lengthInSeconds(); } else { *duration = 0.0; fprintf(stderr, "No audio properties found for file '%s'\n", input_file); return -2; } // Extract replay gain information if (std::string(input_file).find(".mp3") != std::string::npos) { TagLib::MPEG::File mp3File(input_file); TagLib::ID3v2::Tag *id3v2Tag = mp3File.ID3v2Tag(); if (id3v2Tag) { // Retrieve all TXXX frames TagLib::ID3v2::FrameList frames = id3v2Tag->frameList("TXXX"); for (TagLib::ID3v2::FrameList::Iterator it = frames.begin(); it != frames.end(); ++it) { // Cast to the user-text (TXXX) frame // class TagLib::ID3v2::TextIdentificationFrame *txxx = dynamic_cast< TagLib::ID3v2:: TextIdentificationFrame *>( *it); if (!txxx) continue; TagLib::StringList fields = txxx->fieldList(); if (fields.size() >= 2) { TagLib::String desc = fields[0]; TagLib::String val = fields[1]; if (desc.upper() == "REPLAYGAIN_TRACK_GAIN") { tag_settings ->replaygainTrack = parseDecibelValue( val); } else if (desc.upper() == "REPLAYGAIN_ALBUM_" "GAIN") { tag_settings ->replaygainAlbum = parseDecibelValue( val); } } } } TagLib::APE::Tag *apeTag = mp3File.APETag(); if (apeTag) { TagLib::APE::ItemListMap items = apeTag->itemListMap(); for (auto it = items.begin(); it != items.end(); ++it) { std::string key = it->first.upper().toCString(); TagLib::String value = it->second.toString(); if (key == "REPLAYGAIN_TRACK_GAIN") { tag_settings->replaygainTrack = parseDecibelValue(value); } else if (key == "REPLAYGAIN_ALBUM_GAIN") { tag_settings->replaygainAlbum = parseDecibelValue(value); } } } } else if (std::string(input_file).find(".flac") != std::string::npos) { TagLib::FLAC::File flacFile(input_file); TagLib::Ogg::XiphComment *xiphComment = flacFile.xiphComment(); if (xiphComment) { const TagLib::Ogg::FieldListMap &fieldMap = xiphComment->fieldListMap(); auto trackGainIt = fieldMap.find("REPLAYGAIN_TRACK_GAIN"); if (trackGainIt != fieldMap.end()) { const TagLib::StringList & trackGainList = trackGainIt->second; if (!trackGainList.isEmpty()) { tag_settings->replaygainTrack = parseDecibelValue( trackGainList.front()); } } auto albumGainIt = fieldMap.find("REPLAYGAIN_ALBUM_GAIN"); if (albumGainIt != fieldMap.end()) { const TagLib::StringList & albumGainList = albumGainIt->second; if (!albumGainList.isEmpty()) { tag_settings->replaygainAlbum = parseDecibelValue( albumGainList.front()); } } } } std::string filename(input_file); std::string extension = filename.substr(filename.find_last_of('.') + 1); bool coverArtExtracted = false; if (extension == "mp3") { coverArtExtracted = extractCoverArtFromMp3(input_file, coverFilePath); } else if (extension == "flac") { coverArtExtracted = extractCoverArtFromFlac(input_file, coverFilePath); } else if (extension == "m4a" || extension == "aac") { coverArtExtracted = extractCoverArtFromMp4(input_file, coverFilePath); } if (extension == "opus") { coverArtExtracted = extractCoverArtFromOpus(input_file, coverFilePath); } else if (extension == "ogg") { coverArtExtracted = extractCoverArtFromOgg(input_file, coverFilePath); if (!coverArtExtracted) { coverArtExtracted = extractCoverArtFromOggVideo( input_file, coverFilePath); } } else if (extension == "wav") { coverArtExtracted = extractCoverArtFromWav(input_file, coverFilePath); } if (coverArtExtracted) { return 0; } else { return -1; } } } kew/src/data/tagLibWrapper.h000066400000000000000000000011741512074754200162720ustar00rootroot00000000000000/** * @file taglibwrapper.h * @brief C++ wrapper around TagLib for metadata extraction. * * Provides a simplified interface for reading song metadata such as * title, artist, album and embedded artwork using the TagLib library. * Exposes a C-compatible API for integration with the main C codebase. */ #ifndef TAGLIB_WRAPPER_H #define TAGLIB_WRAPPER_H #ifdef __cplusplus extern "C" { #endif #include "common/appstate.h" int extractTags(const char *input_file, TagSettings *tag_settings, double *duration, const char *cover_file_path, Lyrics **lyrics); #ifdef __cplusplus } #endif #endif // TAGLIB_WRAPPER_H kew/src/data/theme.c000066400000000000000000000267231512074754200146330ustar00rootroot00000000000000 /** * @file theme.c * @brief Loads themes. * * Loads themes, and copies the themes to config dir. */ #include "theme.h" #include "common/appstate.h" #include "common/common.h" #include "utils/utils.h" #include #include #include #include #include #include #include #ifndef PREFIX #define PREFIX "/usr/local" // Fallback if not set in the makefile #endif typedef struct { const char *key; ColorValue *field; } ThemeMapping; int hex_to_pixel(const char *hex, PixelData *result) { if (!hex || strlen(hex) != 7 || hex[0] != '#') return -1; if (hex[0] == '#') hex++; // skip # if (strlen(hex) == 6) { char r[3], g[3], b[3]; strncpy(r, hex, 2); r[2] = '\0'; strncpy(g, hex + 2, 2); g[2] = '\0'; strncpy(b, hex + 4, 2); b[2] = '\0'; (*result).r = (unsigned char)strtol(r, NULL, 16); (*result).g = (unsigned char)strtol(g, NULL, 16); (*result).b = (unsigned char)strtol(b, NULL, 16); } return 0; } void trim_whitespace(char *str) { while (isspace((unsigned char)*str)) str++; char *end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) *end-- = '\0'; memmove(str, str, strlen(str) + 1); } void remove_comment(char *str) { char *p = str; while (*p) { if (*p == '#') { // If previous char is whitespace, treat as comment // start if (p == str || isspace((unsigned char)*(p - 1))) { *p = '\0'; break; } } p++; } } // Parse hex color safely (e.g. #aabbcc) int parse_hex_color(const char *hex, PixelData *out) { if (!hex || strlen(hex) != 7 || hex[0] != '#') return 0; unsigned int r, g, b; if (sscanf(hex + 1, "%02x%02x%02x", &r, &g, &b) != 3) return 0; out->r = (unsigned char)r; out->g = (unsigned char)g; out->b = (unsigned char)b; return 1; } int parse_color_value(const char *value, ColorValue *out) { if (!value || !out) return 0; // Check if it's hex (#RRGGBB) if (value[0] == '#') { unsigned int r, g, b; if (sscanf(value, "#%02x%02x%02x", &r, &g, &b) != 3) { return 0; // failed to parse hex } out->type = COLOR_TYPE_RGB; out->rgb.r = (uint8_t)r; out->rgb.g = (uint8_t)g; out->rgb.b = (uint8_t)b; return 1; } // Otherwise, try integer for ANSI index char *endptr = NULL; errno = 0; long index = strtol(value, &endptr, 10); if (errno || endptr == value || *endptr != '\0') { return 0; // invalid number } if (index < -1 || index > 15) { return 0; // out of range for 16-color ANSI } out->type = COLOR_TYPE_ANSI; out->ansiIndex = (int8_t)index; return 1; } int load_theme_from_file(const char *themes_dir, const char *filename, Theme *current_theme) { memset(current_theme, 0, sizeof(Theme)); if (!themes_dir || !filename) { fprintf(stderr, "Theme directory or filename is NULL.\n"); set_error_message("Theme directory or filename is NULL."); return 0; } char path[512]; if (snprintf(path, sizeof(path), "%s/%s", themes_dir, filename) >= (int)sizeof(path)) { fprintf(stderr, "Theme path too long.\n"); return 0; } FILE *file = fopen(path, "r"); if (!file) { fprintf(stderr, "Failed to open theme file.\n"); set_error_message("Failed to open theme file."); return 0; } // Map of all known keys to Theme fields ThemeMapping mappings[] = { {"accent", ¤t_theme->accent}, {"text", ¤t_theme->text}, {"textDim", ¤t_theme->textDim}, {"textMuted", ¤t_theme->textMuted}, {"logo", ¤t_theme->logo}, {"header", ¤t_theme->header}, {"footer", ¤t_theme->footer}, {"help", ¤t_theme->help}, {"link", ¤t_theme->link}, {"nowplaying", ¤t_theme->nowplaying}, {"playlist_rownum", ¤t_theme->playlist_rownum}, {"playlist_title", ¤t_theme->playlist_title}, {"playlist_playing", ¤t_theme->playlist_playing}, {"trackview_title", ¤t_theme->trackview_title}, {"trackview_artist", ¤t_theme->trackview_artist}, {"trackview_album", ¤t_theme->trackview_album}, {"trackview_year", ¤t_theme->trackview_year}, {"trackview_time", ¤t_theme->trackview_time}, {"trackview_visualizer", ¤t_theme->trackview_visualizer}, {"trackview_lyrics", ¤t_theme->trackview_lyrics}, {"library_artist", ¤t_theme->library_artist}, {"library_album", ¤t_theme->library_album}, {"library_track", ¤t_theme->library_track}, {"library_enqueued", ¤t_theme->library_enqueued}, {"library_playing", ¤t_theme->library_playing}, {"search_label", ¤t_theme->search_label}, {"search_query", ¤t_theme->search_query}, {"search_result", ¤t_theme->search_result}, {"search_enqueued", ¤t_theme->search_enqueued}, {"search_playing", ¤t_theme->search_playing}, {"progress_filled", ¤t_theme->progress_filled}, {"progress_elapsed", ¤t_theme->progress_elapsed}, {"progress_empty", ¤t_theme->progress_empty}, {"progress_duration", ¤t_theme->progress_duration}, {"status_info", ¤t_theme->status_info}, {"status_warning", ¤t_theme->status_warning}, {"status_error", ¤t_theme->status_error}, {"status_success", ¤t_theme->status_success}}; const size_t mapping_count = sizeof(mappings) / sizeof(ThemeMapping); AppState *state = get_app_state(); ColorValue default_color; default_color.type = COLOR_TYPE_RGB; default_color.rgb = state->uiSettings.defaultColorRGB; for (size_t i = 0; i < mapping_count; ++i) { *(mappings[i].field) = default_color; } char line[512]; int line_num = 0; int found = 0; while (fgets(line, sizeof(line), file)) { line_num++; remove_comment(line); trim_whitespace(line); if (strlen(line) == 0 || line[0] == '[') continue; // skip empty or section headers char *eq = strchr(line, '='); if (!eq) { continue; } *eq = '\0'; char *key = line; char *value = eq + 1; trim_whitespace(key); trim_whitespace(value); // Replace dots with underscores for (char *c = key; *c; c++) { if (*c == '.') *c = '_'; } for (size_t i = 0; i < mapping_count; ++i) { if (strcmp(key, "name") == 0) { // Copy theme name safely strncpy(current_theme->theme_name, value, sizeof(current_theme->theme_name) - 1); current_theme->theme_name [sizeof(current_theme->theme_name) - 1] = '\0'; found = 1; break; } if (strcmp(key, "author") == 0) { // Copy theme name safely strncpy(current_theme->theme_author, value, sizeof(current_theme->theme_author) - 1); current_theme->theme_author [sizeof(current_theme->theme_author) - 1] = '\0'; found = 1; break; } else if (strcmp(key, mappings[i].key) == 0) { ColorValue color; if (!parse_color_value(value, &color)) { fprintf(stderr, "Invalid color value at line " "%d: %s\n", line_num, value); } else { *(mappings[i].field) = color; found = 1; } break; } } } fclose(file); return found; } bool ensure_default_themes(void) { bool copied = false; char *config_path = get_config_path(); if (!config_path) return false; char themes_path[PATH_MAX]; if (snprintf(themes_path, sizeof(themes_path), "%s/themes", config_path) >= (int)sizeof(themes_path)) { free(config_path); return false; } struct stat st; if (stat(themes_path, &st) == -1) { // Directory doesn't exist → create it if (mkdir(themes_path, 0755) == -1) { free(config_path); return false; } } const char *system_themes = PREFIX "/share/kew/themes"; DIR *dir = opendir(system_themes); if (!dir) { free(config_path); return false; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // Only copy real files that look like themes if (entry->d_type == DT_REG && (strstr(entry->d_name, ".theme") || strstr(entry->d_name, ".txt"))) { char src[PATH_MAX], dst[PATH_MAX]; if (snprintf(src, sizeof(src), "%s/%s", system_themes, entry->d_name) >= (int)sizeof(src)) continue; if (snprintf(dst, sizeof(dst), "%s/%s", themes_path, entry->d_name) >= (int)sizeof(dst)) continue; bool need_copy = true; // Check modification time struct stat src_st, dst_st; if (stat(src, &src_st) == 0 && stat(dst, &dst_st) == 0) { if (difftime(src_st.st_mtime, dst_st.st_mtime) <= 0) { // Destination is newer or same → no need to copy need_copy = false; } } if (need_copy) { if (copy_file(src, dst)) copied = true; } } } closedir(dir); free(config_path); return copied; } kew/src/data/theme.h000066400000000000000000000005071512074754200146300ustar00rootroot00000000000000/** * @file theme.h * @brief Loads themes. * * Loads themes, and copies the themes to config dir. */ #include "common/appstate.h" #include #include #include int load_theme_from_file(const char *themes_dir, const char *filename, Theme *current_theme); bool ensure_default_themes(void); kew/src/kew.c000066400000000000000000000576431512074754200134130ustar00rootroot00000000000000/* kew - Music For The Shell Copyright (C) 2022 Ravachol http://codeberg.org/ravachol/kew $$\ $$ | $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$ | $$ |$$ __$$\ $$ | $$ | $$ | $$$$$$ / $$$$$$$$ |$$ | $$ | $$ | $$ _$$< $$ ____|$$ | $$ | $$ | $$ | \$$\ \$$$$$$$\ \$$$$$\$$$$ | \__| \__| \_______| \_____\____/ 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 of the License, or (at your option) any later version, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __USE_POSIX #define __USE_POSIX #endif #ifdef __FreeBSD__ #define __BSD_VISIBLE 1 #endif #include "common/appstate.h" #include "common/common.h" #include "sys/mpris.h" #include "sys/notifications.h" #include "sys/sys_integration.h" #include "ui/chroma.h" #include "ui/cli.h" #include "ui/common_ui.h" #include "ui/control_ui.h" #include "ui/input.h" #include "ui/player_ui.h" #include "ui/playlist_ui.h" #include "ui/queue_ui.h" #include "ui/search_ui.h" #include "ui/settings.h" #include "ui/termbox2_input.h" #include "ui/visuals.h" #include "ops/library_ops.h" #include "ops/playback_clock.h" #include "ops/playback_ops.h" #include "ops/playback_state.h" #include "ops/playback_system.h" #include "ops/playlist_ops.h" #include "ops/track_manager.h" #include "utils/cache.h" #include "utils/file.h" #include "utils/term.h" #include "utils/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char VERSION[] = "3.7.3"; AppState *state_ptr = NULL; void update_player(void) { AppState *state = get_app_state(); UIState *uis = &(state->uiState); struct winsize ws; ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); if (ws.ws_col != state->uiState.windowSize.ws_col || ws.ws_row != state->uiState.windowSize.ws_row) { uis->resizeFlag = 1; state->uiState.windowSize = ws; } if (uis->resizeFlag) { resize(uis); } else { refresh_player(); } } void prepare_next_song(void) { AppState *state = get_app_state(); reset_clock(); handle_skip_out_of_order(); finish_loading(); set_next_song(NULL); trigger_refresh(); Node *current = get_current_song(); if (!is_repeat_enabled() || current == NULL) { unload_previous_song(); } if (current == NULL) { if (state->uiSettings.quitAfterStopping) { quit(); } else { set_end_of_list_reached(); } } else { determine_song_and_notify(); } } int prepare_and_play_song(Node *song) { if (!song) return -1; set_current_song(song); AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); stop(); pthread_mutex_lock(&(ps->loadingdata.mutex)); pthread_mutex_lock(&(state->data_source_mutex)); unload_song_a(); unload_song_b(); pthread_mutex_unlock(&(ps->loadingdata.mutex)); pthread_mutex_unlock(&(state->data_source_mutex)); int res = load_first(get_current_song()); finish_loading(); if (res >= 0) { res = create_playback_device(); } if (res >= 0) { resume_playback(); } if (res < 0) set_end_of_list_reached(); trigger_refresh(); reset_clock(); return res; } void check_and_load_next_song(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); if (audio_data.restart) { PlayList *playlist = get_playlist(); if (playlist->head == NULL) return; if (ps->waitingForPlaylist || ps->waitingForNext) { ps->songLoading = true; Node *next_song = determine_next_song(playlist); if (!next_song) return; audio_data.restart = false; ps->waitingForPlaylist = false; ps->waitingForNext = false; state->uiState.songWasRemoved = false; if (is_shuffle_enabled()) reshuffle_playlist(); int res = prepare_and_play_song(next_song); if (res < 0) set_end_of_list_reached(); ps->loadedNextSong = false; set_next_song(NULL); } } else if (get_current_song() != NULL && (ps->nextSongNeedsRebuilding || get_next_song() == NULL) && !ps->songLoading) { update_next_song_if_needed(); } } void load_waiting_music(void) { PlayList *playlist = get_playlist(); AudioData *audio_data = get_audio_data(); PlaybackState *ps = get_playback_state(); if (playlist->head != NULL) { if ((ps->skipFromStopped || !ps->loadedNextSong || ps->nextSongNeedsRebuilding) && !audio_data->end_of_list_reached) { check_and_load_next_song(); } if (ps->songHasErrors) try_load_next(); if (is_EOF_reached()) { prepare_next_song(); switch_audio_implementation(); } } else { set_EOF_handled(); } } gboolean mainloop_callback(gpointer data) { (void)data; calc_elapsed_time(get_current_song_duration()); increment_update_counter(); handle_cooldown(); int update_counter = get_update_counter(); // Different views run at different speeds to lower the impact on system // requirements if ((update_counter % 2 == 0 && state_ptr->currentView == SEARCH_VIEW) || (state_ptr->currentView == TRACK_VIEW || update_counter % 3 == 0)) { process_d_bus_events(); update_player(); load_waiting_music(); } return TRUE; } static gboolean quit_on_signal(gpointer user_data) { GMainLoop *loop = (GMainLoop *)user_data; g_main_loop_quit(loop); quit(); return G_SOURCE_REMOVE; // Remove the signal source } void create_loop(void) { update_last_input_time(); GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); g_unix_signal_add(SIGINT, quit_on_signal, main_loop); g_unix_signal_add(SIGHUP, quit_on_signal, main_loop); g_timeout_add(34, mainloop_callback, NULL); g_main_loop_run(main_loop); g_main_loop_unref(main_loop); } void run(bool start_playing) { AppState *state = get_app_state(); PlayList *playlist = get_playlist(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlaybackState *ps = get_playback_state(); UserData *user_data = audio_data.pUserData; if (unshuffled_playlist == NULL) { set_unshuffled_playlist(deep_copy_playlist(playlist)); } if (state->uiSettings.saveRepeatShuffleSettings) { if (state->uiSettings.repeatState == 1) toggle_repeat(); if (state->uiSettings.repeatState == 2) { toggle_repeat(); toggle_repeat(); } if (state->uiSettings.shuffle_enabled) toggle_shuffle(); } if (playlist->head == NULL) { state->currentView = LIBRARY_VIEW; } init_mpris(); if (state->uiSettings.chromaPreset >= 0) { chroma_set_current_preset(state->uiSettings.chromaPreset); state->uiSettings.visualizations_instead_of_cover = true; } ps->loadedNextSong = false; if (start_playing) ps->waitingForPlaylist = true; audio_data.currentFileIndex = 0; user_data->current_song_data = NULL; user_data->songdataA = NULL; user_data->songdataB = NULL; user_data->songdataADeleted = true; user_data->songdataBDeleted = true; if (playlist->count != 0) check_and_load_next_song(); create_loop(); clear_screen(); fflush(stdout); } void init_locale(void) { setlocale(LC_ALL, ""); setlocale(LC_CTYPE, ""); bindtextdomain("kew", LOCALEDIR); textdomain("kew"); } void kew_init(bool set_library_enqueued_status) { AppState *state = get_app_state(); set_nonblocking_mode(); disable_terminal_line_input(); init_resize(); ioctl(STDOUT_FILENO, TIOCGWINSZ, &state->uiState.windowSize); enable_scrolling(); init_input(); // This is to not stop Chroma when we can't keep up with it, instead just return an error signal(SIGPIPE, SIG_IGN); PlaybackState *ps = get_playback_state(); UserData *user_data = audio_data.pUserData; PlayList *playlist = get_playlist(); state->tmpCache = create_cache(); c_strcpy(ps->loadingdata.file_path, "", sizeof(ps->loadingdata.file_path)); ps->loadingdata.songdataA = NULL; ps->loadingdata.songdataB = NULL; ps->loadingdata.loadA = true; ps->loadingdata.loadingFirstDecoder = true; audio_data.restart = true; user_data->songdataADeleted = true; user_data->songdataBDeleted = true; unsigned int seed = (unsigned int)time(NULL); srand(seed); pthread_mutex_init(&(ps->loadingdata.mutex), NULL); pthread_mutex_init(&(playlist->mutex), NULL); free_search_results(); reset_chosen_dir(); create_library(set_library_enqueued_status); state->uiSettings.LAST_ROW = _(" [F2 Playlist|F3 Library|F4 Track|F5 Search|F6 Help]"); clear_screen(); fflush(stdout); #ifdef DEBUG // g_setenv("G_MESSAGES_DEBUG", "all", TRUE); state->uiState.logFile = freopen("error.log", "w", stderr); if (state->uiState.logFile == NULL) { fprintf(stdout, "Failed to redirect stderr to error.log\n"); } #else FILE *null_stream = freopen("/dev/null", "w", stderr); (void)null_stream; #endif } void init_default_state(void) { bool set_library_enqueued_status = true; kew_init(set_library_enqueued_status); AppState *state = get_app_state(); FileSystemEntry *library = get_library(); PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); add_enqueued_songs_to_playlist(library, playlist); set_unshuffled_playlist(deep_copy_playlist(playlist)); reset_list_after_dequeuing_playing_song(); audio_data.restart = true; audio_data.end_of_list_reached = true; ps->loadedNextSong = false; state->currentView = LIBRARY_VIEW; run(false); } void restore_music_path(){ AppSettings *settings = get_app_settings(); if (settings->original_music_path[0] != '\0') { c_strcpy(settings->path, settings->original_music_path, sizeof(settings->path)); set_path(settings->path); settings->original_music_path[0] = '\0'; } } static bool handle_play_command(int *argc, char **argv, AppSettings *settings) { char de_expanded[PATH_MAX]; if (expand_path(argv[2], de_expanded) != 0) { return false; } else if (!exists_file(de_expanded)) { return false; } strcpy(settings->original_music_path, settings->path); // Check if it's a directory if ( is_directory(de_expanded)) { // It's a directory, return true (path should change) c_strcpy(settings->path, de_expanded, sizeof(settings->path)); set_path(settings->path); return true; } else{ // It's a file, change argv[1] to the song name char directory[PATH_MAX]; get_directory_from_path(de_expanded, directory); c_strcpy(settings->path, directory, sizeof(settings->path)); *argc = 2; argv[1] = strrchr(de_expanded, '/') ? strrchr(de_expanded, '/') + 1 : de_expanded; return false; } } void kew_shutdown() { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); FileSystemEntry *library = get_library(); AppSettings *settings = get_app_settings(); PlayList *favorites_playlist = get_favorites_playlist(); restore_music_path(); #ifndef __ANDROID__ stop_at_shutdown(); #endif pthread_mutex_lock(&(state->data_source_mutex)); sound_shutdown(); free_decoders(); emit_playback_stopped_mpris(); if (chroma_is_started()) state->uiSettings.chromaPreset = chroma_get_current_preset(); else state->uiSettings.chromaPreset = -1; chroma_stop(); bool noMusicFound = false; if (library == NULL || library->children == NULL) { noMusicFound = true; } UserData *user_data = audio_data.pUserData; unload_songs(user_data); #ifdef CHAFA_VERSION_1_16 retire_passthrough_workarounds_tmux(); #endif bool wait_until_complete = true; update_library_if_changed_detected(wait_until_complete); shutdown_input(); free_search_results(); cleanup_mpris(); set_path(settings->path); set_prefs(settings, &(state->uiSettings)); save_favorites_playlist(settings->path, favorites_playlist); delete_cache(state_ptr->tmpCache); save_library(); free_library(); free_playlists(); set_default_text_color(); if (audio_data.pUserData != NULL) free(audio_data.pUserData); pthread_mutex_destroy(&(ps->loadingdata.mutex)); pthread_mutex_destroy(&(state->switch_mutex)); pthread_mutex_unlock(&(state->data_source_mutex)); pthread_mutex_destroy(&(state->data_source_mutex)); free_visuals(); #ifdef USE_DBUS cleanup_notifications(); #endif #ifdef DEBUG if (state->uiState.logFile) fclose(state->uiState.logFile); #endif if (freopen("/dev/stderr", "w", stderr) == NULL) { perror("freopen error"); } if (state_ptr->uiSettings.mouseEnabled) disable_terminal_mouse_buttons(); printf("\n"); show_cursor(); exit_alternate_screen_buffer(); restore_terminal_mode(); if (state_ptr->uiSettings.trackTitleAsWindowTitle) restore_terminal_window_title(); if (noMusicFound) { printf(_("No Music found.\n")); printf(_("Please make sure the path is set correctly. \n")); printf(_("To set it type: kew path \"/path/to/Music\". \n")); } else if (state->uiState.noPlaylist) { printf(_("Music not found.\n")); } if (has_error_message()) { printf(_("%s\n"), get_error_message()); } fflush(stdout); } void init_state(void) { AppState *state = get_app_state(); state->uiSettings.VERSION = VERSION; state->uiSettings.uiEnabled = true; state->uiSettings.color.r = 125; state->uiSettings.color.g = 125; state->uiSettings.color.b = 125; state->uiSettings.coverEnabled = true; state->uiSettings.hideLogo = false; state->uiSettings.hideHelp = false; state->uiSettings.quitAfterStopping = false; state->uiSettings.hideGlimmeringText = false; state->uiSettings.coverAnsi = false; state->uiSettings.visualizerEnabled = true; state->uiSettings.visualizer_height = 5; state->uiSettings.visualizer_color_type = 0; state->uiSettings.visualizerBrailleMode = false; state->uiSettings.visualizer_bar_mode = 2; state->uiSettings.titleDelay = 9; state->uiSettings.cacheLibrary = -1; state->uiSettings.mouseEnabled = true; state->uiSettings.mouseLeftClickAction = 0; state->uiSettings.mouseMiddleClickAction = 1; state->uiSettings.mouseRightClickAction = 2; state->uiSettings.mouseScrollUpAction = 3; state->uiSettings.mouseScrollDownAction = 4; state->uiSettings.mouseAltScrollUpAction = 7; state->uiSettings.mouseAltScrollDownAction = 8; state->uiSettings.replayGainCheckFirst = 0; state->uiSettings.saveRepeatShuffleSettings = 1; state->uiSettings.repeatState = 0; state->uiSettings.shuffle_enabled = 0; state->uiSettings.trackTitleAsWindowTitle = 1; state->uiState.numDirectoryTreeEntries = 0; state->uiState.num_progress_bars = 35; state->uiState.chosen_node_id = 0; state->uiState.resetPlaylistDisplay = true; state->uiState.allowChooseSongs = false; state->uiState.openedSubDir = false; state->uiState.numSongsAboveSubDir = 0; state->uiState.resizeFlag = 0; state->uiState.collapseView = false; state->uiState.refresh = true; state->uiState.isFastForwarding = false; state->uiState.isRewinding = false; state->uiState.songWasRemoved = false; state->uiState.startFromTop = false; state->uiState.lastNotifiedId = -1; state->uiState.noPlaylist = false; state->uiState.logFile = NULL; state->uiState.showLyricsPage = false; state->uiState.currentLibEntry = NULL; state->tmpCache = NULL; state->uiSettings.default_color = 150; state->uiSettings.defaultColorRGB.r = state->uiSettings.default_color; state->uiSettings.defaultColorRGB.g = state->uiSettings.default_color; state->uiSettings.defaultColorRGB.b = state->uiSettings.default_color; state->uiSettings.kewColorRGB.r = 222; state->uiSettings.kewColorRGB.g = 43; state->uiSettings.kewColorRGB.b = 77; state->uiSettings.chromaPreset = -1; state->uiSettings.visualizations_instead_of_cover = false; PlaybackState *ps = get_playback_state(); ps->lastPlayedId = -1; ps->usingSongDataA = true; ps->nextSongNeedsRebuilding = false; ps->songHasErrors = false; ps->forceSkip = false; ps->skipFromStopped = false; ps->skipping = false; ps->skipOutOfOrder = false; ps->clearingErrors = false; ps->hasSilentlySwitched = false; ps->songLoading = false; ps->loadedNextSong = false; ps->waitingForNext = false; ps->waitingForPlaylist = false; ps->notifySwitch = false; ps->notifyPlaying = false; pthread_mutex_init(&(state->data_source_mutex), NULL); pthread_mutex_init(&(state->switch_mutex), NULL); set_unshuffled_playlist(NULL); set_favorites_playlist(NULL); audio_data.pUserData = malloc(sizeof(UserData)); reset_digits_pressed(); state_ptr = state; } void force_terminal_restore(int sig) { ssize_t res; // Show cursor res = write(STDOUT_FILENO, "\033[?25h", 7); (void)res; // Leave alternate screen res = write(STDOUT_FILENO, "\033[?1049l", 8); (void)res; // Disable mouse res = write(STDOUT_FILENO, "\033[?1000l", 9); (void)res; // Restore default handler for this signal signal(sig, SIG_DFL); // Re-raise the signal so the kernel prints the crash message raise(sig); } int main(int argc, char *argv[]) { AppState *state = get_app_state(); init_state(); init_locale(); restart_if_already_running(argv); AppSettings *settings = get_app_settings(); PlayList *playlist = get_playlist(); PlayList *favorites_playlist = get_favorites_playlist(); if ((argc == 2 && ((strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "-?") == 0)))) { show_help(); exit(0); } else if (argc == 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0)) { state->uiSettings.colorMode = COLOR_MODE_ALBUM; state->uiSettings.color = state->uiSettings.defaultColorRGB; print_about_for_version(NULL); exit(0); } *settings = init_settings(); transfer_settings_to_ui(); init_key_mappings(settings); set_track_title_as_window_title(); bool run_for_temporary_path = false; if (argc == 3 && (strcmp(argv[1], "path") == 0)) { char de_expanded[PATH_MAX]; collapse_path(argv[2], de_expanded); c_strcpy(settings->path, de_expanded, sizeof(settings->path)); set_path(settings->path); exit(0); } else if (argc == 3 && (strcmp(argv[1], "play") == 0)) { run_for_temporary_path = handle_play_command(&argc, argv, settings); } enable_mouse(&(state->uiSettings)); enter_alternate_screen_buffer(); atexit(kew_shutdown); signal(SIGINT, force_terminal_restore); signal(SIGSEGV, force_terminal_restore); signal(SIGABRT, force_terminal_restore); if (settings->path[0] == '\0') { set_music_path(); } bool exact_search = false; handle_options(&argc, argv, &exact_search); load_favorites_playlist(settings->path, &favorites_playlist); ensure_default_theme_pack(); init_theme(argc, argv); if ((argc == 1) || (run_for_temporary_path == true)) { init_default_state(); } else if (argc == 2 && strcmp(argv[1], "all") == 0) { kew_init(false); play_all(); run(true); } else if (argc == 2 && strcmp(argv[1], "albums") == 0) { kew_init(false); play_all_albums(); run(true); } else if (argc == 2 && strcmp(argv[1], ".") == 0 && favorites_playlist->count != 0) { kew_init(false); play_favorites_playlist(); run(true); } else if (argc >= 2) { kew_init(false); make_playlist(&playlist, argc, argv, exact_search, settings->path); if (playlist->count == 0) { if (argc > 1 && argv[1] && strcmp(argv[1], "theme") != 0) state->uiState.noPlaylist = true; exit(0); } FileSystemEntry *library = get_library(); mark_list_as_enqueued(library, playlist); run(true); } return 0; } kew/src/ops/000077500000000000000000000000001512074754200132435ustar00rootroot00000000000000kew/src/ops/library_ops.c000066400000000000000000000376721512074754200157530ustar00rootroot00000000000000/** * @file library_ops.h * @brief Music library management and scanning operations. * * Responsible for reading directories, and updating the in-memory library * representation used by playlists * and the UI browser. */ #include "common/appstate.h" #include "common/common.h" #include "playlist_ops.h" #include "track_manager.h" #include "data/directorytree.h" #include "utils/file.h" #include "utils/utils.h" #include #include typedef struct { char *path; bool wait_until_complete; AppState *state; } UpdateLibraryThreadArgs; static int current_sort = 0; static bool updating_library = false; void reset_sort_library(void) { FileSystemEntry *library = get_library(); if (current_sort == 1) { sort_file_system_tree(library, compare_entry_natural); current_sort = 0; } } void sort_library(void) { FileSystemEntry *library = get_library(); if (current_sort == 0) { sort_file_system_tree(library, compare_folders_by_age_files_alphabetically); current_sort = 1; } else { sort_file_system_tree(library, compare_entry_natural); current_sort = 0; } trigger_refresh(); } bool mark_as_enqueued(FileSystemEntry *root, char *path) { if (root == NULL) return false; if (path == NULL) return false; if (!root->is_directory) { if (strcmp(root->full_path, path) == 0) { root->is_enqueued = true; return true; } } else { FileSystemEntry *child = root->children; bool found = false; while (child != NULL) { found = mark_as_enqueued(child, path); child = child->next; if (found) break; } if (found) { root->is_enqueued = true; return true; } } return false; } void mark_list_as_enqueued(FileSystemEntry *root, PlayList *playlist) { Node *node = playlist->head; for (int i = 0; i < playlist->count; i++) { if (node == NULL) break; if (node->song.file_path == NULL) break; mark_as_enqueued(root, node->song.file_path); node = node->next; } root->is_enqueued = false; // Don't mark the absolute root } bool mark_as_dequeued(FileSystemEntry *root, char *path) { int num_children_enqueued = 0; if (root == NULL) return false; if (!root->is_directory) { if (strcmp(root->full_path, path) == 0) { root->is_enqueued = false; return true; } } else { FileSystemEntry *child = root->children; bool found = false; while (child != NULL) { found = mark_as_dequeued(child, path); child = child->next; if (found) break; } if (found) { child = root->children; while (child != NULL) { if (child->is_enqueued) num_children_enqueued++; child = child->next; } if (num_children_enqueued == 0) root->is_enqueued = false; return true; } } return false; } typedef struct { char *path; AppState *state; } UpdateLibraryArgs; void *update_library_thread(void *arg) { if (arg == NULL || updating_library) return NULL; updating_library = true; UpdateLibraryArgs *args = arg; FileSystemEntry *library = get_library(); char *path = args->path; AppState *state = args->state; int tmp_directory_tree_entries = 0; char expanded_path[PATH_MAX]; expand_path(path, expanded_path); FileSystemEntry *tmp = create_directory_tree(expanded_path, &tmp_directory_tree_entries); if (!tmp) { perror("create_directory_tree"); pthread_mutex_unlock(&(state->switch_mutex)); free(args->path); free(args); updating_library = false; return NULL; } pthread_mutex_lock(&(state->switch_mutex)); copy_is_enqueued(library, tmp); set_library(tmp); free_tree(library); state->uiState.numDirectoryTreeEntries = tmp_directory_tree_entries; pthread_mutex_unlock(&(state->switch_mutex)); c_sleep(1000); // Don't refresh immediately or we risk the error message // not clearing trigger_refresh(); free(args->path); free(args); updating_library = false; return NULL; } void update_library(char *path, bool wait_until_complete) { AppState *state = get_app_state(); pthread_t thread_id; UpdateLibraryArgs *args = malloc(sizeof(UpdateLibraryArgs)); if (!args) return; // handle allocation failure args->path = strdup(path); args->state = state; if (pthread_create(&thread_id, NULL, update_library_thread, args) != 0) { perror("Failed to create thread"); return; } if (wait_until_complete) pthread_join(thread_id, NULL); state->uiSettings.last_time_app_ran = time(NULL); } time_t get_modification_time(struct stat *path_stat) { return path_stat->st_mtime; } void *update_if_top_level_folders_mtimes_changed_thread(void *arg) { UpdateLibraryThreadArgs *args = (UpdateLibraryThreadArgs *) arg; // Cast `arg` back to the structure pointer char *path = args->path; AppState *state = args->state; UISettings *ui = &(state->uiSettings); struct stat path_stat; if (stat(path, &path_stat) == -1) { if (args->path) free(args->path); free(args); pthread_exit(NULL); } if (get_modification_time(&path_stat) > ui->last_time_app_ran && ui->last_time_app_ran > 0) { update_library(path, args->wait_until_complete); if (args->path) free(args->path); free(args); pthread_exit(NULL); } DIR *dir = opendir(path); if (!dir) { perror("opendir"); if (args->path) free(args->path); pthread_exit(NULL); } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name); if (stat(full_path, &path_stat) == -1) { continue; } if (S_ISDIR(path_stat.st_mode)) { if (get_modification_time(&path_stat) > ui->last_time_app_ran && ui->last_time_app_ran > 0) { update_library(path, args->wait_until_complete); break; } } } closedir(dir); if (args->path) free(args->path); free(args); pthread_exit(NULL); } // This only checks the library mtime and toplevel subfolders mtimes void update_library_if_changed_detected(bool wait_until_complete) { AppState *state = get_app_state(); pthread_t tid; UpdateLibraryThreadArgs *args = malloc(sizeof(UpdateLibraryThreadArgs)); if (args == NULL) { perror("malloc"); return; } AppSettings *settings = get_app_settings(); char expanded[PATH_MAX]; expand_path(settings->path, expanded); args->path = strdup(expanded); if (args->path == NULL) { perror("strdup"); free(args); return; } args->wait_until_complete = wait_until_complete; args->state = state; if (pthread_create(&tid, NULL, update_if_top_level_folders_mtimes_changed_thread, (void *)args) != 0) { perror("pthread_create"); free(args->path); free(args); } if (wait_until_complete) pthread_join(tid, NULL); } void create_library(bool set_enqueued_status) { AppSettings *settings = get_app_settings(); AppState *state = get_app_state(); FileSystemEntry *library = NULL; char expanded[PATH_MAX]; expand_path(settings->path, expanded); char *lib_path = get_library_file_path(); library = read_tree_from_binary( lib_path, expanded, &(state->uiState.numDirectoryTreeEntries), set_enqueued_status); free(lib_path); set_library(library); bool wait_until_complete = true; update_library_if_changed_detected(wait_until_complete); library = get_library(); bool library_path_changed = false; if (library && strcmp(library->full_path, expanded) != 0) library_path_changed = true; if (library == NULL || library->children == NULL || library_path_changed) { char expanded[PATH_MAX]; expand_path(settings->path, expanded); library = create_directory_tree(expanded, &(state->uiState.numDirectoryTreeEntries)); } if (library == NULL || library->children == NULL) { char message[PATH_MAX + 64]; snprintf(message, PATH_MAX + 64, "No music found at %s.", settings->path); set_error_message(message); } set_library(library); } void enqueue_song(FileSystemEntry *child) { int id = increment_node_id(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); Node *node = NULL; create_node(&node, child->full_path, id); if (add_to_list(unshuffled_playlist, node) == -1) destroy_node(node); Node *node2 = NULL; create_node(&node2, child->full_path, id); if (add_to_list(playlist, node2) == -1) destroy_node(node2); child->is_enqueued = 1; child->parent->is_enqueued = 1; } void set_childrens_queued_status_on_parents(FileSystemEntry *parent, bool wanted_status) { if (parent == NULL) return; bool is_enqueued = false; FileSystemEntry *ch = parent->children; while (ch != NULL) { if (ch->is_enqueued) { is_enqueued = true; break; } ch = ch->next; } if (is_enqueued == wanted_status) { parent->is_enqueued = wanted_status; } parent = parent->parent; if (parent && parent->parent != NULL) set_childrens_queued_status_on_parents(parent, wanted_status); } void dequeue_song(FileSystemEntry *child) { PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); Node *node1 = find_last_path_in_playlist(child->full_path, unshuffled_playlist); Node *current = get_current_song(); if (node1 == NULL) return; if (current != NULL && current->id == node1->id) { remove_currently_playing_song(); } else { if (get_song_to_start_from() != NULL) { set_song_to_start_from(get_list_next(node1)); } } int id = node1->id; Node *node2 = find_selected_entry_by_id(playlist, id); if (node1 != NULL) delete_from_list(unshuffled_playlist, node1); if (node2 != NULL) delete_from_list(playlist, node2); child->is_enqueued = 0; } void dequeue_children(FileSystemEntry *parent) { FileSystemEntry *child = parent->children; while (child != NULL) { if (child->is_directory && child->children != NULL) { dequeue_children(child); } else { dequeue_song(child); } child = child->next; } set_childrens_queued_status_on_parents(parent, false); } int enqueue_children(FileSystemEntry *child, FileSystemEntry **first_enqueued_entry) { int has_enqueued = 0; if (!child) return has_enqueued; FileSystemEntry *parent = child->parent; while (child != NULL) { if (child->is_directory && child->children != NULL) { child->is_enqueued = enqueue_children(child->children, first_enqueued_entry); if (child->is_enqueued == 1) has_enqueued = 1; } else if (!child->is_enqueued) { if (*first_enqueued_entry == NULL) *first_enqueued_entry = child; if (!(path_ends_with(child->full_path, "m3u") || path_ends_with(child->full_path, "m3u8"))) { enqueue_song(child); has_enqueued = 1; } } else if (child->is_directory == 0 && child->is_enqueued) { has_enqueued = 1; } child = child->next; } set_childrens_queued_status_on_parents(parent, true); return has_enqueued; } bool has_song_children(FileSystemEntry *entry) { if (!entry) return false; FileSystemEntry *child = entry->children; int num_songs = 0; while (child != NULL) { if (!child->is_directory) num_songs++; child = child->next; } if (num_songs == 0) { return false; } return true; } bool has_dequeued_children(FileSystemEntry *parent) { FileSystemEntry *child = parent->children; bool isDequeued = false; while (child != NULL) { if (!child->is_enqueued) { if ((child->is_directory != 1 && !(path_ends_with(child->full_path, "m3u") || path_ends_with(child->full_path, "m3u8"))) || has_dequeued_children(child)) isDequeued = true; } child = child->next; } return isDequeued; } bool is_contained_within(FileSystemEntry *entry, FileSystemEntry *containing_entry) { if (entry == NULL || containing_entry == NULL) return false; FileSystemEntry *tmp = entry->parent; while (tmp != NULL) { if (strcmp(tmp->full_path, containing_entry->full_path) == 0) return true; tmp = tmp->parent; } return false; } kew/src/ops/library_ops.h000066400000000000000000000022561512074754200157460ustar00rootroot00000000000000/** * @file library_ops.h * @brief Music library management and scanning operations. * * Responsible for reading directories, and updating the in-memory library * representation used by playlists * and the UI browser. */ #ifndef LIBRARY_OPS_H #define LIBRARY_OPS_H #include "common/appstate.h" void create_library(bool set_enqueued_status); void update_library(char *path, bool wait_until_complete); void ask_if_cache_library(void); void sort_library(void); void reset_sort_library(void); void mark_list_as_enqueued(FileSystemEntry *root, PlayList *playlist); void enqueue_song(FileSystemEntry *child); void dequeue_song(FileSystemEntry *child); void dequeue_children(FileSystemEntry *parent); void set_childrens_queued_status_on_parents(FileSystemEntry *parent, bool wanted_status); int enqueue_children(FileSystemEntry *child, FileSystemEntry **first_enqueued_entry); bool mark_as_dequeued(FileSystemEntry *root, char *path); bool has_song_children(FileSystemEntry *entry); bool has_dequeued_children(FileSystemEntry *parent); bool is_contained_within(FileSystemEntry *entry, FileSystemEntry *containing_entry); void update_library_if_changed_detected(bool wait_until_complete); #endif kew/src/ops/playback_clock.c000066400000000000000000000115461512074754200163570ustar00rootroot00000000000000/** * @file playback_clock.c * @brief Playback timing and synchronization utilities. * * Handles timing measurements for song progress, seek operations, * and playback duration calculations. Uses system timers or * monotonic clocks to maintain precise playback timing. */ #include "playback_clock.h" #include "common/appstate.h" #include "playback_state.h" #include "sound/decoders.h" #ifdef USE_FAAD #include "sound/m4a.h" #endif #include "sound/playback.h" #include "sys/sys_integration.h" #include "utils/utils.h" #include static struct timespec start_time; static struct timespec pause_time; static struct timespec current_time; static double seek_accumulated_seconds = 0.0; static struct timespec last_update_time = {0, 0}; static double elapsed_seconds = 0.0; struct timespec get_pause_time(void) { return pause_time; } double get_elapsed_seconds(void) { return elapsed_seconds; } void reset_clock(void) { elapsed_seconds = 0.0; set_pause_seconds(0.0); set_total_pause_seconds(0.0); set_seek_elapsed(0.0); clock_gettime(CLOCK_MONOTONIC, &start_time); } void calc_elapsed_time(double duration) { if (pb_is_stopped()) return; clock_gettime(CLOCK_MONOTONIC, ¤t_time); double time_since_last_update = (double)(current_time.tv_sec - last_update_time.tv_sec) + (double)(current_time.tv_nsec - last_update_time.tv_nsec) / 1e9; if (!pb_is_paused()) { elapsed_seconds = (double)(current_time.tv_sec - start_time.tv_sec) + (double)(current_time.tv_nsec - start_time.tv_nsec) / 1e9; double seek_elapsed = get_seek_elapsed(); double diff = elapsed_seconds + (seek_elapsed + seek_accumulated_seconds - get_total_pause_seconds()); if (diff < 0) seek_elapsed -= diff; elapsed_seconds += seek_elapsed + seek_accumulated_seconds - get_total_pause_seconds(); if (elapsed_seconds > duration) elapsed_seconds = duration; set_seek_elapsed(seek_elapsed); if (elapsed_seconds < 0.0) { elapsed_seconds = 0.0; } if (get_current_song() != NULL && time_since_last_update >= 1.0) { last_update_time = current_time; } } else { set_pause_seconds((double)(current_time.tv_sec - pause_time.tv_sec) + (double)(current_time.tv_nsec - pause_time.tv_nsec) / 1e9); } } bool set_position(gint64 new_position, double duration) { if (pb_is_paused()) return false; gint64 currentPositionMicroseconds = llround(get_elapsed_seconds() * G_USEC_PER_SEC); if (duration != 0.0) { gint64 step = new_position - currentPositionMicroseconds; step = step / G_USEC_PER_SEC; seek_accumulated_seconds += step; return true; } else { return false; } } bool seek_position(gint64 offset, double duration) { if (pb_is_paused()) return false; if (duration != 0.0) { gint64 step = offset; step = step / G_USEC_PER_SEC; seek_accumulated_seconds += step; return true; } else { return false; } } void add_to_accumulated_seconds(double value) { seek_accumulated_seconds += value; } void update_pause_time(void) { clock_gettime(CLOCK_MONOTONIC, &pause_time); } bool flush_seek(void) { if (seek_accumulated_seconds != 0.0) { Node *current_song = get_current_song(); if (current_song != NULL) { #ifdef USE_FAAD if (path_ends_with(current_song->song.file_path, "aac")) { m4a_decoder *decoder = get_current_m4a_decoder(); if (decoder->file_type == k_rawAAC) return false; } #endif } set_seek_elapsed(get_seek_elapsed() + seek_accumulated_seconds); seek_accumulated_seconds = 0.0; double duration = get_current_song_duration(); calc_elapsed_time(duration); float percentage = elapsed_seconds / (float)duration * 100.0; if (percentage < 0.0) { set_seek_elapsed(0.0); percentage = 0.0; } seek_percentage(percentage); emit_seeked_signal(elapsed_seconds); return true; } return false; } kew/src/ops/playback_clock.h000066400000000000000000000013241512074754200163550ustar00rootroot00000000000000/** * @file playback_clock.h * @brief Playback timing and synchronization utilities. * * Handles timing measurements for song progress, seek operations, * and playback duration calculations. Uses system timers or * monotonic clocks to maintain precise playback timing. */ #include #include #include void reset_clock(void); void calc_elapsed_time(double duration); void update_pause_time(void); void add_to_accumulated_seconds(double value); bool set_position(gint64 new_position, double duration); bool seek_position(gint64 offset, double duration); bool flush_seek(void); double get_elapsed_seconds(void); struct timespec get_pause_time(void); double get_current_song_duration(void); kew/src/ops/playback_ops.c000066400000000000000000000270761512074754200160720ustar00rootroot00000000000000/** * @file playback_ops.c * @brief Core playback control API. * * Contains functions to control playback: play, pause, stop, seek, * volume adjustments, and track skipping. This module is UI-agnostic * and interacts directly with the playback state and audio backends. */ #include "common/appstate.h" #include "playback_ops.h" #include "common/common.h" #include "playback_clock.h" #include "playback_state.h" #include "playback_system.h" #include "playlist_ops.h" #include "sound/decoders.h" #ifdef USE_FAAD #include "sound/m4a.h" #endif #include "sound/playback.h" #include "sound/volume.h" #include "sys/sys_integration.h" #include "data/song_loader.h" #include "utils/file.h" #include "utils/utils.h" void resume_playback(void) { sound_resume_playback(); } int load_decoder(SongData *song_data, bool *song_data_deleted) { PlaybackState *ps = get_playback_state(); int result = 0; if (song_data != NULL) { *song_data_deleted = false; // This should only be done for the second song, as // switch_audio_implementation() handles the first one if (!ps->loadingdata.loadingFirstDecoder) { if (has_builtin_decoder(song_data->file_path)) result = prepare_next_decoder(song_data->file_path); else if (path_ends_with(song_data->file_path, "opus")) result = prepare_next_opus_decoder(song_data->file_path); else if (path_ends_with(song_data->file_path, "ogg")) result = prepare_next_vorbis_decoder( song_data->file_path); else if (path_ends_with(song_data->file_path, "webm")) result = prepare_next_webm_decoder(song_data); #ifdef USE_FAAD else if (path_ends_with(song_data->file_path, "m4a") || path_ends_with(song_data->file_path, "aac")) result = prepare_next_m4a_decoder(song_data); #endif } } return result; } int assign_loaded_data(void) { PlaybackState *ps = get_playback_state(); int result = 0; if (ps->loadingdata.loadA) { audio_data.pUserData->songdataA = ps->loadingdata.songdataA; result = load_decoder(ps->loadingdata.songdataA, &(audio_data.pUserData->songdataADeleted)); } else { audio_data.pUserData->songdataB = ps->loadingdata.songdataB; result = load_decoder(ps->loadingdata.songdataB, &(audio_data.pUserData->songdataBDeleted)); } return result; } void *song_data_reader_thread(void *arg) { PlaybackState *ps = (PlaybackState *)arg; pthread_mutex_lock(&(ps->loadingdata.mutex)); char filepath[PATH_MAX]; c_strcpy(filepath, ps->loadingdata.file_path, sizeof(filepath)); SongData *songdata = exists_file(filepath) >= 0 ? load_song_data(filepath) : NULL; if (ps->loadingdata.loadA) { if (!audio_data.pUserData->songdataADeleted) { unload_song_data(&(ps->loadingdata.songdataA)); } audio_data.pUserData->songdataADeleted = false; audio_data.pUserData->songdataA = songdata; ps->loadingdata.songdataA = songdata; } else { if (!audio_data.pUserData->songdataBDeleted) { unload_song_data(&(ps->loadingdata.songdataB)); } audio_data.pUserData->songdataBDeleted = false; audio_data.pUserData->songdataB = songdata; ps->loadingdata.songdataB = songdata; } int result = assign_loaded_data(); if (result < 0) songdata->hasErrors = true; pthread_mutex_unlock(&(ps->loadingdata.mutex)); if (songdata == NULL || songdata->hasErrors) { ps->songHasErrors = true; ps->clearingErrors = true; set_next_song(NULL); } else { ps->songHasErrors = false; ps->clearingErrors = false; set_next_song(get_try_next_song()); set_try_next_song(NULL); } ps->loadedNextSong = true; ps->skipping = false; ps->songLoading = false; return NULL; } void load_song(Node *song, LoadingThreadData *loadingdata) { PlaybackState *ps = get_playback_state(); if (song == NULL) { ps->loadedNextSong = true; ps->skipping = false; ps->songLoading = false; return; } c_strcpy(loadingdata->file_path, song->song.file_path, sizeof(loadingdata->file_path)); pthread_t loading_thread; pthread_create(&loading_thread, NULL, song_data_reader_thread, ps); } void try_load_next(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); Node *current = get_current_song(); Node *try_next_song = get_try_next_song(); ps->songHasErrors = false; ps->clearingErrors = true; if (try_next_song == NULL && current != NULL) try_next_song = current->next; else if (try_next_song != NULL) try_next_song = try_next_song->next; if (try_next_song != NULL) { ps->songLoading = true; ps->loadingdata.state = state; ps->loadingdata.loadA = !ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = false; load_song(try_next_song, &ps->loadingdata); } else { ps->clearingErrors = false; } } void pause_song(void) { if (!pb_is_paused()) { emit_string_property_changed("PlaybackStatus", "Paused"); update_pause_time(); } pause_playback(); } void skip_to_begginning_of_song(void) { reset_clock(); if (get_current_song() != NULL) { seek_percentage(0); emit_seeked_signal(0.0); } } void prepare_if_skipped_silent(void) { PlaybackState *ps = get_playback_state(); if (ps->hasSilentlySwitched) { ps->skipping = true; ps->hasSilentlySwitched = false; reset_clock(); set_current_implementation_type(NONE); set_repeat_enabled(false); audio_data.end_of_list_reached = false; ps->usingSongDataA = !ps->usingSongDataA; ps->skipping = false; } } void play(void) { PlaybackState *ps = get_playback_state(); if (pb_is_paused()) { set_total_pause_seconds(get_total_pause_seconds() + get_pause_seconds()); emit_string_property_changed("PlaybackStatus", "Playing"); } else if (pb_is_stopped()) { emit_string_property_changed("PlaybackStatus", "Playing"); } if (pb_is_stopped() && !ps->hasSilentlySwitched) { skip_to_begginning_of_song(); } sound_resume_playback(); if (ps->hasSilentlySwitched) { set_total_pause_seconds(0); prepare_if_skipped_silent(); } } bool is_valid_audio_node(Node *node) { if (!node) return false; if (node->id < 0) return false; if (!node->song.file_path || strnlen(node->song.file_path, PATH_MAX) == 0) return false; return true; } int play_song(Node *node) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); if (!is_valid_audio_node(node)) { fprintf(stderr, "Song is invalid.\n"); return -1; } set_current_song(node); ps->skipping = true; ps->skipOutOfOrder = false; ps->songLoading = true; ps->forceSkip = false; ps->loadedNextSong = false; // Cancel starting from top if (ps->waitingForPlaylist || audio_data.restart) { ps->waitingForPlaylist = false; audio_data.restart = false; if (is_shuffle_enabled()) reshuffle_playlist(); } ps->loadingdata.state = state; ps->loadingdata.loadA = !ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = true; load_song(node, &ps->loadingdata); int max_num_tries = 50; int numtries = 0; while (!ps->loadedNextSong && numtries < max_num_tries) { c_sleep(100); numtries++; } if (ps->songHasErrors) { ps->songHasErrors = false; ps->forceSkip = true; if (node->next != NULL) { return -1; } } reset_clock(); skip(); return 0; } void volume_change(int change_percent) { adjust_volume_percent(change_percent); } void skip_to_song(int id, bool start_playing) { PlaybackState *ps = get_playback_state(); if (ps->songLoading || !ps->loadedNextSong || ps->skipping || ps->clearingErrors) if (!ps->forceSkip) return; PlayList *playlist = get_playlist(); Node *found = NULL; find_node_in_list(playlist, id, &found); if (start_playing) { double total_pause_seconds = get_total_pause_seconds(); double pause_seconds = get_total_pause_seconds(); play(); set_total_pause_seconds(total_pause_seconds); set_pause_seconds(pause_seconds); } play_song(found); } void stop_at_shutdown(void) { stop_playback(); } void stop(void) { stop_playback(); if (pb_is_stopped()) { skip_to_begginning_of_song(); emit_string_property_changed("PlaybackStatus", "Stopped"); } } void ops_toggle_pause(void) { PlaybackState *ps = get_playback_state(); if (pb_is_stopped()) { reset_clock(); } if (get_current_song() == NULL && pb_is_paused()) { return; } toggle_pause_playback(); if (pb_is_paused()) { emit_string_property_changed("PlaybackStatus", "Paused"); update_pause_time(); } else { if (ps->hasSilentlySwitched && !ps->skipping) { set_total_pause_seconds(0); prepare_if_skipped_silent(); } else { set_total_pause_seconds(get_total_pause_seconds() + get_pause_seconds()); } emit_string_property_changed("PlaybackStatus", "Playing"); } } void seek(int seconds) { Node *current = get_current_song(); if (current == NULL) return; #ifdef USE_FAAD if (path_ends_with(current->song.file_path, "aac")) { m4a_decoder *decoder = get_current_m4a_decoder(); if (decoder != NULL && decoder->file_type == k_rawAAC) return; } #endif if (pb_is_paused()) return; double duration = current->song.duration; if (duration <= 0.0) return; add_to_accumulated_seconds(seconds); } kew/src/ops/playback_ops.h000066400000000000000000000012071512074754200160630ustar00rootroot00000000000000/** * @file playback_ops.h * @brief Core playback control API. * * Contains functions to control playback: play, pause, stop, seek, * volume adjustments, and track skipping. This module is UI-agnostic * and interacts directly with the playback state and audio backends. */ #ifndef PLAYBACK_OPS_H #define PLAYBACK_OPS_H #include "common/appstate.h" int play_song(Node *node); void pause_song(void); void play(void); void stop(void); void stop_at_shutdown(void); void ops_toggle_pause(void); void resume_playback(void); void volume_change(int change_percent); void seek(int seconds); void skip_to_song(int id, bool start_playing); #endif kew/src/ops/playback_state.c000066400000000000000000000067761512074754200164150ustar00rootroot00000000000000/** * @file playback_state.c * @brief Maintains playback runtime state. * * Stores and manages the current playback status, elapsed time, * current song pointer, and related flags (paused, stopped, etc.). * Provides accessors and mutators for playback state data. */ #include "playback_state.h" #include "sound/playback.h" #include "sound/volume.h" static bool repeat_list_enabled = false; static bool shuffle_enabled = false; bool is_repeat_list_enabled(void) { return repeat_list_enabled; } void set_repeat_list_enabled(bool value) { repeat_list_enabled = value; } bool is_shuffle_enabled(void) { return shuffle_enabled; } void set_shuffle_enabled(bool value) { shuffle_enabled = value; } bool is_repeat_enabled(void) { return pb_is_repeat_enabled(); } bool is_paused(void) { return pb_is_paused(); } bool is_stopped(void) { return pb_is_stopped(); } bool is_EOF_reached(void) { return pb_is_EOF_reached(); } bool is_impl_switch_reached(void) { return pb_is_impl_switch_reached(); } bool is_current_song_deleted(void) { return (audio_data.currentFileIndex == 0) ? audio_data.pUserData->songdataADeleted == true : audio_data.pUserData->songdataBDeleted == true; } bool is_valid_song(SongData *song_data) { return song_data != NULL && song_data->hasErrors == false && song_data->metadata != NULL; } void set_EOF_handled(void) { pb_set_EOF_handled(); } double get_current_song_duration(void) { double duration = 0.0; SongData *current_song_data = get_current_song_data(); if (current_song_data != NULL) duration = current_song_data->duration; return duration; } bool determine_current_song_data(SongData **current_song_data) { *current_song_data = (audio_data.currentFileIndex == 0) ? audio_data.pUserData->songdataA : audio_data.pUserData->songdataB; bool isDeleted = (audio_data.currentFileIndex == 0) ? audio_data.pUserData->songdataADeleted == true : audio_data.pUserData->songdataBDeleted == true; if (isDeleted) { *current_song_data = (audio_data.currentFileIndex != 0) ? audio_data.pUserData->songdataA : audio_data.pUserData->songdataB; isDeleted = (audio_data.currentFileIndex != 0) ? audio_data.pUserData->songdataADeleted == true : audio_data.pUserData->songdataBDeleted == true; if (!isDeleted) { activate_switch(&audio_data); audio_data.switchFiles = false; } } return isDeleted; } int get_volume() { return get_current_volume(); } void get_format_and_sample_rate(ma_format *format, ma_uint32 *sample_rate) { get_current_format_and_sample_rate(format, sample_rate); } SongData *get_current_song_data(void) { if (get_current_song() == NULL) return NULL; if (is_current_song_deleted()) return NULL; SongData *song_data = NULL; bool isDeleted = determine_current_song_data(&song_data); if (isDeleted) return NULL; if (!is_valid_song(song_data)) return NULL; return song_data; } kew/src/ops/playback_state.h000066400000000000000000000017521512074754200164070ustar00rootroot00000000000000/** * @file playback_state.h * @brief Maintains playback runtime state. * * Stores and manages the current playback status, elapsed time, * current song pointer, and related flags (paused, stopped, etc.). * Provides accessors and mutators for playback state data. */ #include "common/appstate.h" #include // Getters bool is_repeat_list_enabled(void); bool is_shuffle_enabled(void); bool is_repeat_enabled(void); bool is_paused(void); bool is_stopped(void); bool is_playback_done(void); bool is_EOF_reached(void); bool is_current_song_deleted(void); bool is_impl_switch_reached(void); bool determine_current_song_data(SongData **current_song_data); int get_volume(); double get_current_song_duration(void); void get_format_and_sample_rate(ma_format *format, ma_uint32 *sample_rate); SongData *get_current_song_data(void); // Setters void set_repeat_enabled(bool enabled); void set_repeat_list_enabled(bool value); void set_shuffle_enabled(bool value); void set_EOF_handled(void); kew/src/ops/playback_system.c000066400000000000000000000040001512074754200165730ustar00rootroot00000000000000/** * @file playback_system.c * @brief Low-level audio system integration. * * Manages initialization, configuration, and teardown of * audio backends and decoders. Provides the connection between * playback operations and sound output subsystems. */ #include "playback_system.h" #include "common/appstate.h" #include "common/common.h" #include "sound/audiotypes.h" #include "sound/decoders.h" #include "sound/playback.h" #include "sound/sound.h" #include "data/song_loader.h" void playback_safe_cleanup(void) { AppState *state = get_app_state(); pthread_mutex_lock(&(state->data_source_mutex)); pb_cleanup_playback_device(); pthread_mutex_unlock(&(state->data_source_mutex)); } void playback_cleanup(void) { pb_cleanup_playback_device(); } void switch_audio_implementation(void) { pb_switch_audio_implementation(); } int create_playback_device(void) { return pb_create_audio_device(); } void sound_shutdown(void) { pb_sound_shutdown(); } void unload_songs(UserData *user_data) { PlaybackState *ps = get_playback_state(); if (!user_data->songdataADeleted) { user_data->songdataADeleted = true; unload_song_data(&(ps->loadingdata.songdataA)); } if (!user_data->songdataBDeleted) { user_data->songdataBDeleted = true; unload_song_data(&(ps->loadingdata.songdataB)); } } void skip(void) { PlaybackState *ps = get_playback_state(); set_current_implementation_type(NONE); set_repeat_enabled(false); audio_data.end_of_list_reached = false; if (!is_playing()) { pb_switch_audio_implementation(); ps->skipFromStopped = true; } else { set_skip_to_next(true); } if (!ps->skipOutOfOrder) trigger_refresh(); } void free_decoders(void) { reset_all_decoders(); } void ensure_default_theme_pack() { ensure_default_themes(); } kew/src/ops/playback_system.h000066400000000000000000000011231512074754200166030ustar00rootroot00000000000000/** * @file playback_system.h * @brief Low-level audio system integration. * * Manages initialization, configuration, and teardown of * audio backends and decoders. Provides the connection between * playback operations and sound output subsystems. */ #include "common/appstate.h" #include "data/theme.h" int create_playback_device(void); void playback_safe_cleanup(void); void playback_cleanup(void); void skip(void); void switch_audio_implementation(void); void sound_shutdown(void); void unload_songs(UserData *user_data); void free_decoders(void); void ensure_default_theme_pack(); kew/src/ops/playlist_ops.c000066400000000000000000000643451512074754200161450ustar00rootroot00000000000000/** * @file playlist_ops.c * @brief Playlist management operations. * * Implements functionality for adding, removing, reordering, * shuffling, and retrieving songs within playlists. * Coordinates with the playback system for next/previous transitions. */ #include "playlist_ops.h" #include "common/common.h" #include "playback_clock.h" #include "playback_ops.h" #include "playback_state.h" #include "playback_system.h" #include "library_ops.h" #include "track_manager.h" #include "common/appstate.h" #include "sound/audiotypes.h" #include "sound/playback.h" #include "data/song_loader.h" #include "sys/sys_integration.h" #include "ui/player_ui.h" #include "utils/utils.h" static bool skip_in_progress = false; Node *choose_next_song(void) { Node *current = get_current_song(); Node *next_song = get_next_song(); if (next_song != NULL) return next_song; else if (current != NULL && current->next != NULL) { return current->next; } else { return NULL; } } Node *find_selected_entry_by_id(PlayList *playlist, int id) { Node *node = playlist->head; if (node == NULL || id < 0) return NULL; bool found = false; for (int i = 0; i < playlist->count; i++) { if (node != NULL && node->id == id) { found = true; break; } else if (node == NULL) { return NULL; } node = node->next; } if (found) { return node; } return NULL; } Node *find_selected_entry(PlayList *playlist, int row) { Node *node = playlist->head; if (node == NULL) return NULL; bool found = false; for (int i = 0; i < playlist->count; i++) { if (i == row) { found = true; break; } node = node->next; } if (found) { return node; } return NULL; } void remove_currently_playing_song(void) { Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); if (current != NULL) { stop_playback(); emit_string_property_changed("PlaybackStatus", "Stopped"); clear_current_track(); clear_current_song(); } ps->loadedNextSong = false; audio_data.restart = true; audio_data.end_of_list_reached = true; if (current != NULL) { ps->lastPlayedId = current->id; set_song_to_start_from(get_list_next(current)); } ps->waitingForNext = true; current = NULL; } void rebuild_next_song(Node *song) { if (song == NULL) return; AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); ps->loadingdata.state = state; ps->loadingdata.loadA = !ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = false; ps->songLoading = true; load_song(song, &ps->loadingdata); int max_num_tries = 50; int numtries = 0; while (ps->songLoading && !ps->loadedNextSong && numtries < max_num_tries) { c_sleep(100); numtries++; } ps->songLoading = false; } void remove_song(Node *node) { PlayList *playlist = get_playlist(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); Node *current = get_current_song(); bool rebuild = false; if (node == NULL) { return; } Node *song = choose_next_song(); int id = node->id; int current_id = (current != NULL) ? current->id : -1; if (current_id == node->id) { remove_currently_playing_song(); } else { if (get_song_to_start_from() != NULL) { set_song_to_start_from(get_list_next(node)); } } pthread_mutex_lock(&(playlist->mutex)); if (node != NULL && song != NULL && current != NULL) { if (strcmp(song->song.file_path, node->song.file_path) == 0 || (current != NULL && current->next != NULL && id == current->next->id)) rebuild = true; } if (node != NULL) mark_as_dequeued(get_library(), node->song.file_path); Node *node2 = find_selected_entry_by_id(playlist, id); if (node != NULL) delete_from_list(unshuffled_playlist, node); if (node2 != NULL) delete_from_list(playlist, node2); if (is_shuffle_enabled()) rebuild = true; current = find_selected_entry_by_id(playlist, current_id); if (rebuild && current != NULL) { node = NULL; set_next_song(NULL); reshuffle_playlist(); set_try_next_song(current->next); PlaybackState *ps = get_playback_state(); ps->nextSongNeedsRebuilding = false; set_next_song(NULL); set_next_song(get_list_next(current)); rebuild_next_song(get_next_song()); ps->loadedNextSong = true; } pthread_mutex_unlock(&(playlist->mutex)); } void handle_remove(int chosen_row) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); Node *node = NULL; if (state->currentView == PLAYLIST_VIEW) { node = find_selected_entry(unshuffled_playlist, chosen_row); remove_song(node); trigger_redraw_side_cover(); trigger_refresh(); } else { Node *current = get_current_song(); if (current) node = find_selected_entry_by_id(unshuffled_playlist, current->id); remove_song(node); Node *next = get_next_song(); if (next) { clear_and_play(next); } } } void add_to_favorites_playlist(void) { Node *current = get_current_song(); PlayList *favorites_playlist = get_favorites_playlist(); if (current == NULL) return; int id = current->id; Node *node = NULL; if (find_selected_entry_by_id(favorites_playlist, id) != NULL) // Song is already in list return; create_node(&node, current->song.file_path, id); add_to_list(favorites_playlist, node); } // Go through the display playlist and the shuffle playlist to remove all songs // except the current one. If no active song (if stopped rather than paused for // example) entire playlist will be removed void dequeue_all_except_playing_song(void) { bool clearAll = false; int current_id = -1; Node *current = get_current_song(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); AppState *state = get_app_state(); // Do we need to clear the entire playlist? if (current == NULL) { clearAll = true; } else { current_id = current->id; } int next_in_playlist_id; pthread_mutex_lock(&(playlist->mutex)); Node *song_to_be_removed; Node *next_in_playlist = unshuffled_playlist->head; while (next_in_playlist != NULL) { next_in_playlist_id = next_in_playlist->id; if (clearAll || next_in_playlist_id != current_id) { song_to_be_removed = next_in_playlist; next_in_playlist = next_in_playlist->next; int id = song_to_be_removed->id; // Update Library if (song_to_be_removed != NULL) mark_as_dequeued(get_library(), song_to_be_removed->song.file_path); // Remove from Display playlist if (song_to_be_removed != NULL) delete_from_list(unshuffled_playlist, song_to_be_removed); // Remove from Shuffle playlist Node *node2 = find_selected_entry_by_id(playlist, id); if (node2 != NULL) delete_from_list(playlist, node2); } else { next_in_playlist = next_in_playlist->next; } } pthread_mutex_unlock(&(playlist->mutex)); PlaybackState *ps = get_playback_state(); ps->nextSongNeedsRebuilding = true; set_next_song(NULL); // Only refresh the screen if it makes sense to do so if (state->currentView == PLAYLIST_VIEW || state->currentView == LIBRARY_VIEW) { trigger_refresh(); } } Node *get_song_by_number(PlayList *playlist, int song_number) { Node *song = playlist->head; if (!song) return get_current_song(); if (song_number <= 0) { return song; } int count = 1; while (song->next != NULL && count != song_number) { song = get_list_next(song); count++; } return song; } void set_current_song_to_next(void) { Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); if (current != NULL) ps->lastPlayedId = current->id; set_current_song(choose_next_song()); } void set_current_song_to_prev(void) { Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); if (current != NULL && current->prev != NULL) { ps->lastPlayedId = current->id; set_current_song(current->prev); } } void silent_switch_to_next(bool load_song) { PlaybackState *ps = get_playback_state(); ps->skipping = true; set_next_song(NULL); set_current_song_to_next(); activate_switch(&audio_data); ps->skipOutOfOrder = true; ps->usingSongDataA = (audio_data.currentFileIndex == 0); if (load_song) { load_next_song(); finish_loading(); ps->loadedNextSong = true; ps->notifySwitch = true; } reset_clock(); trigger_refresh(); ps->skipping = false; ps->hasSilentlySwitched = true; ps->nextSongNeedsRebuilding = true; set_paused(true); set_stopped(false); set_next_song(NULL); } void silent_switch_to_prev(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); ps->skipping = true; set_current_song_to_prev(); activate_switch(&audio_data); ps->loadedNextSong = false; ps->songLoading = true; ps->forceSkip = false; ps->usingSongDataA = !ps->usingSongDataA; ps->loadingdata.state = state; ps->loadingdata.loadA = ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = true; load_song(get_current_song(), &ps->loadingdata); finish_loading(); reset_clock(); trigger_refresh(); ps->skipping = false; ps->nextSongNeedsRebuilding = true; set_paused(true); set_stopped(false); set_next_song(NULL); ps->notifySwitch = true; ps->skipOutOfOrder = true; ps->hasSilentlySwitched = true; } void skip_to_next_song(void) { AppState *state = get_app_state(); Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); // Stop if there is no song or no next song if (current == NULL || current->next == NULL) { if (is_repeat_list_enabled()) { clear_current_song(); } else if (!pb_is_stopped() && !pb_is_paused()) { stop(); return; } else { return; } } if (ps->songLoading || ps->nextSongNeedsRebuilding || ps->skipping || ps->clearingErrors) return; if (pb_is_stopped() || pb_is_paused()) { silent_switch_to_next(true); return; } if (is_shuffle_enabled()) state->uiState.resetPlaylistDisplay = true; double total_pause_seconds = get_total_pause_seconds(); double pause_seconds = get_total_pause_seconds(); play(); set_total_pause_seconds(total_pause_seconds); set_pause_seconds(pause_seconds); ps->skipping = true; ps->skipOutOfOrder = false; reset_clock(); skip(); } void skip_to_prev_song(void) { if (skip_in_progress) return; skip_in_progress = true; AppState *state = get_app_state(); Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); retry: if (current == NULL) { if (!pb_is_stopped() && !pb_is_paused()) stop(); skip_in_progress = false; return; } if (ps->songLoading || ps->skipping || ps->clearingErrors) if (!ps->forceSkip) { skip_in_progress = false; return; } if (pb_is_stopped() || pb_is_paused()) { silent_switch_to_prev(); skip_in_progress = false; return; } Node *song = current; set_current_song_to_prev(); if (song == current) { reset_clock(); update_playback_position( 0); // We need to signal to mpris that the song was // reset to the beginning } double total_pause_seconds = get_total_pause_seconds(); double pause_seconds = get_total_pause_seconds(); play(); set_total_pause_seconds(total_pause_seconds); set_pause_seconds(pause_seconds); ps->skipping = true; ps->skipOutOfOrder = true; ps->songLoading = true; ps->forceSkip = false; ps->loadingdata.state = state; ps->loadingdata.loadA = !ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = true; ps->loadedNextSong = false; load_song(get_current_song(), &ps->loadingdata); int max_num_tries = 50; int numtries = 0; while (!ps->loadedNextSong && numtries < max_num_tries) { c_sleep(100); numtries++; } if (ps->songHasErrors) { ps->songHasErrors = false; ps->forceSkip = true; goto retry; } reset_clock(); skip(); skip_in_progress = false; } void skip_to_numbered_song(int song_number) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); if (ps->songLoading || !ps->loadedNextSong || ps->skipping || ps->clearingErrors) if (!ps->forceSkip) return; double total_pause_seconds = get_total_pause_seconds(); double pause_seconds = get_total_pause_seconds(); play(); set_total_pause_seconds(total_pause_seconds); set_pause_seconds(pause_seconds); ps->skipping = true; ps->skipOutOfOrder = true; ps->loadedNextSong = false; ps->songLoading = true; ps->forceSkip = false; set_current_song(get_song_by_number(unshuffled_playlist, song_number)); ps->loadingdata.state = state; ps->loadingdata.loadA = !ps->usingSongDataA; ps->loadingdata.loadingFirstDecoder = true; load_song(get_current_song(), &ps->loadingdata); int max_num_tries = 50; int numtries = 0; while (!ps->loadedNextSong && numtries < max_num_tries) { c_sleep(100); numtries++; } if (ps->songHasErrors) { ps->songHasErrors = false; ps->forceSkip = true; if (song_number < playlist->count) skip_to_numbered_song(song_number + 1); } reset_clock(); skip(); } void skip_to_last_song(void) { PlayList *playlist = get_playlist(); Node *song = playlist->head; if (!song) return; int count = 1; while (song->next != NULL) { song = get_list_next(song); count++; } skip_to_numbered_song(count); } void repeat_list(void) { PlaybackState *ps = get_playback_state(); ps->waitingForPlaylist = true; ps->nextSongNeedsRebuilding = true; audio_data.end_of_list_reached = false; } void move_song_up(int *chosen_row) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); if (state->currentView != PLAYLIST_VIEW) { return; } bool rebuild = false; Node *node = find_selected_entry(unshuffled_playlist, *chosen_row); if (node == NULL) { return; } int id = node->id; pthread_mutex_lock(&(playlist->mutex)); Node *current = get_current_song(); if (node != NULL && current != NULL) { // Rebuild if current song, the next song or the song after are // affected if (current != NULL) { Node *tmp = current; for (int i = 0; i < 3; i++) { if (tmp == NULL) break; if (tmp->id == id) { rebuild = true; } tmp = tmp->next; } } } move_up_list(unshuffled_playlist, node); Node *pl_node = find_selected_entry_by_id(playlist, node->id); if (!is_shuffle_enabled()) move_up_list(playlist, pl_node); *chosen_row = *chosen_row - 1; *chosen_row = (*chosen_row > 0) ? *chosen_row : 0; if (rebuild && current != NULL) { node = NULL; ps->nextSongNeedsRebuilding = false; set_try_next_song(current->next); set_next_song(get_list_next(current)); rebuild_next_song(get_next_song()); ps->loadedNextSong = true; } pthread_mutex_unlock(&(playlist->mutex)); trigger_redraw_side_cover(); trigger_refresh(); } void move_song_down(int *chosen_row) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); if (state->currentView != PLAYLIST_VIEW) { return; } bool rebuild = false; Node *node = find_selected_entry(unshuffled_playlist, *chosen_row); Node *current = get_current_song(); if (node == NULL) { return; } int id = node->id; pthread_mutex_lock(&(playlist->mutex)); if (node != NULL && current != NULL) { // Rebuild if current song, the next song or the previous song // are affected if (current != NULL) { Node *tmp = current; for (int i = 0; i < 2; i++) { if (tmp == NULL) break; if (tmp->id == id) { rebuild = true; } tmp = tmp->next; } if (current->prev != NULL && current->prev->id == id) rebuild = true; } } move_down_list(unshuffled_playlist, node); Node *pl_node = find_selected_entry_by_id(playlist, node->id); if (!is_shuffle_enabled()) move_down_list(playlist, pl_node); *chosen_row = *chosen_row + 1; *chosen_row = (*chosen_row >= unshuffled_playlist->count) ? unshuffled_playlist->count - 1 : *chosen_row; if (rebuild && current != NULL) { node = NULL; ps->nextSongNeedsRebuilding = false; set_try_next_song(current->next); set_next_song(get_list_next(current)); rebuild_next_song(get_next_song()); ps->loadedNextSong = true; } pthread_mutex_unlock(&(playlist->mutex)); trigger_redraw_side_cover(); trigger_refresh(); } void reshuffle_playlist(void) { PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); if (is_shuffle_enabled()) { Node *current = get_current_song(); if (current != NULL) shuffle_playlist_starting_from_song(playlist, current); else shuffle_playlist(playlist); ps->nextSongNeedsRebuilding = true; } } void handle_skip_out_of_order(void) { PlaybackState *ps = get_playback_state(); if (!ps->skipOutOfOrder && !is_repeat_enabled()) { set_current_song_to_next(); } else { ps->skipOutOfOrder = false; } } Node *determine_next_song(PlayList *playlist) { AppState *state = get_app_state(); Node *current = NULL; PlaybackState *ps = get_playback_state(); if (ps->waitingForPlaylist) { return playlist->head; } else if (ps->waitingForNext) { Node *song_to_start_from = get_song_to_start_from(); if (song_to_start_from != NULL) { find_node_in_list(playlist, song_to_start_from->id, ¤t); return current ? current : NULL; } else if (ps->lastPlayedId >= 0) { current = find_selected_entry_by_id(playlist, ps->lastPlayedId); if (current != NULL && current->next != NULL) current = current->next; return current; } // fallback if nothing else if (state->uiState.startFromTop) { state->uiState.startFromTop = false; return playlist->head; } else { return playlist->tail; } } return NULL; } bool play_pre_processing() { bool was_end_of_list = false; if (audio_data.end_of_list_reached) was_end_of_list = true; return was_end_of_list; } void play_post_processing(bool was_end_of_list) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); if ((state->uiState.songWasRemoved && get_current_song() != NULL)) { state->uiState.songWasRemoved = false; } if (was_end_of_list) { ps->skipOutOfOrder = false; } audio_data.end_of_list_reached = false; } void clear_and_play(Node *song) { PlaybackState *ps = get_playback_state(); set_song_to_start_from(song); set_current_implementation_type(NONE); audio_data.restart = true; audio_data.end_of_list_reached = false; audio_data.currentFileIndex = 0; ps->nextSongNeedsRebuilding = true; ps->skipOutOfOrder = false; ps->usingSongDataA = false; ps->loadingdata.loadA = true; ps->waitingForNext = true; ps->loadedNextSong = false; ps->lastPlayedId = -1; } void playlist_play(PlayList *playlist) { AppState *state = get_app_state(); Node *current = get_current_song(); if (is_paused() && current != NULL && state->uiState.chosen_node_id == current->id) { ops_toggle_pause(); } else { Node *song = NULL; find_node_in_list(playlist, state->uiState.chosen_node_id, &song); clear_and_play(song); } } void play_favorites_playlist(void) { PlayList *playlist = get_playlist(); PlayList *favorites_playlist = get_favorites_playlist(); if (favorites_playlist->count == 0) { printf(_("Couldn't find any songs in the special playlist. Add a " "song by pressing '.' while it's playing. \n")); exit(0); } FileSystemEntry *library = get_library(); deep_copy_play_list_onto_list(favorites_playlist, &playlist); shuffle_playlist(playlist); mark_list_as_enqueued(library, playlist); } void play_all(void) { FileSystemEntry *library = get_library(); PlayList *playlist = get_playlist(); create_play_list_from_file_system_entry(library, playlist, MAX_FILES); if (playlist->count == 0) { exit(0); } shuffle_playlist(playlist); mark_list_as_enqueued(library, playlist); } void play_all_albums(void) { PlayList *playlist = get_playlist(); FileSystemEntry *library = get_library(); add_shuffled_albums_to_play_list(library, playlist, MAX_FILES); if (playlist->count == 0) { exit(0); } mark_list_as_enqueued(library, playlist); } kew/src/ops/playlist_ops.h000066400000000000000000000025351512074754200161430ustar00rootroot00000000000000/** * @file playlist_ops.h * @brief Playlist management operations. * * Implements functionality for adding, removing, reordering, * shuffling, and retrieving songs within playlists. * Coordinates with the playback system for next/previous transitions. */ #ifndef PLAYLIST_OPS_H #define PLAYLIST_OPS_H #include "common/appstate.h" #include "data/playlist.h" Node *get_song_by_number(PlayList *playlist, int song_number); Node *find_selected_entry_by_id(PlayList *playlist, int id); Node *find_selected_entry(PlayList *playlist, int row); Node *determine_next_song(PlayList *playlist); void rebuild_next_song(Node *song); void repeat_list(void); void move_song_up(int *chosen_row); void move_song_down(int *chosen_row); void handle_remove(int chosen_row); void skip_to_numbered_song(int song_number); void remove_currently_playing_song(void); void skip_to_last_song(void); void skip_to_prev_song(void); void skip_to_next_song(void); void set_current_song_to_next(void); void dequeue_all_except_playing_song(void); void add_to_favorites_playlist(void); void reshuffle_playlist(void); void handle_skip_out_of_order(void); void playlist_play(PlayList *playlist); void clear_and_play(Node *song); bool play_pre_processing(); void play_post_processing(bool was_end_of_list); void play_favorites_playlist(void); void play_all(void); void play_all_albums(void); #endif kew/src/ops/track_manager.c000066400000000000000000000132551512074754200162130ustar00rootroot00000000000000/** * @file track_manager.c * @brief Central track indexing and metadata manager. * * Manages track objects, metadata lookup, and synchronization * between the library and active playlists. Provides helper * functions for finding and referencing track data. */ #include "track_manager.h" #include "common/appstate.h" #include "data/song_loader.h" #include "utils/utils.h" void load_first_song(Node *song) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); if (song == NULL) return; ps->loadingdata.state = state; ps->loadingdata.loadingFirstDecoder = true; load_song(song, &ps->loadingdata); int i = 0; while (!ps->loadedNextSong && i < 10000) { if (i != 0 && i % 1000 == 0 && state->uiSettings.uiEnabled) printf("."); c_sleep(10); fflush(stdout); i++; } } void unload_song_a(void) { PlaybackState *ps = get_playback_state(); if (audio_data.pUserData->songdataADeleted == false) { audio_data.pUserData->songdataADeleted = true; unload_song_data(&(ps->loadingdata.songdataA)); audio_data.pUserData->songdataA = NULL; } } void unload_song_b(void) { PlaybackState *ps = get_playback_state(); if (audio_data.pUserData->songdataBDeleted == false) { audio_data.pUserData->songdataBDeleted = true; unload_song_data(&(ps->loadingdata.songdataB)); audio_data.pUserData->songdataB = NULL; } } void unload_previous_song(void) { AppState *state = get_app_state(); UserData *user_data = audio_data.pUserData; PlaybackState *ps = get_playback_state(); pthread_mutex_lock(&(ps->loadingdata.mutex)); pthread_mutex_lock(&(state->data_source_mutex)); if (ps->usingSongDataA && (ps->skipping || (user_data->current_song_data == NULL || user_data->songdataADeleted == false || (ps->loadingdata.songdataA != NULL && user_data->songdataADeleted == false && user_data->current_song_data->hasErrors == 0 && user_data->current_song_data->track_id != NULL && strcmp(ps->loadingdata.songdataA->track_id, user_data->current_song_data->track_id) != 0)))) { unload_song_a(); if (!audio_data.end_of_list_reached) ps->loadedNextSong = false; ps->usingSongDataA = false; } else if (!ps->usingSongDataA && (ps->skipping || (user_data->current_song_data == NULL || user_data->songdataBDeleted == false || (ps->loadingdata.songdataB != NULL && user_data->songdataBDeleted == false && user_data->current_song_data->hasErrors == 0 && user_data->current_song_data->track_id != NULL && strcmp(ps->loadingdata.songdataB->track_id, user_data->current_song_data->track_id) != 0)))) { unload_song_b(); if (!audio_data.end_of_list_reached) ps->loadedNextSong = false; ps->usingSongDataA = true; } pthread_mutex_unlock(&(state->data_source_mutex)); pthread_mutex_unlock(&(ps->loadingdata.mutex)); } int load_first(Node *song) { load_first_song(song); Node *current = get_current_song(); PlaybackState *ps = get_playback_state(); ps->usingSongDataA = true; while (ps->songHasErrors && current->next != NULL) { ps->songHasErrors = false; ps->loadedNextSong = false; current = current->next; load_first_song(current); } if (ps->songHasErrors) { // Couldn't play any of the songs unload_previous_song(); current = NULL; ps->songHasErrors = false; return -1; } UserData *user_data = audio_data.pUserData; user_data->currentPCMFrame = 0; user_data->current_song_data = user_data->songdataA; return 0; } void load_next_song(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); ps->songLoading = true; ps->nextSongNeedsRebuilding = false; ps->skipFromStopped = false; ps->loadingdata.loadA = !ps->usingSongDataA; set_try_next_song(get_list_next(get_current_song())); set_next_song(get_try_next_song()); ps->loadingdata.state = state; ps->loadingdata.loadingFirstDecoder = false; load_song(get_next_song(), &ps->loadingdata); } void finish_loading(void) { PlaybackState *ps = get_playback_state(); int max_num_tries = 20; int numtries = 0; while (!ps->loadedNextSong && numtries < max_num_tries) { c_sleep(100); numtries++; } ps->loadedNextSong = true; } void autostart_if_stopped(const char *path) { if (path == NULL) return; PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); ps->waitingForPlaylist = false; ps->waitingForNext = true; audio_data.end_of_list_reached = false; set_song_to_start_from(find_path_in_playlist(path, playlist)); ps->lastPlayedId = -1; } kew/src/ops/track_manager.h000066400000000000000000000011251512074754200162110ustar00rootroot00000000000000/** * @file track_manager.h * @brief Central track indexing and metadata manager. * * Manages track objects, metadata lookup, and synchronization * between the library and active playlists. Provides helper * functions for finding and referencing track data. */ #include "common/appstate.h" void load_song(Node *song, LoadingThreadData *loadingdata); void load_next_song(void); void finish_loading(void); void unload_song_a(void); void unload_song_b(void); void unload_previous_song(void); void try_load_next(void); void autostart_if_stopped(const char *path); int load_first(Node *song); kew/src/sound/000077500000000000000000000000001512074754200135725ustar00rootroot00000000000000kew/src/sound/audio_file_info.c000066400000000000000000000070361512074754200170570ustar00rootroot00000000000000#include "audio_file_info.h" #ifdef USE_FAAD #include "m4a.h" #endif #include "miniaudio_libopus.h" #include "miniaudio_libvorbis.h" #include "webm.h" void get_file_info(const char *filename, ma_uint32 *sample_rate, ma_uint32 *channels, ma_format *format) { ma_decoder tmp; if (ma_decoder_init_file(filename, NULL, &tmp) == MA_SUCCESS) { *sample_rate = tmp.outputSampleRate; *channels = tmp.outputChannels; *format = tmp.outputFormat; ma_decoder_uninit(&tmp); } else { // Handle file open error. } } void get_vorbis_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map) { ma_libvorbis decoder; if (ma_libvorbis_init_file(filename, NULL, NULL, &decoder) == MA_SUCCESS) { *format = decoder.format; ma_libvorbis_get_data_format(&decoder, format, channels, sample_rate, channel_map, MA_MAX_CHANNELS); ma_libvorbis_uninit(&decoder, NULL); } } void get_opus_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map) { ma_libopus decoder; if (ma_libopus_init_file(filename, NULL, NULL, &decoder) == MA_SUCCESS) { *format = decoder.format; ma_libopus_get_data_format(&decoder, format, channels, sample_rate, channel_map, MA_MAX_CHANNELS); ma_libopus_uninit(&decoder, NULL); } } void get_webm_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map) { ma_webm tmp; if (ma_webm_init_file(filename, NULL, NULL, &tmp) == MA_SUCCESS) { *sample_rate = tmp.sample_rate; *channels = tmp.channels; *format = tmp.format; ma_webm_uninit(&tmp, NULL); } (void)channel_map; } #ifdef USE_FAAD void get_m4a_file_info_full(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map, int *avg_bit_rate, k_m4adec_filetype *file_type) { m4a_decoder decoder; if (m4a_decoder_init_file(filename, NULL, NULL, &decoder) == MA_SUCCESS) { *format = decoder.format; m4a_decoder_get_data_format(&decoder, format, channels, sample_rate, channel_map, MA_MAX_CHANNELS); *avg_bit_rate = decoder.avg_bit_rate / 1000; *file_type = decoder.file_type; m4a_decoder_uninit(&decoder, NULL); } } void get_m4a_file_info( const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map) { int unusedBitRate; k_m4adec_filetype unusedFileType; // We just discard these values here get_m4a_file_info_full(filename, format, channels, sample_rate, channel_map, &unusedBitRate, &unusedFileType); } #endif kew/src/sound/audio_file_info.h000066400000000000000000000020131512074754200170520ustar00rootroot00000000000000#ifndef AUDIO_FILEINFO_H #define AUDIO_FILEINFO_H #include "common/common.h" #include void get_file_info(const char *filename, ma_uint32 *sample_rate, ma_uint32 *channels, ma_format *format); void get_m4a_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map); void get_m4a_file_info_full(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map, int *avg_bit_rate, k_m4adec_filetype *file_type); void get_vorbis_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map); void get_opus_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map); void get_webm_file_info(const char *filename, ma_format *format, ma_uint32 *channels, ma_uint32 *sample_rate, ma_channel *channel_map); #endif kew/src/sound/audiobuffer.c000066400000000000000000000146441512074754200162420ustar00rootroot00000000000000#include "audiobuffer.h" #include #include static int fft_size = 2048; static int hop_size = 512; static int fft_size_milliseconds = 45; static int write_head = 0; static int buf_size; static bool buffer_ready = false; static float audio_buffer[MAX_BUFFER_SIZE]; int closest_power_of_two(int x) { int n = 1; while (n < x) n <<= 1; return n; } bool is_buffer_ready(void) { return buffer_ready; } void set_buffer_ready(bool val) { buffer_ready = val; } int get_buffer_size(void) { return buf_size; } void set_buffer_size(int value) { buf_size = value; } // Sign-extend s24 ma_int32 unpack_s24(const ma_uint8 *p) { ma_int32 sample = p[0] | (p[1] << 8) | (p[2] << 16); if (sample & 0x800000) sample |= ~0xFFFFFF; return sample; } void set_audio_buffer(void *buf, int num_frames, ma_uint32 sample_rate, ma_uint32 channels, ma_format format) { int buf_index = 0; // Dynamically determine FFT and hop size float hop_fraction = 0.25f; // 25% hop (75% overlap) // Compute power-of-two window/hop sizes in samples int want_fft_samples = (int)(fft_size_milliseconds * sample_rate / 1000.0f); fft_size = closest_power_of_two(want_fft_samples); // 2048 or 4096 int want_hop_samples = (int)(fft_size * hop_fraction); // 25% of window length hop_size = closest_power_of_two(want_hop_samples); // 256, 512, 1024 if (fft_size > MAX_BUFFER_SIZE) fft_size = MAX_BUFFER_SIZE; // Ensure hop is never >= window if (hop_size >= fft_size) hop_size = fft_size / 2; // fallback minimum overlap while (buf_index < num_frames) { if (write_head >= fft_size) break; int frames_left = num_frames - buf_index; int space_left = fft_size - write_head; int frames_to_copy = frames_left < space_left ? frames_left : space_left; switch (format) { case ma_format_u8: { ma_uint8 *src = (ma_uint8 *)buf + buf_index * channels; for (int i = 0; i < frames_to_copy; ++i) { float sum = 0.0f; for (ma_uint32 ch = 0; ch < channels; ++ch) { // Convert 0..255 to -1..1 sum += ((float)src[i * channels + ch] - 128.0f) / 128.0f; } audio_buffer[write_head++] = sum / channels; } break; } case ma_format_s16: { ma_int16 *src = (ma_int16 *)buf + buf_index * channels; for (int i = 0; i < frames_to_copy; ++i) { float sum = 0.0f; for (ma_uint32 ch = 0; ch < channels; ++ch) { sum += (float)src[i * channels + ch] / 32768.0f; } audio_buffer[write_head++] = sum / channels; } break; } case ma_format_s24: { ma_uint8 *src = (ma_uint8 *)buf + buf_index * channels * 3; for (int i = 0; i < frames_to_copy; ++i) { float sum = 0.0f; for (ma_uint32 ch = 0; ch < channels; ++ch) { int idx = i * channels * 3 + ch * 3; int32_t s = unpack_s24(&src[idx]); sum += (float)s / 8388608.0f; } audio_buffer[write_head++] = sum / channels; } break; } case ma_format_s32: { int32_t *src = (int32_t *)buf + buf_index * channels; for (int i = 0; i < frames_to_copy; ++i) { float sum = 0.0f; for (ma_uint32 ch = 0; ch < channels; ++ch) { sum += (float)src[i * channels + ch] / 2147483648.0f; } audio_buffer[write_head++] = sum / channels; } break; } case ma_format_f32: { float *src = (float *)buf + buf_index * channels; for (int i = 0; i < frames_to_copy; ++i) { float sum = 0.0f; for (ma_uint32 ch = 0; ch < channels; ++ch) { sum += src[i * channels + ch]; } audio_buffer[write_head++] = sum / channels; } break; } default: fprintf(stderr, "Unsupported format in set_audio_buffer!\n"); return; } buf_index += frames_to_copy; // Process full window(s), maintain overlap (hop) while (write_head >= fft_size) { set_buffer_ready(true); // let main loop know FFT is ready // Shift buffer for overlap (keep last fft_size-hop_size // samples) memmove(audio_buffer, audio_buffer + hop_size, sizeof(float) * (fft_size - hop_size)); write_head -= hop_size; } } } void reset_audio_buffer(void) { memset(audio_buffer, 0, sizeof(ma_int32) * MAX_BUFFER_SIZE); write_head = 0; set_buffer_ready(false); } void *get_audio_buffer(void) { return audio_buffer; } int get_fft_size(void) { return fft_size; }; kew/src/sound/audiobuffer.h000066400000000000000000000010401512074754200162310ustar00rootroot00000000000000#ifndef AUDIOBUFFER_H #define AUDIOBUFFER_H #include "audiotypes.h" #include #include #ifndef MAX_BUFFER_SIZE #define MAX_BUFFER_SIZE 32768 #endif void *get_audio_buffer(void); void reset_audio_buffer(void); void freeAudioBuffer(void); void set_audio_buffer(void *buf, int num_samples, ma_uint32 sample_rate, ma_uint32 channels, ma_format format); void set_buffer_ready(bool val); void set_buffer_size(int value); int get_fft_size(void); bool is_buffer_ready(void); int32_t unpack_s24(const ma_uint8 *p); #endif kew/src/sound/audiotypes.h000066400000000000000000000005401512074754200161300ustar00rootroot00000000000000#ifndef AUDIOTYPES_H #define AUDIOTYPES_H #include #include #include enum AudioImplementation { PCM, BUILTIN, VORBIS, OPUS, M4A, WEBM, NONE }; struct m4a_decoder; typedef struct m4a_decoder m4a_decoder; typedef void (*uninit_func)(void *decoder); #endif kew/src/sound/decoders.c000066400000000000000000000557541512074754200155460ustar00rootroot00000000000000#include "decoders.h" #include "playback.h" #ifdef USE_FAAD #include "m4a.h" #endif #include "sound/audio_file_info.h" #define MAX_DECODERS 2 static ma_decoder *first_decoder; static ma_decoder *current_decoder; static ma_decoder *decoders[MAX_DECODERS]; static ma_libopus *opus_decoders[MAX_DECODERS]; static ma_libopus *first_opus_decoder; static ma_libvorbis *vorbis_decoders[MAX_DECODERS]; static ma_libvorbis *first_vorbis_decoder; static ma_webm *webm_decoders[MAX_DECODERS]; static ma_webm *first_webm_decoder; #ifdef USE_FAAD static m4a_decoder *m4a_decoders[MAX_DECODERS]; static m4a_decoder *first_m4a_decoder; #endif static int decoder_index = -1; static int m4a_decoder_index = -1; static int opus_decoder_index = -1; static int vorbis_decoder_index = -1; static int webm_decoder_index = -1; void switch_specific_decoder(int *decoder_index) { if (*decoder_index == -1) *decoder_index = 0; else *decoder_index = 1 - *decoder_index; } void switch_decoder(void) { switch_specific_decoder(&decoder_index); switch_specific_decoder(&opus_decoder_index); switch_specific_decoder(&m4a_decoder_index); switch_specific_decoder(&vorbis_decoder_index); switch_specific_decoder(&webm_decoder_index); } void uninit_ma_decoder(void *decoder) { ma_decoder_uninit((ma_decoder *)decoder); } void uninit_opus_decoder(void *decoder) { ma_libopus_uninit((ma_libopus *)decoder, NULL); } void uninit_vorbis_decoder(void *decoder) { ma_libvorbis_uninit((ma_libvorbis *)decoder, NULL); } void uninit_webm_decoder(void *decoder) { ma_webm_uninit((ma_webm *)decoder, NULL); } #ifdef USE_FAAD void uninit_m4a_decoder(void *decoder) { m4a_decoder_uninit((m4a_decoder *)decoder, NULL); } #endif void uninit_previous_decoder(void **decoder_array, int index, uninit_func uninit) { if (index == -1) { return; } void *to_uninit = decoder_array[1 - index]; if (to_uninit != NULL) { uninit(to_uninit); free(to_uninit); decoder_array[1 - index] = NULL; } } bool has_builtin_decoder(const char *file_path) { char *extension = strrchr(file_path, '.'); return (extension != NULL && (strcasecmp(extension, ".wav") == 0 || strcasecmp(extension, ".flac") == 0 || strcasecmp(extension, ".mp3") == 0)); } void clear_decoder_chain() { ma_data_source_set_next(current_decoder, NULL); } ma_decoder *get_first_decoder(void) { return first_decoder; } ma_decoder *get_current_builtin_decoder(void) { if (decoder_index == -1) return get_first_decoder(); else return decoders[decoder_index]; } #ifdef USE_FAAD m4a_decoder *get_first_m4a_decoder(void) { return first_m4a_decoder; } m4a_decoder *get_current_m4a_decoder(void) { if (m4a_decoder_index == -1) return get_first_m4a_decoder(); else return m4a_decoders[m4a_decoder_index]; } #endif void reset_decoders(void **decoder_array, void **first_decoder, int array_size, int *decoder_index, uninit_func uninit) { *decoder_index = -1; if (*first_decoder != NULL) { uninit(*first_decoder); free(*first_decoder); *first_decoder = NULL; } for (int i = 0; i < array_size; i++) { if (decoder_array[i] != NULL) { uninit(decoder_array[i]); free(decoder_array[i]); decoder_array[i] = NULL; } } } void reset_all_decoders(void) { reset_decoders((void **)decoders, (void **)&first_decoder, MAX_DECODERS, &decoder_index, uninit_ma_decoder); reset_decoders((void **)vorbis_decoders, (void **)&first_vorbis_decoder, MAX_DECODERS, &vorbis_decoder_index, uninit_vorbis_decoder); reset_decoders((void **)opus_decoders, (void **)&first_opus_decoder, MAX_DECODERS, &opus_decoder_index, uninit_opus_decoder); reset_decoders((void **)webm_decoders, (void **)&first_webm_decoder, MAX_DECODERS, &webm_decoder_index, uninit_webm_decoder); #ifdef USE_FAAD reset_decoders((void **)m4a_decoders, (void **)&first_m4a_decoder, MAX_DECODERS, &m4a_decoder_index, uninit_m4a_decoder); #endif } void set_next_decoder(void **decoder_array, void **decoder, void **first_decoder, int *decoder_index, uninit_func uninit) { if (*decoder_index == -1 && *first_decoder == NULL) { *first_decoder = *decoder; } else if (*decoder_index == -1) // Array hasn't been used yet { if (decoder_array[0] != NULL) { uninit(decoder_array[0]); free(decoder_array[0]); decoder_array[0] = NULL; } decoder_array[0] = *decoder; } else { int next_index = 1 - *decoder_index; if (decoder_array[next_index] != NULL) { uninit(decoder_array[next_index]); free(decoder_array[next_index]); decoder_array[next_index] = NULL; } decoder_array[next_index] = *decoder; } } MA_API ma_result ma_libopus_read_pcm_frames_wrapper(void *pDecoder, void *p_frames_out, size_t frame_count, size_t *p_frames_read) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libopus_read_pcm_frames((ma_libopus *)dec->pUserData, p_frames_out, frame_count, (ma_uint64 *)p_frames_read); } MA_API ma_result ma_libopus_seek_to_pcm_frame_wrapper(void *pDecoder, long long int frame_index, ma_seek_origin origin) { (void)origin; ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libopus_seek_to_pcm_frame((ma_libopus *)dec->pUserData, frame_index); } MA_API ma_result ma_libopus_get_cursor_in_pcm_frames_wrapper( void *pDecoder, long long int *p_cursor) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libopus_get_cursor_in_pcm_frames((ma_libopus *)dec->pUserData, (ma_uint64 *)p_cursor); } MA_API ma_result ma_libvorbis_read_pcm_frames_wrapper(void *pDecoder, void *p_frames_out, size_t frame_count, size_t *p_frames_read) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libvorbis_read_pcm_frames((ma_libvorbis *)dec->pUserData, p_frames_out, frame_count, (ma_uint64 *)p_frames_read); } MA_API ma_result ma_libvorbis_seek_to_pcm_frame_wrapper( void *pDecoder, long long int frame_index, ma_seek_origin origin) { (void)origin; ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis *)dec->pUserData, frame_index); } MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames_wrapper( void *pDecoder, long long int *p_cursor) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_libvorbis_get_cursor_in_pcm_frames( (ma_libvorbis *)dec->pUserData, (ma_uint64 *)p_cursor); } MA_API ma_result ma_webm_read_pcm_frames_wrapper(void *pDecoder, void *p_frames_out, size_t frame_count, size_t *p_frames_read) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_webm_read_pcm_frames((ma_webm *)dec->pUserData, p_frames_out, frame_count, (ma_uint64 *)p_frames_read); } MA_API ma_result ma_webm_seek_to_pcm_frame_wrapper(void *pDecoder, long long int frame_index, ma_seek_origin origin) { (void)origin; ma_decoder *dec = (ma_decoder *)pDecoder; return ma_webm_seek_to_pcm_frame((ma_webm *)dec->pUserData, frame_index); } MA_API ma_result ma_webm_get_cursor_in_pcm_frames_wrapper(void *pDecoder, long long int *p_cursor) { ma_decoder *dec = (ma_decoder *)pDecoder; return ma_webm_get_cursor_in_pcm_frames((ma_webm *)dec->pUserData, (ma_uint64 *)p_cursor); } #ifdef USE_FAAD MA_API ma_result m4a_read_pcm_frames_wrapper(void *pDecoder, void *p_frames_out, size_t frame_count, size_t *p_frames_read) { ma_decoder *dec = (ma_decoder *)pDecoder; return m4a_decoder_read_pcm_frames((m4a_decoder *)dec->pUserData, p_frames_out, frame_count, (ma_uint64 *)p_frames_read); } MA_API ma_result m4a_seek_to_pcm_frame_wrapper(void *pDecoder, long long int frame_index, ma_seek_origin origin) { (void)origin; ma_decoder *dec = (ma_decoder *)pDecoder; return m4a_decoder_seek_to_pcm_frame((m4a_decoder *)dec->pUserData, frame_index); } MA_API ma_result m4a_get_cursor_in_pcm_frames_wrapper(void *pDecoder, long long int *p_cursor) { ma_decoder *dec = (ma_decoder *)pDecoder; return m4a_decoder_get_cursor_in_pcm_frames( (m4a_decoder *)dec->pUserData, (ma_uint64 *)p_cursor); } int prepare_next_m4a_decoder(SongData *song_data) { m4a_decoder *current_decoder; if (song_data == NULL) return -1; char *filepath = song_data->file_path; if (m4a_decoder_index == -1) { current_decoder = get_first_m4a_decoder(); } else { current_decoder = m4a_decoders[m4a_decoder_index]; } ma_uint32 sample_rate; ma_uint32 channels; ma_format format; ma_channel channel_map[MA_MAX_CHANNELS]; m4a_decoder_get_data_format(current_decoder, &format, &channels, &sample_rate, channel_map, MA_MAX_CHANNELS); uninit_previous_decoder((void **)m4a_decoders, m4a_decoder_index, (uninit_func)uninit_m4a_decoder); m4a_decoder *decoder = (m4a_decoder *)malloc(sizeof(m4a_decoder)); ma_result result = m4a_decoder_init_file(filepath, NULL, NULL, decoder); if (result != MA_SUCCESS) return -1; ma_format nformat; ma_uint32 nchannels; ma_uint32 nsampleRate; ma_channel nchannelMap[MA_MAX_CHANNELS]; m4a_decoder_get_data_format(decoder, &nformat, &nchannels, &nsampleRate, nchannelMap, MA_MAX_CHANNELS); bool sameFormat = (current_decoder == NULL || (format == nformat && channels == nchannels && sample_rate == nsampleRate && current_decoder->file_type == decoder->file_type && current_decoder->file_type != k_rawAAC)); if (!sameFormat) { m4a_decoder_uninit(decoder, NULL); free(decoder); return 0; } m4a_decoder *first = get_first_m4a_decoder(); if (first != NULL) { decoder->pReadSeekTellUserData = (AudioData *)first->pReadSeekTellUserData; } decoder->format = nformat; decoder->onRead = m4a_read_pcm_frames_wrapper; decoder->onSeek = m4a_seek_to_pcm_frame_wrapper; decoder->onTell = m4a_get_cursor_in_pcm_frames_wrapper; decoder->cursor = 0; set_next_decoder((void **)m4a_decoders, (void **)&decoder, (void **)&first_m4a_decoder, &m4a_decoder_index, (uninit_func)uninit_m4a_decoder); if (song_data != NULL) { if (decoder != NULL && decoder->file_type == k_rawAAC) { song_data->duration = decoder->duration; } } if (current_decoder != NULL && decoder != NULL && decoder->file_type != k_rawAAC) { if (!pb_is_EOF_reached()) ma_data_source_set_next(current_decoder, decoder); } return 0; } #endif ma_libvorbis *get_first_vorbis_decoder(void) { return first_vorbis_decoder; } ma_libopus *get_first_opus_decoder(void) { return first_opus_decoder; } ma_libvorbis *get_current_vorbis_decoder(void) { if (vorbis_decoder_index == -1) return get_first_vorbis_decoder(); else return vorbis_decoders[vorbis_decoder_index]; } ma_libopus *get_current_opus_decoder(void) { if (opus_decoder_index == -1) return get_first_opus_decoder(); else return opus_decoders[opus_decoder_index]; } int prepare_next_vorbis_decoder(const char *filepath) { ma_libvorbis *current_decoder; if (vorbis_decoder_index == -1) { current_decoder = get_first_vorbis_decoder(); } else { current_decoder = vorbis_decoders[vorbis_decoder_index]; } ma_uint32 sample_rate; ma_uint32 channels; ma_format format; ma_channel channel_map[MA_MAX_CHANNELS]; ma_libvorbis_get_data_format(current_decoder, &format, &channels, &sample_rate, channel_map, MA_MAX_CHANNELS); uninit_previous_decoder((void **)vorbis_decoders, vorbis_decoder_index, (uninit_func)uninit_vorbis_decoder); ma_libvorbis *decoder = (ma_libvorbis *)malloc(sizeof(ma_libvorbis)); ma_result result = ma_libvorbis_init_file(filepath, NULL, NULL, decoder); if (result != MA_SUCCESS) return -1; ma_format nformat; ma_uint32 nchannels; ma_uint32 nsampleRate; ma_channel nchannelMap[MA_MAX_CHANNELS]; ma_libvorbis_get_data_format(decoder, &nformat, &nchannels, &nsampleRate, nchannelMap, MA_MAX_CHANNELS); bool sameFormat = (current_decoder == NULL || (format == nformat && channels == nchannels && sample_rate == nsampleRate)); if (!sameFormat) { ma_libvorbis_uninit(decoder, NULL); free(decoder); return 0; } ma_libvorbis *first = get_first_vorbis_decoder(); if (first != NULL) { decoder->pReadSeekTellUserData = (AudioData *)first->pReadSeekTellUserData; } decoder->format = nformat; decoder->onRead = ma_libvorbis_read_pcm_frames_wrapper; decoder->onSeek = ma_libvorbis_seek_to_pcm_frame_wrapper; decoder->onTell = ma_libvorbis_get_cursor_in_pcm_frames_wrapper; set_next_decoder((void **)vorbis_decoders, (void **)&decoder, (void **)&first_vorbis_decoder, &vorbis_decoder_index, (uninit_func)uninit_vorbis_decoder); if (current_decoder != NULL && decoder != NULL) { if (!pb_is_EOF_reached()) ma_data_source_set_next(current_decoder, decoder); } return 0; } int prepare_next_decoder(const char *filepath) { ma_decoder *current_decoder; if (decoder_index == -1) { current_decoder = get_first_decoder(); } else { current_decoder = decoders[decoder_index]; } ma_uint32 sample_rate; ma_uint32 channels; ma_format format; get_file_info(filepath, &sample_rate, &channels, &format); bool sameFormat = (current_decoder == NULL || (format == current_decoder->outputFormat && channels == current_decoder->outputChannels && sample_rate == current_decoder->outputSampleRate)); if (!sameFormat) { return 0; } uninit_previous_decoder((void **)decoders, decoder_index, (uninit_func)uninit_ma_decoder); ma_decoder *decoder = (ma_decoder *)malloc(sizeof(ma_decoder)); ma_result result = ma_decoder_init_file(filepath, NULL, decoder); if (result != MA_SUCCESS) { free(decoder); return -1; } set_next_decoder((void **)decoders, (void **)&decoder, (void **)&first_decoder, &decoder_index, (uninit_func)uninit_ma_decoder); if (current_decoder != NULL && decoder != NULL) { if (!pb_is_EOF_reached()) ma_data_source_set_next(current_decoder, decoder); } return 0; } int prepare_next_opus_decoder(const char *filepath) { ma_libopus *current_decoder; if (opus_decoder_index == -1) { current_decoder = get_first_opus_decoder(); } else { current_decoder = opus_decoders[opus_decoder_index]; } ma_uint32 sample_rate; ma_uint32 channels; ma_format format; ma_channel channel_map[MA_MAX_CHANNELS]; ma_libopus_get_data_format(current_decoder, &format, &channels, &sample_rate, channel_map, MA_MAX_CHANNELS); uninit_previous_decoder((void **)opus_decoders, opus_decoder_index, (uninit_func)uninit_opus_decoder); ma_libopus *decoder = (ma_libopus *)malloc(sizeof(ma_libopus)); ma_result result = ma_libopus_init_file(filepath, NULL, NULL, decoder); if (result != MA_SUCCESS) return -1; ma_format nformat; ma_uint32 nchannels; ma_uint32 nsampleRate; ma_channel nchannelMap[MA_MAX_CHANNELS]; ma_libopus_get_data_format(decoder, &nformat, &nchannels, &nsampleRate, nchannelMap, MA_MAX_CHANNELS); bool sameFormat = (current_decoder == NULL || (format == nformat && channels == nchannels && sample_rate == nsampleRate)); if (!sameFormat) { ma_libopus_uninit(decoder, NULL); free(decoder); return 0; } if (first_opus_decoder != NULL) { decoder->pReadSeekTellUserData = (AudioData *)first_opus_decoder->pReadSeekTellUserData; } decoder->format = nformat; decoder->onRead = ma_libopus_read_pcm_frames_wrapper; decoder->onSeek = ma_libopus_seek_to_pcm_frame_wrapper; decoder->onTell = ma_libopus_get_cursor_in_pcm_frames_wrapper; set_next_decoder((void **)opus_decoders, (void **)&decoder, (void **)&first_opus_decoder, &opus_decoder_index, (uninit_func)uninit_opus_decoder); if (current_decoder != NULL && decoder != NULL) { if (!pb_is_EOF_reached()) ma_data_source_set_next(current_decoder, decoder); } return 0; } int prepare_next_webm_decoder(SongData *song_data) { ma_webm *current_decoder; if (song_data == NULL) return -1; char *filepath = song_data->file_path; if (webm_decoder_index == -1) { current_decoder = get_first_webm_decoder(); } else { current_decoder = webm_decoders[webm_decoder_index]; } ma_uint32 sample_rate; ma_uint32 channels; ma_format format; ma_channel channel_map[MA_MAX_CHANNELS]; ma_webm_get_data_format(current_decoder, &format, &channels, &sample_rate, channel_map, MA_MAX_CHANNELS); uninit_previous_decoder((void **)webm_decoders, webm_decoder_index, (uninit_func)uninit_webm_decoder); ma_webm *decoder = (ma_webm *)malloc(sizeof(ma_webm)); ma_result result = ma_webm_init_file(filepath, NULL, NULL, decoder); if (result != MA_SUCCESS) return -1; ma_format nformat; ma_uint32 nchannels; ma_uint32 nsampleRate; ma_channel nchannelMap[MA_MAX_CHANNELS]; ma_webm_get_data_format(decoder, &nformat, &nchannels, &nsampleRate, nchannelMap, MA_MAX_CHANNELS); bool sameFormat = (current_decoder == NULL); // Gapless playback disabled for webm // bool sameFormat = (current_decoder == NULL || (format == nformat && // channels == nchannels && // sample_rate == // nsampleRate)); if (!sameFormat) { ma_webm_uninit(decoder, NULL); free(decoder); return 0; } if (first_webm_decoder != NULL) { decoder->pReadSeekTellUserData = (AudioData *)first_webm_decoder->pReadSeekTellUserData; } decoder->format = nformat; decoder->onRead = ma_webm_read_pcm_frames_wrapper; decoder->onSeek = ma_webm_seek_to_pcm_frame_wrapper; decoder->onTell = ma_webm_get_cursor_in_pcm_frames_wrapper; set_next_decoder((void **)webm_decoders, (void **)&decoder, (void **)&first_webm_decoder, &webm_decoder_index, (uninit_func)uninit_webm_decoder); if (song_data != NULL) { if (decoder != NULL) { song_data->duration = decoder->duration; } } if (current_decoder != NULL && decoder != NULL) { if (!pb_is_EOF_reached()) ma_data_source_set_next(current_decoder, decoder); } return 0; } ma_webm *get_first_webm_decoder(void) { return first_webm_decoder; } ma_webm *get_current_webm_decoder(void) { if (webm_decoder_index == -1) return get_first_webm_decoder(); else return webm_decoders[webm_decoder_index]; } kew/src/sound/decoders.h000066400000000000000000000020731512074754200155350ustar00rootroot00000000000000#ifndef DECODERS_H #define DECODERS_H #include "common/appstate.h" #include "audiotypes.h" #include "webm.h" #include #include #include #include void reset_all_decoders(void); bool has_builtin_decoder(const char *file_path); void switch_decoder(void); int prepare_next_decoder(const char *filepath); int prepare_next_opus_decoder(const char *filepath); int prepare_next_vorbis_decoder(const char *filepath); int prepare_next_m4a_decoder(SongData *song_data); int prepare_next_webm_decoder(SongData *song_data); ma_decoder *get_first_decoder(void); ma_decoder *get_current_builtin_decoder(void); ma_libopus *get_current_opus_decoder(void); ma_libopus *get_first_opus_decoder(void); ma_libvorbis *get_current_vorbis_decoder(void); ma_libvorbis *get_first_vorbis_decoder(void); ma_webm *get_current_webm_decoder(void); ma_webm *get_first_webm_decoder(void); void clear_decoder_chain(); #ifdef USE_FAAD m4a_decoder *get_current_m4a_decoder(void); m4a_decoder *get_first_m4a_decoder(void); #endif #endif kew/src/sound/m4a.c000066400000000000000000000005101512074754200144130ustar00rootroot00000000000000 #define MINIMP4_IMPLEMENTATION #include "../include/minimp4/minimp4.h" #include /** * @file m4a.[c] * @brief M4A/AAC decoder interface. * * Provides decoding support for M4A and AAC-encoded audio files, * wrapping platform or library-specific decoding routines. */ #ifdef USE_FAAD #include "m4a.h" #endif kew/src/sound/m4a.h000066400000000000000000001221621512074754200144300ustar00rootroot00000000000000/** * @file m4a.[h] * @brief M4A/AAC decoder interface. * * Provides decoding support for M4A and AAC-encoded audio files, * wrapping platform or library-specific decoding routines. */ #ifndef M4A_H #define M4A_H #ifdef __cplusplus extern "C" { #endif #ifdef USE_FAAD #include "common/common.h" #include "../include/minimp4/minimp4.h" #include "neaacdec.h" #include #include #include #include typedef struct m4a_decoder { ma_data_source_base ds; // The m4a decoder can be used independently as a data source. ma_read_proc onRead; ma_seek_proc onSeek; ma_tell_proc onTell; void *pReadSeekTellUserData; ma_format format; FILE *mf; // faad2 related fields... NeAACDecHandle hDecoder; NeAACDecFrameInfo frameInfo; unsigned char *buffer; unsigned int buffer_size; ma_uint32 sampleSize; int bit_depth; ma_uint32 sample_rate; ma_uint32 channels; ma_uint32 avg_bit_rate; double duration; unsigned long totalFrames; k_m4adec_filetype file_type; // minimp4 fields... MP4D_demux_t mp4; MP4D_track_t *track; int32_t audio_track_index; uint32_t current_sample; uint32_t total_samples; // For m4a_decoder_init_file FILE *file; ma_uint64 cursor; } m4a_decoder; #define FOUR_CHAR_INT(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d)) MA_API ma_result m4a_decoder_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, m4a_decoder *pM4a); MA_API ma_result m4a_decoder_init_file(const char *pFilePath, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, m4a_decoder *pM4a); MA_API void m4a_decoder_uninit(m4a_decoder *pM4a, const ma_allocation_callbacks *p_allocation_callbacks); MA_API ma_result m4a_decoder_read_pcm_frames(m4a_decoder *pM4a, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read); MA_API ma_result m4a_decoder_seek_to_pcm_frame(m4a_decoder *pM4a, ma_uint64 frame_index); MA_API ma_result m4a_decoder_get_data_format(m4a_decoder *pM4a, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap); MA_API ma_result m4a_decoder_get_cursor_in_pcm_frames(m4a_decoder *pM4a, ma_uint64 *p_cursor); MA_API ma_result m4a_decoder_get_length_in_pcm_frames(m4a_decoder *pM4a, ma_uint64 *p_length); ma_result m4a_decoder_ds_get_data_format(ma_data_source *p_data_source, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap); ma_result m4a_decoder_ds_read(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read); ma_result m4a_decoder_ds_seek(ma_data_source *p_data_source, ma_uint64 frame_index); ma_result m4a_decoder_ds_get_cursor(ma_data_source *p_data_source, ma_uint64 *p_cursor); ma_result m4a_decoder_ds_get_length(ma_data_source *p_data_source, ma_uint64 *p_length); #if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) #define MAX_CHANNELS 2 #define MAX_SAMPLES 4800 // Maximum expected frame size #define MAX_SAMPLE_SIZE 4 static uint8_t leftoverBuffer[MAX_SAMPLES * MAX_CHANNELS * MAX_SAMPLE_SIZE]; static ma_uint64 leftoverSampleCount = 0; ma_result m4a_decoder_ds_read(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { return m4a_decoder_read_pcm_frames((m4a_decoder *)p_data_source, p_frames_out, frame_count, p_frames_read); } ma_result m4a_decoder_ds_seek(ma_data_source *p_data_source, ma_uint64 frame_index) { return m4a_decoder_seek_to_pcm_frame((m4a_decoder *)p_data_source, frame_index); } ma_result m4a_decoder_ds_get_data_format(ma_data_source *p_data_source, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap) { return m4a_decoder_get_data_format((m4a_decoder *)p_data_source, p_format, p_channels, p_sample_rate, p_channel_map, channel_map_cap); } ma_result m4a_decoder_ds_get_cursor(ma_data_source *p_data_source, ma_uint64 *p_cursor) { return m4a_decoder_get_cursor_in_pcm_frames((m4a_decoder *)p_data_source, p_cursor); } ma_result m4a_decoder_ds_get_length(ma_data_source *p_data_source, ma_uint64 *p_length) { return m4a_decoder_get_length_in_pcm_frames((m4a_decoder *)p_data_source, p_length); } ma_data_source_vtable g_m4a_decoder_ds_vtable = { m4a_decoder_ds_read, m4a_decoder_ds_seek, m4a_decoder_ds_get_data_format, m4a_decoder_ds_get_cursor, m4a_decoder_ds_get_length, NULL, (ma_uint64)0}; static ma_result file_on_read(void *pUserData, void *pBufferOut, size_t bytesToRead, size_t *pBytesRead) { FILE *fp = (FILE *)pUserData; size_t bytes_read = fread(pBufferOut, 1, bytesToRead, fp); if (bytes_read < bytesToRead && ferror(fp)) { return MA_ERROR; } if (pBytesRead) { *pBytesRead = bytes_read; } return MA_SUCCESS; } static ma_result file_on_seek(void *pUserData, ma_int64 offset, ma_seek_origin origin) { FILE *fp = (FILE *)pUserData; int whence = (origin == ma_seek_origin_start) ? SEEK_SET : SEEK_CUR; if (fseeko(fp, offset, whence) != 0) { return MA_ERROR; } return MA_SUCCESS; } static int minimp4_read_callback(int64_t offset, void *buffer, size_t size, void *token) { m4a_decoder *pM4a = (m4a_decoder *)token; // Cast int64_t to ma_int64 for onSeek ma_int64 ma_offset = (ma_int64)offset; if (file_on_seek(pM4a->file, ma_offset, ma_seek_origin_start) != MA_SUCCESS) { return 1; // Error } size_t bytes_read = 0; if (file_on_read(pM4a->file, buffer, size, &bytes_read) != MA_SUCCESS || bytes_read != size) { return 1; // Error } return 0; // Success } int64_t minimp4_seek_callback(void *user_data, int64_t offset) { m4a_decoder *pM4a = (m4a_decoder *)user_data; ma_result result = file_on_seek(pM4a->file, offset, ma_seek_origin_start); if (result != MA_SUCCESS) { return -1; // Signal error } return offset; // Return the new position if possible } static ma_result m4a_decoder_init_internal(const ma_decoding_backend_config *p_config, m4a_decoder *pM4a) { if (pM4a == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pM4a); pM4a->format = ma_format_f32; if (p_config != NULL && (p_config->preferredFormat == ma_format_f32 || p_config->preferredFormat == ma_format_s16)) { pM4a->format = p_config->preferredFormat; } ma_data_source_config dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_m4a_decoder_ds_vtable; ma_result result = ma_data_source_init(&dataSourceConfig, &pM4a->ds); if (result != MA_SUCCESS) { return result; } return MA_SUCCESS; } // Note: This isn't used by kew and is untested MA_API ma_result m4a_decoder_init( ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, m4a_decoder *pM4a) { (void)p_allocation_callbacks; if (pM4a == NULL || onRead == NULL || onSeek == NULL || onTell == NULL) { return MA_INVALID_ARGS; } ma_result result = m4a_decoder_init_internal(p_config, pM4a); if (result != MA_SUCCESS) { return result; } // Store the custom read, seek, and tell functions pM4a->pReadSeekTellUserData = pReadSeekTellUserData; // Get the size of the data source ma_int64 currentPos = 0; if (pM4a->onTell(pM4a->pReadSeekTellUserData, ¤tPos) != MA_SUCCESS) { return MA_ERROR; } if (pM4a->onSeek(pM4a->pReadSeekTellUserData, 0, ma_seek_origin_end) != MA_SUCCESS) { return MA_ERROR; } ma_int64 file_size = 0; if (pM4a->onTell(pM4a->pReadSeekTellUserData, &file_size) != MA_SUCCESS) { return MA_ERROR; } // Seek back to original position if (pM4a->onSeek(pM4a->pReadSeekTellUserData, currentPos, ma_seek_origin_start) != MA_SUCCESS) { return MA_ERROR; } // Initialize minimp4 with custom read_callback if (MP4D_open(&pM4a->mp4, minimp4_read_callback, pM4a, file_size) != 0) { return MA_ERROR; } // Find the audio track pM4a->audio_track_index = -1; for (unsigned int i = 0; i < pM4a->mp4.track_count; i++) { MP4D_track_t *track = &pM4a->mp4.track[i]; if (track->handler_type == MP4D_HANDLER_TYPE_SOUN) { pM4a->audio_track_index = i; pM4a->track = track; break; } } if (pM4a->audio_track_index == -1) { // No audio track found MP4D_close(&pM4a->mp4); return MA_ERROR; } pM4a->current_sample = 0; pM4a->total_samples = pM4a->track->sample_count; // Initialize faad2 decoder pM4a->hDecoder = NeAACDecOpen(); // Extract the decoder configuration const uint8_t *decoder_config = pM4a->track->dsi; uint32_t decoder_config_len = pM4a->track->dsi_bytes; unsigned long sample_rate; unsigned char channels; if (NeAACDecInit2(pM4a->hDecoder, (unsigned char *)decoder_config, decoder_config_len, &sample_rate, &channels) < 0) { // Error initializing decoder NeAACDecClose(pM4a->hDecoder); MP4D_close(&pM4a->mp4); return MA_ERROR; } // Configure output format NeAACDecConfigurationPtr config = NeAACDecGetCurrentConfiguration(pM4a->hDecoder); if (pM4a->format == ma_format_s16) { config->outputFormat = FAAD_FMT_16BIT; pM4a->sampleSize = sizeof(int16_t); pM4a->bit_depth = 16; } else if (pM4a->format == ma_format_f32) { config->outputFormat = FAAD_FMT_FLOAT; pM4a->sampleSize = sizeof(float); pM4a->bit_depth = 32; } else { // Unsupported format NeAACDecClose(pM4a->hDecoder); MP4D_close(&pM4a->mp4); return MA_ERROR; } NeAACDecSetConfiguration(pM4a->hDecoder, config); // Initialize other fields leftoverSampleCount = 0; pM4a->cursor = 0; return MA_SUCCESS; } double calculate_aac_duration(FILE *fp, unsigned long sample_rate, unsigned long *totalFrames) { if (fp == NULL || sample_rate == 0 || totalFrames == NULL) { return -1.0; } unsigned char buffer[7]; unsigned long file_size = 0; *totalFrames = 0; // Get file size fseek(fp, 0, SEEK_END); file_size = ftell(fp); fseek(fp, 0, SEEK_SET); // Loop to count frames while (ftell(fp) < (long)(file_size - 7)) // Ensure at least an ADTS header remains { // Read header if (fread(buffer, 1, 7, fp) < 7) break; // Extract frame size unsigned int frameSize = ((buffer[3] & 0x03) << 11) | ((buffer[4] & 0xFF) << 3) | ((buffer[5] & 0xE0) >> 5); if (frameSize <= 7) break; // Skip to next frame fseek(fp, frameSize - 7, SEEK_CUR); (*totalFrames)++; } // Compute duration using: duration = (totalFrames * 1024) / sample_rate double duration = (double)(*totalFrames * 1024) / sample_rate; fseek(fp, 0, SEEK_SET); return duration; } uint32_t read_u32be(FILE *fp) { unsigned char b[4]; if (fread(b, 1, 4, fp) != 4) return 0; return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | ((uint32_t)b[3]); } int find_atom(FILE *fp, uint32_t atom_name, long max_search_length, uint32_t *atom_size_out) { long start_pos = ftell(fp); while ((ftell(fp) - start_pos) < max_search_length) { unsigned char header[8]; if (fread(header, 1, 8, fp) != 8) return 0; uint32_t atom_size = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]; uint32_t atom_type = (header[4] << 24) | (header[5] << 16) | (header[6] << 8) | header[7]; if (atom_size < 8) return 0; // Invalid atom size if (atom_type == atom_name) { if (atom_size_out) *atom_size_out = atom_size - 8; return 1; // Found } if (fseek(fp, atom_size - 8, SEEK_CUR) != 0) return 0; } return 0; // Not found } int is_alac(FILE *fp, uint8_t *dsi_out, size_t *dsi_size_out) { fseek(fp, 0, SEEK_SET); uint32_t atom_size; if (!find_atom(fp, FOUR_CHAR_INT('m', 'o', 'o', 'v'), 0x7FFFFFFF, &atom_size)) return 0; if (!find_atom(fp, FOUR_CHAR_INT('t', 'r', 'a', 'k'), atom_size, &atom_size)) return 0; if (!find_atom(fp, FOUR_CHAR_INT('m', 'd', 'i', 'a'), atom_size, &atom_size)) return 0; if (!find_atom(fp, FOUR_CHAR_INT('m', 'i', 'n', 'f'), atom_size, &atom_size)) return 0; if (!find_atom(fp, FOUR_CHAR_INT('s', 't', 'b', 'l'), atom_size, &atom_size)) return 0; if (!find_atom(fp, FOUR_CHAR_INT('s', 't', 's', 'd'), atom_size, &atom_size)) return 0; fseek(fp, 8, SEEK_CUR); // Skip stsd header (version+entry) read_u32be(fp); // uint32_t sample_entry_size uint32_t sample_entry_fourcc = read_u32be(fp); if (sample_entry_fourcc != FOUR_CHAR_INT('a', 'l', 'a', 'c')) return 0; fseek(fp, 28, SEEK_CUR); // Skip audio sample entry fields uint32_t config_atom_size = read_u32be(fp); uint32_t config_atom_fourcc = read_u32be(fp); if (config_atom_fourcc != FOUR_CHAR_INT('a', 'l', 'a', 'c')) return 0; fseek(fp, 4, SEEK_CUR); // Skip 1-byte version and 3-byte flags (4 bytes total)! uint32_t alac_dsi_size = config_atom_size - 12; // size(4)+fourcc(4)+version/flags(4) total=12 bytes overhead if (alac_dsi_size < 24 || alac_dsi_size > 64) return 0; // Sanity check if (fread(dsi_out, 1, alac_dsi_size, fp) != alac_dsi_size) return 0; *dsi_size_out = alac_dsi_size; return 1; } MA_API ma_result m4a_decoder_init_file( const char *pFilePath, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, m4a_decoder *pM4a) { (void)p_allocation_callbacks; if (pFilePath == NULL || pM4a == NULL) { return MA_INVALID_ARGS; } ma_result result = m4a_decoder_init_internal(p_config, pM4a); if (result != MA_SUCCESS) { return result; } FILE *fp = fopen(pFilePath, "rb"); if (fp == NULL) { return MA_INVALID_FILE; } // Get the file size if (fseeko(fp, 0, SEEK_END) != 0) { fclose(fp); return MA_ERROR; } ma_int64 file_size = ftello(fp); if (file_size < 0) { fclose(fp); return MA_ERROR; } if (fseeko(fp, 0, SEEK_SET) != 0) { fclose(fp); return MA_ERROR; } // Store the FILE pointer in the decoder struct pM4a->file = fp; // Try to detect the file format (ADTS, MP4, LATM, etc.) unsigned char buffer[7]; size_t bytes_read = fread(buffer, 1, sizeof(buffer), fp); // Check for ADTS header if (bytes_read >= 7 && buffer[0] == 0xFF && (buffer[1] & 0xF0) == 0xF0) { pM4a->file_type = k_rawAAC; } else { // Check if it's an MP4 file (using MP4D_open or similar) if (MP4D_open(&pM4a->mp4, minimp4_read_callback, pM4a, file_size) == 1) { pM4a->file_type = k_unknown; // It's an MP4 container } else { fclose(fp); return MA_ERROR; // Unknown format } } if (pM4a->file_type == k_rawAAC) { // Raw AAC handling // Extract the frame size from the ADTS header unsigned int frameSize = ((buffer[3] & 0x03) << 11) | ((buffer[4] & 0xFF) << 3) | ((buffer[5] & 0xE0) >> 5); if (frameSize <= 7) { fclose(fp); return MA_ERROR; // Invalid frame size } unsigned char *frameData = malloc(frameSize); if (frameData == NULL) { fclose(fp); return MA_ERROR; // Memory allocation failed } // The first 7 bytes are already in the buffer, so copy them to frameData memcpy(frameData, buffer, 7); // Read the rest of the frame (audio data) size_t remainingBytes = frameSize - 7; size_t additionalBytesRead = fread(frameData + 7, 1, remainingBytes, fp); if (additionalBytesRead < remainingBytes) { free(frameData); fclose(fp); return MA_ERROR; // Failed to read the full frame } // Allocate decoder config unsigned char *decoder_config = malloc(2); if (decoder_config == NULL) { return MA_OUT_OF_MEMORY; } unsigned long decoder_config_size = 2; decoder_config[0] = ((buffer[2] & 0xC0) >> 6) + 1; // Object type decoder_config[0] |= ((buffer[2] & 0x3C) >> 2) << 2; decoder_config[1] = ((buffer[2] & 0x07) << 1) | ((buffer[3] & 0x80) >> 7); // Channels and sample_rate decoder_config[1] <<= 4; // Shift to upper 4 bits unsigned char objectType = decoder_config[0]; if (objectType == 5 || objectType >= 29) { set_error_message("File is encoded with HE-AAC which is not supported"); free(frameData); free(decoder_config); fclose(fp); return MA_ERROR; } unsigned long sample_rate = 0; unsigned char channels = 0; pM4a->hDecoder = NeAACDecOpen(); int initResult = NeAACDecInit2(pM4a->hDecoder, (unsigned char *)decoder_config, decoder_config_size, &sample_rate, &channels); if (initResult < 0) { printf("Error initializing decoder. Code: %d\n", initResult); free(frameData); free(decoder_config); NeAACDecClose(pM4a->hDecoder); fclose(fp); return MA_ERROR; } free(decoder_config); // Check if the sample_rate and channels are correctly initialized if (sample_rate == 0 || channels == 0) { printf("Error: Invalid sample rate or channel count.\n"); free(frameData); NeAACDecClose(pM4a->hDecoder); fclose(fp); return MA_ERROR; } pM4a->sample_rate = (ma_uint32)sample_rate; pM4a->channels = (ma_uint32)channels; pM4a->duration = calculate_aac_duration(fp, pM4a->sample_rate, &pM4a->totalFrames); // Clean up the frame data after processing free(frameData); // Configure output format NeAACDecConfigurationPtr config_ptr = NeAACDecGetCurrentConfiguration(pM4a->hDecoder); if (pM4a->format == ma_format_s16) { config_ptr->outputFormat = FAAD_FMT_16BIT; pM4a->sampleSize = sizeof(int16_t); pM4a->bit_depth = 16; } else if (pM4a->format == ma_format_f32) { config_ptr->outputFormat = FAAD_FMT_FLOAT; pM4a->sampleSize = sizeof(float); pM4a->bit_depth = 32; } else { // Unsupported format NeAACDecClose(pM4a->hDecoder); fclose(fp); return MA_ERROR; } NeAACDecSetConfiguration(pM4a->hDecoder, config_ptr); // Initialize other fields leftoverSampleCount = 0; pM4a->cursor = 0; fseek(pM4a->file, 0, SEEK_SET); return MA_SUCCESS; } else { // Find the audio track pM4a->audio_track_index = -1; for (unsigned int i = 0; i < pM4a->mp4.track_count; i++) { MP4D_track_t *track = &pM4a->mp4.track[i]; if (track->handler_type == MP4D_HANDLER_TYPE_SOUN) { pM4a->audio_track_index = i; pM4a->track = track; break; } } // M4A (MP4-wrapped AAC) handling if (pM4a->audio_track_index == -1) { // No audio track found MP4D_close(&pM4a->mp4); fclose(fp); return MA_ERROR; } pM4a->current_sample = 0; pM4a->total_samples = pM4a->track->sample_count; pM4a->avg_bit_rate = pM4a->track->avg_bitrate_bps; uint8_t alac_dsi[32]; size_t alac_dsi_size; long original_position = ftell(pM4a->file); if (is_alac(fp, alac_dsi, &alac_dsi_size)) { // This is an alac file and is currently unsupported. set_error_message("M4a files that use the ALAC encoder are not supported."); return MA_ERROR; } else // AAC { pM4a->file_type = k_aac; fseek(pM4a->file, original_position, SEEK_SET); // Initialize faad2 decoder pM4a->hDecoder = NeAACDecOpen(); // Extract the decoder configuration const uint8_t *decoder_config = pM4a->track->dsi; uint32_t decoder_config_len = pM4a->track->dsi_bytes; unsigned long sample_rate; unsigned char channels; if (decoder_config_len >= 2) { uint8_t object_type = (decoder_config[0] >> 3) & 0x1F; if (object_type == 5 || object_type == 29) { set_error_message("Unsupported AAC object type: (HE-AAC or PS)"); return MA_ERROR; } } if (NeAACDecInit2(pM4a->hDecoder, (unsigned char *)decoder_config, decoder_config_len, &sample_rate, &channels) < 0) { // Error initializing decoder NeAACDecClose(pM4a->hDecoder); MP4D_close(&pM4a->mp4); fclose(fp); return MA_ERROR; } pM4a->sample_rate = (ma_uint32)sample_rate; pM4a->channels = (ma_uint32)channels; // Configure output format NeAACDecConfigurationPtr config_ptr = NeAACDecGetCurrentConfiguration(pM4a->hDecoder); if (pM4a->format == ma_format_s16) { config_ptr->outputFormat = FAAD_FMT_16BIT; pM4a->sampleSize = sizeof(int16_t); pM4a->bit_depth = 16; } else if (pM4a->format == ma_format_f32) { config_ptr->outputFormat = FAAD_FMT_FLOAT; pM4a->sampleSize = sizeof(float); pM4a->bit_depth = 32; } else { // Unsupported format NeAACDecClose(pM4a->hDecoder); MP4D_close(&pM4a->mp4); fclose(fp); return MA_ERROR; } NeAACDecSetConfiguration(pM4a->hDecoder, config_ptr); // Initialize other fields leftoverSampleCount = 0; pM4a->cursor = 0; return MA_SUCCESS; } } } MA_API void m4a_decoder_uninit(m4a_decoder *pM4a, const ma_allocation_callbacks *p_allocation_callbacks) { (void)p_allocation_callbacks; if (pM4a == NULL) { return; } if (pM4a->hDecoder) { NeAACDecClose(pM4a->hDecoder); pM4a->hDecoder = NULL; } if (pM4a->file_type != k_rawAAC) { MP4D_close(&pM4a->mp4); } if (pM4a->file) { fclose(pM4a->file); pM4a->file = NULL; } } MA_API ma_result m4a_decoder_read_pcm_frames( m4a_decoder *pM4a, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { if (pM4a == NULL || p_frames_out == NULL || frame_count == 0) { return MA_INVALID_ARGS; } ma_result result = MA_SUCCESS; ma_uint32 channels = pM4a->channels; ma_uint32 sampleSize = pM4a->sampleSize; ma_uint64 totalFramesProcessed = 0; // Handle any leftover samples from previous call using the global/static leftover buffer if (leftoverSampleCount > 0) { ma_uint64 leftoverToProcess = (leftoverSampleCount < frame_count) ? leftoverSampleCount : frame_count; ma_uint64 leftoverBytes = leftoverToProcess * channels * sampleSize; memcpy(p_frames_out, leftoverBuffer, leftoverBytes); totalFramesProcessed += leftoverToProcess; // Shift the leftover buffer ma_uint64 samplesLeft = leftoverSampleCount - leftoverToProcess; if (samplesLeft > 0) { memmove(leftoverBuffer, leftoverBuffer + leftoverBytes, samplesLeft * channels * sampleSize); } leftoverSampleCount = samplesLeft; } while (totalFramesProcessed < frame_count) { if (pM4a->file_type == k_rawAAC) { unsigned int headerSize = 7; uint8_t buffer[headerSize]; if (fread(buffer, 1, headerSize, pM4a->file) != headerSize) { result = MA_ERROR; break; } unsigned int frame_bytes = ((buffer[3] & 0x03) << 11) | ((buffer[4] & 0xFF) << 3) | ((buffer[5] & 0xE0) >> 5); if (frame_bytes < headerSize || frame_bytes > 8192) { result = MA_ERROR; break; } // Allocate memory for the frame unsigned char *sample_data = (unsigned char *)malloc(frame_bytes); if (!sample_data) { result = MA_OUT_OF_MEMORY; break; } // Copy the header to the sample_data buffer memcpy(sample_data, buffer, headerSize); // Read the rest of the frame (audio data) size_t remaining_bytes = frame_bytes - headerSize; size_t additionalBytesRead = fread(sample_data + headerSize, 1, remaining_bytes, pM4a->file); if (additionalBytesRead < remaining_bytes) { free(sample_data); result = MA_ERROR; break; // Failed to read full frame } pM4a->current_sample++; // Decode the AAC frame using faad2 void *decodedData = NeAACDecDecode(pM4a->hDecoder, &(pM4a->frameInfo), sample_data + 7, frame_bytes - 7); free(sample_data); if (pM4a->frameInfo.error > 0) { // Error in decoding, skip to the next frame. continue; } // Remove support for HE-AAC components (SBR or PS) if (pM4a->frameInfo.sbr || pM4a->frameInfo.ps) { // File is encoded with HE-AAC which is not supported return MA_ERROR; } unsigned long samplesDecoded = pM4a->frameInfo.samples; // Total samples decoded (channels * frames) ma_uint64 framesDecoded = samplesDecoded / channels; // Calculate how many frames we can process in this call ma_uint64 framesNeeded = frame_count - totalFramesProcessed; ma_uint64 frames_to_copy = (framesDecoded < framesNeeded) ? framesDecoded : framesNeeded; ma_uint64 bytesToCopy = frames_to_copy * channels * sampleSize; memcpy((uint8_t *)p_frames_out + totalFramesProcessed * channels * sampleSize, decodedData, bytesToCopy); totalFramesProcessed += frames_to_copy; // Handle leftover frames using the global/static leftover buffer if (frames_to_copy < framesDecoded) { // There are leftover frames leftoverSampleCount = framesDecoded - frames_to_copy; ma_uint64 leftoverBytes = leftoverSampleCount * channels * sampleSize; if (leftoverBytes > sizeof(leftoverBuffer)) { // Safety check to avoid overflow in the buffer. leftoverSampleCount = sizeof(leftoverBuffer) / (channels * sampleSize); leftoverBytes = leftoverSampleCount * channels * sampleSize; } memcpy(leftoverBuffer, (uint8_t *)decodedData + bytesToCopy, leftoverBytes); } else { leftoverSampleCount = 0; } } else { if (pM4a->current_sample >= pM4a->total_samples) { result = MA_AT_END; break; // No more samples } unsigned int frame_bytes = 0; unsigned int timestamp = 0; unsigned int duration = 0; // Get the sample offset and size using minimp4 ma_int64 sample_offset = MP4D_frame_offset( &pM4a->mp4, pM4a->audio_track_index, pM4a->current_sample, &frame_bytes, ×tamp, &duration); if (sample_offset == (ma_int64)(MP4D_file_offset_t)-1 || frame_bytes == 0) { // Error getting sample info result = MA_ERROR; break; } // Allocate buffer for the sample data uint8_t *sample_data = (uint8_t *)malloc(frame_bytes); if (sample_data == NULL) { result = MA_OUT_OF_MEMORY; break; } // Read the sample data directly from the file size_t bytes_read = 0; if (file_on_read(pM4a->file, sample_data, frame_bytes, &bytes_read) != MA_SUCCESS || bytes_read != frame_bytes) { free(sample_data); result = MA_ERROR; break; } pM4a->current_sample++; // Decode the AAC frame using faad2 void *decodedData = NeAACDecDecode(pM4a->hDecoder, &(pM4a->frameInfo), sample_data, frame_bytes); free(sample_data); // Free the sample data buffer if (pM4a->frameInfo.error > 0) { set_error_message("Decoding Error: could be mislabeled and unsupported HE-AAC or PS file"); // Error in decoding, skip to the next frame. continue; } // Remove support for HE-AAC components (SBR or PS) if (pM4a->frameInfo.sbr || pM4a->frameInfo.ps) { // HE-AAC detected (either SBR or PS is present), skip processing continue; } unsigned long samplesDecoded = pM4a->frameInfo.samples; // Total samples decoded (channels * frames) ma_uint64 framesDecoded = samplesDecoded / channels; // Calculate how many frames we can process in this call ma_uint64 framesNeeded = frame_count - totalFramesProcessed; ma_uint64 frames_to_copy = (framesDecoded < framesNeeded) ? framesDecoded : framesNeeded; ma_uint64 bytesToCopy = frames_to_copy * channels * sampleSize; memcpy((uint8_t *)p_frames_out + totalFramesProcessed * channels * sampleSize, decodedData, bytesToCopy); totalFramesProcessed += frames_to_copy; // Handle leftover frames using the global/static leftover buffer if (frames_to_copy < framesDecoded) { // There are leftover frames leftoverSampleCount = framesDecoded - frames_to_copy; ma_uint64 leftoverBytes = leftoverSampleCount * channels * sampleSize; if (leftoverBytes > sizeof(leftoverBuffer)) { // Safety check to avoid overflow in the buffer. leftoverSampleCount = sizeof(leftoverBuffer) / (channels * sampleSize); leftoverBytes = leftoverSampleCount * channels * sampleSize; } memcpy(leftoverBuffer, (uint8_t *)decodedData + bytesToCopy, leftoverBytes); } else { leftoverSampleCount = 0; } } } pM4a->cursor += totalFramesProcessed; if (p_frames_read != NULL) { *p_frames_read = totalFramesProcessed; } return (totalFramesProcessed > 0) ? MA_SUCCESS : result; } MA_API ma_result m4a_decoder_seek_to_pcm_frame(m4a_decoder *pM4a, ma_uint64 frame_index) { if (pM4a == NULL) return MA_INVALID_ARGS; if (frame_index >= pM4a->total_samples) return MA_INVALID_ARGS; pM4a->current_sample = (uint32_t)frame_index; if (pM4a->file_type == k_rawAAC) { return MA_ERROR; } else if (pM4a->file_type == k_ALAC) { unsigned int frame_bytes = 0; unsigned int timestamp = 0; unsigned int duration = 0; ma_int64 sample_offset = MP4D_frame_offset( &pM4a->mp4, pM4a->audio_track_index, pM4a->current_sample, &frame_bytes, ×tamp, &duration); if (sample_offset == (ma_int64)(MP4D_file_offset_t)-1 || frame_bytes == 0) { return MA_ERROR; } if (file_on_seek(pM4a->file, sample_offset, ma_seek_origin_start) != MA_SUCCESS) { return MA_ERROR; } leftoverSampleCount = 0; pM4a->cursor = frame_index; return MA_SUCCESS; } else { unsigned int frame_bytes = 0; unsigned int timestamp = 0; unsigned int duration = 0; ma_int64 sample_offset = MP4D_frame_offset( &pM4a->mp4, pM4a->audio_track_index, pM4a->current_sample, &frame_bytes, ×tamp, &duration); if (sample_offset == (ma_int64)(MP4D_file_offset_t)-1 || frame_bytes == 0) { return MA_ERROR; } if (file_on_seek(pM4a->file, sample_offset, ma_seek_origin_start) != MA_SUCCESS) { return MA_ERROR; } NeAACDecPostSeekReset(pM4a->hDecoder, (long)pM4a->current_sample); leftoverSampleCount = 0; pM4a->cursor = frame_index; return MA_SUCCESS; } } MA_API ma_result m4a_decoder_get_data_format( m4a_decoder *pM4a, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap) { // Initialize output variables if (p_format != NULL) { *p_format = ma_format_unknown; } if (p_channels != NULL) { *p_channels = 0; } if (p_sample_rate != NULL) { *p_sample_rate = 0; } if (p_channel_map != NULL) { MA_ZERO_MEMORY(p_channel_map, sizeof(*p_channel_map) * channel_map_cap); } if (pM4a == NULL) { return MA_INVALID_OPERATION; } if (pM4a->file_type != k_rawAAC) { if (pM4a->track == NULL) { return MA_INVALID_OPERATION; } } if (p_format != NULL) { *p_format = pM4a->format; } if (p_channels != NULL) { *p_channels = pM4a->channels; } if (p_sample_rate != NULL) { *p_sample_rate = pM4a->sample_rate; } // Set a standard channel map if requested if (p_channel_map != NULL) { ma_channel_map_init_standard(ma_standard_channel_map_microsoft, p_channel_map, channel_map_cap, *p_channels); } return MA_SUCCESS; } MA_API ma_result m4a_decoder_get_cursor_in_pcm_frames(m4a_decoder *pM4a, ma_uint64 *p_cursor) { if (p_cursor == NULL) { return MA_INVALID_ARGS; } *p_cursor = 0; /* Safety. */ if (pM4a == NULL) { return MA_INVALID_ARGS; } *p_cursor = pM4a->cursor; return MA_SUCCESS; } MA_API ma_result m4a_decoder_get_length_in_pcm_frames(m4a_decoder *pM4a, ma_uint64 *p_length) { if (p_length == NULL) { return MA_INVALID_ARGS; } *p_length = 0; // Safety. if (pM4a == NULL || pM4a->track == NULL) { return MA_INVALID_ARGS; } // Calculate the length in PCM frames using the total number of samples and the sample rate. if (pM4a->total_samples > 0 && pM4a->sample_rate > 0) { *p_length = (ma_uint64)pM4a->total_samples; return MA_SUCCESS; } return MA_ERROR; } #endif #endif #ifdef __cplusplus } #endif #endif kew/src/sound/playback.c000066400000000000000000000674701512074754200155420ustar00rootroot00000000000000 #include "common/common.h" #include "audiobuffer.h" #include "decoders.h" #include "sound.h" #ifdef USE_FAAD #include "m4a.h" #endif #include static ma_device device = {0}; static bool device_initialized = false; static bool paused = false; static bool stopped = true; static bool repeat_enabled = false; static bool seek_requested = false; static float seek_percent = 0.0; static double seek_elapsed; static bool skip_to_next = false; static _Atomic bool EOF_reached = false; static _Atomic bool switch_reached = false; static pthread_mutex_t data_source_mutex = PTHREAD_MUTEX_INITIALIZER; static enum AudioImplementation current_implementation = NONE; static double seek_elapsed; ma_uint64 last_cursor = 0; static pthread_mutex_t switch_mutex = PTHREAD_MUTEX_INITIALIZER; double get_seek_elapsed(void) { return seek_elapsed; } void set_seek_elapsed(double value) { seek_elapsed = value; } float get_seek_percentage(void) { return seek_percent; } bool is_seek_requested(void) { return seek_requested; } void set_seek_requested(bool value) { seek_requested = value; } void seek_percentage(float percent) { seek_percent = percent; seek_requested = true; } bool pb_is_EOF_reached(void) { return atomic_load(&EOF_reached); } void set_EOF_reached(void) { atomic_store(&EOF_reached, true); } void pb_set_EOF_handled(void) { atomic_store(&EOF_reached, false); } bool pb_is_paused(void) { return paused; } void set_paused(bool val) { paused = val; } bool pb_is_stopped(void) { return stopped; } void set_stopped(bool val) { stopped = val; } bool pb_is_repeat_enabled(void) { return repeat_enabled; } void set_repeat_enabled(bool value) { repeat_enabled = value; } bool is_playing(void) { return ma_device_is_started(&device); } void stop_playback(void) { AppState *state = get_app_state(); if (ma_device_is_started(&device)) { ma_device_stop(&device); } set_paused(false); set_stopped(true); if (state->currentView != TRACK_VIEW) { trigger_refresh(); } } void sound_resume_playback(void) { // If this was unpaused with no song loaded AppState *state = get_app_state(); if (audio_data.restart) { audio_data.end_of_list_reached = false; } if (!ma_device_is_started(&device)) { if (ma_device_start(&device) != MA_SUCCESS) { pb_create_audio_device(); ma_device_start(&device); } } set_paused(false); set_stopped(false); if (state->currentView != TRACK_VIEW) { trigger_refresh(); } } void pause_playback(void) { AppState *state = get_app_state(); if (ma_device_is_started(&device)) { ma_device_stop(&device); } set_paused(true); if (state->currentView != TRACK_VIEW) { trigger_refresh(); } } void toggle_pause_playback(void) { if (ma_device_is_started(&device)) { pause_playback(); } else if (pb_is_paused() || pb_is_stopped()) { sound_resume_playback(); } } int init_playback_device(ma_context *context, ma_format format, ma_uint32 channels, ma_uint32 sample_rate, ma_device *device, ma_device_data_proc data_callback, void *pUserData) { ma_result result; ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.format = format; deviceConfig.playback.channels = channels; deviceConfig.sampleRate = sample_rate; deviceConfig.dataCallback = data_callback; deviceConfig.pUserData = pUserData; result = ma_device_init(context, &deviceConfig, device); if (result != MA_SUCCESS) { set_error_message("Failed to initialize miniaudio device."); return -1; } else { device_initialized = true; } result = ma_device_start(device); if (result != MA_SUCCESS) { set_error_message("Failed to start miniaudio device."); return -1; } set_paused(false); set_stopped(false); return 0; } void pb_cleanup_playback_device(void) { if (!device_initialized) return; // Stop device safely before uninitializing. ma_result result = ma_device_stop(&device); if (result != MA_SUCCESS) { fprintf(stderr, "Warning: ma_device_stop() failed: %d\n", result); } // Uninit the device. This will block until the audio thread stops. ma_device_uninit(&device); // Clear memory so we don’t accidentally reuse it. memset(&device, 0, sizeof(device)); device_initialized = false; set_stopped(true); } void shutdown_android(void) { // Avoid race condition when shutting down memset(&device, 0, sizeof(device)); } void pb_sound_shutdown() { if (is_context_initialized()) { #ifdef __ANDROID__ shutdown_android(); #else ma_device_uninit(&device); memset(&device, 0, sizeof(device)); cleanup_audio_context(); #endif } } ma_device *get_device(void) { return &device; } enum AudioImplementation get_current_implementation_type(void) { return current_implementation; } void get_current_format_and_sample_rate(ma_format *format, ma_uint32 *sample_rate) { *format = ma_format_unknown; if (get_current_implementation_type() == BUILTIN) { ma_decoder *decoder = get_current_builtin_decoder(); if (decoder != NULL) *format = decoder->outputFormat; } else if (get_current_implementation_type() == OPUS) { ma_libopus *decoder = get_current_opus_decoder(); if (decoder != NULL) *format = decoder->format; } else if (get_current_implementation_type() == VORBIS) { ma_libvorbis *decoder = get_current_vorbis_decoder(); if (decoder != NULL) *format = decoder->format; } else if (get_current_implementation_type() == WEBM) { ma_webm *decoder = get_current_webm_decoder(); if (decoder != NULL) *format = decoder->format; } else if (get_current_implementation_type() == M4A) { #ifdef USE_FAAD m4a_decoder *decoder = get_current_m4a_decoder(); if (decoder != NULL) *format = decoder->format; #endif } *sample_rate = get_audio_data()->sample_rate; } void execute_switch(AudioData *p_audio_data) { p_audio_data->switchFiles = false; switch_decoder(); if (p_audio_data == NULL) return; p_audio_data->pUserData->current_song_data = (p_audio_data->currentFileIndex == 0) ? p_audio_data->pUserData->songdataA : p_audio_data->pUserData->songdataB; p_audio_data->totalFrames = 0; p_audio_data->currentPCMFrame = 0; set_seek_elapsed(0.0); set_EOF_reached(); } bool pb_is_impl_switch_reached(void) { return atomic_load(&switch_reached) ? true : false; } void set_impl_switch_reached(void) { atomic_store(&switch_reached, true); } void set_impl_switch_not_reached(void) { atomic_store(&switch_reached, false); } void set_current_implementation_type(enum AudioImplementation value) { current_implementation = value; } bool is_skip_to_next(void) { return skip_to_next; } void set_skip_to_next(bool value) { skip_to_next = value; } void activate_switch(AudioData *p_audio_data) { set_skip_to_next(false); if (!pb_is_repeat_enabled()) { pthread_mutex_lock(&switch_mutex); p_audio_data->currentFileIndex = 1 - p_audio_data->currentFileIndex; // Toggle between 0 and 1 pthread_mutex_unlock(&switch_mutex); } p_audio_data->switchFiles = true; } void clear_current_track(void) { if (ma_device_is_started(&device)) { // Stop the device (which stops playback) ma_device_stop(&device); } clear_decoder_chain(); reset_all_decoders(); } #ifdef USE_FAAD void m4a_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { m4a_decoder *m4a = (m4a_decoder *)p_data_source; AudioData *p_audio_data = (AudioData *)m4a->pReadSeekTellUserData; ma_uint64 frames_read = 0; while (frames_read < frame_count) { if (pb_is_impl_switch_reached()) return; if (pthread_mutex_trylock(&data_source_mutex) != 0) { return; } // Check if a file switch is required if (p_audio_data->switchFiles) { execute_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); break; // Exit the loop after the file switch } if (get_current_implementation_type() != M4A && !is_skip_to_next()) { pthread_mutex_unlock(&data_source_mutex); return; } m4a_decoder *decoder = get_current_m4a_decoder(); if (p_audio_data->totalFrames == 0) ma_data_source_get_length_in_pcm_frames( decoder, &(p_audio_data->totalFrames)); // Check if seeking is requested if (is_seek_requested()) { if (decoder && decoder->file_type != k_rawAAC) { ma_uint64 totalFrames = p_audio_data->totalFrames; ma_uint64 seek_percent = get_seek_percentage(); if (seek_percent >= 100.0) seek_percent = 100.0; ma_uint64 targetFrame = (ma_uint64)((totalFrames - 1) * seek_percent / 100.0); if (targetFrame >= totalFrames) targetFrame = totalFrames - 1; // Set the read pointer for the decoder ma_result seekResult = m4a_decoder_seek_to_pcm_frame(decoder, targetFrame); if (seekResult != MA_SUCCESS) { // Handle seek error set_seek_requested(false); pthread_mutex_unlock(&data_source_mutex); return; } } set_seek_requested(false); // Reset seek flag } // Read from the current decoder ma_uint64 frames_to_read = 0; ma_result result; ma_uint64 remaining_frames = frame_count - frames_read; m4a_decoder *first_decoder = get_first_m4a_decoder(); ma_uint64 cursor = 0; if (first_decoder == NULL) { pthread_mutex_unlock(&data_source_mutex); return; } if (pb_is_EOF_reached()) { pthread_mutex_unlock(&data_source_mutex); return; } result = call_read_PCM_frames( first_decoder, m4a->format, p_frames_out, frames_read, p_audio_data->channels, remaining_frames, &frames_to_read); ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); if (((cursor != 0 && cursor == last_cursor) || frames_to_read == 0 || is_skip_to_next() || result != MA_SUCCESS) && !pb_is_EOF_reached()) { activate_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); continue; } last_cursor = cursor; frames_read += frames_to_read; set_buffer_size(frames_to_read); pthread_mutex_unlock(&data_source_mutex); } set_audio_buffer(p_frames_out, frames_read, p_audio_data->sample_rate, p_audio_data->channels, p_audio_data->format); if (p_frames_read != NULL) { *p_frames_read = frames_read; } } void m4a_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count) { AudioData *p_data_source = (AudioData *)p_device->pUserData; ma_uint64 frames_read = 0; m4a_read_pcm_frames(&(p_data_source->base), p_frames_out, frame_count, &frames_read); (void)p_frames_in; } #endif void opus_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { ma_libopus *opus = (ma_libopus *)p_data_source; AudioData *p_audio_data = (AudioData *)opus->pReadSeekTellUserData; ma_uint64 frames_read = 0; while (frames_read < frame_count) { if (pb_is_impl_switch_reached()) return; if (pthread_mutex_trylock(&data_source_mutex) != 0) { return; } // Check if a file switch is required if (p_audio_data->switchFiles) { execute_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); break; // Exit the loop after the file switch } if (get_current_implementation_type() != OPUS && !is_skip_to_next()) { pthread_mutex_unlock(&data_source_mutex); return; } ma_libopus *decoder = get_current_opus_decoder(); if (p_audio_data->totalFrames == 0) ma_data_source_get_length_in_pcm_frames( decoder, &(p_audio_data->totalFrames)); // Check if seeking is requested if (is_seek_requested()) { ma_uint64 totalFrames = 0; ma_libopus_get_length_in_pcm_frames(decoder, &totalFrames); ma_uint64 seek_percent = get_seek_percentage(); if (seek_percent >= 100.0) seek_percent = 100.0; ma_uint64 targetFrame = (ma_uint64)((totalFrames - 1) * seek_percent / 100.0); if (targetFrame >= totalFrames) targetFrame = totalFrames - 1; // Set the read pointer for the decoder ma_result seekResult = ma_libopus_seek_to_pcm_frame(decoder, targetFrame); if (seekResult != MA_SUCCESS) { // Handle seek error set_seek_requested(false); pthread_mutex_unlock(&data_source_mutex); return; } set_seek_requested(false); // Reset seek flag } // Read from the current decoder ma_uint64 frames_to_read = 0; ma_result result; ma_uint64 remaining_frames = frame_count - frames_read; ma_libopus *first_decoder = get_first_opus_decoder(); ma_uint64 cursor = 0; if (first_decoder == NULL) { pthread_mutex_unlock(&data_source_mutex); return; } if (pb_is_EOF_reached()) { pthread_mutex_unlock(&data_source_mutex); return; } result = call_read_PCM_frames( first_decoder, opus->format, p_frames_out, frames_read, p_audio_data->channels, remaining_frames, &frames_to_read); ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); if (((cursor != 0 && cursor >= p_audio_data->totalFrames) || frames_to_read == 0 || is_skip_to_next() || result != MA_SUCCESS) && !pb_is_EOF_reached()) { activate_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); continue; } frames_read += frames_to_read; set_buffer_size(frames_to_read); pthread_mutex_unlock(&data_source_mutex); } set_audio_buffer(p_frames_out, frames_read, p_audio_data->sample_rate, p_audio_data->channels, p_audio_data->format); if (p_frames_read != NULL) { *p_frames_read = frames_read; } } void opus_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count) { AudioData *p_data_source = (AudioData *)p_device->pUserData; ma_uint64 frames_read = 0; opus_read_pcm_frames(&(p_data_source->base), p_frames_out, frame_count, &frames_read); (void)p_frames_in; } void vorbis_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { ma_libvorbis *vorbis = (ma_libvorbis *)p_data_source; AudioData *p_audio_data = (AudioData *)vorbis->pReadSeekTellUserData; ma_uint64 frames_read = 0; while (frames_read < frame_count) { if (pb_is_impl_switch_reached()) return; if (pthread_mutex_trylock(&data_source_mutex) != 0) { return; } // Check if a file switch is required if (p_audio_data->switchFiles) { execute_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); break; } ma_libvorbis *decoder = get_current_vorbis_decoder(); if (p_audio_data->totalFrames == 0) ma_data_source_get_length_in_pcm_frames( decoder, &(p_audio_data->totalFrames)); if ((get_current_implementation_type() != VORBIS && !is_skip_to_next()) || (decoder == NULL)) { pthread_mutex_unlock(&data_source_mutex); return; } // Check if seeking is requested if (is_seek_requested()) { ma_uint64 totalFrames = 0; ma_libvorbis_get_length_in_pcm_frames(decoder, &totalFrames); ma_uint64 seek_percent = get_seek_percentage(); if (seek_percent >= 100.0) seek_percent = 100.0; ma_uint64 targetFrame = (ma_uint64)((totalFrames - 1) * seek_percent / 100.0); if (targetFrame >= totalFrames) targetFrame = totalFrames - 1; // Set the read pointer for the decoder ma_result seekResult = ma_libvorbis_seek_to_pcm_frame( decoder, targetFrame); if (seekResult != MA_SUCCESS) { // Handle seek error set_seek_requested(false); pthread_mutex_unlock(&data_source_mutex); return; } set_seek_requested(false); // Reset seek flag } // Read from the current decoder ma_uint64 frames_to_read = 0; ma_result result; ma_uint64 framesRequested = frame_count - frames_read; ma_libvorbis *first_decoder = get_first_vorbis_decoder(); ma_uint64 cursor = 0; if (first_decoder == NULL) { pthread_mutex_unlock(&data_source_mutex); return; } if (pb_is_EOF_reached()) { pthread_mutex_unlock(&data_source_mutex); return; } result = call_read_PCM_frames( first_decoder, vorbis->format, p_frames_out, frames_read, p_audio_data->channels, framesRequested, &frames_to_read); ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); if (((cursor != 0 && cursor >= p_audio_data->totalFrames) || is_skip_to_next() || result != MA_SUCCESS) && !pb_is_EOF_reached()) { activate_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); continue; } frames_read += frames_to_read; set_buffer_size(frames_to_read); pthread_mutex_unlock(&data_source_mutex); } set_audio_buffer(p_frames_out, frames_read, p_audio_data->sample_rate, p_audio_data->channels, p_audio_data->format); if (p_frames_read != NULL) { *p_frames_read = frames_read; } } void vorbis_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count) { AudioData *p_data_source = (AudioData *)p_device->pUserData; ma_uint64 frames_read = 0; vorbis_read_pcm_frames(&(p_data_source->base), p_frames_out, frame_count, &frames_read); (void)p_frames_in; } void webm_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { ma_webm *webm = (ma_webm *)p_data_source; AudioData *p_audio_data = (AudioData *)webm->pReadSeekTellUserData; ma_uint64 frames_read = 0; while (frames_read < frame_count) { if (pb_is_impl_switch_reached()) return; if (pthread_mutex_trylock(&data_source_mutex) != 0) { return; } // Check if a file switch is required if (p_audio_data->switchFiles) { execute_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); break; } ma_webm *decoder = get_current_webm_decoder(); if (p_audio_data->totalFrames == 0) ma_data_source_get_length_in_pcm_frames( decoder, &(p_audio_data->totalFrames)); if ((get_current_implementation_type() != WEBM && !is_skip_to_next()) || (decoder == NULL)) { pthread_mutex_unlock(&data_source_mutex); return; } // Check if seeking is requested if (is_seek_requested()) { ma_uint64 totalFrames = 0; ma_webm_get_length_in_pcm_frames(decoder, &totalFrames); ma_uint64 seek_percent = get_seek_percentage(); if (seek_percent >= 100.0) seek_percent = 100.0; ma_uint64 targetFrame = (ma_uint64)((totalFrames - 1) * seek_percent / 100.0); if (targetFrame >= totalFrames) targetFrame = totalFrames - 1; // Set the read pointer for the decoder ma_result seekResult = ma_webm_seek_to_pcm_frame(decoder, targetFrame); if (seekResult != MA_SUCCESS) { // Handle seek error set_seek_requested(false); pthread_mutex_unlock(&data_source_mutex); return; } set_seek_requested(false); // Reset seek flag } // Read from the current decoder ma_uint64 frames_to_read = 0; ma_result result; ma_uint64 framesRequested = frame_count - frames_read; ma_webm *first_decoder = get_first_webm_decoder(); ma_uint64 cursor = 0; if (first_decoder == NULL) { pthread_mutex_unlock(&data_source_mutex); return; } if (pb_is_EOF_reached()) { pthread_mutex_unlock(&data_source_mutex); return; } result = call_read_PCM_frames( first_decoder, webm->format, p_frames_out, frames_read, p_audio_data->channels, framesRequested, &frames_to_read); ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); if (((cursor != 0 && cursor >= p_audio_data->totalFrames) || is_skip_to_next() || result != MA_SUCCESS) && !pb_is_EOF_reached()) { activate_switch(p_audio_data); pthread_mutex_unlock(&data_source_mutex); continue; } frames_read += frames_to_read; set_buffer_size(frames_to_read); pthread_mutex_unlock(&data_source_mutex); } set_audio_buffer(p_frames_out, frames_read, p_audio_data->sample_rate, p_audio_data->channels, p_audio_data->format); if (p_frames_read != NULL) { *p_frames_read = frames_read; } } void webm_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count) { AudioData *p_data_source = (AudioData *)p_device->pUserData; ma_uint64 frames_read = 0; webm_read_pcm_frames(&(p_data_source->base), p_frames_out, frame_count, &frames_read); if (frames_read < frame_count) { ma_webm *webm = (ma_webm *)&(p_data_source->base); float *output = (float *)p_frames_out; memset(output + frames_read * webm->channels, 0, (frame_count - frames_read) * webm->channels * sizeof(float)); } (void)p_frames_in; } kew/src/sound/playback.h000066400000000000000000000040521512074754200155320ustar00rootroot00000000000000#ifndef PLAYBACK_H #define PLAYBACK_H #include "common/appstate.h" #include #include void stop_playback(void); void pause_playback(void); void toggle_pause_playback(void); void set_paused(bool val); void set_stopped(bool val); bool pb_is_repeat_enabled(); void set_repeat_enabled(bool value); bool pb_is_paused(void); bool pb_is_stopped(void); bool is_playing(void); void sound_resume_playback(void); void set_seek_elapsed(double value); void set_seek_requested(bool value); void seek_percentage(float percent); double get_seek_elapsed(void); float get_seek_percentage(void); bool is_seek_requested(void); void set_skip_to_next(bool value); bool pb_is_EOF_reached(void); void set_EOF_reached(void); void pb_set_EOF_handled(void); void set_impl_switch_reached(void); void set_impl_switch_not_reached(void); enum AudioImplementation get_current_implementation_type(void); void set_current_implementation_type(enum AudioImplementation value); bool is_skip_to_next(void); bool pb_is_EOF_reached(void); bool pb_is_impl_switch_reached(void); void activate_switch(AudioData *p_pcm_data_source); void execute_switch(AudioData *p_pcm_data_source); ma_device *get_device(void); void get_current_format_and_sample_rate(ma_format *format, ma_uint32 *sample_rate); void pb_cleanup_playback_device(void); int init_playback_device(ma_context *context, ma_format format, ma_uint32 channels, ma_uint32 sample_rate, ma_device *device, ma_device_data_proc data_callback, void *pUserData); void m4a_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count); void opus_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count); void vorbis_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count); void webm_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count); void clear_current_track(void); void shutdown_android(void); void pb_sound_shutdown(); #endif kew/src/sound/sound.c000066400000000000000000000565611512074754200151030ustar00rootroot00000000000000#define MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING #define MA_NO_ENGINE #define MINIAUDIO_IMPLEMENTATION /** * @file sound.[c] * @brief High-level audio playback interface. * * Provides a unified API for creating an audio device * and switching decoders. */ #include "sound.h" #include "common/appstate.h" #include "common/common.h" #include "utils/file.h" #include "playback.h" #include "sound_builtin.h" #ifdef USE_FAAD #include "m4a.h" #endif #include "audio_file_info.h" #include "audiobuffer.h" #include "audiotypes.h" #include "decoders.h" #include "volume.h" #include "utils/utils.h" #include #include #include #include typedef enum { CODEC_VORBIS, CODEC_WEBM } CodecType; typedef struct { void *(*getDecoder)(); void (*get_file_info)( const char *file_path, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map); ma_result (*get_decoder_format)( ma_data_source *p_data_source, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap); int (*create_audio_device)( UserData *user_data, ma_device *device, ma_context *context); enum AudioImplementation implType; bool supportsGapless; } CodecOps; static ma_context context; static bool context_initialized = false; int builtin_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context, ma_data_source_vtable *vtable); int builtin_createAudioDeviceWrapper(UserData *user_data, ma_device *device, ma_context *context); int vorbis_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context); int opus_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context); int webm_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context); #ifdef USE_FAAD int m4a_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context); #endif bool valid_file_path(char *file_path) { if (file_path == NULL || file_path[0] == '\0' || file_path[0] == '\r') return false; if (exists_file(file_path) < 0) return false; return true; } long long get_file_size(const char *filename) { struct stat st; if (stat(filename, &st) == 0) { return (long long)st.st_size; } else { return -1; } } ma_result call_read_PCM_frames(ma_data_source *p_data_source, ma_format format, void *p_frames_out, ma_uint64 frames_read, ma_uint32 channels, ma_uint64 remaining_frames, ma_uint64 *p_frames_to_read) { ma_result result; switch (format) { case ma_format_u8: { ma_uint8 *p_out = (ma_uint8 *)p_frames_out; result = ma_data_source_read_pcm_frames( p_data_source, p_out + (frames_read * channels), remaining_frames, p_frames_to_read); } break; case ma_format_s16: { ma_int16 *p_out = (ma_int16 *)p_frames_out; result = ma_data_source_read_pcm_frames( p_data_source, p_out + (frames_read * channels), remaining_frames, p_frames_to_read); } break; case ma_format_s24: { ma_uint8 *p_out = (ma_uint8 *)p_frames_out; result = ma_data_source_read_pcm_frames( p_data_source, p_out + (frames_read * channels * 3), remaining_frames, p_frames_to_read); } break; case ma_format_s32: { ma_int32 *p_out = (ma_int32 *)p_frames_out; result = ma_data_source_read_pcm_frames( p_data_source, p_out + (frames_read * channels), remaining_frames, p_frames_to_read); } break; case ma_format_f32: { float *p_out = (float *)p_frames_out; result = ma_data_source_read_pcm_frames( p_data_source, p_out + (frames_read * channels), remaining_frames, p_frames_to_read); } break; default: { result = MA_INVALID_ARGS; } break; } return result; } int calc_avg_bit_rate(double duration, const char *file_path) { long long file_size = get_file_size(file_path); // in bytes int avg_bit_rate = 0; if (duration > 0.0) avg_bit_rate = (int)((file_size * 8.0) / duration / 1000.0); // use 1000 for kbps return avg_bit_rate; } int handle_codec( const char *file_path, CodecOps ops, AudioData *audio_data, AppState *state, ma_context *context) { ma_uint32 sample_rate, channels, nSampleRate, nChannels; ma_format format, nFormat; ma_channel channel_map[MA_MAX_CHANNELS], nChannelMap[MA_MAX_CHANNELS]; enum AudioImplementation current_implementation = get_current_implementation_type(); int avg_bit_rate = 0; #ifdef USE_FAAD k_m4adec_filetype file_type = 0; if (ops.implType == M4A) { get_m4a_file_info_full(file_path, &format, &channels, &sample_rate, channel_map, &avg_bit_rate, &file_type); } else { ops.get_file_info(file_path, &format, &channels, &sample_rate, channel_map); } #else ops.get_file_info(file_path, &format, &channels, &sample_rate, channel_map); #endif void *decoder = ops.getDecoder(); if (decoder != NULL && ops.get_decoder_format) ops.get_decoder_format(decoder, &nFormat, &nChannels, &nSampleRate, nChannelMap, MA_MAX_CHANNELS); // sameFormat computation bool sameFormat = false; if (ops.supportsGapless && decoder != NULL) { sameFormat = (format == nFormat && channels == nChannels && sample_rate == nSampleRate); #ifdef USE_FAAD if (ops.implType == M4A && decoder != NULL) { sameFormat = sameFormat && (((m4a_decoder *)decoder)->file_type == file_type) && (((m4a_decoder *)decoder)->file_type != k_rawAAC); } #endif } // Avg bitrate assignment if (audio_data->pUserData->current_song_data) { if (ops.implType == M4A) audio_data->pUserData->current_song_data->avg_bit_rate = audio_data->avg_bit_rate = avg_bit_rate; else audio_data->pUserData->current_song_data->avg_bit_rate = audio_data->avg_bit_rate = calc_avg_bit_rate(audio_data->pUserData->current_song_data->duration, file_path); } else { audio_data->avg_bit_rate = 0; } if (pb_is_repeat_enabled() || !(sameFormat && current_implementation == ops.implType)) { set_impl_switch_reached(); pthread_mutex_lock(&(state->data_source_mutex)); set_current_implementation_type(ops.implType); pb_cleanup_playback_device(); reset_all_decoders(); reset_audio_buffer(); audio_data->sample_rate = sample_rate; int result; if (ops.implType == BUILTIN) result = builtin_createAudioDevice(audio_data->pUserData, get_device(), context, &builtin_file_data_source_vtable); else result = ops.create_audio_device(audio_data->pUserData, get_device(), context); if (result < 0) { set_current_implementation_type(NONE); set_impl_switch_not_reached(); set_EOF_reached(); pthread_mutex_unlock(&(state->data_source_mutex)); return -1; } pthread_mutex_unlock(&(state->data_source_mutex)); set_impl_switch_not_reached(); } return 0; } static void get_builtin_file_info_wrapper( const char *file_path, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map) { (void)p_channel_map; // not used for builtin decoders get_file_info(file_path, p_sample_rate, p_channels, p_format); } static const struct { const char *extension; CodecOps ops; } codec_ops_list[] = { {NULL, {.getDecoder = (void *(*)(void))get_current_builtin_decoder, .get_file_info = get_builtin_file_info_wrapper, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))ma_decoder_get_data_format, .create_audio_device = builtin_createAudioDeviceWrapper, .implType = BUILTIN, .supportsGapless = true}}, {"opus", {.getDecoder = (void *(*)(void))get_current_opus_decoder, .get_file_info = get_opus_file_info, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))ma_libopus_ds_get_data_format, .create_audio_device = opus_createAudioDevice, .implType = OPUS, .supportsGapless = true}}, {"ogg", {.getDecoder = (void *(*)(void))get_current_vorbis_decoder, .get_file_info = get_vorbis_file_info, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))ma_libvorbis_ds_get_data_format, .create_audio_device = vorbis_createAudioDevice, .implType = VORBIS, .supportsGapless = true}}, {"webm", {.getDecoder = (void *(*)(void))get_current_webm_decoder, .get_file_info = get_webm_file_info, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))ma_webm_ds_get_data_format, .create_audio_device = webm_createAudioDevice, .implType = WEBM, .supportsGapless = false}}, #ifdef USE_FAAD {"m4a", {.getDecoder = (void *(*)(void))get_current_m4a_decoder, .get_file_info = get_m4a_file_info, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))m4a_decoder_ds_get_data_format, .create_audio_device = m4a_createAudioDevice, .implType = M4A, .supportsGapless = true}}, {"aac", {.getDecoder = (void *(*)(void))get_current_m4a_decoder, .get_file_info = get_m4a_file_info, .get_decoder_format = (ma_result (*)(ma_data_source *, ma_format *, ma_uint32 *, ma_uint32 *, ma_channel *, size_t))m4a_decoder_ds_get_data_format, .create_audio_device = m4a_createAudioDevice, .implType = M4A, .supportsGapless = true}}, #endif }; static const CodecOps *find_codec_ops(const char *file_path) { if (has_builtin_decoder(file_path)) return &codec_ops_list[0].ops; for (size_t i = 1; i < sizeof(codec_ops_list) / sizeof(codec_ops_list[0]); i++) { if (path_ends_with(file_path, codec_ops_list[i].extension)) return &codec_ops_list[i].ops; } return NULL; } static void handle_builtin_avg_bit_rate(AudioData *audio_data, SongData *song_data, const char *file_path) { if (path_ends_with(file_path, ".mp3") && song_data) { int avg_bit_rate = calc_avg_bit_rate(song_data->duration, file_path); if (avg_bit_rate > 320) avg_bit_rate = 320; song_data->avg_bit_rate = audio_data->avg_bit_rate = avg_bit_rate; } else { audio_data->avg_bit_rate = 0; } } static int prepare_next_decoder_for_codec(const char *file_path, const CodecOps *ops) { if (!ops) return -1; switch (ops->implType) { case BUILTIN: return prepare_next_decoder(file_path); // existing builtin prep case OPUS: return prepare_next_opus_decoder(file_path); case VORBIS: return prepare_next_vorbis_decoder(file_path); case WEBM: return prepare_next_webm_decoder(audio_data.pUserData->current_song_data); case M4A: #ifdef USE_FAAD return prepare_next_m4a_decoder(audio_data.pUserData->current_song_data); #else return -1; #endif default: return -1; } } static ma_data_source *get_first_decoder_for_codec(const CodecOps *ops) { if (!ops) return NULL; switch (ops->implType) { case BUILTIN: return (ma_data_source *)get_first_decoder(); case OPUS: return (ma_data_source *)get_first_opus_decoder(); case VORBIS: return (ma_data_source *)get_first_vorbis_decoder(); case WEBM: return (ma_data_source *)get_first_webm_decoder(); case M4A: #ifdef USE_FAAD return (ma_data_source *)get_first_m4a_decoder(); #else return NULL; #endif default: return NULL; } } static int init_audio_data_from_codec_decoder(const CodecOps *ops, void *decoder, AudioData *audio_data) { if (!ops || !decoder || !audio_data) return -1; ma_channel channel_map[MA_MAX_CHANNELS]; switch (ops->implType) { case BUILTIN: { ma_decoder *d = (ma_decoder *)decoder; audio_data->format = d->outputFormat; audio_data->channels = d->outputChannels; audio_data->sample_rate = d->outputSampleRate; ma_data_source_get_length_in_pcm_frames((ma_data_source *)d, &audio_data->totalFrames); break; } case OPUS: { ma_libopus *d = (ma_libopus *)decoder; ma_libopus_ds_get_data_format(d, &audio_data->format, &audio_data->channels, &audio_data->sample_rate, channel_map, MA_MAX_CHANNELS); ma_data_source_get_length_in_pcm_frames((ma_data_source *)d, &audio_data->totalFrames); ((ma_data_source_base *)d)->pCurrent = d; d->pReadSeekTellUserData = audio_data; break; } case VORBIS: { ma_libvorbis *d = (ma_libvorbis *)decoder; ma_libvorbis_ds_get_data_format(d, &audio_data->format, &audio_data->channels, &audio_data->sample_rate, channel_map, MA_MAX_CHANNELS); ma_data_source_get_length_in_pcm_frames((ma_data_source *)d, &audio_data->totalFrames); ((ma_data_source_base *)d)->pCurrent = d; d->pReadSeekTellUserData = audio_data; break; } case WEBM: { ma_webm *d = (ma_webm *)decoder; ma_webm_ds_get_data_format(d, &audio_data->format, &audio_data->channels, &audio_data->sample_rate, channel_map, MA_MAX_CHANNELS); ma_data_source_get_length_in_pcm_frames((ma_data_source *)d, &audio_data->totalFrames); ((ma_data_source_base *)d)->pCurrent = d; d->pReadSeekTellUserData = audio_data; break; } #ifdef USE_FAAD case M4A: { m4a_decoder *d = (m4a_decoder *)decoder; m4a_decoder_ds_get_data_format(d, &audio_data->format, &audio_data->channels, &audio_data->sample_rate, channel_map, MA_MAX_CHANNELS); ma_data_source_get_length_in_pcm_frames((ma_data_source *)d, &audio_data->totalFrames); ((ma_data_source_base *)d)->pCurrent = d; d->pReadSeekTellUserData = audio_data; break; } #endif default: return -1; } return 0; } int init_first_datasource(UserData *user_data) { if (!user_data) return MA_ERROR; SongData *song_data = (audio_data.currentFileIndex == 0) ? user_data->songdataA : user_data->songdataB; if (!song_data) return MA_ERROR; const char *file_path = song_data->file_path; if (!file_path) return MA_ERROR; audio_data.pUserData = user_data; audio_data.currentPCMFrame = 0; audio_data.restart = false; const CodecOps *ops = find_codec_ops(file_path); if (!ops) return MA_ERROR; int result = prepare_next_decoder_for_codec(file_path, ops); if (result < 0) return -1; void *decoder = get_first_decoder_for_codec(ops); if (!decoder) return -1; result = init_audio_data_from_codec_decoder(ops, decoder, &audio_data); if (result < 0) return -1; // BUILTIN MP3 special handling if (ops->implType == BUILTIN) handle_builtin_avg_bit_rate(&audio_data, song_data, file_path); return MA_SUCCESS; } int create_device(UserData *user_data, ma_device *device, ma_context *context, ma_data_source_vtable *vtable, ma_device_data_proc callback) { PlaybackState *ps = get_playback_state(); ma_result result; result = init_first_datasource(user_data); if (result != MA_SUCCESS) { set_error_message("Failed to initialize audio file."); return -1; } audio_data.base.vtable = vtable; result = init_playback_device(context, audio_data.format, audio_data.channels, audio_data.sample_rate, device, callback, &audio_data); set_volume(get_current_volume()); ps->notifyPlaying = true; return 0; } int builtin_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context, ma_data_source_vtable *vtable) { return create_device(user_data, device, context, vtable, builtin_on_audio_frames); } int builtin_createAudioDeviceWrapper(UserData *user_data, ma_device *device, ma_context *context) { return builtin_createAudioDevice(user_data, device, context, &builtin_file_data_source_vtable); } int vorbis_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context) { PlaybackState *ps = get_playback_state(); ma_result result = init_first_datasource(user_data); if (result != MA_SUCCESS) { set_error_message("Failed to initialize ogg vorbis file."); return -1; } ma_libvorbis *decoder = get_first_vorbis_decoder(); result = init_playback_device(context, decoder->format, audio_data.channels, audio_data.sample_rate, device, vorbis_on_audio_frames, decoder); set_volume(get_current_volume()); ps->notifyPlaying = true; return 0; } #ifdef USE_FAAD int m4a_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context) { PlaybackState *ps = get_playback_state(); ma_result result = init_first_datasource(user_data); if (result != MA_SUCCESS) { if (!has_error_message()) set_error_message("M4a type not supported."); return -1; } m4a_decoder *decoder = get_first_m4a_decoder(); result = init_playback_device(context, decoder->format, audio_data.channels, audio_data.sample_rate, device, m4a_on_audio_frames, decoder); set_volume(get_current_volume()); ps->notifyPlaying = true; return 0; } #endif int opus_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context) { PlaybackState *ps = get_playback_state(); ma_result result; result = init_first_datasource(user_data); if (result != MA_SUCCESS) { printf(_("\n\nFailed to initialize opus file.\n")); return -1; } ma_libopus *decoder = get_first_opus_decoder(); result = init_playback_device(context, decoder->format, audio_data.channels, audio_data.sample_rate, device, opus_on_audio_frames, decoder); set_volume(get_current_volume()); ps->notifyPlaying = true; return 0; } int webm_createAudioDevice(UserData *user_data, ma_device *device, ma_context *context) { PlaybackState *ps = get_playback_state(); ma_result result; result = init_first_datasource(user_data); if (result != MA_SUCCESS) { printf(_("\n\nFailed to initialize webm file.\n")); return -1; } ma_webm *decoder = get_first_webm_decoder(); result = init_playback_device(context, decoder->format, audio_data.channels, audio_data.sample_rate, device, webm_on_audio_frames, decoder); set_volume(get_current_volume()); ps->notifyPlaying = true; return 0; } int pb_switch_audio_implementation(void) { AppState *state = get_app_state(); if (audio_data.end_of_list_reached) { pb_set_EOF_handled(); set_current_implementation_type(NONE); return 0; } audio_data.pUserData->current_song_data = (audio_data.currentFileIndex == 0) ? audio_data.pUserData->songdataA : audio_data.pUserData->songdataB; if (!audio_data.pUserData->current_song_data) { pb_set_EOF_handled(); return 0; } char *file_path = strdup(audio_data.pUserData->current_song_data->file_path); if (!valid_file_path(file_path)) { free(file_path); set_EOF_reached(); return -1; } const CodecOps *ops = find_codec_ops(file_path); if (!ops) { free(file_path); return -1; } if (ops->implType == BUILTIN) handle_builtin_avg_bit_rate(&audio_data, audio_data.pUserData->current_song_data, file_path); int result = handle_codec(file_path, *ops, &audio_data, state, &context); free(file_path); if (result < 0) { set_current_implementation_type(NONE); set_impl_switch_not_reached(); set_EOF_reached(); return -1; } pb_set_EOF_handled(); return 0; } bool is_context_initialized(void) { return context_initialized; } void cleanup_audio_context(void) { ma_context_uninit(&context); context_initialized = false; } int pb_create_audio_device(void) { PlaybackState *ps = get_playback_state(); if (context_initialized) { ma_context_uninit(&context); context_initialized = false; } ma_context_init(NULL, 0, NULL, &context); context_initialized = true; if (pb_switch_audio_implementation() >= 0) { ps->notifySwitch = true; } else { return -1; } return 0; } kew/src/sound/sound.h000066400000000000000000000015401512074754200150730ustar00rootroot00000000000000/** * @file sound.[h] * @brief High-level audio playback interface. * * Provides a unified API for creating an audio device * and switching decoders. */ #ifndef SOUND_H #define SOUND_H #include "common/appstate.h" #include #include #include #include #include #include #include #include #include bool is_context_initialized(void); int pb_create_audio_device(void); int pb_switch_audio_implementation(void); void cleanup_audio_context(void); ma_result call_read_PCM_frames(ma_data_source *p_data_source, ma_format format, void *p_frames_out, ma_uint64 frames_read, ma_uint32 channels, ma_uint64 remaining_frames, ma_uint64 *p_frames_to_read); #endif kew/src/sound/sound_builtin.c000066400000000000000000000340631512074754200166220ustar00rootroot00000000000000/** * @file sound_builtin.[c] * @brief Built-in audio backend implementation. * * Functions related to miniaudio implementation for miniaudio built-in decoders * (flac, wav and mp3). */ #include "sound_builtin.h" #include "common/appstate.h" #include "sound/audiobuffer.h" #include "sound/decoders.h" #include "sound/playback.h" #include "sound/sound.h" #include #include #include #include static ma_result builtin_file_data_source_read(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { // Dummy implementation (void)p_data_source; (void)p_frames_out; (void)frame_count; (void)p_frames_read; return MA_SUCCESS; } static ma_result builtin_file_data_source_seek(ma_data_source *p_data_source, ma_uint64 frame_index) { if (p_data_source == NULL) { return MA_INVALID_ARGS; } AudioData *audio_data = (AudioData *)p_data_source; if (get_current_builtin_decoder() == NULL) { return MA_INVALID_ARGS; } ma_result result = ma_decoder_seek_to_pcm_frame( get_current_builtin_decoder(), frame_index); if (result == MA_SUCCESS) { audio_data->currentPCMFrame = frame_index; return MA_SUCCESS; } else { return result; } } static ma_result builtin_file_data_source_get_data_format( ma_data_source *p_data_source, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap) { (void)p_channel_map; (void)channel_map_cap; if (p_data_source == NULL) { return MA_INVALID_ARGS; } AudioData *audio_data = (AudioData *)p_data_source; if (p_format == NULL || p_channels == NULL || p_sample_rate == NULL) { return MA_INVALID_ARGS; } *p_format = audio_data->format; *p_channels = audio_data->channels; *p_sample_rate = audio_data->sample_rate; return MA_SUCCESS; } static ma_result builtin_file_data_source_get_cursor(ma_data_source *p_data_source, ma_uint64 *p_cursor) { if (p_data_source == NULL) { return MA_INVALID_ARGS; } AudioData *audio_data = (AudioData *)p_data_source; *p_cursor = audio_data->currentPCMFrame; return MA_SUCCESS; } static ma_result builtin_file_data_source_get_length(ma_data_source *p_data_source, ma_uint64 *p_length) { (void)p_data_source; ma_uint64 totalFrames = 0; if (get_current_builtin_decoder() == NULL) { return MA_INVALID_ARGS; } ma_result result = ma_decoder_get_length_in_pcm_frames( get_current_builtin_decoder(), &totalFrames); if (result != MA_SUCCESS) { return result; } *p_length = totalFrames; return MA_SUCCESS; } static ma_result builtin_file_data_source_set_looping(ma_data_source *p_data_source, ma_bool32 is_looping) { // Dummy implementation (void)p_data_source; (void)is_looping; return MA_SUCCESS; } ma_data_source_vtable builtin_file_data_source_vtable = { builtin_file_data_source_read, builtin_file_data_source_seek, builtin_file_data_source_get_data_format, builtin_file_data_source_get_cursor, builtin_file_data_source_get_length, builtin_file_data_source_set_looping, 0 // Flags }; double db_to_linear(double db) { return pow(10.0, db / 20.0); } bool is_valid_gain(double gain) { return gain > -50.0 && gain < 50.0 && !isnan(gain) && isfinite(gain); } static bool compute_replay_gain(AudioData *audio_data, double *out_gain_db) { if (audio_data->pUserData == NULL) return false; UserData *ud = audio_data->pUserData; bool result = false; double gain_db = 0.0; if ((!ud->songdataADeleted && ud->current_song_data == ud->songdataA) || (!ud->songdataBDeleted && ud->current_song_data == ud->songdataB)) { SongData *song = ud->current_song_data; if (song != NULL && song->magic == SONG_MAGIC && song->metadata != NULL) { double track_gain = song->metadata->replaygainTrack; double album_gain = song->metadata->replaygainAlbum; bool useTrackFirst = (ud->replayGainCheckFirst == 0); if (useTrackFirst && is_valid_gain(track_gain)) { gain_db = track_gain; result = true; } else if (is_valid_gain(album_gain)) { gain_db = album_gain; result = true; } else if (!useTrackFirst && is_valid_gain(track_gain)) { gain_db = track_gain; result = true; } } } if (result) { *out_gain_db = gain_db; } return result; } static void apply_gain_to_interleaved_frames(void *raw_frames, ma_format format, ma_uint64 frames_to_read, int channels, double gain) { if (gain == 1.0) { return; // No gain to apply } // Prevent multiplication overflow: if (channels <= 0) return; if (frames_to_read > (UINT64_MAX / channels)) { // Overflow would happen return; } switch (format) { case ma_format_f32: { float *frames = (float *)raw_frames; for (ma_uint64 i = 0; i < frames_to_read; ++i) { for (int ch = 0; ch < channels; ++ch) { ma_uint64 frame_index = i * channels + ch; float originalSample = frames[frame_index]; double sample = (double)originalSample; sample *= gain; frames[frame_index] = (float)sample; } } break; } case ma_format_s16: { ma_int16 *frames = (ma_int16 *)raw_frames; for (ma_uint64 i = 0; i < frames_to_read; ++i) { for (int ch = 0; ch < channels; ++ch) { ma_uint64 frame_index = i * channels + ch; ma_int16 originalSample = frames[frame_index]; double sample = (double)originalSample; sample *= gain; if (sample > 32767.0) sample = 32767.0; else if (sample < -32768.0) sample = -32768.0; frames[frame_index] = (ma_int16)sample; } } break; } case ma_format_s32: { ma_int32 *frames = (ma_int32 *)raw_frames; for (ma_uint64 i = 0; i < frames_to_read; ++i) { for (int ch = 0; ch < channels; ++ch) { ma_uint64 frame_index = i * channels + ch; ma_int32 originalSample = frames[frame_index]; double sample = (double)originalSample; sample *= gain; if (sample > 2147483647.0) sample = 2147483647.0; else if (sample < -2147483648.0) sample = -2147483648.0; frames[frame_index] = (ma_int32)sample; } } break; } default: // Unsupported format break; } } static bool perform_seek_if_requested(ma_decoder *decoder, AudioData *audio_data) { if (!is_seek_requested()) return true; ma_uint64 totalFrames = audio_data->totalFrames; double seek_percent = get_seek_percentage(); if (seek_percent > 100.0) seek_percent = 100.0; ma_uint64 targetFrame = (ma_uint64)((totalFrames - 1) * seek_percent / 100.0); if (targetFrame >= totalFrames) targetFrame = totalFrames - 1; ma_result result = ma_decoder_seek_to_pcm_frame(decoder, targetFrame); set_seek_requested(false); return result == MA_SUCCESS; } ma_uint64 lastCursor = 0; static bool should_switch(AudioData *audio_data, ma_uint64 frames_to_read, ma_result result, ma_uint64 cursor) { if (cursor != 0ULL && lastCursor == cursor) { lastCursor = 0; return true; } return (((audio_data->totalFrames != 0 && cursor >= audio_data->totalFrames) || frames_to_read == 0 || is_skip_to_next() || result != MA_SUCCESS) && !pb_is_EOF_reached()); } void builtin_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { AudioData *audio_data = (AudioData *)p_data_source; ma_uint64 frames_read = 0; AppState *state = get_app_state(); // Step 1: Compute gain while (frames_read < frame_count) { ma_uint64 remaining_frames = frame_count - frames_read; if (pthread_mutex_trylock(&(state->data_source_mutex)) != 0) return; // Step 2: Handle file switching or state invalidation if (audio_data == NULL || audio_data->pUserData == NULL || pb_is_impl_switch_reached()) { pthread_mutex_unlock(&(state->data_source_mutex)); return; } if (audio_data->switchFiles) { execute_switch(audio_data); pthread_mutex_unlock(&(state->data_source_mutex)); break; } ma_decoder *decoder = get_current_builtin_decoder(); if ((get_current_implementation_type() != BUILTIN && !is_skip_to_next()) || decoder == NULL) { pthread_mutex_unlock(&(state->data_source_mutex)); return; } // Step 3: Get total frames if needed if (audio_data->totalFrames == 0) { ma_data_source_get_length_in_pcm_frames( decoder, &(audio_data->totalFrames)); } // Step 4: Seek if requested if (!perform_seek_if_requested(decoder, audio_data)) { pthread_mutex_unlock(&(state->data_source_mutex)); return; } // Step 5: Read frames ma_uint64 frames_to_read = 0; ma_decoder *first_decoder = get_first_decoder(); ma_uint64 cursor = 0; if (first_decoder == NULL || pb_is_EOF_reached()) { pthread_mutex_unlock(&(state->data_source_mutex)); return; } double gain_db = 0.0; bool gainAvailable = compute_replay_gain(audio_data, &gain_db); double gain_factor = gainAvailable ? db_to_linear(gain_db) : 1.0; ma_result result = call_read_PCM_frames( first_decoder, audio_data->format, p_frames_out, frames_read, audio_data->channels, remaining_frames, &frames_to_read); ma_data_source_get_cursor_in_pcm_frames(decoder, &cursor); void *frame_start = (void *)((float *)p_frames_out + frames_read * audio_data->channels); // Cast matches format // Step 6: Apply gain if (gain_factor != 1.0) { apply_gain_to_interleaved_frames( frame_start, audio_data->format, frames_to_read, audio_data->channels, gain_factor); } // Step 7: Check for switch if (should_switch(audio_data, frames_to_read, result, cursor)) { activate_switch(audio_data); pthread_mutex_unlock(&(state->data_source_mutex)); lastCursor = 0; continue; } lastCursor = cursor; // Step 8: Update state frames_read += frames_to_read; set_buffer_size(frames_to_read); pthread_mutex_unlock(&(state->data_source_mutex)); } // Step 9: Finalize set_audio_buffer(p_frames_out, frames_read, audio_data->sample_rate, audio_data->channels, audio_data->format); if (p_frames_read != NULL) *p_frames_read = frames_read; } void builtin_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count) { AudioData *p_data_source = (AudioData *)p_device->pUserData; ma_uint64 frames_read = 0; builtin_read_pcm_frames(&(p_data_source->base), p_frames_out, frame_count, &frames_read); (void)p_frames_in; } kew/src/sound/sound_builtin.h000066400000000000000000000011501512074754200166160ustar00rootroot00000000000000/** * @file sound_builtin.[h] * @brief Built-in audio backend implementation. * * Implements a simple internal audio output backend used when no * external library is available. Useful for portability and testing. */ #ifndef sound_builtin_H #define sound_builtin_H #include extern ma_data_source_vtable builtin_file_data_source_vtable; void builtin_read_pcm_frames(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read); void builtin_on_audio_frames(ma_device *p_device, void *p_frames_out, const void *p_frames_in, ma_uint32 frame_count); #endif kew/src/sound/volume.c000066400000000000000000000010741512074754200152470ustar00rootroot00000000000000#include "volume.h" #include "playback.h" #include static int sound_volume = 100; int get_current_volume(void) { return sound_volume; } void set_volume(int volume) { if (volume > 100) { volume = 100; } else if (volume < 0) { volume = 0; } sound_volume = volume; ma_device_set_master_volume(get_device(), (float)volume / 100); } int adjust_volume_percent(int volume_change) { sound_volume += volume_change; set_volume(sound_volume); return 0; } kew/src/sound/volume.h000066400000000000000000000002241512074754200152500ustar00rootroot00000000000000#ifndef VOLUME_H #define VOLUME_H int get_current_volume(void); int adjust_volume_percent(int volume_change); void set_volume(int volume); #endif kew/src/sound/webm.h000066400000000000000000001150431512074754200147010ustar00rootroot00000000000000/** * @file webm.h * @brief WebM audio decoding interface. * * Declares functions and types for decoding WebM/Opus audio files. */ #ifndef WEBM_H #define WEBM_H #ifdef __cplusplus extern "C" { #endif #include "miniaudio.h" #if !defined(MA_NO_WEBM) #include #include #include #endif typedef struct { ma_data_source_base ds; /* The webm decoder can be used independently as a data source. */ ma_read_proc onRead; ma_seek_proc onSeek; ma_tell_proc onTell; void *pReadSeekTellUserData; ma_format format; /* Will be f32 */ #if !defined(MA_NO_WEBM) ma_uint64 audio_track; ma_uint64 cursorInPCMFrames; ma_uint64 seekTargetPCMFrame; ma_uint32 sample_rate; ma_uint32 channels; ma_uint64 lengthInPCMFrames; double duration; // Nestegg fields nestegg *ctx; unsigned int codec_id; nestegg_packet *currentPacket; unsigned int numFramesInPacket; unsigned int currentPacketFrame; ma_bool32 hasPacket; // Opus fields OpusDecoder *opusDecoder; // Vorbis fields vorbis_block vorbisBlock; vorbis_dsp_state vorbisDSP; vorbis_comment vorbisComment; vorbis_info vorbisInfo; ma_uint16 opusPreSkip; ma_uint16 preSkipLeft; ma_uint64 bufferLeftoverFrameCount; ma_uint64 bufferLeftoverFrameOffset; #endif } ma_webm; MA_API ma_result ma_webm_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, ma_webm *p_webm); MA_API ma_result ma_webm_init_file(const char *pFilePath, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, ma_webm *p_webm); MA_API void ma_webm_uninit(ma_webm *p_opus, const ma_allocation_callbacks *p_allocation_callbacks); MA_API ma_result ma_webm_read_pcm_frames(ma_webm *p_webm, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read); MA_API ma_result ma_webm_seek_to_pcm_frame(ma_webm *p_webm, ma_uint64 frame_index); MA_API ma_result ma_webm_get_data_format(ma_webm *p_opus, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap); MA_API ma_result ma_webm_get_cursor_in_pcm_frames(ma_webm *p_webm, ma_uint64 *p_cursor); MA_API ma_result ma_webm_get_length_in_pcm_frames(ma_webm *p_webm, ma_uint64 *p_length); #ifdef __cplusplus } #endif #endif #if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) #define MAX_OPUS_CHANNELS 8 #define MAX_OPUS_SAMPLES 5760 // Maximum expected frame size static float opusLeftoverBuffer[MAX_OPUS_SAMPLES * MAX_OPUS_CHANNELS]; #define MAX_VORBIS_PACKET_FRAMES 4096 #define MAX_VORBIS_CHANNELS 8 float vorbisLeftoverBuffer[MAX_VORBIS_PACKET_FRAMES * MAX_VORBIS_CHANNELS]; static ma_result ma_webm_ds_read(ma_data_source *p_data_source, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { return ma_webm_read_pcm_frames((ma_webm *)p_data_source, p_frames_out, frame_count, p_frames_read); } static ma_result ma_webm_ds_seek(ma_data_source *p_data_source, ma_uint64 frame_index) { return ma_webm_seek_to_pcm_frame((ma_webm *)p_data_source, frame_index); } static ma_result ma_webm_ds_get_data_format(ma_data_source *p_data_source, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap) { return ma_webm_get_data_format((ma_webm *)p_data_source, p_format, p_channels, p_sample_rate, p_channel_map, channel_map_cap); } static ma_result ma_webm_ds_get_cursor(ma_data_source *p_data_source, ma_uint64 *p_cursor) { return ma_webm_get_cursor_in_pcm_frames((ma_webm *)p_data_source, p_cursor); } static ma_result ma_webm_ds_get_length(ma_data_source *p_data_source, ma_uint64 *p_length) { return ma_webm_get_length_in_pcm_frames((ma_webm *)p_data_source, p_length); } static ma_data_source_vtable g_ma_webm_ds_vtable = { ma_webm_ds_read, ma_webm_ds_seek, ma_webm_ds_get_data_format, ma_webm_ds_get_cursor, ma_webm_ds_get_length, NULL, (ma_uint64)0}; static ma_result ma_webm_init_internal(const ma_decoding_backend_config *p_config, ma_webm *p_webm) { ma_result result; ma_data_source_config dataSourceConfig; if (p_webm == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(p_webm); p_webm->format = ma_format_f32; // f32 by default. p_webm->seekTargetPCMFrame = (ma_uint64)-1; // Clear leftover buffer p_webm->bufferLeftoverFrameCount = 0; p_webm->bufferLeftoverFrameOffset = 0; if (p_config != NULL && (p_config->preferredFormat == ma_format_f32 || p_config->preferredFormat == ma_format_s16)) { p_webm->format = p_config->preferredFormat; } else { /* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */ } dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_webm_ds_vtable; result = ma_data_source_init(&dataSourceConfig, &p_webm->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ } return MA_SUCCESS; } static int nestegg_io_read(void *buffer, size_t length, void *userdata) { ma_webm *webm = (ma_webm *)userdata; size_t bytes_read = 0; if (webm->onRead(webm->pReadSeekTellUserData, buffer, length, &bytes_read) == MA_SUCCESS) return (bytes_read == length) ? 1 : 0; return -1; } static int nestegg_io_seek(int64_t offset, int whence, void *userdata) { ma_webm *webm = (ma_webm *)userdata; ma_seek_origin origin; switch (whence) { case NESTEGG_SEEK_SET: origin = ma_seek_origin_start; break; case NESTEGG_SEEK_CUR: origin = ma_seek_origin_current; break; case NESTEGG_SEEK_END: origin = ma_seek_origin_end; break; default: return -1; } return (webm->onSeek(webm->pReadSeekTellUserData, offset, origin) == MA_SUCCESS) ? 0 : -1; } static int64_t nestegg_io_tell(void *userdata) { ma_webm *webm = (ma_webm *)userdata; ma_int64 pos = 0; return (webm->onTell(webm->pReadSeekTellUserData, &pos) == MA_SUCCESS) ? pos : -1; } double calcWebmDuration(nestegg *ctx) { double duration = 0.0f; uint64_t duration_ns = 0; if (nestegg_duration(ctx, &duration_ns) == 0) { duration = (double)duration_ns / 1e9; } return duration; } static int ma_webm_init_vorbis_decoder( nestegg *ctx, unsigned int audio_track, ma_webm *p_webm) { unsigned char *id = NULL, *comment = NULL, *setup = NULL; size_t id_size = 0, comment_size = 0, setup_size = 0; ogg_packet header_packet; // Fetch header packets as delivered by WebM/Matroska. if (nestegg_track_codec_data(ctx, audio_track, 0, &id, &id_size) != 0 || nestegg_track_codec_data(ctx, audio_track, 1, &comment, &comment_size) != 0 || nestegg_track_codec_data(ctx, audio_track, 2, &setup, &setup_size) != 0) { return -1; // invalid file or track } // Setup libvorbis structures. vorbis_info_init(&p_webm->vorbisInfo); vorbis_comment_init(&p_webm->vorbisComment); memset(&header_packet, 0, sizeof(header_packet)); // Header 1: ID header_packet.packet = id; header_packet.bytes = id_size; header_packet.b_o_s = 1; header_packet.e_o_s = 0; if (vorbis_synthesis_headerin(&p_webm->vorbisInfo, &p_webm->vorbisComment, &header_packet) != 0) goto fail; // Header 2: COMMENT header_packet.packet = comment; header_packet.bytes = comment_size; header_packet.b_o_s = 0; // header_packet.e_o_s remains 0 if (vorbis_synthesis_headerin(&p_webm->vorbisInfo, &p_webm->vorbisComment, &header_packet) != 0) goto fail; // Header 3: SETUP header_packet.packet = setup; header_packet.bytes = setup_size; // header_packet.b_o_s remains 0 if (vorbis_synthesis_headerin(&p_webm->vorbisInfo, &p_webm->vorbisComment, &header_packet) != 0) goto fail; // Setup decoder if (vorbis_synthesis_init(&p_webm->vorbisDSP, &p_webm->vorbisInfo) != 0) goto fail; if (vorbis_block_init(&p_webm->vorbisDSP, &p_webm->vorbisBlock) != 0) goto fail; p_webm->channels = p_webm->vorbisInfo.channels; p_webm->sample_rate = p_webm->vorbisInfo.rate; p_webm->format = ma_format_f32; return 0; // success fail: vorbis_block_clear(&p_webm->vorbisBlock); vorbis_dsp_clear(&p_webm->vorbisDSP); vorbis_comment_clear(&p_webm->vorbisComment); vorbis_info_clear(&p_webm->vorbisInfo); return -2; // error } MA_API ma_result ma_webm_init( ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, ma_webm *p_webm) { ma_result result; (void)p_allocation_callbacks; result = ma_webm_init_internal(p_config, p_webm); if (result != MA_SUCCESS) { return result; } if (onRead == NULL || onSeek == NULL) { return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */ } p_webm->onRead = onRead; p_webm->onSeek = onSeek; p_webm->onTell = onTell; p_webm->pReadSeekTellUserData = pReadSeekTellUserData; #if !defined(MA_NO_WEBM) nestegg_io io = {0}; // Adapter functions for nestegg io.read = nestegg_io_read; io.seek = nestegg_io_seek; io.tell = nestegg_io_tell; io.userdata = p_webm; nestegg *ctx = NULL; if (nestegg_init(&ctx, io, NULL, -1) < 0) { return MA_INVALID_FILE; } // Find Audio Track unsigned int num_tracks = 0; if (nestegg_track_count(ctx, &num_tracks) != 0) { nestegg_destroy(ctx); return MA_INVALID_FILE; } p_webm->audio_track = (unsigned int)-1; p_webm->codec_id = -1; for (unsigned int i = 0; i < num_tracks; ++i) { unsigned int type = 0; type = nestegg_track_type(ctx, i); if (type == NESTEGG_TRACK_AUDIO) { p_webm->audio_track = i; p_webm->codec_id = nestegg_track_codec_id(ctx, i); if (p_webm->codec_id == 0) { break; // first audio } } } if (p_webm->audio_track == (unsigned int)-1) { nestegg_destroy(ctx); return MA_INVALID_FILE; } // Prepare decoder if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { unsigned char *header = NULL; size_t header_size = 0; if (nestegg_track_codec_data(ctx, p_webm->audio_track, 0, &header, &header_size) != 0 || header_size < 19 || memcmp(header, "OpusHead", 8) != 0) { nestegg_destroy(ctx); return MA_INVALID_FILE; } p_webm->sample_rate = 48000; p_webm->channels = header[9]; ma_uint16 preSkip = header[10] | (header[11] << 8); // Little-endian p_webm->opusPreSkip = preSkip; p_webm->preSkipLeft = preSkip; int opusErr = 0; p_webm->opusDecoder = opus_decoder_create(p_webm->sample_rate, p_webm->channels, &opusErr); if (!p_webm->opusDecoder) { nestegg_destroy(ctx); return MA_INVALID_FILE; } p_webm->format = ma_format_f32; } else if (p_webm->codec_id == NESTEGG_CODEC_VORBIS) { if (ma_webm_init_vorbis_decoder(ctx, p_webm->audio_track, p_webm) != 0) { nestegg_destroy(ctx); return MA_INVALID_FILE; } } else { nestegg_destroy(ctx); return MA_NOT_IMPLEMENTED; } p_webm->ctx = ctx; p_webm->duration = calcWebmDuration(ctx); p_webm->seekTargetPCMFrame = (ma_uint64)(-1); return MA_SUCCESS; #else (void)pReadSeekTellUserData; (void)p_config; (void)p_webm; return MA_NOT_IMPLEMENTED; #endif } int nread(void *buf, size_t len, void *ud) { FILE *f = (FILE *)ud; size_t r = fread(buf, 1, len, f); if (r == len) return 1; if (feof(f)) return 0; return -1; } int nseek(int64_t o, int w, void *ud) { FILE *f = (FILE *)ud; int wh; switch (w) { case NESTEGG_SEEK_SET: wh = SEEK_SET; break; case NESTEGG_SEEK_CUR: wh = SEEK_CUR; break; case NESTEGG_SEEK_END: wh = SEEK_END; break; default: return -1; } return fseek(f, (long)o, wh); } int64_t ntell(void *ud) { FILE *f = (FILE *)ud; return ftell(f); } MA_API ma_result ma_webm_init_file(const char *pFilePath, const ma_decoding_backend_config *p_config, const ma_allocation_callbacks *p_allocation_callbacks, ma_webm *p_webm) { ma_result result; (void)p_allocation_callbacks; result = ma_webm_init_internal(p_config, p_webm); if (result != MA_SUCCESS) { return result; } #if !defined(MA_NO_WEBM) FILE *fp = fopen(pFilePath, "rb"); if (!fp) return MA_INVALID_FILE; nestegg_io io = {nread, nseek, ntell, fp}; nestegg *ctx = NULL; if (nestegg_init(&ctx, io, NULL, -1) < 0) { fclose(fp); return MA_INVALID_FILE; } unsigned int num_tracks = 0; nestegg_track_count(ctx, &num_tracks); p_webm->audio_track = (unsigned int)(-1); p_webm->codec_id = -1; for (unsigned int i = 0; i < num_tracks; ++i) { unsigned int type = 0; type = nestegg_track_type(ctx, i); if (type == NESTEGG_TRACK_AUDIO) { p_webm->audio_track = i; p_webm->codec_id = nestegg_track_codec_id(ctx, i); break; } } if (p_webm->audio_track == (unsigned int)(-1)) { nestegg_destroy(ctx); fclose(fp); return MA_ERROR; } // Fetch and handle header if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { unsigned char *header = NULL; size_t header_size = 0; nestegg_track_codec_data(ctx, p_webm->audio_track, 0, &header, &header_size); if (header_size < 19 || memcmp(header, "OpusHead", 8) != 0) { nestegg_destroy(ctx); fclose(fp); return MA_ERROR; } p_webm->channels = header[9]; ma_uint16 preSkip = header[10] | (header[11] << 8); // Little-endian p_webm->opusPreSkip = preSkip; p_webm->preSkipLeft = preSkip; int opusErr = 0; p_webm->opusDecoder = opus_decoder_create(48000, p_webm->channels, &opusErr); if (!p_webm->opusDecoder) { nestegg_destroy(ctx); fclose(fp); return MA_ERROR; } p_webm->format = ma_format_f32; p_webm->sample_rate = 48000; } else if (p_webm->codec_id == NESTEGG_CODEC_VORBIS) { if (ma_webm_init_vorbis_decoder(ctx, p_webm->audio_track, p_webm) != 0) { nestegg_destroy(ctx); return MA_INVALID_FILE; } } else { nestegg_destroy(ctx); fclose(fp); return MA_NOT_IMPLEMENTED; } p_webm->ctx = ctx; p_webm->duration = calcWebmDuration(ctx); p_webm->seekTargetPCMFrame = (ma_uint64)(-1); return MA_SUCCESS; #else /* webm is disabled. */ (void)pFilePath; return MA_NOT_IMPLEMENTED; #endif } MA_API void ma_webm_uninit(ma_webm *p_webm, const ma_allocation_callbacks *p_allocation_callbacks) { if (p_webm == NULL) { return; } (void)p_allocation_callbacks; #if !defined(MA_NO_WEBM) { if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { opus_decoder_destroy(p_webm->opusDecoder); p_webm->opusDecoder = NULL; } else if (p_webm->codec_id == NESTEGG_CODEC_VORBIS) { vorbis_block_clear(&p_webm->vorbisBlock); vorbis_dsp_clear(&p_webm->vorbisDSP); vorbis_comment_clear(&p_webm->vorbisComment); vorbis_info_clear(&p_webm->vorbisInfo); } if (p_webm->ctx) { nestegg_destroy(p_webm->ctx); p_webm->ctx = NULL; } } #else { /* webm is disabled. Should never hit this since initialization would have failed. */ MA_ASSERT(MA_FALSE); } #endif ma_data_source_uninit(&p_webm->ds); } MA_API ma_result ma_webm_read_pcm_frames(ma_webm *p_webm, void *p_frames_out, ma_uint64 frame_count, ma_uint64 *p_frames_read) { if (p_frames_read) *p_frames_read = 0; if (frame_count == 0 || p_webm == NULL) return MA_INVALID_ARGS; #if !defined(MA_NO_WEBM) ma_result result = MA_SUCCESS; ma_uint64 totalFramesRead = 0; ma_uint32 channels = p_webm->channels; float *f32Out = (float *)p_frames_out; float decodeBuf[MAX_OPUS_SAMPLES * MAX_OPUS_CHANNELS]; // Support up to 8 channels ma_uint64 seekTarget = (p_webm->seekTargetPCMFrame != (ma_uint64)-1) ? p_webm->seekTargetPCMFrame : 0; while (totalFramesRead < frame_count) { ma_uint64 framesNeeded = frame_count - totalFramesRead; // If there's a cached packet/frame in progress, decode that if (!p_webm->hasPacket) { nestegg_packet *pkt = NULL; // Next audio packet... while (nestegg_read_packet(p_webm->ctx, &pkt) > 0) { unsigned int track; nestegg_packet_track(pkt, &track); if (track == p_webm->audio_track) { p_webm->currentPacket = pkt; p_webm->currentPacketFrame = 0; p_webm->numFramesInPacket = 0; nestegg_packet_count(pkt, &p_webm->numFramesInPacket); p_webm->hasPacket = MA_TRUE; break; } nestegg_free_packet(pkt); // not audio, discard } if (!p_webm->hasPacket) { result = MA_AT_END; // no more data break; } } // Decode remaining frames in this packet/frame nestegg_packet *pkt = p_webm->currentPacket; while (p_webm->currentPacketFrame < p_webm->numFramesInPacket && totalFramesRead < frame_count) { unsigned char *data = NULL; size_t dataSize = 0; nestegg_packet_data(pkt, p_webm->currentPacketFrame, &data, &dataSize); int nframes = 0; if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { if (p_webm->bufferLeftoverFrameCount > 0) { ma_uint64 frames_to_copy = p_webm->bufferLeftoverFrameCount < framesNeeded ? p_webm->bufferLeftoverFrameCount : framesNeeded; memcpy(f32Out + totalFramesRead * channels, opusLeftoverBuffer + p_webm->bufferLeftoverFrameOffset * channels, frames_to_copy * channels * sizeof(float)); p_webm->bufferLeftoverFrameOffset += frames_to_copy; totalFramesRead += frames_to_copy; framesNeeded -= frames_to_copy; p_webm->bufferLeftoverFrameCount -= frames_to_copy; if (p_webm->bufferLeftoverFrameCount == 0) p_webm->bufferLeftoverFrameOffset = 0; if (framesNeeded == 0) break; } nframes = opus_decode_float(p_webm->opusDecoder, data, (opus_int32)dataSize, decodeBuf, 5760, 0); if (nframes < 0) { result = MA_ERROR; break; } ma_uint64 skipFrames = 0; ma_uint64 usableFrames = 0; // On first packets, discard enough to fulfill pre-skip value if (p_webm->preSkipLeft > 0) { if ((ma_uint64)nframes <= p_webm->preSkipLeft) { // All output is to be skipped p_webm->preSkipLeft -= (ma_uint16)nframes; p_webm->cursorInPCMFrames += nframes; // Don't copy anything to output buffer goto NextFrame; } else { // Skip part, keep rest skipFrames = p_webm->preSkipLeft; p_webm->preSkipLeft = 0; } } else if (seekTarget != (ma_uint64)-1 && p_webm->cursorInPCMFrames < seekTarget) { skipFrames = seekTarget - p_webm->cursorInPCMFrames; if (skipFrames > (ma_uint64)nframes) skipFrames = nframes; } usableFrames = (ma_uint64)nframes - skipFrames; if (usableFrames > frame_count - totalFramesRead) usableFrames = frame_count - totalFramesRead; // Only copy if there are any usable frames left if (usableFrames > 0) { memcpy( f32Out + totalFramesRead * channels, decodeBuf + skipFrames * channels, usableFrames * channels * sizeof(float)); totalFramesRead += usableFrames; } ma_uint64 framesUsed = skipFrames + usableFrames; ma_uint64 frames_left = nframes - framesUsed; if (frames_left > 0) { memcpy(opusLeftoverBuffer, decodeBuf + framesUsed * channels, frames_left * channels * sizeof(float)); p_webm->bufferLeftoverFrameCount = frames_left; p_webm->bufferLeftoverFrameOffset = 0; } else { p_webm->bufferLeftoverFrameCount = 0; p_webm->bufferLeftoverFrameOffset = 0; } // Always advance the PCM cursor by all decoded frames (skipped + copied) p_webm->cursorInPCMFrames += (ma_uint64)nframes; // If we've finished discarding, clear seek mode ("not discarding anymore") if (seekTarget != (ma_uint64)-1 && p_webm->cursorInPCMFrames >= seekTarget) { p_webm->seekTargetPCMFrame = (ma_uint64)-1; } NextFrame:; } else if (p_webm->codec_id == NESTEGG_CODEC_VORBIS) { ogg_packet oggPkt = {0}; oggPkt.packet = data; oggPkt.bytes = (long)dataSize; oggPkt.b_o_s = (p_webm->currentPacketFrame == 0) ? 1 : 0; oggPkt.e_o_s = 0; oggPkt.granulepos = -1; if (p_webm->bufferLeftoverFrameCount > 0) { ma_uint32 avail = p_webm->bufferLeftoverFrameCount - p_webm->bufferLeftoverFrameOffset; ma_uint32 toCopy = (frame_count - totalFramesRead) < avail ? (frame_count - totalFramesRead) : avail; memcpy( f32Out + totalFramesRead * channels, vorbisLeftoverBuffer + p_webm->bufferLeftoverFrameOffset * channels, toCopy * channels * sizeof(float)); p_webm->bufferLeftoverFrameOffset += toCopy; totalFramesRead += toCopy; if (p_webm->bufferLeftoverFrameOffset == p_webm->bufferLeftoverFrameCount) { p_webm->bufferLeftoverFrameCount = 0; p_webm->bufferLeftoverFrameOffset = 0; } if (totalFramesRead >= frame_count) break; // Buffer full } int ret = vorbis_synthesis(&p_webm->vorbisBlock, &oggPkt); if (ret == 0) { vorbis_synthesis_blockin(&p_webm->vorbisDSP, &p_webm->vorbisBlock); float **pcm; int framesAvail = vorbis_synthesis_pcmout(&p_webm->vorbisDSP, &pcm); if (framesAvail > 0) { ma_uint64 skipFrames = 0; if (seekTarget != (ma_uint64)-1 && p_webm->cursorInPCMFrames < seekTarget) { skipFrames = seekTarget - p_webm->cursorInPCMFrames; if (skipFrames > (ma_uint64)framesAvail) skipFrames = framesAvail; } ma_uint64 usableFrames = (ma_uint64)framesAvail - skipFrames; if (usableFrames > frame_count - totalFramesRead) usableFrames = frame_count - totalFramesRead; while (framesAvail > 0 && totalFramesRead < frame_count) { ma_uint64 frames_to_copy = (frame_count - totalFramesRead) < (ma_uint64)framesAvail ? (frame_count - totalFramesRead) : (ma_uint64)framesAvail; // Interleave frames_to_copy to output buffer directly for (ma_uint64 f = 0; f < frames_to_copy; ++f) for (ma_uint32 c = 0; c < channels; ++c) f32Out[(totalFramesRead + f) * channels + c] = pcm[c][f]; totalFramesRead += frames_to_copy; framesAvail -= frames_to_copy; // If left-over decoded frames after output buffer fills, write to leftover if (framesAvail > 0) { for (ma_uint32 f = 0; f < (ma_uint64)framesAvail; ++f) for (ma_uint32 c = 0; c < channels; ++c) vorbisLeftoverBuffer[f * channels + c] = pcm[c][frames_to_copy + f]; p_webm->bufferLeftoverFrameCount = (ma_uint64)framesAvail; p_webm->bufferLeftoverFrameOffset = 0; framesAvail = 0; // Don't call vorbis_synthesis_read or increment cursor yet, do after finished with all available data! } // Consume these frames, even if we buffered them vorbis_synthesis_read(&p_webm->vorbisDSP, frames_to_copy + p_webm->bufferLeftoverFrameCount); // or just all at once depending on your loop p_webm->cursorInPCMFrames += (ma_uint64)(frames_to_copy + p_webm->bufferLeftoverFrameCount); break; // Output full, let next call handle leftovers } // Always read/consume all frames we got (even those discarded) vorbis_synthesis_read(&p_webm->vorbisDSP, (int)framesAvail); p_webm->cursorInPCMFrames += (ma_uint64)framesAvail; // Done seeking? if (seekTarget != (ma_uint64)-1 && p_webm->cursorInPCMFrames >= seekTarget) { p_webm->seekTargetPCMFrame = (ma_uint64)-1; } } } } ++p_webm->currentPacketFrame; } if (p_webm->currentPacketFrame >= p_webm->numFramesInPacket) { if (p_webm->currentPacket) nestegg_free_packet(p_webm->currentPacket); p_webm->currentPacket = NULL; p_webm->hasPacket = MA_FALSE; } } if (totalFramesRead < frame_count) { memset(f32Out + totalFramesRead * channels, 0, (frame_count - totalFramesRead) * channels * sizeof(float)); } if (p_frames_read) *p_frames_read = totalFramesRead; if (result == MA_SUCCESS && totalFramesRead == 0) result = MA_AT_END; return result; #else { MA_ASSERT(MA_FALSE); (void)p_frames_out; (void)frame_count; (void)p_frames_read; return MA_NOT_IMPLEMENTED; } #endif } MA_API ma_result ma_webm_seek_to_pcm_frame(ma_webm *p_webm, ma_uint64 frame_index) { if (!p_webm) return MA_INVALID_ARGS; // For Opus: 80ms preroll = 3840 @ 48000Hz ma_uint64 preroll = 0; ma_uint64 prerollFrame = frame_index; ma_uint64 tstamp_ns = 0; if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { preroll = (frame_index > 3840) ? 3840 : frame_index; prerollFrame = (frame_index > preroll) ? (frame_index - preroll) : 0; tstamp_ns = (prerollFrame * 1000000000ULL) / 48000; } else { prerollFrame = frame_index; tstamp_ns = (prerollFrame * 1000000000ULL) / p_webm->sample_rate; } if (nestegg_track_seek(p_webm->ctx, p_webm->audio_track, tstamp_ns) != 0) return MA_INVALID_OPERATION; // Reset packet and decoder state p_webm->hasPacket = MA_FALSE; if (p_webm->currentPacket) { nestegg_free_packet(p_webm->currentPacket); p_webm->currentPacket = NULL; } p_webm->currentPacketFrame = 0; p_webm->numFramesInPacket = 0; if (p_webm->codec_id == NESTEGG_CODEC_OPUS) opus_decoder_ctl(p_webm->opusDecoder, OPUS_RESET_STATE); else if (p_webm->codec_id == NESTEGG_CODEC_VORBIS) { vorbis_dsp_clear(&p_webm->vorbisDSP); vorbis_block_clear(&p_webm->vorbisBlock); vorbis_synthesis_init(&p_webm->vorbisDSP, &p_webm->vorbisInfo); vorbis_block_init(&p_webm->vorbisDSP, &p_webm->vorbisBlock); } p_webm->bufferLeftoverFrameCount = 0; p_webm->bufferLeftoverFrameOffset = 0; p_webm->cursorInPCMFrames = prerollFrame; p_webm->seekTargetPCMFrame = frame_index; if (p_webm->seekTargetPCMFrame == 0) p_webm->preSkipLeft = p_webm->opusPreSkip; else p_webm->preSkipLeft = 0; return MA_SUCCESS; } MA_API ma_result ma_webm_get_data_format( ma_webm *p_webm, ma_format *p_format, ma_uint32 *p_channels, ma_uint32 *p_sample_rate, ma_channel *p_channel_map, size_t channel_map_cap) { /* Defaults for safety. */ if (p_format != NULL) *p_format = ma_format_unknown; if (p_channels != NULL) *p_channels = 0; if (p_sample_rate != NULL) *p_sample_rate = 0; if (p_channel_map != NULL) MA_ZERO_MEMORY(p_channel_map, sizeof(*p_channel_map) * channel_map_cap); if (p_webm == NULL) return MA_INVALID_OPERATION; if (p_format != NULL) *p_format = p_webm->format; #if !defined(MA_NO_WEBM) { if (p_channels != NULL) { *p_channels = p_webm->channels; } if (p_sample_rate != NULL) { *p_sample_rate = p_webm->sample_rate; } if (p_channel_map != NULL) { if (p_channel_map != NULL) { ma_channel_map_init_standard( ma_standard_channel_map_vorbis, p_channel_map, channel_map_cap, p_webm->channels); } } return MA_SUCCESS; } #else { MA_ASSERT(MA_FALSE); return MA_NOT_IMPLEMENTED; } #endif } MA_API ma_result ma_webm_get_cursor_in_pcm_frames(ma_webm *p_webm, ma_uint64 *p_cursor) { if (p_cursor == NULL || p_webm == NULL) { return MA_INVALID_ARGS; } #if !defined(MA_NO_WEBM) { *p_cursor = p_webm->cursorInPCMFrames; return MA_SUCCESS; } #else { MA_ASSERT(MA_FALSE); return MA_NOT_IMPLEMENTED; } #endif } ma_uint64 calculate_length_in_pcm_frames(ma_webm *p_webm) { uint64_t duration_ns = 0; if (nestegg_duration(p_webm->ctx, &duration_ns) == 0 && duration_ns > 0) { // For Opus, duration_ns is always in 48kHz timebase per WebM spec if (p_webm->codec_id == NESTEGG_CODEC_OPUS) { // Convert nanoseconds to 48kHz PCM frames uint64_t total_frames_48k = (duration_ns * 48000ull) / 1000000000ull; // Subtract pre-skip and trimming (if known) uint64_t pre_skip = p_webm->opusPreSkip; if (total_frames_48k > pre_skip) total_frames_48k -= pre_skip; return total_frames_48k; } else { // For Vorbis and others, just use sample_rate return (ma_uint64)((duration_ns * (uint64_t)p_webm->sample_rate) / 1000000000ull); } } return 0; } MA_API ma_result ma_webm_get_length_in_pcm_frames(ma_webm *p_webm, ma_uint64 *p_length) { if (p_length == NULL || p_webm == NULL) { return MA_INVALID_ARGS; } #if !defined(MA_NO_WEBM) { if (p_webm->lengthInPCMFrames == 0) { p_webm->lengthInPCMFrames = calculate_length_in_pcm_frames(p_webm); } *p_length = p_webm->lengthInPCMFrames; return MA_SUCCESS; } #else { MA_ASSERT(MA_FALSE); return MA_NOT_IMPLEMENTED; } #endif } #endif kew/src/sys/000077500000000000000000000000001512074754200132605ustar00rootroot00000000000000kew/src/sys/mpris.c000066400000000000000000001432401512074754200145620ustar00rootroot00000000000000/** * @file mpris.c * @brief MPRIS (Media Player Remote Interfacing Specification) integration. * * Implements D-Bus MPRIS interface support for desktop integration, * allowing external clients to control playback (e.g., GNOME, KDE, etc.). */ #include "mpris.h" #include "sys_integration.h" #include "ui/control_ui.h" #include "ops/playback_clock.h" #include "ops/playback_ops.h" #include "ops/playback_state.h" #include "ops/playlist_ops.h" #include "sound/playback.h" #include "sound/volume.h" #include #include #ifndef __APPLE__ static guint registration_id; static guint bus_name_id; static guint player_registration_id; static const gchar *loop_status = "None"; static gdouble rate = 1.0; static gdouble volume = 0.5; static gdouble minimum_rate = 1.0; static gdouble maximum_rate = 1.0; static gboolean can_go_next = TRUE; static gboolean can_go_previous = TRUE; static gboolean can_play = TRUE; static gboolean can_pause = TRUE; static gboolean can_seek = FALSE; static gboolean can_control = TRUE; #define MAX_STATUS_LEN 64 const gchar *introspection_xml = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"; static const gchar *identity = "kew"; static const gchar *desktop_icon_name = ""; // Without file extension static const gchar *desktop_entry = ""; // The name of your .desktop file static void handle_raise(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)invocation; (void)user_data; g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_quit(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)invocation; (void)user_data; quit(); } static gboolean get_identity(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_string(identity); return TRUE; } static gboolean get_desktop_entry(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_string(desktop_entry); return TRUE; } static gboolean get_desktop_icon_name(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_string(desktop_icon_name); return TRUE; } static void handle_next(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)user_data; skip_to_next_song(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_previous(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)user_data; skip_to_prev_song(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_pause(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)invocation; (void)user_data; pause_song(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_play_pause(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)user_data; ops_toggle_pause(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_stop(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)user_data; if (!pb_is_stopped()) stop(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_play(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)parameters; (void)invocation; (void)user_data; play(); g_dbus_method_invocation_return_value(invocation, NULL); } static void handle_seek(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)user_data; gint64 offset; g_variant_get(parameters, "(x)", &offset); gboolean success = seek_position(offset, get_current_song_duration()); if (success) { g_dbus_method_invocation_return_value(invocation, NULL); } else { g_dbus_method_invocation_return_error( invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to seek to position"); } } static void handle_set_position(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)method_name; (void)user_data; const gchar *track_id; gint64 new_position; // - "o" is an object path (or track identifier) // - "x" is a 64-bit integer representing the position g_variant_get(parameters, "(&ox)", &track_id, &new_position); gboolean success = set_position(new_position, get_current_song_duration()); if (success) { // If setting the position was successful, return success with // no additional value g_dbus_method_invocation_return_value(invocation, NULL); } else { // If setting the position failed, return an error g_dbus_method_invocation_return_error( invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to set position for track %s", track_id); } } #endif #ifndef __APPLE__ static void handle_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0(method_name, "PlayPause") == 0) { handle_play_pause(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Next") == 0) { handle_next(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Previous") == 0) { handle_previous(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Pause") == 0) { handle_pause(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Stop") == 0) { handle_stop(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Play") == 0) { handle_play(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Seek") == 0) { handle_seek(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "SetPosition") == 0) { handle_set_position(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Raise") == 0) { handle_raise(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else if (g_strcmp0(method_name, "Quit") == 0) { handle_quit(connection, sender, object_path, interface_name, method_name, parameters, invocation, user_data); } else { g_dbus_method_invocation_return_dbus_error( invocation, "org.freedesktop.DBus.Error.UnknownMethod", "No such method"); } } #endif #ifndef __APPLE__ static void on_bus_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { (void)connection; (void)name; (void)user_data; } static void on_bus_name_lost(GDBusConnection *connection, const gchar *name, gpointer user_data) { (void)connection; (void)name; (void)user_data; } static gboolean get_playback_status(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; const gchar *status = "Stopped"; if (pb_is_paused()) { status = "Paused"; } else if (get_current_song() == NULL || pb_is_stopped()) { status = "Stopped"; } else { status = "Playing"; } *value = g_variant_new_string(status); return TRUE; } static gboolean get_loop_status(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_string(loop_status); return TRUE; } static gboolean get_rate(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_double(rate); return TRUE; } static gboolean get_shuffle(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_boolean(is_shuffle_enabled() ? TRUE : FALSE); return TRUE; } static gboolean get_metadata(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; SongData *current_song_data = get_current_song_data(); GVariantBuilder metadata_builder; g_variant_builder_init(&metadata_builder, G_VARIANT_TYPE_DICTIONARY); if (get_current_song() != NULL && current_song_data != NULL && current_song_data->metadata != NULL) { g_variant_builder_add( &metadata_builder, "{sv}", "xesam:title", g_variant_new_string(current_song_data->metadata->title)); // Build list of strings for artist const gchar *artist_list_storage[2]; artist_list_storage[0] = (g_strcmp0(current_song_data->metadata->artist, "") == 0) ? "" : current_song_data->metadata->artist; artist_list_storage[1] = NULL; g_variant_builder_add(&metadata_builder, "{sv}", "xesam:artist", g_variant_new_strv(artist_list_storage, -1)); gchar *coverArtUrl = g_strdup_printf("file://%s", current_song_data->cover_art_path); g_variant_builder_add( &metadata_builder, "{sv}", "xesam:album", g_variant_new_string(current_song_data->metadata->album)); g_variant_builder_add( &metadata_builder, "{sv}", "xesam:contentCreated", g_variant_new_string(current_song_data->metadata->date)); g_variant_builder_add(&metadata_builder, "{sv}", "mpris:artUrl", g_variant_new_string(coverArtUrl)); g_variant_builder_add( &metadata_builder, "{sv}", "mpris:trackid", g_variant_new_object_path(current_song_data->track_id)); gint64 length = llround(current_song_data->duration * G_USEC_PER_SEC); g_variant_builder_add(&metadata_builder, "{sv}", "mpris:length", g_variant_new_int64(length)); g_free(coverArtUrl); } else { g_variant_builder_add(&metadata_builder, "{sv}", "xesam:title", g_variant_new_string("")); static const gchar *empty_artist_list[] = { "", NULL }; g_variant_builder_add( &metadata_builder, "{sv}", "xesam:artist", g_variant_new_strv(empty_artist_list, -1)); g_variant_builder_add(&metadata_builder, "{sv}", "xesam:album", g_variant_new_string("")); g_variant_builder_add(&metadata_builder, "{sv}", "xesam:contentCreated", g_variant_new_string("")); g_variant_builder_add(&metadata_builder, "{sv}", "mpris:artUrl", g_variant_new_string("")); g_variant_builder_add( &metadata_builder, "{sv}", "mpris:trackid", g_variant_new_object_path( "/org/mpris/MediaPlayer2/TrackList/NoTrack")); gint64 placeholderLength = 0; g_variant_builder_add(&metadata_builder, "{sv}", "mpris:length", g_variant_new_int64(placeholderLength)); } GVariant *metadata_variant = g_variant_builder_end(&metadata_builder); *value = g_variant_ref_sink(metadata_variant); return TRUE; } static gboolean get_volume_mpris(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; volume = (gdouble)get_current_volume(); if (volume >= 1) volume = volume / 100; *value = g_variant_new_double(volume); return TRUE; } static gboolean get_position(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; // Convert elapsed_seconds from milliseconds to microseconds gint64 positionMicroseconds = llround(get_elapsed_seconds() * G_USEC_PER_SEC); *value = g_variant_new_int64(positionMicroseconds); return TRUE; } static gboolean get_minimum_rate(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_double(minimum_rate); return TRUE; } static gboolean get_maximum_rate(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_double(maximum_rate); return TRUE; } static gboolean get_can_go_next(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; Node *current = get_current_song(); PlayList *playlist = get_playlist(); can_go_next = (current == NULL || current->next != NULL) ? TRUE : FALSE; can_go_next = (is_repeat_list_enabled() && playlist->head != NULL) ? TRUE : can_go_next; *value = g_variant_new_boolean(can_go_next); return TRUE; } static gboolean get_can_go_previous(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; Node *current = get_current_song(); can_go_previous = (current == NULL || current->prev != NULL) ? TRUE : FALSE; *value = g_variant_new_boolean(can_go_previous); return TRUE; } static gboolean get_can_play(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; if (get_current_song() == NULL) can_play = FALSE; else can_play = TRUE; *value = g_variant_new_boolean(can_play); return TRUE; } static gboolean get_can_pause(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; if (get_current_song() == NULL) can_pause = FALSE; else can_pause = TRUE; *value = g_variant_new_boolean(can_pause); return TRUE; } static gboolean get_can_seek(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_boolean(can_seek); return TRUE; } static gboolean get_can_control(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant **value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)error; (void)user_data; *value = g_variant_new_boolean(can_control); return TRUE; } #endif #ifndef __APPLE__ static GVariant *get_property_callback(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { GVariant *value = NULL; if (g_strcmp0(property_name, "PlaybackStatus") == 0) { get_playback_status(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "LoopStatus") == 0) { get_loop_status(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Rate") == 0) { get_rate(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Shuffle") == 0) { get_shuffle(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Metadata") == 0) { get_metadata(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Volume") == 0) { get_volume_mpris(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Position") == 0) { get_position(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "MinimumRate") == 0) { get_minimum_rate(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "MaximumRate") == 0) { get_maximum_rate(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanGoNext") == 0) { get_can_go_next(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanGoPrevious") == 0) { get_can_go_previous(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanPlay") == 0) { get_can_play(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanPause") == 0) { get_can_pause(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanSeek") == 0) { get_can_seek(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "CanControl") == 0) { get_can_control(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "DesktopIconName") == 0) { get_desktop_icon_name(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "DesktopEntry") == 0) { get_desktop_entry(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else if (g_strcmp0(property_name, "Identity") == 0) { get_identity(connection, sender, object_path, interface_name, property_name, &value, error, user_data); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown property"); } // Check if value is NULL and set an error if needed if (value == NULL && error == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Property value is NULL"); } return value; } static gboolean set_property_callback(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant *value, GError **error, gpointer user_data) { (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)property_name; (void)user_data; if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2.Player") == 0) { if (g_strcmp0(property_name, "PlaybackStatus") == 0) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, "Setting PlaybackStatus property not supported"); return FALSE; } else if (g_strcmp0(property_name, "Volume") == 0) { double new_volume; g_variant_get(value, "d", &new_volume); if (new_volume > 1.0) new_volume = 1.0; if (new_volume < 0.0) new_volume = 0.0; new_volume *= 100; set_volume((int)new_volume); return TRUE; } else if (g_strcmp0(property_name, "LoopStatus") == 0) { toggle_repeat(); return TRUE; } else if (g_strcmp0(property_name, "Shuffle") == 0) { toggle_shuffle(); return TRUE; } else if (g_strcmp0(property_name, "Position") == 0) { gint64 new_position; g_variant_get(value, "x", &new_position); return set_position(new_position, get_current_song_duration()); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Setting property not supported"); return FALSE; } } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown interface"); return FALSE; } } #endif #ifndef __APPLE__ // MPRIS MediaPlayer2 interface vtable static const GDBusInterfaceVTable media_player_interface_vtable = { .method_call = handle_method_call, // We're using individual method handlers .get_property = get_property_callback, // Handle the property getters individually .set_property = set_property_callback, .padding = {handle_raise, handle_quit}}; // MPRIS Player interface vtable static const GDBusInterfaceVTable player_interface_vtable = { .method_call = handle_method_call, // We're using individual method handlers .get_property = get_property_callback, // Handle the property getters individually .set_property = set_property_callback, .padding = {handle_next, handle_previous, handle_pause, handle_play_pause, handle_stop, handle_play, handle_seek, handle_set_position}}; #endif void emit_playback_stopped_mpris() { #ifndef __APPLE__ if (get_gd_bus_connection()) { g_dbus_connection_call( get_gd_bus_connection(), NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set", g_variant_new("(ssv)", "org.mpris.MediaPlayer2.Player", "PlaybackStatus", g_variant_new_string("Stopped")), G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } #endif } void cleanup_mpris(void) { #ifndef __APPLE__ if (registration_id > 0) { g_dbus_connection_unregister_object(get_gd_bus_connection(), registration_id); registration_id = -1; } if (player_registration_id > 0) { g_dbus_connection_unregister_object(get_gd_bus_connection(), player_registration_id); player_registration_id = -1; } if (bus_name_id > 0) { g_bus_unown_name(bus_name_id); bus_name_id = -1; } if (get_gd_bus_connection() != NULL) { g_object_unref(get_gd_bus_connection()); set_gd_bus_connection(NULL); } if (get_g_main_context() != NULL) { g_main_context_unref(get_g_main_context()); set_g_main_context(NULL); } #endif } void init_mpris(void) { #ifndef __APPLE__ AppState *state = get_app_state(); if (get_g_main_context() == NULL) { set_g_main_context(g_main_context_new()); } GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); set_gd_bus_connection(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL)); if (!get_gd_bus_connection()) { g_dbus_node_info_unref(introspection_data); g_printerr("Failed to connect to D-Bus\n"); exit(0); } const char *app_name = "org.mpris.MediaPlayer2.kew"; GError *error = NULL; bus_name_id = g_bus_own_name_on_connection( get_gd_bus_connection(), app_name, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_name_acquired, on_bus_name_lost, NULL, NULL); if (bus_name_id == 0) { printf(_("Failed to own D-Bus name: %s\n"), app_name); exit(0); } registration_id = g_dbus_connection_register_object( get_gd_bus_connection(), "/org/mpris/MediaPlayer2", introspection_data->interfaces[0], &media_player_interface_vtable, state, NULL, &error); if (!registration_id) { g_dbus_node_info_unref(introspection_data); g_printerr("Failed to register media player object: %s\n", error->message); g_error_free(error); exit(0); } player_registration_id = g_dbus_connection_register_object( get_gd_bus_connection(), "/org/mpris/MediaPlayer2", introspection_data->interfaces[1], &player_interface_vtable, state, NULL, &error); if (!player_registration_id) { g_dbus_node_info_unref(introspection_data); g_printerr("Failed to register media player object: %s\n", error->message); g_error_free(error); exit(0); } g_dbus_node_info_unref(introspection_data); #endif } void emit_start_playing_mpris() { #ifndef __APPLE__ GVariant *parameters = g_variant_new("(s)", "Playing"); g_dbus_connection_emit_signal( get_gd_bus_connection(), NULL, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", "PlaybackStatusChanged", parameters, NULL); #endif } gchar *sanitize_title(const gchar *title) { gchar *sanitized = g_strdup(title); // Replace underscores with hyphens, otherwise some widgets have a // problem g_strdelimit(sanitized, "_", '-'); // Duplicate string otherwise widgets have a problem with certain // strings for some reason gchar *sanitized_dup = g_strdup_printf("%s", sanitized); g_free(sanitized); return sanitized_dup; } #ifndef __APPLE__ static guint64 last_emit_time = 0; #endif void emit_properties_changed(GDBusConnection *connection, const gchar *property_name, GVariant *new_value) { #ifndef __APPLE__ GVariantBuilder changed_properties_builder; if (connection == NULL || property_name == NULL || new_value == NULL) return; // Initialize the builder for changed properties g_variant_builder_init(&changed_properties_builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&changed_properties_builder, "{sv}", property_name, new_value); GError *error = NULL; gboolean result = g_dbus_connection_emit_signal( connection, NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &changed_properties_builder, NULL), &error); if (!result) { g_critical("Failed to emit PropertiesChanged signal: %s", error->message); g_error_free(error); } else { g_debug("PropertiesChanged signal emitted successfully."); } g_variant_builder_clear(&changed_properties_builder); #else (void)connection; (void)property_name; (void)new_value; #endif } void emit_volume_changed(void) { #ifndef __APPLE__ gdouble newVolume = (gdouble)get_current_volume() / 100; if (newVolume > 1.0) return; // Emit the PropertiesChanged signal for the volume property GVariant *volume_variant = g_variant_new_double(newVolume); emit_properties_changed(get_gd_bus_connection(), "volume", volume_variant); #endif } void emit_shuffle_changed(void) { #ifndef __APPLE__ gboolean shuffle_enabled = is_shuffle_enabled(); // Emit the PropertiesChanged signal for the volume property GVariant *volume_variant = g_variant_new_boolean(shuffle_enabled); emit_properties_changed(get_gd_bus_connection(), "Shuffle", volume_variant); #endif } void emit_metadata_changed(const gchar *title, const gchar *artist, const gchar *album, const gchar *cover_art_path, const gchar *track_id, Node *current_song, gint64 length) { #ifndef __APPLE__ guint64 current_time = g_get_monotonic_time(); if (current_time - last_emit_time < 500000) // 0.5 seconds { g_debug("Debounced signal emission."); return; } last_emit_time = current_time; if (!title || !album || !track_id) { g_warning( "Invalid metadata: title, album, or track_id is NULL."); return; } gchar *coverArtUrl = NULL; gchar *sanitized_title = sanitize_title(title); g_debug("Starting to build metadata."); GVariantBuilder metadata_builder; g_variant_builder_init(&metadata_builder, G_VARIANT_TYPE_DICTIONARY); g_variant_builder_add(&metadata_builder, "{sv}", "xesam:title", g_variant_new_string(sanitized_title)); g_free(sanitized_title); const gchar *artist_list[2]; if (artist) { artist_list[0] = artist; artist_list[1] = NULL; } else { artist_list[0] = ""; artist_list[1] = NULL; } g_variant_builder_add(&metadata_builder, "{sv}", "xesam:artist", g_variant_new_strv(artist_list, -1)); g_variant_builder_add(&metadata_builder, "{sv}", "xesam:album", g_variant_new_string(album)); if (cover_art_path && *cover_art_path != '\0') { coverArtUrl = g_strdup_printf("file://%s", cover_art_path); g_variant_builder_add(&metadata_builder, "{sv}", "mpris:artUrl", g_variant_new_string(coverArtUrl)); g_debug("Cover art URL added: %s", coverArtUrl); g_free(coverArtUrl); } g_variant_builder_add(&metadata_builder, "{sv}", "mpris:trackid", g_variant_new_object_path(track_id)); g_variant_builder_add(&metadata_builder, "{sv}", "mpris:length", g_variant_new_int64(length)); GVariant *metadata_variant = g_variant_builder_end(&metadata_builder); if (!metadata_variant) { g_warning("Failed to end metadata GVariantBuilder."); return; } g_debug("Metadata built successfully."); GVariantBuilder changed_properties_builder; g_variant_builder_init(&changed_properties_builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&changed_properties_builder, "{sv}", "Metadata", metadata_variant); g_variant_builder_add( &changed_properties_builder, "{sv}", "CanGoPrevious", g_variant_new_boolean( (current_song != NULL && current_song->prev != NULL))); PlayList *playlist = get_playlist(); can_go_next = (current_song == NULL || current_song->next != NULL) ? TRUE : FALSE; can_go_next = (is_repeat_list_enabled() && playlist->head != NULL) ? TRUE : can_go_next; g_variant_builder_add(&changed_properties_builder, "{sv}", "CanGoNext", g_variant_new_boolean(can_go_next)); g_variant_builder_add(&changed_properties_builder, "{sv}", "Shuffle", g_variant_new_boolean(is_shuffle_enabled())); g_variant_builder_add( &changed_properties_builder, "{sv}", "CanPlay", g_variant_new_boolean(length != 0 ? true : false)); g_variant_builder_add( &changed_properties_builder, "{sv}", "CanPause", g_variant_new_boolean(length != 0 ? true : false)); if (is_repeat_enabled()) g_variant_builder_add(&changed_properties_builder, "{sv}", "LoopStatus", g_variant_new_string("Track")); else if (is_repeat_list_enabled()) g_variant_builder_add(&changed_properties_builder, "{sv}", "LoopStatus", g_variant_new_string("List")); else g_variant_builder_add(&changed_properties_builder, "{sv}", "LoopStatus", g_variant_new_string("None")); can_seek = true; g_variant_builder_add(&changed_properties_builder, "{sv}", "CanSeek", g_variant_new_boolean(can_seek)); g_debug("PropertiesChanged signal is ready to be emitted."); GError *error = NULL; gboolean result = g_dbus_connection_emit_signal( get_gd_bus_connection(), NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &changed_properties_builder, NULL), &error); if (!result) { g_critical("Failed to emit PropertiesChanged signal: %s", error->message); g_error_free(error); } else { g_debug("PropertiesChanged signal emitted successfully."); } g_variant_builder_clear(&changed_properties_builder); g_variant_builder_clear(&metadata_builder); #else (void)title; (void)artist; (void)album; (void)cover_art_path; (void)track_id; (void)current_song; (void)length; #endif } kew/src/sys/mpris.h000066400000000000000000000016151512074754200145660ustar00rootroot00000000000000/** * @file mpris.h * @brief MPRIS (Media Player Remote Interfacing Specification) integration. * * Implements D-Bus MPRIS interface support for desktop integration, * allowing external clients to control playback (e.g., GNOME, KDE, etc.). */ #ifndef MPRIS_H #define MPRIS_H #include "common/appstate.h" #include "data/playlist.h" #include void init_mpris(void); void emit_string_property_changed(const gchar *property_name, const gchar *new_value); void emit_boolean_property_changed(const gchar *property_name, gboolean new_value); void emit_volume_changed(void); void emit_shuffle_changed(void); void emit_metadata_changed(const gchar *title, const gchar *artist, const gchar *album, const gchar *cover_art_path, const gchar *track_id, Node *current_song, gint64 length); void emit_start_playing_mpris(void); void emit_playback_stopped_mpris(void); void cleanup_mpris(void); #endif kew/src/sys/notifications.c000066400000000000000000000325101512074754200162760ustar00rootroot00000000000000/** * @file notifications.c * @brief Desktop notifications integration. * * Sends system notifications for playback events such as * song changes, errors, or status updates. */ #include "notifications.h" #include #include #include #include #include #include #include #include #include #include bool is_valid_filepath(const char *path) { if (path == NULL || *path == '\0' || strnlen(path, PATH_MAX) >= PATH_MAX) return false; struct stat st; return stat(path, &st) == 0 && S_ISREG(st.st_mode); } void remove_blacklisted_chars( const char *input, const char *blacklist, char *output, size_t output_size) { if (!input || !blacklist || !output || output_size < 2) { if (output && output_size > 0) output[0] = '\0'; return; } const char *in_ptr = input; char *out_ptr = output; size_t chars_copied = 0; while (*in_ptr && chars_copied < output_size - 1) { const char *start = in_ptr; unsigned char c = (unsigned char)*in_ptr; int len = 1; // Determine UTF-8 character length if ((c & 0x80) == 0x00) { len = 1; } else if ((c & 0xe0) == 0xc0) { len = 2; } else if ((c & 0xf0) == 0xe0) { len = 3; } else if ((c & 0xf8) == 0xf0) { len = 4; } // Check if the character is blacklisted bool blacklisted = false; const char *bl_ptr = blacklist; while (*bl_ptr) { const char *bl_start = bl_ptr; unsigned char bc = (unsigned char)*bl_ptr; int bl_len = 1; if ((bc & 0x80) == 0x00) { bl_len = 1; } else if ((bc & 0xe0) == 0xc0) { bl_len = 2; } else if ((bc & 0xf0) == 0xe0) { bl_len = 3; } else if ((bc & 0xf8) == 0xf0) { bl_len = 4; } if (len == bl_len && memcmp(start, bl_start, len) == 0) { blacklisted = true; break; } bl_ptr += bl_len; } if (!blacklisted) { for (int i = 0; i < len; i++) { if (chars_copied >= output_size - 1) break; *out_ptr++ = *in_ptr++; chars_copied++; } } else { in_ptr += len; } } *out_ptr = '\0'; } void ensure_non_empty(char *str, size_t buffer_size) { if (str == NULL || buffer_size < 2) { return; } if (str[0] == '\0') { str[0] = ' '; str[1] = '\0'; } } #ifdef USE_DBUS #define NOTIFICATION_INTERVAL_MICROSECONDS 500000 // 0.5 seconds struct timeval last_notification_time = {0, 0}; static char sanitized_artist[512]; static char sanitized_title[512]; static pthread_mutex_t notification_mutex = PTHREAD_MUTEX_INITIALIZER; int can_show_notification(void) { struct timeval now; gettimeofday(&now, NULL); pthread_mutex_lock(¬ification_mutex); // Calculate elapsed time in microseconds using 64-bit unsigned math int64_t sec_diff = (int64_t)(now.tv_sec - last_notification_time.tv_sec); int64_t usec_diff = (int64_t)(now.tv_usec - last_notification_time.tv_usec); if (usec_diff < 0) { usec_diff += 1000000; sec_diff -= 1; } uint64_t elapsed = (uint64_t)(sec_diff * 1000000 + usec_diff); if (elapsed >= NOTIFICATION_INTERVAL_MICROSECONDS) { last_notification_time = now; pthread_mutex_unlock(¬ification_mutex); return 1; } pthread_mutex_unlock(¬ification_mutex); return 0; } void on_notification_closed(void) { } static GDBusConnection *connection = NULL; static guint last_notification_id = 0; static guint signal_subscription_id = 0; static void on_dbus_call_complete(GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GVariant *result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); if (error) { fprintf(stderr, "D-Bus call failed or timed out: %s\n", error->message); g_error_free(error); return; } // Extract the notification ID from the result guint32 *last_notification_id = (guint32 *)user_data; g_variant_get(result, "(u)", last_notification_id); g_variant_unref(result); } static void on_notification_closed_signal(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { (void)connection; (void)sender_name; (void)object_path; (void)interface_name; (void)signal_name; (void)user_data; guint32 id, reason; g_variant_get(parameters, "(uu)", &id, &reason); if (id == last_notification_id) { last_notification_id = 0; } } static void on_close_notification_complete(GObject *source_object, GAsyncResult *res, gpointer user_data) { (void)user_data; GError *error = NULL; GVariant *result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); if (error) { fprintf(stderr, "Failed to close notification: %s\n", error->message); g_error_free(error); } else if (result) { g_variant_unref(result); } last_notification_id = 0; } typedef struct { GMainLoop *loop; gboolean connected; GDBusConnection *connection; gboolean timeout_triggered; int ref_count; } BusConnectionData; static void bus_connection_data_ref(BusConnectionData *data) { g_atomic_int_inc(&(data->ref_count)); } static void bus_connection_data_unref(BusConnectionData *data) { if (g_atomic_int_dec_and_test(&(data->ref_count))) { g_main_loop_unref(data->loop); g_free(data); } } static gboolean on_timeout(gpointer user_data) { BusConnectionData *data = (BusConnectionData *)user_data; if (!data->connected) { fprintf(stderr, "D-Bus connection timed out.\n"); data->timeout_triggered = TRUE; g_main_loop_quit(data->loop); } // Decrement reference count bus_connection_data_unref(data); return FALSE; // Stop the timeout callback from repeating } static void on_bus_get_complete(GObject *source_object, GAsyncResult *res, gpointer user_data) { (void)source_object; BusConnectionData *data = (BusConnectionData *)user_data; GError *error = NULL; data->connection = g_bus_get_finish(res, &error); if (error) { fprintf(stderr, "Failed to connect to D-Bus: %s\n", error->message); g_error_free(error); } else { data->connected = TRUE; } if (!data->timeout_triggered) { g_main_loop_quit(data->loop); } // Decrement reference count bus_connection_data_unref(data); } GDBusConnection *get_dbus_connection_with_timeout(GBusType bus_type, guint timeout_ms) { // Allocate and initialize the data structure BusConnectionData *data = g_new0(BusConnectionData, 1); data->loop = g_main_loop_new(NULL, FALSE); data->connected = FALSE; data->connection = NULL; data->timeout_triggered = FALSE; data->ref_count = 1; // Start with a single reference // Increment reference count for each callback bus_connection_data_ref(data); // For on_timeout bus_connection_data_ref(data); // For on_bus_get_complete // Start the asynchronous bus connection g_bus_get(bus_type, NULL, on_bus_get_complete, data); // Add a timeout callback g_timeout_add(timeout_ms, on_timeout, data); // Run the main loop g_main_loop_run(data->loop); // Store the connection result before cleaning up GDBusConnection *connection = data->connection; // Decrement reference count for the main loop bus_connection_data_unref(data); return connection; } void cleanup_previous_notification() { if (last_notification_id != 0) { // Send CloseNotification call for the active notification g_dbus_connection_call( connection, "org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "CloseNotification", g_variant_new("(u)", last_notification_id), NULL, // No return value expected G_DBUS_CALL_FLAGS_NONE, 100, // Timeout in milliseconds NULL, // Cancellable on_close_notification_complete, NULL); } } int display_song_notification(const char *artist, const char *title, const char *cover, UISettings *ui) { if (!ui->allowNotifications || !can_show_notification()) { return 0; } if (connection == NULL) { connection = get_dbus_connection_with_timeout(G_BUS_TYPE_SESSION, 100); if (connection == NULL) { fprintf(stderr, "Failed to connect to session bus\n"); return -1; } signal_subscription_id = g_dbus_connection_signal_subscribe( connection, "org.freedesktop.Notifications", "org.freedesktop.Notifications", "NotificationClosed", "/org/freedesktop/Notifications", NULL, G_DBUS_SIGNAL_FLAGS_NONE, on_notification_closed_signal, NULL, NULL); } const char *blacklist = "&;|*~<>^()[]{}$\\\""; remove_blacklisted_chars(artist, blacklist, sanitized_artist, sizeof(sanitized_artist)); remove_blacklisted_chars(title, blacklist, sanitized_title, sizeof(sanitized_title)); ensure_non_empty(sanitized_artist, sizeof(sanitized_title)); ensure_non_empty(sanitized_title, sizeof(sanitized_title)); int coverExists = is_valid_filepath(cover); cleanup_previous_notification(); // Create a new notification const gchar *app_name = "kew"; const gchar *app_icon = (coverExists && cover) ? cover : ""; const gchar *summary = sanitized_artist; const gchar *body = sanitized_title; GVariantBuilder actions_builder; g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as")); GVariantBuilder hints_builder; g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}")); gint32 expire_timeout = -1; g_dbus_connection_call( connection, "org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify", g_variant_new("(susssasa{sv}i)", app_name, 0, // New notification, no replaces_id app_icon, summary, body, &actions_builder, &hints_builder, expire_timeout), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, 100, // Timeout in milliseconds NULL, // Cancellable on_dbus_call_complete, &last_notification_id); return 0; } void cleanup_notifications() { cleanup_previous_notification(); // Unsubscribe from signals if (signal_subscription_id != 0) { g_dbus_connection_signal_unsubscribe(connection, signal_subscription_id); signal_subscription_id = 0; } // Release the connection if (connection != NULL) { g_object_unref(connection); connection = NULL; } } #endif kew/src/sys/notifications.h000066400000000000000000000007421512074754200163050ustar00rootroot00000000000000/** * @file notifications.h * @brief Desktop notifications integration. * * Sends system notifications for playback events such as * song changes, errors, or status updates. */ #ifndef NOTIFICATIONS_H #define NOTIFICATIONS_H #include "common/appstate.h" #ifndef PATH_MAX #define PATH_MAX 4096 #endif #ifdef USE_DBUS int display_song_notification(const char *artist, const char *title, const char *cover, UISettings *ui); void cleanup_notifications(void); #endif #endif kew/src/sys/sys_integration.c000066400000000000000000000277061512074754200166610ustar00rootroot00000000000000/** * @file sys_integration.c * @brief System-level integrations and OS interop. * * Provides hooks for platform-specific features such as * power management, clipboard access, or system event handling. */ #include "sys_integration.h" #include "common/common.h" #include "mpris.h" #include "notifications.h" #include "ui/player_ui.h" #include "utils/file.h" #include "utils/term.h" #include "utils/utils.h" #include "gio/gio.h" #include "math.h" #include #include static GDBusConnection *connection = NULL; static GMainContext *global_main_context = NULL; void set_g_main_context(GMainContext *val) { global_main_context = val; } void *get_g_main_context(void) { return global_main_context; } GDBusConnection *get_gd_bus_connection(void) { return connection; } void set_gd_bus_connection(GDBusConnection *val) { connection = val; } void process_d_bus_events(void) { while (g_main_context_pending(get_g_main_context())) { g_main_context_iteration(get_g_main_context(), FALSE); } } void resize(UIState *uis) { alarm(1); // Timer while (uis->resizeFlag) { uis->resizeFlag = 0; c_sleep(100); } alarm(0); // Cancel timer printf("\033[1;1H"); clear_screen(); trigger_redraw_side_cover(); trigger_refresh(); } void emit_string_property_changed(const gchar *property_name, const gchar *new_value) { #ifndef __APPLE__ GVariantBuilder changed_properties_builder; g_variant_builder_init(&changed_properties_builder, G_VARIANT_TYPE("a{sv}")); GVariant *value_variant = g_variant_new_string(new_value); g_variant_builder_add(&changed_properties_builder, "{sv}", property_name, value_variant); GVariant *signal_variant = g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &changed_properties_builder, NULL); g_variant_ref_sink(signal_variant); g_dbus_connection_emit_signal( connection, NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", signal_variant, NULL); g_variant_builder_clear(&changed_properties_builder); g_variant_unref(signal_variant); #else (void)property_name; (void)new_value; #endif } void update_playback_position(double elapsed_seconds) { #ifndef __APPLE__ if (elapsed_seconds < 0.0) elapsed_seconds = 0.0; // Max safe seconds to avoid overflow when multiplied by 1,000,000 const double max_seconds = (double)(LLONG_MAX / G_USEC_PER_SEC); if (elapsed_seconds > max_seconds) elapsed_seconds = max_seconds; GVariantBuilder changedPropertiesBuilder; g_variant_builder_init(&changedPropertiesBuilder, G_VARIANT_TYPE_DICTIONARY); g_variant_builder_add( &changedPropertiesBuilder, "{sv}", "Position", g_variant_new_int64(llround(elapsed_seconds * G_USEC_PER_SEC))); GVariant *parameters = g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &changedPropertiesBuilder, NULL); g_dbus_connection_emit_signal(connection, NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", parameters, NULL); #else (void)elapsed_seconds; #endif } void emit_seeked_signal(double new_position_seconds) { #ifndef __APPLE__ if (new_position_seconds < 0.0) new_position_seconds = 0.0; const double max_seconds = (double)(LLONG_MAX / G_USEC_PER_SEC); if (new_position_seconds > max_seconds) new_position_seconds = max_seconds; gint64 newPositionMicroseconds = llround(new_position_seconds * G_USEC_PER_SEC); GVariant *parameters = g_variant_new("(x)", newPositionMicroseconds); g_dbus_connection_emit_signal( connection, NULL, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", "Seeked", parameters, NULL); #else (void)new_position_seconds; #endif } void emit_boolean_property_changed(const gchar *property_name, gboolean new_value) { #ifndef __APPLE__ GVariantBuilder changed_properties_builder; g_variant_builder_init(&changed_properties_builder, G_VARIANT_TYPE("a{sv}")); GVariant *value_variant = g_variant_new_boolean(new_value); if (value_variant == NULL) { fprintf(stderr, "Failed to allocate GVariant for boolean property\n"); return; } g_variant_builder_add(&changed_properties_builder, "{sv}", property_name, value_variant); GVariant *signal_variant = g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &changed_properties_builder, NULL); if (signal_variant == NULL) { fprintf(stderr, "Failed to allocate GVariant for " "PropertiesChanged signal\n"); g_variant_builder_clear(&changed_properties_builder); return; } g_dbus_connection_emit_signal( connection, NULL, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", signal_variant, NULL); g_variant_builder_clear(&changed_properties_builder); #else (void)property_name; (void)new_value; #endif } void notify_mpris_switch(SongData *current_song_data) { if (current_song_data == NULL) return; gint64 length = get_length_in_micro_sec(current_song_data->duration); // Update mpris emit_metadata_changed( current_song_data->metadata->title, current_song_data->metadata->artist, current_song_data->metadata->album, current_song_data->cover_art_path, current_song_data->track_id != NULL ? current_song_data->track_id : "", get_current_song(), length); } void notify_song_switch(SongData *current_song_data) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); if (current_song_data != NULL && current_song_data->hasErrors == 0 && current_song_data->metadata && strnlen(current_song_data->metadata->title, 10) > 0) { #ifdef USE_DBUS display_song_notification(current_song_data->metadata->artist, current_song_data->metadata->title, current_song_data->cover_art_path, ui); #else (void)ui; #endif notify_mpris_switch(current_song_data); Node *current = get_current_song(); if (current != NULL) state->uiState.lastNotifiedId = current->id; } } int is_process_running(pid_t pid) { if (pid <= 0) { return 0; // Invalid PID } // Send signal 0 to check if the process exists if (kill(pid, 0) == 0) { return 1; // Process exists } // Check errno for detailed status if (errno == ESRCH) { return 0; // No such process } else if (errno == EPERM) { return 1; // Process exists but we don't have permission } return 0; // Other errors } int is_kew_process(pid_t pid) { char comm_path[64]; char process_name[256]; FILE *file; // First check /proc/[pid]/comm for the process name snprintf(comm_path, sizeof(comm_path), "/proc/%d/comm", pid); file = fopen(comm_path, "r"); if (file != NULL) { if (fgets(process_name, sizeof(process_name), file)) { fclose(file); // Remove trailing newline process_name[strcspn(process_name, "\n")] = 0; // Check if it's kew (process name might be truncated to // 15 chars) if (strstr(process_name, "kew") != NULL) { return 1; // It's likely kew } } else { fclose(file); } } return 0; // Not kew or couldn't determine } void delete_pid_file() { char pidfile_path[PATH_MAX]; const char *temp_dir = get_temp_dir(); snprintf(pidfile_path, sizeof(pidfile_path), "%s/kew_%d.pid", temp_dir, getuid()); FILE *pidfile; pidfile = fopen(pidfile_path, "r"); if (pidfile != NULL) { fclose(pidfile); unlink(pidfile_path); } } pid_t read_pid_file() { char pidfile_path[PATH_MAX]; const char *temp_dir = get_temp_dir(); snprintf(pidfile_path, sizeof(pidfile_path), "%s/kew_%d.pid", temp_dir, getuid()); FILE *pidfile; pid_t pid; pidfile = fopen(pidfile_path, "r"); if (pidfile != NULL) { if (fscanf(pidfile, "%d", &pid) == 1) { fclose(pidfile); } } else { { pid = -1; unlink(pidfile_path); } } return pid; } void create_pid_file() { char pidfile_path[PATH_MAX]; const char *temp_dir = get_temp_dir(); snprintf(pidfile_path, sizeof(pidfile_path), "%s/kew_%d.pid", temp_dir, getuid()); FILE *pidfile = fopen(pidfile_path, "w"); if (pidfile == NULL) { perror("Unable to create PID file"); exit(1); } fprintf(pidfile, "%d\n", getpid()); fclose(pidfile); } void restart_kew(char *argv[]) { pid_t oldpid = read_pid_file(); if (oldpid > 0) { if (kill(oldpid, SIGUSR1) != 0) { if (errno == ESRCH) { fprintf(stderr, "No running kew process found.\n"); delete_pid_file(); } else { fprintf(stderr, "Failed to stop old kew (pid %d): %s\n", oldpid, strerror(errno)); } } else { int status; if (waitpid(oldpid, &status, 0) == -1 && errno != ECHILD) { perror("waitpid"); } delete_pid_file(); } } execvp("kew", argv); fprintf(stderr, "Failed to restart kew via execvp: %s\n", strerror(errno)); exit(1); } void handle_shutdown(int sig) { (void)sig; exit(0); // runs all atexit handlers } // Ensures only a single instance of kew can run at a time for the current user. void restart_if_already_running(char *argv[]) { signal(SIGUSR1, handle_shutdown); pid_t pid = read_pid_file(); #ifdef __ANDROID__ if (is_process_running(pid) && is_kew_process(pid)) #else if (is_process_running(pid)) #endif { restart_kew(argv); } else { delete_pid_file(); } create_pid_file(); } void handle_resize(int sig) { (void)sig; AppState *state = get_app_state(); state->uiState.resizeFlag = 1; } void reset_resize_flag(int sig) { (void)sig; AppState *state = get_app_state(); state->uiState.resizeFlag = 0; } void init_resize(void) { signal(SIGWINCH, handle_resize); struct sigaction sa; sa.sa_handler = reset_resize_flag; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); } void quit(void) { exit(0); } kew/src/sys/sys_integration.h000066400000000000000000000020751512074754200166560ustar00rootroot00000000000000/** * @file sys_integration.h * @brief System-level integrations and OS interop. * * Provides hooks for platform-specific features such as * power management, clipboard access, or system event handling. */ #ifndef SYS_INTEGRATION_H #define SYS_INTEGRATION_H #include "common/appstate.h" #include "gio/gio.h" #include "glib.h" void set_g_main_context(GMainContext *val); void *get_g_main_context(void); void set_gd_bus_connection(GDBusConnection *val); void emit_string_property_changed(const gchar *property_name, const gchar *new_value); void update_playback_position(double elapsed_seconds); void emit_seeked_signal(double new_position_seconds); void emit_boolean_property_changed(const gchar *property_name, gboolean new_value); void notify_mpris_switch(SongData *current_song_data); void notify_song_switch(SongData *current_song_data); void process_d_bus_events(void); void resize(UIState *uis); void restart_if_already_running(char *argv[]); void restart_kew(char *argv[]); void init_resize(void); void quit(void); GDBusConnection *get_gd_bus_connection(void); #endif kew/src/ui/000077500000000000000000000000001512074754200130575ustar00rootroot00000000000000kew/src/ui/chroma.c000066400000000000000000000227741512074754200145100ustar00rootroot00000000000000/** * @file chroma.c * @brief To handle running Chroma in a thread and printing its output * * Contains functions to start, stop and print a chroma frame. */ #include "chroma.h" #include "data/img_func.h" #include "utils/term.h" #include "utils/utils.h" #include #include #include #include #include #include #include typedef struct { char *frame; pthread_mutex_t lock; int height; int width; bool running; pthread_t thread; int preset; size_t frame_capacity; } Chroma; Chroma g_viz = { .lock = PTHREAD_MUTEX_INITIALIZER, .height = 0, .width = 0, .running = false, .preset = 0, }; #define CHROMA_MAX_BUF (512 * 1024) #define MAX_HEIGHT 4000 #define MAX_PRESET 11 volatile int chroma_new_frame = 0; static int centered_indent = 0; static bool chroma_started; void chroma_set_next_preset(int height) { g_viz.preset++; if (g_viz.preset == 22) g_viz.preset = 0; if (g_viz.preset % MAX_PRESET == 0) { chroma_stop(); } else { chroma_stop(); chroma_start(height); } } int calc_chroma_width(int height) { TermSize ts; tty_init(); get_tty_size(&ts); int cell_w = (ts.width_pixels > 0 && ts.width_cells > 0) ? ts.width_pixels / ts.width_cells : 8; int cell_h = (ts.height_pixels > 0 && ts.height_cells > 0) ? ts.height_pixels / ts.height_cells : 16; float aspect = (float)cell_h / (float)cell_w; float aspect_ratio_correction = aspect; int corrected_width = (int)(height * aspect_ratio_correction) - 1; if (corrected_width < 0) corrected_width = 0; if (corrected_width > ts.width_cells) corrected_width = ts.width_cells; centered_indent = (ts.width_cells - corrected_width) / 2; int width = (int)(height * aspect); if (width < 1) width = 1; return width; } // FIXME: Enable Chroma // static void *chroma_thread(void *arg) // { // int height = *(int *)arg; // free(arg); // Caller must malloc the int // if (height <= 0) // height = 1; // if (height > MAX_HEIGHT) // height = MAX_HEIGHT; // int synced = 0; // g_viz.frame_capacity = CHROMA_MAX_BUF; // g_viz.frame = malloc(g_viz.frame_capacity); // if (!g_viz.frame) { // exit(1); // } // g_viz.frame[0] = '\0'; // while (g_viz.running) { // pthread_mutex_lock(&g_viz.lock); // g_viz.height = height; // g_viz.width = calc_chroma_width(height); // pthread_mutex_unlock(&g_viz.lock); // char cmd[256]; // int n = snprintf(cmd, sizeof(cmd), // //"chroma --stream %dx%d --config /home/ravachol/Documents/chroma/examples/%d.toml --fps 30", width, height, g_viz.preset); // "chroma --stream %dx%d --fps 30", g_viz.width, g_viz.height); // if (n < 0 || n >= (int)sizeof(cmd)) { // // Truncated or error; skip this iteration // usleep(100000); // continue; // } // FILE *fp = popen(cmd, "r"); // if (!fp) { // usleep(100000); // continue; // } // size_t pos = 0; // char *buf = malloc(CHROMA_MAX_BUF); // if (!buf) { // pclose(fp); // usleep(100000); // continue; // } // int nl_streak = 0; // while (g_viz.running) { // ssize_t r = read(fileno(fp), buf + pos, CHROMA_MAX_BUF - pos); // if (r <= 0) // break; // EOF or error // size_t end = pos + r; // total valid bytes in buf // size_t start = 0; // start of unprocessed bytes // for (size_t i = pos; i < end; i++) { // if (buf[i] == '\n') { // nl_streak++; // } else { // nl_streak = 0; // } // if (nl_streak == 2) { // size_t frame_len = i - start + 1; // if (!synced) { // synced = 1; // start = i + 1; // nl_streak = 0; // continue; // } // if (frame_len < CHROMA_MAX_BUF && pthread_mutex_trylock(&g_viz.lock) == 0) { // memcpy(g_viz.frame, buf + start, frame_len); // g_viz.frame[frame_len] = '\0'; // chroma_new_frame = 1; // pthread_mutex_unlock(&g_viz.lock); // } // start = i + 1; // move past this complete frame // nl_streak = 0; // } // } // // Move leftover bytes to the start of buffer for next read // pos = end - start; // if (pos > 0) { // memmove(buf, buf + start, pos); // } else { // pos = 0; // } // // If buffer overflows, skip frame to avoid corruption // if (pos >= CHROMA_MAX_BUF) { // pos = 0; // nl_streak = 0; // synced = 0; // } // } // if (buf) // free(buf); // pclose(fp); // } // return NULL; // } void chroma_start(int height) { // FIXME: Enable Chroma (void)height; // if (g_viz.running) // return; // g_viz.running = 1; // int *arg = malloc(sizeof(int)); // *arg = height; // pthread_create(&g_viz.thread, NULL, chroma_thread, arg); // chroma_started = true; } void chroma_stop() { if (!g_viz.running) return; g_viz.running = 0; // Tell thread to exit pthread_join(g_viz.thread, NULL); // Wait until it finishes pthread_mutex_lock(&g_viz.lock); if (g_viz.frame) { free(g_viz.frame); g_viz.frame = NULL; } pthread_mutex_unlock(&g_viz.lock); chroma_started = false; } void chroma_print_frame(int row, int col, bool centered) { if (!chroma_new_frame) return; char *tmp_buf = NULL; size_t frame_len = 0; pthread_mutex_lock(&g_viz.lock); if (g_viz.frame) { frame_len = strlen(g_viz.frame); tmp_buf = malloc(frame_len + 1); if (tmp_buf) { memcpy(tmp_buf, g_viz.frame, frame_len + 1); chroma_new_frame = 0; // Mark as consumed } } pthread_mutex_unlock(&g_viz.lock); char *p = tmp_buf; if (!p) return; if (centered) col = centered_indent; printf("\033[%d;%dH", row, col + 1); int row_pos = row; while (*p) { const char *start = p; while (*p && *p != '\n') p++; if (*p == '\n') { printf("\033[%d;%dH", row_pos, col + 1); fwrite(start, 1, p - start, stdout); p++; row_pos++; } if (*p == '\n') { row_pos = row; p++; } } fflush(stdout); if (tmp_buf) { free(tmp_buf); tmp_buf = NULL; } } bool chroma_is_installed(void) { const char *path = getenv("PATH"); if (!path) return false; char *p = strdup(path); if (!p) return false; char *dir = strtok(p, ":"); char fullpath[512]; while (dir) { snprintf(fullpath, sizeof(fullpath), "%s/chroma", dir); if (access(fullpath, X_OK) == 0) { free(p); return true; } dir = strtok(NULL, ":"); } free(p); return false; } bool chroma_is_started(void) { return chroma_started; } int chroma_get_current_preset(void) { return g_viz.preset; } void chroma_set_current_preset(int preset) { g_viz.preset = preset; } kew/src/ui/chroma.h000066400000000000000000000010161512074754200144770ustar00rootroot00000000000000/** * @file chroma.h * @brief To handle running Chroma in a thread and printing its output * * Contains functions to start, stop and print a chroma frame. */ #ifndef CHROMA_H #define CHROMA_H #include void chroma_start(int height); void chroma_stop(void); void chroma_print_frame(int row, int col, bool centered); void chroma_set_next_preset(int height); bool chroma_is_installed(void); bool chroma_is_started(void); int chroma_get_current_preset(void); void chroma_set_current_preset(int preset); #endif kew/src/ui/cli.c000066400000000000000000000147431512074754200140030ustar00rootroot00000000000000/** * @file cli.c * @brief Handles command-line related functions * * Contains the function that shows the welcome screen and sets the path for the first time. */ #include "cli.h" #include "common/appstate.h" #include "ui/common_ui.h" #include "ui/player_ui.h" #include "ui/settings.h" #include "utils/file.h" #include "utils/term.h" #include "utils/utils.h" #include void remove_arg_element(char *argv[], int index, int *argc) { if (index < 0 || index >= *argc) { // Invalid index return; } // Shift elements after the index for (int i = index; i < *argc - 1; i++) { argv[i] = argv[i + 1]; } // Update the argument count (*argc)--; } void handle_options(int *argc, char *argv[], bool *exact_search) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); const char *no_ui_option = "--noui"; const char *no_cover_option = "--nocover"; const char *quit_on_stop = "--quitonstop"; const char *quit_on_stop2 = "-q"; const char *exact_option = "--exact"; const char *exact_option2 = "-e"; int max_len = 1000; int idx = -1; for (int i = 0; i < *argc; i++) { if (c_strcasestr(argv[i], no_ui_option, max_len)) { ui->uiEnabled = false; idx = i; } } if (idx >= 0) remove_arg_element(argv, idx, argc); idx = -1; for (int i = 0; i < *argc; i++) { if (c_strcasestr(argv[i], no_cover_option, max_len)) { ui->coverEnabled = false; idx = i; } } if (idx >= 0) remove_arg_element(argv, idx, argc); idx = -1; for (int i = 0; i < *argc; i++) { if (c_strcasestr(argv[i], quit_on_stop, max_len) || c_strcasestr(argv[i], quit_on_stop2, max_len)) { ui->quitAfterStopping = true; idx = i; } } if (idx >= 0) remove_arg_element(argv, idx, argc); idx = -1; for (int i = 0; i < *argc; i++) { if (c_strcasestr(argv[i], exact_option, max_len) || c_strcasestr(argv[i], exact_option2, max_len)) { *exact_search = true; idx = i; } } if (idx >= 0) remove_arg_element(argv, idx, argc); } void set_music_path(void) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); clear_screen(); ui->color.r = ui->kewColorRGB.r; ui->color.g = ui->kewColorRGB.g; ui->color.b = ui->kewColorRGB.b; ui->colorMode = COLOR_MODE_ALBUM; int row = 4; int col = 1; print_logo_art(row, col, ui, true, true, false); printf("\033[%d;%dH", row + 4, col); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->defaultColorRGB); int indent = calc_indent_normal() + 1; // Music folder names in different languages const char *music_folder_names[] = { "Music", "Música", "Musique", "Musik", "Musica", "Muziek", "Музыка", "音乐", "音楽", "음악", "موسيقى", "संगीत", "Müzik", "Musikk", "Μουσική", "Muzyka", "Hudba", "Musiikki", "Zene", "Muzică", "เพลง", "מוזיקה"}; char path[PATH_MAX]; int found = -1; char choice[PATH_MAX]; AppSettings *settings = get_app_settings(); for (size_t i = 0; i < sizeof(music_folder_names) / sizeof(music_folder_names[0]); i++) { snprintf(path, sizeof(path), "~/%s", music_folder_names[i]); if (directory_exists(path)) { found = 1; printf("\n\n"); print_blank_spaces(indent); printf(_("Music Library: ")); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->kewColorRGB); printf("%s\n\n", path); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->defaultColorRGB); print_blank_spaces(indent); printf(_("Is this correct? Press Enter.\n\n")); print_blank_spaces(indent); printf(_("Or type a path:\n\n")); print_blank_spaces(indent); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->kewColorRGB); if (fgets(choice, sizeof(choice), stdin) == NULL) { print_blank_spaces(indent); printf(_("Error reading input.\n")); exit(1); } if (choice[0] == '\n' || choice[0] == '\0') { c_strcpy(settings->path, path, sizeof(settings->path)); print_blank_spaces(indent); return; } else { break; } } } print_blank_spaces(indent); // No standard music folder was found if (found < 1) { printf(_("Type a path:\n\n")); print_blank_spaces(indent); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->kewColorRGB); if (fgets(choice, sizeof(choice), stdin) == NULL) { print_blank_spaces(indent); printf(_("Error reading input.\n")); exit(1); } } print_blank_spaces(indent); choice[strcspn(choice, "\n")] = '\0'; size_t len = strlen(choice); if (len > 1 && choice[0] == '"' && choice[len - 1] == '"') { memmove(choice, choice + 1, len - 2); choice[len - 2] = '\0'; } // Set the path if the chosen directory exists if (directory_exists(choice)) { c_strcpy(settings->path, choice, sizeof(settings->path)); set_path(settings->path); found = 1; } else { found = -1; } if (found == -1) exit(1); } kew/src/ui/cli.h000066400000000000000000000005311512074754200137760ustar00rootroot00000000000000/** * @file cli.h * @brief Handles command-line related functions * * Contains the function that shows the welcome screen and sets the path for the first time. */ #include void remove_arg_element(char *argv[], int index, int *argc); void handle_options(int *argc, char *argv[], bool *exact_search); void set_music_path(void); kew/src/ui/common_ui.c000066400000000000000000000501221512074754200152100ustar00rootroot00000000000000#define _XOPEN_SOURCE 700 /** * @file common_ui.c * @brief Shared UI utilities and layout helpers. * * Contains reusable functions for drawing text, handling resizing, * and rendering shared UI components across multiple screens. */ #include "common_ui.h" #include "common/appstate.h" #include "common/common.h" #include "common/events.h" #include "data/theme.h" #include "utils/term.h" #include "utils/utils.h" #include #include #include static unsigned int update_counter = 0; static const int start_scrolling_delay = 10; static bool finished_scrolling = false; static int last_name_position = 0; static bool is_long_name = false; static int scroll_delay_skipped_count = 0; void set_RGB(PixelData p) { // ANSI escape code for setting RGB foreground printf("\033[38;2;%d;%d;%dm", p.r, p.g, p.b); } void set_album_color(PixelData color) { if (color.r >= 210 && color.g >= 210 && color.b >= 210) { AppState *state = get_app_state(); set_RGB(state->uiSettings.defaultColorRGB); } else { set_RGB(color); } } enum EventType get_mouse_action(int num) { enum EventType value = EVENT_NONE; switch (num) { case 0: value = EVENT_NONE; break; case 1: value = EVENT_ENQUEUE; break; case 2: value = EVENT_PLAY_PAUSE; break; case 3: value = EVENT_SCROLLUP; break; case 4: value = EVENT_SCROLLDOWN; break; case 5: value = EVENT_SEEKFORWARD; break; case 6: value = EVENT_SEEKBACK; break; case 7: value = EVENT_VOLUME_UP; break; case 8: value = EVENT_VOLUME_DOWN; break; case 9: value = EVENT_NEXTVIEW; break; case 10: value = EVENT_PREVVIEW; break; default: value = EVENT_NONE; break; } return value; } int get_bytes_in_first_char(const char *str) { if (str == NULL || str[0] == '\0') { return 0; } mbstate_t state; memset(&state, 0, sizeof(state)); wchar_t wc; int num_bytes = mbrtowc(&wc, str, MB_CUR_MAX, &state); return num_bytes; } void enable_mouse(UISettings *ui) { if (ui->mouseEnabled) enable_terminal_mouse_buttons(); } void transfer_settings_to_ui(void) { AppState *state = get_app_state(); AppSettings *settings = get_app_settings(); UISettings *ui = &(state->uiSettings); ui->allowNotifications = (settings->allowNotifications[0] == '1'); ui->coverEnabled = (settings->coverEnabled[0] == '1'); ui->coverAnsi = (settings->coverAnsi[0] == '1'); ui->hideHelp = (settings->hideHelp[0] == '1'); ui->visualizerEnabled = (settings->visualizerEnabled[0] == '1'); ui->quitAfterStopping = (settings->quitAfterStopping[0] == '1'); ui->hideGlimmeringText = (settings->hideGlimmeringText[0] == '1'); ui->mouseEnabled = (settings->mouseEnabled[0] == '1'); ui->shuffle_enabled = (settings->shuffle_enabled[0] == '1'); ui->visualizerBrailleMode = (settings->visualizerBrailleMode[0] == '1'); ui->hideLogo = (settings->hideLogo[0] == '1'); ui->hideSideCover = (settings->hideSideCover[0] == '1'); ui->saveRepeatShuffleSettings = (settings->saveRepeatShuffleSettings[0] == '1'); ui->trackTitleAsWindowTitle = (settings->trackTitleAsWindowTitle[0] == '1'); ui->allowNotifications = (settings->allowNotifications[0] == '1'); ui->coverEnabled = (settings->coverEnabled[0] == '1'); ui->coverAnsi = (settings->coverAnsi[0] == '1'); ui->visualizerEnabled = (settings->visualizerEnabled[0] == '1'); ui->shuffle_enabled = (settings->shuffle_enabled[0] == '1'); int tmp_num_bytes = get_bytes_in_first_char(settings->progressBarElapsedEvenChar); if (tmp_num_bytes != 0) settings->progressBarElapsedEvenChar[tmp_num_bytes] = '\0'; tmp_num_bytes = get_bytes_in_first_char(settings->progressBarElapsedOddChar); if (tmp_num_bytes != 0) settings->progressBarElapsedOddChar[tmp_num_bytes] = '\0'; tmp_num_bytes = get_bytes_in_first_char(settings->progressBarApproachingEvenChar); if (tmp_num_bytes != 0) settings->progressBarApproachingEvenChar[tmp_num_bytes] = '\0'; tmp_num_bytes = get_bytes_in_first_char(settings->progressBarApproachingOddChar); if (tmp_num_bytes != 0) settings->progressBarApproachingOddChar[tmp_num_bytes] = '\0'; tmp_num_bytes = get_bytes_in_first_char(settings->progressBarCurrentEvenChar); if (tmp_num_bytes != 0) settings->progressBarCurrentEvenChar[tmp_num_bytes] = '\0'; tmp_num_bytes = get_bytes_in_first_char(settings->progressBarCurrentOddChar); if (tmp_num_bytes != 0) settings->progressBarCurrentOddChar[tmp_num_bytes] = '\0'; int tmp = get_number(settings->repeatState); if (tmp >= 0) ui->repeatState = tmp; tmp = get_number(settings->colorMode); if (tmp >= 0 && tmp < 3) { ui->colorMode = tmp; } tmp = get_number(settings->replayGainCheckFirst); if (tmp >= 0) ui->replayGainCheckFirst = tmp; tmp = get_number(settings->mouseLeftClickAction); enum EventType tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseLeftClickAction = tmp_event; tmp = get_number(settings->mouseMiddleClickAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseMiddleClickAction = tmp_event; tmp = get_number(settings->mouseRightClickAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseRightClickAction = tmp_event; tmp = get_number(settings->mouseScrollUpAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseScrollUpAction = tmp_event; tmp = get_number(settings->mouseScrollDownAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseScrollDownAction = tmp_event; tmp = get_number(settings->mouseAltScrollUpAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseAltScrollUpAction = tmp_event; tmp = get_number(settings->mouseAltScrollDownAction); tmp_event = get_mouse_action(tmp); if (tmp >= 0) ui->mouseAltScrollDownAction = tmp_event; tmp = get_number(settings->visualizer_height); if (tmp > 0) ui->visualizer_height = tmp; tmp = get_number(settings->visualizer_bar_width); if (tmp >= 0) ui->visualizer_bar_mode = tmp; tmp = get_number(settings->visualizer_color_type); if (tmp >= 0) ui->visualizer_color_type = tmp; tmp = get_number(settings->titleDelay); if (tmp >= 0) ui->titleDelay = tmp; snprintf(ui->theme_name, sizeof(ui->theme_name), "%s", settings->theme); if (!(ui->colorMode >= 0 && ui->colorMode < 3)) { bool useConfigColors = (settings->useConfigColors[0] == '1'); if (useConfigColors) ui->colorMode = COLOR_MODE_DEFAULT; else ui->colorMode = COLOR_MODE_ALBUM; } } int get_update_counter() { return update_counter; } void increment_update_counter() { update_counter++; } void reset_color(void) { printf("\033[0m"); } void inverse_text(void) { printf("\x1b[7m"); } void apply_color(ColorMode mode, ColorValue theme_color, PixelData album_color) { reset_color(); switch (mode) { case COLOR_MODE_ALBUM: set_album_color(album_color); break; case COLOR_MODE_THEME: case COLOR_MODE_DEFAULT: if (theme_color.type == COLOR_TYPE_RGB) { set_RGB(theme_color.rgb); } else { set_terminal_color(theme_color.ansiIndex); } break; } } void reset_name_scroll() { last_name_position = 0; is_long_name = false; finished_scrolling = false; scroll_delay_skipped_count = 0; } /* * Markus Kuhn -- 2007-05-26 (Unicode 5.0) * * Permission to use, copy, modify, and distribute this software * for any purpose and without fee is hereby granted. The author * disclaims all warranties with regard to this software. * * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c */ struct interval { wchar_t first; wchar_t last; }; // Auxiliary function for binary search in interval table static int bisearch(wchar_t ucs, const struct interval *table, int max) { if (table == NULL || max < 0) return 0; // Range validation to avoid unsafe casts if (table[0].first > ucs || table[max].last < ucs) return 0; size_t min = 0; size_t maxIndex = (size_t)max; while (min <= maxIndex) { size_t mid = min + ((maxIndex - min) >> 1); if (ucs > table[mid].last) { min = mid + 1; } else if (ucs < table[mid].first) { if (mid == 0) return 0; maxIndex = mid - 1; } else { return 1; } } return 0; } int mk_wcwidth(wchar_t ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B * c" */ static const struct interval combining[] = { {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}}; /* test for 8-bit control characters */ if (ucs == 0) return 0; if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return -1; /* binary search in table of non-spacing characters */ if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) return 0; /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + (ucs >= 0x1100 && (ucs <= 0x115f || /* Hangul Jamo init. consonants */ ucs == 0x2329 || ucs == 0x232a || (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))); } int mk_wcswidth(const wchar_t *pwcs, size_t n) { int width = 0; for (; *pwcs && n-- > 0; pwcs++) { int w; if ((w = mk_wcwidth(*pwcs)) < 0) return -1; else width += w; } return width; } /* End Markus Kuhn code */ void copy_half_or_full_width_chars_with_max_width(const char *src, char *dst, int max_display_width) { mbstate_t state; memset(&state, 0, sizeof(state)); const char *p = src; char *o = dst; wchar_t wc; int width_sum = 0; while (*p) { size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &state); if (len == (size_t)-1) { // Invalid UTF-8/locale error // Skip one byte, reinit state p++; memset(&state, 0, sizeof(state)); continue; } if (len == (size_t)-2) { // Incomplete sequence break; } if (len == 0) { // Null terminator break; } int w = wcwidth(wc); if (w < 0) { // Non-printable character; skip it p += len; continue; } if (width_sum + w > max_display_width) break; // Copy valid multibyte sequence memcpy(o, p, len); o += len; p += len; width_sum += w; } *o = '\0'; } static bool has_fullwidth_chars(const char *str) { mbstate_t state; memset(&state, 0, sizeof(state)); const char *p = str; wchar_t wc; while (*p) { size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &state); if (len == (size_t)-1 || len == (size_t)-2 || len == 0) break; int w = mk_wcwidth(wc); if (w < 0) { break; } if (w > 1) { return true; } p += len; } return false; } void process_name(const char *name, char *output, int max_width, bool strip_unneeded_chars, bool strip_suffix) { if (!name) { output[0] = '\0'; return; } const char *last_dot = strrchr(name, '.'); if (last_dot != NULL && strip_suffix) { char tmp[PATH_MAX]; size_t len = last_dot - name + 1; if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; c_strcpy(tmp, name, len); tmp[len] = '\0'; copy_half_or_full_width_chars_with_max_width(tmp, output, max_width); } else { copy_half_or_full_width_chars_with_max_width(name, output, max_width); } if (strip_unneeded_chars) remove_unneeded_chars(output, strnlen(output, max_width)); trim(output, strlen(output)); } void process_name_scroll(const char *name, char *output, int max_width, bool is_same_name_as_last_time) { size_t scrollableLength = strnlen(name, max_width); size_t nameLength = strlen(name); if (scroll_delay_skipped_count <= start_scrolling_delay && nameLength > (size_t)max_width) { scrollableLength = max_width; scroll_delay_skipped_count++; trigger_refresh(); is_long_name = true; } int start = (is_same_name_as_last_time) ? last_name_position : 0; if (finished_scrolling) scrollableLength = max_width; if (has_fullwidth_chars(name)) { process_name(name, output, max_width, true, true); } else if (nameLength <= (size_t)max_width || finished_scrolling) { process_name(name, output, scrollableLength, true, true); } else { is_long_name = true; if ((size_t)(start + max_width) > nameLength) { start = 0; finished_scrolling = true; } c_strcpy(output, name + start, max_width + 1); remove_unneeded_chars(output, max_width); trim(output, max_width); last_name_position++; trigger_refresh(); } } bool get_is_long_name() { return is_long_name; } PixelData increase_luminosity(PixelData pixel, int amount) { PixelData pixel2; pixel2.r = pixel.r + amount <= 255 ? pixel.r + amount : 255; pixel2.g = pixel.g + amount <= 255 ? pixel.g + amount : 255; pixel2.b = pixel.b + amount <= 255 ? pixel.b + amount : 255; return pixel2; } #define MIN_CHANNEL 50 PixelData decrease_luminosity_pct(PixelData base, float pct) { PixelData c; int r = (int)((float)base.r * pct); int g = (int)((float)base.g * pct); int b = (int)((float)base.b * pct); c.r = (r < MIN_CHANNEL) ? MIN_CHANNEL : r; c.g = (g < MIN_CHANNEL) ? MIN_CHANNEL : g; c.b = (b < MIN_CHANNEL) ? MIN_CHANNEL : b; return c; } PixelData get_gradient_color(PixelData base_color, int row, int max_list_size, int start_gradient, float min_pct) { if (row < start_gradient) return base_color; int steps = max_list_size - start_gradient; float pct; if (steps <= 1) pct = min_pct; else pct = 1.0f - ((row - start_gradient) * (1.0f - min_pct) / (steps - 1)); if (pct < min_pct) pct = min_pct; if (pct > 1.0f) pct = 1.0f; return decrease_luminosity_pct(base_color, pct); } kew/src/ui/common_ui.h000066400000000000000000000022641512074754200152210ustar00rootroot00000000000000/** * @file common_ui.h * @brief Shared UI utilities and layout helpers. * * Contains reusable functions for drawing text, handling resizing, * and rendering shared UI components across multiple screens. */ #ifndef COMMON_UI_H #define COMMON_UI_H #include "common/appstate.h" #include void set_RGB(PixelData p); void set_album_color(PixelData color); void increment_update_counter(void); void inverse_text(void); void apply_color(ColorMode mode, ColorValue theme_color, PixelData album_color); void process_name_scroll(const char *name, char *output, int max_width, bool is_same_name_as_last_time); void reset_name_scroll(void); void reset_color(void); void process_name(const char *name, char *output, int max_width, bool strip_unneeded_chars, bool strip_suffix); void transfer_settings_to_ui(void); void enable_mouse(UISettings *ui); int get_update_counter(void); bool get_is_long_name(void); enum EventType get_mouse_action(int num); PixelData increase_luminosity(PixelData pixel, int amount); PixelData decrease_luminosity_pct(PixelData base, float pct); PixelData get_gradient_color(PixelData base_color, int row, int max_list_size, int start_gradient, float min_pct); #endif kew/src/ui/control_ui.c000066400000000000000000000305231512074754200154030ustar00rootroot00000000000000/** * @file control_ui.c * @brief Handles playback control interface rendering and input. * * Draws the transport controls (play/pause, skip, seek) and * maps user actions to playback operations. */ #include "control_ui.h" #include "ui/chroma.h" #include "ui/player_ui.h" #include "ui/queue_ui.h" #include "common/appstate.h" #include "ops/playback_ops.h" #include "ops/playback_state.h" #include "common/common.h" #include "sys/mpris.h" #include "data/theme.h" #include "utils/term.h" #include "utils/utils.h" #include #include #include #include void seek_forward(void) { AppState *state = get_app_state(); Node *current = get_current_song(); if (current == NULL) return; double duration = current->song.duration; if (duration <= 0.0) return; double step_percent = 100.0 / state->uiState.num_progress_bars; int seconds = (int)(duration * (step_percent / 100.0)); seek(seconds); state->uiState.isFastForwarding = true; } void seek_back(void) { AppState *state = get_app_state(); Node *current = get_current_song(); if (current == NULL) return; double duration = current->song.duration; if (duration <= 0.0) return; double step_percent = 100.0 / state->uiState.num_progress_bars; int seconds = (int)(duration * (step_percent / 100.0)); seek(-seconds); state->uiState.isRewinding = true; } void cycle_color_mode(void) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); clear_screen(); switch (ui->colorMode) { case COLOR_MODE_DEFAULT: ui->colorMode = COLOR_MODE_ALBUM; break; case COLOR_MODE_ALBUM: ui->colorMode = COLOR_MODE_THEME; break; case COLOR_MODE_THEME: ui->colorMode = COLOR_MODE_DEFAULT; break; } bool themeLoaded = false; switch (ui->colorMode) { case COLOR_MODE_DEFAULT: if (load_theme("default", true)) { themeLoaded = true; } break; case COLOR_MODE_ALBUM: themeLoaded = true; break; case COLOR_MODE_THEME: if (ui->theme_name[0] != '\0' && load_theme(ui->theme_name, true)) { themeLoaded = true; } } if (!themeLoaded) { cycle_color_mode(); } trigger_redraw_side_cover(); trigger_refresh(); } void cycle_visualization(void) { if (chroma_is_started()) request_stop_visualization(); request_next_visualization(); } void cycle_themes(void) { clear_screen(); AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); char *config_path = get_config_path(); if (!config_path) return; char themes_path[PATH_MAX]; snprintf(themes_path, sizeof(themes_path), "%s/themes", config_path); DIR *dir = opendir(themes_path); if (!dir) { perror("Failed to open themes directory"); return; } struct dirent *entry; char *themes[256]; int theme_count = 0; // Collect all *.theme files while ((entry = readdir(dir)) != NULL) { if (strstr(entry->d_name, ".theme")) { themes[theme_count++] = strdup(entry->d_name); } } closedir(dir); if (theme_count == 0) { set_error_message("No themes found."); free(config_path); return; } // Find the index of the current theme int current_index = -1; char current_filename[NAME_MAX]; strncpy(current_filename, ui->theme_name, sizeof(current_filename) - 1); current_filename[sizeof(current_filename) - 1] = '\0'; if (strlen(current_filename) + 6 < sizeof(current_filename)) strcat(current_filename, ".theme"); for (int i = 0; i < theme_count; i++) { if (strcmp(themes[i], current_filename) == 0) { current_index = i; break; } } // Get next theme (wrap around) int next_index = (current_index + 1) % theme_count; if (load_theme(themes[next_index], false)) { ui->colorMode = COLOR_MODE_THEME; snprintf(ui->theme_name, sizeof(ui->theme_name), "%s", themes[next_index]); char *dot = strstr(ui->theme_name, ".theme"); if (dot) *dot = '\0'; } trigger_redraw_side_cover(); trigger_refresh(); for (int i = 0; i < theme_count; i++) { free(themes[i]); } free(config_path); } void toggle_visualizer(void) { AppSettings *settings = get_app_settings(); AppState *state = get_app_state(); state->uiSettings.visualizerEnabled = !state->uiSettings.visualizerEnabled; c_strcpy(settings->visualizerEnabled, state->uiSettings.visualizerEnabled ? "1" : "0", sizeof(settings->visualizerEnabled)); restore_cursor_position(); trigger_redraw_side_cover(); trigger_refresh(); } void toggle_show_lyrics_page(void) { AppState *state = get_app_state(); state->uiState.showLyricsPage = !state->uiState.showLyricsPage; trigger_refresh(); } void toggle_ascii(void) { if (chroma_is_started()) { request_stop_visualization(); trigger_refresh(); return; } AppSettings *settings = get_app_settings(); AppState *state = get_app_state(); state->uiSettings.coverAnsi = !state->uiSettings.coverAnsi; c_strcpy(settings->coverAnsi, state->uiSettings.coverAnsi ? "1" : "0", sizeof(settings->coverAnsi)); trigger_redraw_side_cover(); trigger_refresh(); } void toggle_repeat(void) { AppState *state = get_app_state(); bool repeat_enabled = is_repeat_enabled(); bool repeat_list_enabled = is_repeat_list_enabled(); if (repeat_enabled) { set_repeat_enabled(false); set_repeat_list_enabled(true); emit_string_property_changed("loop_status", "List"); state->uiSettings.repeatState = 2; } else if (repeat_list_enabled) { set_repeat_enabled(false); set_repeat_list_enabled(false); emit_string_property_changed("loop_status", "None"); state->uiSettings.repeatState = 0; } else { set_repeat_enabled(true); set_repeat_list_enabled(false); emit_string_property_changed("loop_status", "Track"); state->uiSettings.repeatState = 1; } if (state->currentView != TRACK_VIEW) trigger_refresh(); } void toggle_pause() { if (is_stopped()) { view_enqueue(false); } else { ops_toggle_pause(); } } void toggle_notifications(void) { AppState *state = get_app_state(); AppSettings *settings = get_app_settings(); UISettings *ui = &(state->uiSettings); ui->allowNotifications = !ui->allowNotifications; c_strcpy(settings->allowNotifications, ui->allowNotifications ? "1" : "0", sizeof(settings->allowNotifications)); } void toggle_shuffle(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); state->uiSettings.shuffle_enabled = !is_shuffle_enabled(); set_shuffle_enabled(state->uiSettings.shuffle_enabled); Node *current = get_current_song(); PlayList *playlist = get_playlist(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); if (state->uiSettings.shuffle_enabled) { pthread_mutex_lock(&(playlist->mutex)); shuffle_playlist_starting_from_song(playlist, current); pthread_mutex_unlock(&(playlist->mutex)); emit_boolean_property_changed("Shuffle", TRUE); } else { char *path = NULL; if (current != NULL) { path = strdup(current->song.file_path); } PlayList *playlist = get_playlist(); deep_copy_play_list_onto_list(unshuffled_playlist, &playlist); if (path != NULL) { set_current_song(find_path_in_playlist(path, playlist)); free(path); } emit_boolean_property_changed("Shuffle", FALSE); } ps->loadedNextSong = false; set_next_song(NULL); if (state->currentView == PLAYLIST_VIEW || state->currentView == LIBRARY_VIEW) trigger_refresh(); emit_shuffle_changed(); } bool should_refresh_player(void) { PlaybackState *ps = get_playback_state(); return !ps->skipping && !is_EOF_reached() && !is_impl_switch_reached(); } int load_theme(const char *theme_name, bool is_ansi_theme) { AppState *app_state = get_app_state(); AppSettings *settings = get_app_settings(); if (!app_state || !theme_name) return 0; char *config_path = get_config_path(); if (!config_path) return 0; // Check if config directory exists struct stat st = {0}; if (stat(config_path, &st) == -1) { free(config_path); return 0; } // Build full theme filename char filename[NAME_MAX]; const char *extension = ".theme"; size_t themeNameLen = strlen(theme_name); size_t extLen = strlen(extension); // Check if theme_name already ends with ".theme" int has_extension = (themeNameLen >= extLen && strcmp(theme_name + themeNameLen - extLen, extension) == 0); if (has_extension) { if (snprintf(filename, sizeof(filename), "%s", theme_name) >= (int)sizeof(filename)) { fprintf(stderr, "Theme filename is too long\n"); set_error_message("Theme filename is too long"); free(config_path); return 0; } } else { if (snprintf(filename, sizeof(filename), "%s.theme", theme_name) >= (int)sizeof(filename)) { fprintf(stderr, "Theme filename is too long\n"); set_error_message("Theme filename is too long"); free(config_path); return 0; } } // Build full themes directory path: configDir + "/themes" char themes_dir[PATH_MAX]; if (snprintf(themes_dir, sizeof(themes_dir), "%s/themes", config_path) >= (int)sizeof(themes_dir)) { fprintf(stderr, "Themes path is too long\n"); set_error_message("Themes path is too long"); free(config_path); return 0; } char *lower_filename = string_to_lower(filename); // Call the loader int loaded = load_theme_from_file(themes_dir, lower_filename, &app_state->uiSettings.theme); if (!loaded) { free(config_path); free(lower_filename); return 0; // failed to load } app_state->uiSettings.themeIsSet = true; if (is_ansi_theme) { // Default ANSI theme: store in settings->ansiTheme snprintf(settings->ansiTheme, sizeof(settings->ansiTheme), "%s", theme_name); } else { // Truecolor theme: store in settings->theme snprintf(settings->theme, sizeof(settings->theme), "%s", theme_name); } free(config_path); free(lower_filename); return 1; } kew/src/ui/control_ui.h000066400000000000000000000011551512074754200154070ustar00rootroot00000000000000/** * @file control_ui.h * @brief Handles playback control interface rendering and input. * * Draws the transport controls (play/pause, skip, seek) and * maps user actions to playback operations. */ #include void seek_forward(void); void seek_back(void); void cycle_color_mode(void); void cycle_themes(void); void toggle_show_lyrics_page(void); void toggle_ascii(void); void toggle_visualizer(void); void toggle_shuffle(void); void toggle_notifications(void); void toggle_repeat(void); void toggle_pause(); bool should_refresh_player(void); int load_theme(const char *theme_name, bool is_ansi_theme); kew/src/ui/input.c000066400000000000000000000541211512074754200143650ustar00rootroot00000000000000#ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include "common/events.h" /** * @file input.c * @brief Handles keyboard and terminal input events. */ #include "input.h" #include "common/appstate.h" #include "common/common.h" #define TB_IMPL #include "termbox2_input.h" #include "control_ui.h" #include "player_ui.h" #include "queue_ui.h" #include "search_ui.h" #include "settings.h" #include "ops/library_ops.h" #include "ops/playback_clock.h" #include "ops/playback_ops.h" #include "ops/playback_state.h" #include "ops/playlist_ops.h" #include "sys/mpris.h" #include "sys/sys_integration.h" #include "utils/term.h" #include #include #include #define MAX_TMP_SEQ_LEN 256 #define NUM_KEY_MAPPINGS 64 const int COOLDOWN_MS = 500; const int COOLDOWN2_MS = 100; const int MOUSE_DRAG = 32; const int MOUSE_CLICK = 0; const int max_digits_pressed_count = 9; const int fuzzy_search_threshold = 100; EventMapping key_mappings[NUM_KEY_MAPPINGS]; struct timespec last_input_time; char digits_pressed[MAX_SEQ_LEN]; int digits_pressed_count; double dragged_position_seconds = 0.0; bool dragging_progress_bar = false; struct Event map_tb_key_to_event(struct tb_event *ev) { struct Event event = {0}; event.type = EVENT_NONE; TBKeyBinding *key_bindings = get_key_bindings(); AppState *state = get_app_state(); if (state->currentView == SEARCH_VIEW) { if (ev->key == TB_KEY_SPACE && ev->mod == 0) ev->ch = ' '; // Backspace if (ev->key == TB_KEY_BACKSPACE || ev->key == TB_KEY_BACKSPACE2) { remove_from_search_text(); reset_search_result(); fuzzy_search(get_library(), fuzzy_search_threshold); event.type = EVENT_SEARCH; } // Printable character (not escape, enter, tab, carriage return) #if defined(__ANDROID__) || defined(__APPLE__) else if ((ev->ch > 0 && ev->ch != '\033' && ev->ch != '\n' && ev->ch != '\t' && ev->ch != '\r') || ev->ch == ' ') { if (ev->ch != 'Z' && ev->ch != 'X' && ev->ch != 'C' && ev->ch != 'V' && ev->ch != 'B' && ev->ch != 'N') { char keybuf[5] = {0}; tb_utf8_unicode_to_char(keybuf, ev->ch); add_to_search_text(keybuf); reset_search_result(); fuzzy_search(get_library(), fuzzy_search_threshold); event.type = EVENT_SEARCH; } } #else else if ((ev->ch > 0 && ev->ch != '\033' && ev->ch != '\n' && ev->ch != '\t' && ev->ch != '\r') || ev->ch == ' ') { char keybuf[5] = {0}; tb_utf8_unicode_to_char(keybuf, ev->ch); add_to_search_text(keybuf); reset_search_result(); fuzzy_search(get_library(), fuzzy_search_threshold); event.type = EVENT_SEARCH; } #endif } if (event.type == EVENT_NONE) { for (size_t i = 0; i < keybinding_count; i++) { TBKeyBinding *b = &key_bindings[i]; if (isupper((unsigned char)ev->ch)) { ev->mod |= TB_MOD_SHIFT; ev->ch = tolower(ev->ch); } bool keyMatch = (b->key && ev->key == b->key) || (b->ch && ev->ch == b->ch); bool modsMatch = (b->mods == ev->mod); if (keyMatch && modsMatch) { event.type = b->eventType; strncpy(event.args, b->args, sizeof(event.args)); event.args[sizeof(event.args) - 1] = '\0'; break; } } } return event; } bool is_digits_pressed(void) { return (digits_pressed_count != 0); } char *get_digits_pressed(void) { return digits_pressed; } void press_digit(int digit) { digits_pressed[0] = digit; digits_pressed[1] = '\0'; digits_pressed_count = 1; } void reset_digits_pressed(void) { memset(digits_pressed, '\0', sizeof(digits_pressed)); digits_pressed_count = 0; } void update_last_input_time(void) { clock_gettime(CLOCK_MONOTONIC, &last_input_time); } bool is_cooldown_elapsed(int milli_seconds) { struct timespec current_time; clock_gettime(CLOCK_MONOTONIC, ¤t_time); double elapsed_milliseconds = (current_time.tv_sec - last_input_time.tv_sec) * 1000.0 + (current_time.tv_nsec - last_input_time.tv_nsec) / 1000000.0; return elapsed_milliseconds >= milli_seconds; } void init_key_mappings(AppSettings *settings) { map_settings_to_keys(settings, key_mappings); } int parse_volume_arg(const char *arg_str) { if (!arg_str || !*arg_str) return 0; // Make a copy so we can strip characters like '%' char buf[64]; size_t len = 0; // Skip leading spaces while (*arg_str && isspace((unsigned char)*arg_str)) arg_str++; // Copy allowed characters (+, -, digits) while (*arg_str && len < sizeof(buf) - 1) { if (*arg_str == '+' || *arg_str == '-' || isdigit((unsigned char)*arg_str)) buf[len++] = *arg_str; else if (*arg_str == '%') break; // stop at % else if (isspace((unsigned char)*arg_str)) break; // stop at space else break; // stop on anything unexpected arg_str++; } buf[len] = '\0'; if (len == 0) return 0; // Convert to integer return atoi(buf); } void handle_event(struct Event *event) { AppState *state = get_app_state(); AppSettings *settings = get_app_settings(); PlayList *playlist = get_playlist(); int chosen_row = get_chosen_row(); switch (event->type) { break; case EVENT_ENQUEUE: view_enqueue(false); break; case EVENT_ENQUEUEANDPLAY: view_enqueue(true); break; case EVENT_PLAY_PAUSE: toggle_pause(); break; case EVENT_TOGGLEVISUALIZER: toggle_visualizer(); break; case EVENT_TOGGLEREPEAT: toggle_repeat(); break; case EVENT_TOGGLEASCII: toggle_ascii(); break; case EVENT_TOGGLENOTIFICATIONS: toggle_notifications(); break; case EVENT_SHUFFLE: toggle_shuffle(); break; case EVENT_SHOWLYRICSPAGE: toggle_show_lyrics_page(); break; case EVENT_CYCLECOLORMODE: cycle_color_mode(); break; case EVENT_CYCLETHEMES: cycle_themes(); break; case EVENT_CYCLEVISUALIZATION: // FIXME: Enable Chroma // cycle_visualization(); break; case EVENT_QUIT: quit(); break; case EVENT_SCROLLDOWN: scroll_next(); break; case EVENT_SCROLLUP: scroll_prev(); break; case EVENT_VOLUME_UP: if (event->args[0] != '\0') volume_change(parse_volume_arg(event->args)); else volume_change(5); emit_volume_changed(); break; case EVENT_VOLUME_DOWN: if (event->args[0] != '\0') volume_change(parse_volume_arg(event->args)); else volume_change(-5); emit_volume_changed(); break; case EVENT_NEXT: skip_to_next_song(); break; case EVENT_PREV: skip_to_prev_song(); break; case EVENT_SEEKBACK: seek_back(); break; case EVENT_SEEKFORWARD: seek_forward(); break; case EVENT_ADDTOFAVORITESPLAYLIST: add_to_favorites_playlist(); break; case EVENT_EXPORTPLAYLIST: export_current_playlist(settings->path, playlist); break; case EVENT_UPDATELIBRARY: free_search_results(); set_error_message("Updating Library..."); bool wait_until_complete = false; update_library(settings->path, wait_until_complete); break; case EVENT_SHOWHELP: toggle_show_view(HELP_VIEW); break; case EVENT_SHOWPLAYLIST: toggle_show_view(PLAYLIST_VIEW); break; case EVENT_SHOWSEARCH: toggle_show_view(SEARCH_VIEW); break; break; case EVENT_SHOWLIBRARY: toggle_show_view(LIBRARY_VIEW); break; case EVENT_NEXTPAGE: flip_next_page(); break; case EVENT_PREVPAGE: flip_prev_page(); break; case EVENT_REMOVE: handle_remove(get_chosen_row()); reset_list_after_dequeuing_playing_song(); break; case EVENT_SHOWTRACK: show_track(); break; case EVENT_NEXTVIEW: switch_to_next_view(); break; case EVENT_PREVVIEW: switch_to_previous_view(); break; case EVENT_CLEARPLAYLIST: dequeue_all_except_playing_song(); state->uiState.resetPlaylistDisplay = true; break; case EVENT_MOVESONGUP: move_song_up(&chosen_row); set_chosen_row(chosen_row); break; case EVENT_MOVESONGDOWN: move_song_down(&chosen_row); set_chosen_row(chosen_row); break; case EVENT_STOP: stop(); break; case EVENT_SORTLIBRARY: sort_library(); break; default: state->uiState.isFastForwarding = false; state->uiState.isRewinding = false; break; } } static gint64 last_scroll_event_time = 0; static gint64 last_seek_event_time = 0; static gint64 last_page_event_time = 0; static gint64 last_switch_time = 0; static gboolean should_throttle(struct Event *event) { gint64 now = g_get_real_time(); // microseconds since Epoch gint64 delta; switch (event->type) { case EVENT_NEXT: case EVENT_PREV: delta = now - last_switch_time; if (delta < 400 * 1000) // 400ms return TRUE; last_switch_time = now; break; case EVENT_SCROLLUP: case EVENT_SCROLLDOWN: delta = now - last_scroll_event_time; if (delta < 20 * 1000) // 20ms return TRUE; last_scroll_event_time = now; break; case EVENT_SEEKBACK: case EVENT_SEEKFORWARD: delta = now - last_seek_event_time; if (delta < 20 * 1000) return TRUE; last_seek_event_time = now; break; case EVENT_NEXTPAGE: case EVENT_PREVPAGE: delta = now - last_page_event_time; if (delta < 20 * 1000) return TRUE; last_page_event_time = now; break; default: break; } return FALSE; } #define MAX_SEQ_LEN 1024 // Maximum length of sequence buffer #define MAX_TMP_SEQ_LEN 256 enum EventType get_mouse_last_row_event(int mouse_x_on_last_row) { enum EventType result = EVENT_NONE; AppState *state = get_app_state(); const char *s = state->uiSettings.LAST_ROW; if (!s || mouse_x_on_last_row < 0) return EVENT_NONE; int view_clicked = 1; // Which section is clicked int col_index = 0; // terminal column position const char *ptr = state->uiSettings.LAST_ROW; mbstate_t mbs; memset(&mbs, 0, sizeof(mbs)); while (*ptr) { wchar_t wc; size_t bytes = mbrtowc(&wc, ptr, MB_CUR_MAX, &mbs); if (bytes == (size_t)-1 || bytes == (size_t)-2) { bytes = 1; wc = (unsigned char)*ptr; } int w = wcwidth(wc); // number of terminal columns this char takes if (w < 0) w = 0; if (wc == L'|') view_clicked++; if (col_index + w > mouse_x_on_last_row) break; // cursor is inside this character col_index += w; ptr += bytes; } switch (view_clicked) { case 1: result = EVENT_SHOWPLAYLIST; break; case 2: result = EVENT_SHOWLIBRARY; break; case 3: result = EVENT_SHOWTRACK; break; case 4: result = EVENT_SHOWSEARCH; break; case 5: result = EVENT_SHOWHELP; break; default: result = EVENT_NONE; break; } if (result == EVENT_SHOWTRACK && get_current_song_data() == NULL) { result = EVENT_SHOWLIBRARY; } return result; } bool handle_mouse_event(struct tb_event *ev, struct Event *event) { if (ev->type != TB_EVENT_MOUSE) return false; int mouse_x = ev->x + 1; int mouse_y = ev->y + 1; uint16_t mouse_key = ev->key; ProgressBar *progress_bar = get_progress_bar(); AppState *state = get_app_state(); // Calculate where the user clicked on the progress bar if (progress_bar->length > 0) { long long delta_col = (long long)mouse_x - (long long)progress_bar->col; if (delta_col >= 0 && delta_col <= (long long)progress_bar->length) { double position = (double)delta_col / (double)progress_bar->length; double duration = get_current_song_duration(); dragged_position_seconds = duration * position; } } int footer_row = get_footer_row(); int footer_col = get_footer_col(); // Footer click (e.g., buttons or indicators on last row) if ((mouse_y == footer_row && footer_col > 0 && mouse_x - footer_col >= 0 && mouse_x - footer_col < (int)strlen(state->uiSettings.LAST_ROW)) && mouse_key != TB_KEY_MOUSE_RELEASE) { event->type = get_mouse_last_row_event(mouse_x - footer_col + 1); return true; } if (mouse_key == TB_KEY_MOUSE_RELEASE) { // Mouse moved outside progress bar area → stop dragging dragging_progress_bar = false; return true; } // Progress bar click or hold-drag movement bool inProgressBar = (mouse_y == progress_bar->row) && (mouse_x >= progress_bar->col) && (mouse_x < progress_bar->col + progress_bar->length); if (inProgressBar && state->currentView == TRACK_VIEW) { // Any left press or movement within bar = update if (mouse_key == TB_KEY_MOUSE_LEFT || dragging_progress_bar) { dragging_progress_bar = true; gint64 newPosUs = (gint64)(dragged_position_seconds * G_USEC_PER_SEC); set_position(newPosUs, get_current_song_duration()); return true; } } return false; } void handle_cooldown(void) { bool cooldownElapsed = false; if (is_cooldown_elapsed(COOLDOWN_MS)) cooldownElapsed = true; if (cooldownElapsed) { if (!dragging_progress_bar) { if (flush_seek()) { AppState *state = get_app_state(); state->uiState.isFastForwarding = false; state->uiState.isRewinding = false; if (state->currentView != TRACK_VIEW) trigger_refresh(); } } } } static gboolean on_tb_input(GIOChannel *source, GIOCondition cond, gpointer data) { (void)source; (void)cond; (void)data; char seq[MAX_SEQ_LEN]; int seq_length = 0; seq[0] = '\0'; bool cooldown2Elapsed = false; if (is_cooldown_elapsed(COOLDOWN2_MS)) cooldown2Elapsed = true; // Drain all available input while (1) { if (!is_input_available()) break; char tmp_seq[MAX_TMP_SEQ_LEN]; int len = read_input_sequence(tmp_seq, sizeof(tmp_seq)); if (len <= 0) break; size_t seq_len = strnlen(seq, MAX_SEQ_LEN); size_t remaining_space = MAX_SEQ_LEN - seq_len - 1; if (remaining_space < (size_t)len) len = remaining_space; memcpy(seq + seq_len, tmp_seq, len); seq[seq_len + len] = '\0'; seq_length += len; } // If we got a sequence, convert to tb_event and handle if (seq_length > 0) { struct tb_event ev; memset(&ev, 0, sizeof(ev)); // Feed the sequence into Termbox internal buffer if (seq_length > 0) { bytebuf_nputs(&global.in, seq, seq_length); // feed entire sequence at once } struct Event event; event.type = EVENT_NONE; event.args[0] = '\0'; event.key[0] = '\0'; // Extract all events in the buffer while (tb_peek_event(&ev, 0) == 0) { bool isMouseEvent = handle_mouse_event(&ev, &event); if (!isMouseEvent) { event = map_tb_key_to_event(&ev); } if (isdigit(ev.ch) && event.type == EVENT_NONE) { if (digits_pressed_count < max_digits_pressed_count) digits_pressed[digits_pressed_count++] = ev.ch; } if (event.type != EVENT_NONE) { // Throttle scroll/seek/page events switch (event.type) { case EVENT_SCROLLUP: case EVENT_SCROLLDOWN: case EVENT_SEEKBACK: case EVENT_SEEKFORWARD: case EVENT_NEXTPAGE: case EVENT_PREVPAGE: case EVENT_NEXT: case EVENT_PREV: if (should_throttle(&event)) continue; break; default: break; } // Handle seek/remove cooldown if (!cooldown2Elapsed && (event.type == EVENT_REMOVE || event.type == EVENT_SEEKBACK || event.type == EVENT_SEEKFORWARD)) event.type = EVENT_NONE; else if (event.type == EVENT_REMOVE || event.type == EVENT_SEEKBACK || event.type == EVENT_SEEKFORWARD) { update_last_input_time(); } // Forget Numbers if (event.type != EVENT_ENQUEUE && event.type != EVENT_GOTOENDOFPLAYLIST && event.type != EVENT_NONE) { memset(digits_pressed, '\0', sizeof(digits_pressed)); digits_pressed_count = 0; } handle_event(&event); } } } return TRUE; } void init_input(void) { tb_init(); // Enable SGR (1006) + drag-motion (1002) const char *enable_mouse = "\033[?1000h\033[?1002h\033[?1006h"; ssize_t result = write(tb_get_output_fd(), enable_mouse, strlen(enable_mouse)); if (result < 0) tb_set_input_mode(TB_INPUT_ALT | TB_INPUT_MOUSE | TB_INPUT_ESC); else tb_set_input_mode(TB_INPUT_ALT | TB_INPUT_ESC); int fd = tb_get_input_fd(); GIOChannel *chan = g_io_channel_unix_new(fd); g_io_channel_set_encoding(chan, NULL, NULL); // binary g_io_add_watch(chan, G_IO_IN, (GIOFunc)on_tb_input, NULL); } void shutdown_input(void) { tb_shutdown(); } kew/src/ui/input.h000066400000000000000000000007461512074754200143760ustar00rootroot00000000000000/** * @file input.h * @brief Handles keyboard and terminal input events. */ #include "common/appstate.h" #include "common/events.h" struct Event processInput(void); bool is_digits_pressed(void); char *get_digits_pressed(void); void reset_digits_pressed(void); void update_last_input_time(void); void init_key_mappings(AppSettings *settings); void press_digit(int digit); void init_input(void); void shutdown_input(void); void handle_cooldown(void); void cycle_visualization(void); kew/src/ui/player_ui.c000066400000000000000000002731321512074754200152240ustar00rootroot00000000000000/** * @file player_ui.c * @brief Main player screen rendering. * * Displays current track info, progress bar, and playback status. * Acts as the central visual component of the terminal player. */ #include "player_ui.h" #include "common/events.h" #include "control_ui.h" #include "playlist_ui.h" #include "search_ui.h" #include "settings.h" #include "common/appstate.h" #include "common/common.h" #include "common_ui.h" #include "chroma.h" #include "input.h" #include "ops/library_ops.h" #include "ops/playback_clock.h" #include "ops/playback_state.h" #include "data/directorytree.h" #include "data/img_func.h" #include "data/lyrics.h" #include "data/playlist.h" #include "data/song_loader.h" #include "sys/sys_integration.h" #include "utils/term.h" #include "utils/utils.h" #include "visuals.h" #include #include #include #include #include #include #include #ifdef __APPLE__ const int ABSOLUTE_MIN_WIDTH = 80; #else const int ABSOLUTE_MIN_WIDTH = 65; #endif // clang-format off static const char *LOGO[] = {" __\n", "| |--.-----.--.--.--.\n", "| <| -__| | | |\n", "|__|__|_____|________|"}; // clang-format on static int footer_col = 0; static int footer_row = 0; static const int MAX_TERM_SIZE = 10000; static const int scrolling_interval = 1; static const int LOGO_WIDTH = 22; static const int MIN_COVER_SIZE = 5; static int min_height = 0; static int preferred_width = 0; static int preferred_height = 0; static int text_width = 0; static int indent = 0; static int max_list_size = 0; static int max_search_list_size = 0; static int num_top_level_songs = 0; static int start_lib_iter = 0; static int start_search_iter = 0; static int max_lib_list_size = 0; static int chosen_row = 0; // The row that is chosen in playlist view static int chosen_lib_row = 0; // The row that is chosen in library view static int chosen_search_result_row = 0; // The row that is chosen in search view static int lib_iter = 0; static int lib_song_iter = 0; static int lib_row_count = 0; static int previous_chosen_lib_row = 0; static int lib_current_dir_song_count = 0; static PixelData footer_color = {120, 120, 120}; static FileSystemEntry *last_entry = NULL; static FileSystemEntry *chosen_dir = NULL; static bool is_same_name_as_last_time = false; static int term_w, term_h; static int has_chroma = -1; static bool next_visualization_requested = false; static bool redraw_side_cover = true; static ViewState last_view = LIBRARY_VIEW; static const char *last_cover_path_ptr = NULL; static size_t last_cover_path_hash = (size_t)-1; void request_next_visualization(void) { AppState *state = get_app_state(); state->uiSettings.visualizations_instead_of_cover = true; next_visualization_requested = true; } void request_stop_visualization(void) { AppState *state = get_app_state(); state->uiSettings.visualizations_instead_of_cover = false; next_visualization_requested = false; chroma_stop(); } int get_footer_row(void) { return footer_row; } int get_footer_col(void) { return footer_col; } void trigger_redraw_side_cover(void) { redraw_side_cover = true; } bool init_theme(int argc, char *argv[]) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); bool themeLoaded = false; // Command-line theme handling if (argc > 3 && strcmp(argv[1], "theme") == 0) { set_error_message("Couldn't load theme. Theme file names shouldn't contain space."); } else if (argc == 3 && strcmp(argv[1], "theme") == 0) { // Try to load the user-specified theme if (load_theme(argv[2], false) > 0) { ui->colorMode = COLOR_MODE_THEME; themeLoaded = true; snprintf(ui->theme_name, sizeof(ui->theme_name), "%s", argv[2]); } else { // Failed to load user theme → fall back to // default/ANSI if (ui->colorMode == COLOR_MODE_THEME) { ui->colorMode = COLOR_MODE_DEFAULT; } } } else if (ui->colorMode == COLOR_MODE_THEME) { // If UI has a theme_name stored, try to load it if (load_theme(ui->theme_name, false) > 0) { ui->colorMode = COLOR_MODE_THEME; themeLoaded = true; } } // If still in default mode, load default ANSI theme if (ui->colorMode == COLOR_MODE_DEFAULT) { // Load "default" ANSI theme, but don't overwrite // settings->theme if (load_theme("default", true)) { themeLoaded = true; } } if (!themeLoaded && ui->colorMode != COLOR_MODE_ALBUM) { set_error_message("Couldn't load theme. Forgot to run 'sudo make install'?"); ui->colorMode = COLOR_MODE_ALBUM; } return themeLoaded; } void set_track_title_as_window_title(void) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); if (ui->trackTitleAsWindowTitle) { save_terminal_window_title(); set_terminal_window_title("kew"); } } void set_current_as_chosen_dir(void) { AppState *state = get_app_state(); if (state->uiState.currentLibEntry && state->uiState.currentLibEntry->is_directory) chosen_dir = state->uiState.currentLibEntry; } int calc_ideal_img_size(int *width, int *height, const int visualizer_height, const int metatag_height) { if (!width || !height) return -1; float aspect_ratio = calc_aspect_ratio(); if (!isfinite(aspect_ratio) || aspect_ratio <= 0.0f || aspect_ratio > 100.0f) aspect_ratio = 1.0f; // fallback to square int term_w = 0, term_h = 0; get_term_size(&term_w, &term_h); if (term_w <= 0 || term_h <= 0 || term_w > MAX_TERM_SIZE || term_h > MAX_TERM_SIZE) { *width = 1; *height = 1; return -1; } const int time_display_height = 1; const int height_margin = 4; const int min_height = visualizer_height + metatag_height + time_display_height + height_margin + 1; if (min_height < 0 || min_height > term_h) { *width = 1; *height = 1; return -1; } int available_height = term_h - min_height; if (available_height <= 0) { *width = 1; *height = 1; return -1; } // Safe calculation using double double safe_height = (double)available_height; double safe_aspect = (double)aspect_ratio; double temp_width = safe_height * safe_aspect; // Clamp to INT_MAX and reasonable limits if (temp_width < 1.0) temp_width = 1.0; else if (temp_width > INT_MAX) temp_width = INT_MAX; int calc_width = (int)ceil(temp_width); int calc_height = available_height; if (calc_width > term_w) { calc_width = term_w; if (calc_width <= 0) { *width = 1; *height = 1; return -1; } double temp_height = (double)calc_width / safe_aspect; if (temp_height < 1.0) temp_height = 1.0; else if (temp_height > INT_MAX) temp_height = INT_MAX; calc_height = (int)floor(temp_height); } // Final clamping if (calc_width < 1) calc_width = 1; if (calc_height < 2) calc_height = 2; // Slight adjustment calc_height -= 1; if (calc_height < 1) calc_height = 1; *width = calc_width; *height = calc_height; return 0; } void calc_preferred_size(UISettings *ui) { min_height = 2 + (ui->visualizerEnabled ? ui->visualizer_height : 0); int metadata_height = 4; calc_ideal_img_size(&preferred_width, &preferred_height, (ui->visualizerEnabled ? ui->visualizer_height : 0), metadata_height); } void print_help(void) { int i = system("man kew"); if (i != 0) { printf(_("Run man kew for help.\n")); } } static const char *get_player_status_icon(void) { if (is_paused()) { #if defined(__ANDROID__) || defined(__APPLE__) return "။"; #else return "⏸"; #endif } if (is_stopped()) return "■"; return "▶"; } int print_logo_art(int row, int col, const UISettings *ui, bool centered, bool print_tag_line, bool use_gradient) { int h, w; get_term_size(&w, &h); int centered_col = (w - LOGO_WIDTH) / 2; if (centered_col < 0) centered_col = 0; size_t logoHeight = sizeof(LOGO) / sizeof(LOGO[0]); col = centered ? centered_col : col; for (size_t i = 0; i < logoHeight; i++) { unsigned char default_color = ui->default_color; PixelData row_color = {ui->color.r, ui->color.g, ui->color.b}; if (use_gradient && !(ui->color.r == default_color && ui->color.g == default_color && ui->color.b == default_color)) { row_color = get_gradient_color(ui->color, logoHeight - i, logoHeight, 2, 0.8f); } apply_color(ui->colorMode, ui->theme.logo, row_color); printf("\033[%d;%dH", row, col); printf("%s", LOGO[i]); row++; } if (print_tag_line) { printf("\033[%d;%dH", row++, col); printf("MUSIC FOR THE SHELL"); logoHeight += 2; } return logoHeight; // lines used by logo } static void build_song_title(const SongData *song_data, const UISettings *ui, char *out, size_t out_size, bool show_play_icon) { const char *icon = get_player_status_icon(); if (!song_data || !song_data->metadata) { out[0] = '\0'; //snprintf(out, out_size, "%*s%s", indent, "", icon); return; } char pretty_title[METADATA_MAX_LENGTH] = {0}; snprintf(pretty_title, METADATA_MAX_LENGTH, "%s", song_data->metadata->title); trim(pretty_title, strlen(pretty_title)); if (ui->hideLogo && song_data->metadata->artist[0] != '\0') { snprintf(out, out_size, "%s %s - %s", icon, song_data->metadata->artist, pretty_title); } else if (ui->hideLogo || show_play_icon) { snprintf(out, out_size, "%s %s", icon, pretty_title); } else { strncpy(out, pretty_title, out_size - 1); out[out_size - 1] = '\0'; } } void print_now_playing(SongData *song_data, UISettings *ui, int row, int col, int max_width) { char title[PATH_MAX + 1]; build_song_title(song_data, ui, title, sizeof(title), true); apply_color(ui->colorMode, ui->theme.nowplaying, ui->color); clear_rest_of_line(); if (title[0] != '\0') { char processed[PATH_MAX + 1] = {0}; process_name(title, processed, max_width, false, false); printf("\033[%d;%dH", row, col); printf("%s", processed); } } // FIXME duplicated code print_logo_art and print_logo_art_for_version int print_logo_art_for_version(const UISettings *ui, int indent, bool centered, bool print_tag_line, bool use_gradient) { int h, w; get_term_size(&w, &h); int centered_col = (w - LOGO_WIDTH) / 2; if (centered_col < 0) centered_col = 0; size_t logoHeight = sizeof(LOGO) / sizeof(LOGO[0]); int col = centered ? centered_col : indent; for (size_t i = 0; i < logoHeight; i++) { unsigned char default_color = ui->default_color; PixelData row_color = {ui->color.r, ui->color.g, ui->color.b}; if (use_gradient && !(ui->color.r == default_color && ui->color.g == default_color && ui->color.b == default_color)) { row_color = get_gradient_color(ui->color, logoHeight - i, logoHeight, 2, 0.8f); } apply_color(ui->colorMode, ui->theme.logo, row_color); clear_line(); print_blank_spaces(col); printf("%s", LOGO[i]); } if (print_tag_line) { printf("\n"); print_blank_spaces(col); printf("MUSIC FOR THE SHELL\n"); } return logoHeight; // lines used by logo } // FIXME duplicated code print_logo and print_logo_for_version int print_logo_for_version(SongData *song_data, UISettings *ui) { int term_w, term_h; get_term_size(&term_w, &term_h); int logo_width = (ui->hideLogo == false) ? LOGO_WIDTH : 0; int max_width = term_w - indent - 4 - logo_width - indent; int height = 2; if (ui->hideLogo) { printf("\n"); clear_line(); } else { height = print_logo_art_for_version(ui, indent + 2, false, false, true); } print_now_playing(song_data, ui, height, indent + logo_width + 4, max_width); printf("\n"); clear_line(); printf("\n"); clear_line(); return height + 1; } int print_logo(SongData *song_data, UISettings *ui, int row, int col) { int term_w, term_h; printf("\033[%d;1H", row); get_term_size(&term_w, &term_h); int logo_width = (ui->hideLogo == false) ? LOGO_WIDTH : 0; int max_width = term_w - indent - 4 - logo_width - indent; int height = 2; printf("\033[%d;%dH", row, col); if (ui->hideLogo) { clear_rest_of_line(); printf("\033[%d;%dH", row++, col); clear_rest_of_line(); } else { height = print_logo_art(row, col, ui, false, false, true); } print_now_playing(song_data, ui, height, col + logo_width + 1, max_width); row += height; printf("\033[%d;%dH", row++, col); clear_rest_of_line(); printf("\033[%d;%dH", row++, col); clear_rest_of_line(); return height + 1; } // FIXME duplicated code: print_about, print_about_for_version int print_about_for_version(SongData *songdata) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); clear_line(); int num_rows = print_logo_for_version(songdata, ui); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); print_blank_spaces(indent); printf(_(" kew version: ")); apply_color(ui->colorMode, ui->theme.help, ui->color); printf("%s\n", ui->VERSION); clear_line(); printf("\n"); num_rows += 2; return num_rows; } int get_year(const char *date_string) { int year; if (sscanf(date_string, "%d", &year) != 1) { return -1; } return year; } void print_cover(int row, int col, int target_height, bool centered, SongData *songdata) { AppState *state = get_app_state(); if (songdata != NULL && songdata->cover != NULL && state->uiSettings.coverEnabled) { if (!state->uiSettings.coverAnsi) { print_square_bitmap(row, col, songdata->cover, songdata->coverWidth, songdata->coverHeight, target_height, centered); } else { print_in_ascii(row, col, songdata->cover_art_path, target_height, centered); } } else { if (centered) { for (int i = 0; i <= target_height; ++i) printf("\n"); } } } void print_title_with_delay(int row, int col, const char *text, int delay, int max_width) { int max = strnlen(text, max_width); if (max == max_width) // For long names max -= 2; // Accommodate for the cursor that we display after // the name. for (int i = 0; i <= max && delay; i++) { printf("\033[%d;%dH", row, col); clear_rest_of_line(); for (int j = 0; j < i; j++) { printf("%c", text[j]); } printf("█"); fflush(stdout); c_sleep(delay); } if (delay) c_sleep(delay * 20); printf("\033[%d;%dH", row, col); clear_rest_of_line(); printf("%s", text); printf("\n"); fflush(stdout); } void print_metadata(int row, int col, int max_width, TagSettings const *metadata, UISettings *ui) { if (row < 1) row = 1; if (col < 1) col = 1; if (strnlen(metadata->artist, METADATA_MAX_LENGTH) > 0) { apply_color(ui->colorMode, ui->theme.trackview_artist, ui->color); printf("\033[%d;%dH", row + 1, col); clear_rest_of_line(); printf(" %.*s", max_width, metadata->artist); } if (strnlen(metadata->album, METADATA_MAX_LENGTH) > 0) { apply_color(ui->colorMode, ui->theme.trackview_album, ui->color); printf("\033[%d;%dH", row + 2, col); clear_rest_of_line(); printf(" %.*s", max_width, metadata->album); } if (strnlen(metadata->date, METADATA_MAX_LENGTH) > 0) { apply_color(ui->colorMode, ui->theme.trackview_year, ui->color); printf("\033[%d;%dH", row + 3, col); clear_rest_of_line(); int year = get_year(metadata->date); if (year == -1) printf(" %s", metadata->date); else printf(" %d", year); } PixelData pixel = increase_luminosity(ui->color, 20); if (pixel.r == 255 && pixel.g == 255 && pixel.b == 255) { unsigned char default_color = ui->default_color; pixel.r = default_color; pixel.g = default_color; pixel.b = default_color; } apply_color(ui->colorMode, ui->theme.trackview_title, pixel); if (strnlen(metadata->title, METADATA_MAX_LENGTH) > 0) { // Clean up title before printing char pretty_title[PATH_MAX + 1]; pretty_title[0] = '\0'; process_name(metadata->title, pretty_title, max_width, false, false); print_title_with_delay(row, col + 1, pretty_title, ui->titleDelay, max_width); } } int calc_elapsed_bars(double elapsed_seconds, double duration, int num_progress_bars) { if (elapsed_seconds == 0) return 0; return (int)((elapsed_seconds / duration) * num_progress_bars); } void print_progress(double elapsed_seconds, double total_seconds, ma_uint32 sample_rate, int avg_bit_rate, int allowed_width) { int progress_width = 39; if (allowed_width < progress_width) return; int elapsed_hours = (int)(elapsed_seconds / 3600); int elapsed_minutes = (int)(((int)elapsed_seconds / 60) % 60); int elapsed_seconds_remainder = (int)elapsed_seconds % 60; int total_hours = (int)(total_seconds / 3600); int total_minutes = (int)(((int)total_seconds / 60) % 60); int total_seconds_remainder = (int)total_seconds % 60; int progress_percentage = (int)((elapsed_seconds / total_seconds) * 100); int vol = get_volume(); if (total_seconds >= 3600) { // Song is more than 1 hour long: use full HH:MM:SS format printf(" %02d:%02d:%02d / %02d:%02d:%02d (%d%%) Vol:%d%%", elapsed_hours, elapsed_minutes, elapsed_seconds_remainder, total_hours, total_minutes, total_seconds_remainder, progress_percentage, vol); } else { // Song is less than 1 hour: use M:SS format int elapsed_total_minutes = elapsed_seconds / 60; int elapsed_secs = (int)elapsed_seconds % 60; int total_total_minutes = total_seconds / 60; int total_secs = (int)total_seconds % 60; printf(" %d:%02d / %d:%02d (%d%%) Vol:%d%%", elapsed_total_minutes, elapsed_secs, total_total_minutes, total_secs, progress_percentage, vol); } double rate = ((float)sample_rate) / 1000; if (allowed_width > progress_width + 10) { if (rate == (int)rate) printf(" %dkHz", (int)rate); else printf(" %.1fkHz", rate); } if (allowed_width > progress_width + 19) { if (avg_bit_rate > 0) printf(" %dkb/s ", avg_bit_rate); } } void print_time(int row, int col, double elapsed_seconds, ma_uint32 sample_rate, int avg_bit_rate, int allowed_width) { AppState *state = get_app_state(); apply_color(state->uiSettings.colorMode, state->uiSettings.theme.trackview_time, state->uiSettings.color); int term_w, term_h; get_term_size(&term_w, &term_h); printf("\033[%d;%dH", row, col); if (term_h > min_height) { double duration = get_current_song_duration(); double elapsed = elapsed_seconds; print_progress(elapsed, duration, sample_rate, avg_bit_rate, allowed_width); clear_rest_of_line(); } } int calc_indent_normal(void) { int text_width = (ABSOLUTE_MIN_WIDTH > preferred_width) ? ABSOLUTE_MIN_WIDTH : preferred_width; return get_indentation(text_width - 1) - 1; } int calc_indent_track_view(TagSettings *metadata) { if (metadata == NULL) return calc_indent_normal(); int title_length = strnlen(metadata->title, METADATA_MAX_LENGTH); int album_length = strnlen(metadata->album, METADATA_MAX_LENGTH); int max_text_length = (album_length > title_length) ? album_length : title_length; text_width = (ABSOLUTE_MIN_WIDTH > preferred_width) ? ABSOLUTE_MIN_WIDTH : preferred_width; int term_w, term_h; get_term_size(&term_w, &term_h); int max_size = term_w - 2; if (max_text_length > 0 && max_text_length < max_size && max_text_length > text_width) text_width = max_text_length; if (text_width > max_size) text_width = max_size; return get_indentation(text_width - 1) - 1; } void calc_indent(SongData *songdata) { AppState *state = get_app_state(); if ((state->currentView == TRACK_VIEW && songdata == NULL) || state->currentView != TRACK_VIEW) { indent = calc_indent_normal(); } else { indent = calc_indent_track_view(songdata->metadata); } } int get_indent() { return indent; } void print_glimmering_text(int row, int col, char *text, int text_length, char *nerd_font_text, PixelData color) { int bright_index = 0; PixelData vbright = increase_luminosity(color, 120); PixelData bright = increase_luminosity(color, 60); printf("\033[%d;%dH", row, col); clear_rest_of_line(); while (bright_index < text_length) { for (int i = 0; i < text_length; i++) { if (i == bright_index) { set_text_color_RGB(vbright.r, vbright.g, vbright.b); printf("%c", text[i]); } else if (i == bright_index - 1 || i == bright_index + 1) { set_text_color_RGB(bright.r, bright.g, bright.b); printf("%c", text[i]); } else { set_text_color_RGB(color.r, color.g, color.b); printf("%c", text[i]); } fflush(stdout); c_usleep(50); } printf("%s", nerd_font_text); fflush(stdout); c_usleep(50); bright_index++; printf("\033[%d;%dH", row, col); } } void print_error_row(int row, int col, UISettings *ui) { int term_w, term_h; get_term_size(&term_w, &term_h); printf("\033[%d;%dH", row, col); if (!has_printed_error_message() && has_error_message()) { apply_color(ui->colorMode, ui->theme.footer, footer_color); printf(" %s", get_error_message()); mark_error_message_as_printed(); } clear_rest_of_line(); fflush(stdout); } bool is_ascii_only(const char *text) { if (text == NULL) return false; // or true, depending on how you want to handle NULL for (const char *p = text; *p; p++) { if ((unsigned char)*p >= 128) { return false; } } return true; } void print_footer(int row, int col) { int term_w, term_h; get_term_size(&term_w, &term_h); AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); UIState *uis = &(state->uiState); if (preferred_width < 0 || preferred_height < 0) // mini view return; footer_row = row; footer_col = col; printf("\033[%d;%dH", row, col); PixelData f_color; f_color.r = footer_color.r; f_color.g = footer_color.g; f_color.b = footer_color.b; apply_color(ui->colorMode, ui->theme.footer, f_color); if (ui->themeIsSet && ui->theme.footer.type == COLOR_TYPE_RGB) { f_color.r = ui->theme.footer.rgb.r; f_color.g = ui->theme.footer.rgb.g; f_color.b = ui->theme.footer.rgb.b; } char text[100]; char playlist[32], library[32], track[32], search[32], help[32]; snprintf(playlist, sizeof(playlist), "%s", get_binding_string(EVENT_SHOWPLAYLIST, true)); snprintf(library, sizeof(library), "%s", get_binding_string(EVENT_SHOWLIBRARY, true)); snprintf(track, sizeof(track), "%s", get_binding_string(EVENT_SHOWTRACK, true)); snprintf(search, sizeof(search), "%s", get_binding_string(EVENT_SHOWSEARCH, true)); snprintf(help, sizeof(search), "%s", get_binding_string(EVENT_SHOWHELP, true)); snprintf(text, sizeof(text), _("%s Playlist|%s Library|%s Track|%s Search|%s Help"), playlist, library, track, search, help); char icons_text[100] = ""; size_t max_length = sizeof(icons_text); size_t currentLength = strnlen(icons_text, max_length); #ifndef __ANDROID__ if (term_w >= ABSOLUTE_MIN_WIDTH) { #endif if (is_paused()) { #if defined(__ANDROID__) || defined(__APPLE__) char pause_text[] = " ။"; #else char pause_text[] = " ⏸"; #endif snprintf(icons_text + currentLength, max_length - currentLength, "%s", pause_text); currentLength += strlen(pause_text); } else if (is_stopped()) { char pause_text[] = " ■"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", pause_text); currentLength += strlen(pause_text); } else { char pause_text[] = " ▶"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", pause_text); currentLength += strlen(pause_text); } #ifndef __ANDROID__ } #endif if (is_repeat_enabled()) { char repeat_text[] = " ↻"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", repeat_text); currentLength += strlen(repeat_text); } else if (is_repeat_list_enabled()) { char repeat_text[] = " ↻L"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", repeat_text); currentLength += strlen(repeat_text); } if (is_shuffle_enabled()) { char shuffle_text[] = " ⇄"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", shuffle_text); currentLength += strlen(shuffle_text); } if (uis->isFastForwarding) { char forward_text[] = " ⇉"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", forward_text); currentLength += strlen(forward_text); } if (uis->isRewinding) { char rewind_text[] = " ⇇"; snprintf(icons_text + currentLength, max_length - currentLength, "%s", rewind_text); currentLength += strlen(rewind_text); } if (term_w < ABSOLUTE_MIN_WIDTH) { #ifndef __ANDROID__ if (term_w > (int)currentLength + indent) { printf("%s", icons_text); // Print just the shuffle // and replay settings } #else // Always try to print the footer on Android because it will // most likely be too narrow. We use two rows for the footer on // Android. print_blank_spaces(indent); printf("%.*s", term_w * 2, text); printf("%s", icons_text); #endif clear_rest_of_line(); return; } int text_length = strnlen(text, 100); int random_number = get_random_number(1, 808); if (random_number == 808 && !ui->hideGlimmeringText && is_ascii_only(text)) { print_glimmering_text(row, col, text, text_length, icons_text, f_color); } else { printf("%s", text); printf("%s", icons_text); } clear_rest_of_line(); } void calc_and_print_last_row_and_error_row(void) { AppState *state = get_app_state(); int term_w, term_h; get_term_size(&term_w, &term_h); #if defined(__ANDROID__) // Use two rows for the footer on Android. It makes everything // fit even with narrow terminal widths. if (has_error_message()) print_error_row(term_h - 1, indent, &(state->uiSettings)); else print_footer(term_h - 1, indent); #else print_error_row(term_h - 1, indent, &(state->uiSettings)); print_footer(term_h, indent + 1); #endif } int print_about(SongData *songdata) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); clear_line(); int col = indent + (indent / 4); if (ui->hideSideCover) col = indent; int row = print_logo(songdata, ui, 1, col + 2); printf("\033[%d;%dH", ++row, col); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); printf(_(" kew version: ")); apply_color(ui->colorMode, ui->theme.help, ui->color); printf("%s", ui->VERSION); clear_rest_of_line(); printf("\033[%d;%dH", ++row, col); clear_rest_of_line(); return row; } #define CHECK_LIST_LIMIT() \ do { \ if (num_printed_rows > max_list_size) { \ calc_and_print_last_row_and_error_row(); \ return num_printed_rows; \ } \ } while (0) int show_key_bindings(SongData *songdata) { AppState *state = get_app_state(); int num_printed_rows = 0; int term_w, term_h; get_term_size(&term_w, &term_h); max_list_size = term_h - 3; UISettings *ui = &(state->uiSettings); num_printed_rows += print_about(songdata); CHECK_LIST_LIMIT(); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); int indentation = indent + (indent / 4) + 1; if (ui->hideSideCover) indentation = indent + 1; printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); printf(_(" Theme: ")); if (ui->colorMode == COLOR_MODE_ALBUM) { apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); printf(_("Using ")); apply_color(ui->colorMode, ui->theme.text, ui->color); printf(_("Colors ")); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); printf(_("From Track Covers")); } else { apply_color(ui->colorMode, ui->theme.help, ui->color); printf("%s", ui->theme.theme_name); } apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); if (ui->colorMode != COLOR_MODE_ALBUM && strcmp(ui->theme.theme_author, "Ravachol") != 0) { printf(_(" Author: ")); apply_color(ui->colorMode, ui->theme.help, ui->color); printf("%s", ui->theme.theme_author); } num_printed_rows += 2; CHECK_LIST_LIMIT(); printf("\033[%d;%dH", num_printed_rows, indentation + 1); printf(_(" · Play/Pause: %s"), get_binding_string(EVENT_PLAY_PAUSE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Enqueue/Dequeue: %s"), get_binding_string(EVENT_ENQUEUE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Enqueue and Play: %s"), get_binding_string(EVENT_ENQUEUEANDPLAY, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Quit: %s"), get_binding_string(EVENT_QUIT, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Switch tracks: %s"), get_binding_string(EVENT_PREV, false)); printf(_(" and %s"), get_binding_string(EVENT_NEXT, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Volume: %s "), get_binding_string(EVENT_VOLUME_UP, false)); printf(_("and %s"), get_binding_string(EVENT_VOLUME_DOWN, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Scroll: %s"), get_binding_string(EVENT_PREVPAGE, false)); printf(_(", %s"), get_binding_string(EVENT_NEXTPAGE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Clear List: %s"), get_binding_string(EVENT_CLEARPLAYLIST, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Remove from playlist: %s"), get_binding_string(EVENT_REMOVE, true)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Move songs: %s"), get_binding_string(EVENT_MOVESONGUP, true)); printf(_("/%s"), get_binding_string(EVENT_MOVESONGDOWN, true)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Change View: %s or "), get_binding_string(EVENT_NEXTVIEW, false)); printf("%s, ", get_binding_string(EVENT_SHOWPLAYLIST, true)); printf("%s, ", get_binding_string(EVENT_SHOWLIBRARY, true)); printf("%s, ", get_binding_string(EVENT_SHOWTRACK, true)); printf("%s, ", get_binding_string(EVENT_SHOWSEARCH, true)); printf("%s", get_binding_string(EVENT_SHOWHELP, true)); printf(_(" or click the footer")); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf( _(" · Cycle Color Mode: %s (default theme, theme or cover colors)"), get_binding_string(EVENT_CYCLECOLORMODE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Cycle Themes: %s"), get_binding_string(EVENT_CYCLETHEMES, false)); CHECK_LIST_LIMIT(); // FIXME: Enable Chroma // print_blank_spaces(indentation); // printf(_(" · Cycle Chroma Visualization: %s (requires Chroma)\n"), get_binding_string(EVENT_CYCLEVISUALIZATION, false)); // num_printed_rows++; // CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Stop: %s"), get_binding_string(EVENT_STOP, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Update Library: %s"), get_binding_string(EVENT_UPDATELIBRARY, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Sort Library: %s"), get_binding_string(EVENT_SORTLIBRARY, true)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Toggle Visualizer: %s"), get_binding_string(EVENT_TOGGLEVISUALIZER, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); // FIXME: Enable Chroma //printf(_(" · Toggle ASCII Cover: %s (disables Chroma)\n"), get_binding_string(EVENT_TOGGLEASCII, false)); printf(_(" · Toggle ASCII Cover: %s"), get_binding_string(EVENT_TOGGLEASCII, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Toggle Lyrics Page on Track View: %s"), get_binding_string(EVENT_SHOWLYRICSPAGE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Toggle Notifications: %s"), get_binding_string(EVENT_TOGGLENOTIFICATIONS, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Cycle Repeat: %s (repeat/repeat list/off)"), get_binding_string(EVENT_TOGGLEREPEAT, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Shuffle: %s"), get_binding_string(EVENT_SHUFFLE, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Seek: %s and"), get_binding_string(EVENT_SEEKBACK, false)); printf(_(" %s"), get_binding_string(EVENT_SEEKFORWARD, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Export Playlist: %s (named after the first song)"), get_binding_string(EVENT_EXPORTPLAYLIST, false)); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); printf(_(" · Add Song To 'kew favorites.m3u': %s (run with 'kew .')"), get_binding_string(EVENT_ADDTOFAVORITESPLAYLIST, false)); num_printed_rows += 2; CHECK_LIST_LIMIT(); printf("\033[%d;%dH", num_printed_rows, indentation + 1); apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); printf(_(" Project URL: ")); apply_color(ui->colorMode, ui->theme.link, ui->color); printf("https://codeberg.org/ravachol/kew"); CHECK_LIST_LIMIT(); printf("\033[%d;%dH", ++num_printed_rows, indentation + 1); apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); printf(_(" Please Donate: ")); apply_color(ui->colorMode, ui->theme.link, ui->color); printf("https://ko-fi.com/ravachol"); num_printed_rows += 2; CHECK_LIST_LIMIT(); apply_color(ui->colorMode, ui->theme.text, ui->defaultColorRGB); printf("\033[%d;%dH", num_printed_rows, indentation + 1); printf(" Copyright © 2022-2025 Ravachol\n"); num_printed_rows += 1; CHECK_LIST_LIMIT(); calc_and_print_last_row_and_error_row(); num_printed_rows += 2; return num_printed_rows; } void toggle_show_view(ViewState view_to_show) { AppState *state = get_app_state(); trigger_refresh(); if (state->currentView == TRACK_VIEW) clear_screen(); if (state->currentView == view_to_show) { state->currentView = TRACK_VIEW; } else { state->currentView = view_to_show; } } void switch_to_next_view(void) { AppState *state = get_app_state(); switch (state->currentView) { case PLAYLIST_VIEW: state->currentView = LIBRARY_VIEW; break; case LIBRARY_VIEW: state->currentView = (get_current_song() != NULL) ? TRACK_VIEW : SEARCH_VIEW; break; case TRACK_VIEW: state->currentView = SEARCH_VIEW; clear_screen(); break; case SEARCH_VIEW: state->currentView = HELP_VIEW; break; case HELP_VIEW: state->currentView = PLAYLIST_VIEW; break; } trigger_refresh(); } void switch_to_previous_view(void) { AppState *state = get_app_state(); switch (state->currentView) { case PLAYLIST_VIEW: state->currentView = HELP_VIEW; break; case LIBRARY_VIEW: state->currentView = PLAYLIST_VIEW; break; case TRACK_VIEW: state->currentView = LIBRARY_VIEW; clear_screen(); break; case SEARCH_VIEW: state->currentView = (get_current_song() != NULL) ? TRACK_VIEW : LIBRARY_VIEW; break; case HELP_VIEW: state->currentView = SEARCH_VIEW; break; } trigger_refresh(); } void show_track(void) { AppState *state = get_app_state(); trigger_refresh(); state->currentView = TRACK_VIEW; } void flip_next_page(void) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); if (state->currentView == LIBRARY_VIEW) { chosen_lib_row += max_lib_list_size - 1; start_lib_iter += max_lib_list_size - 1; trigger_refresh(); } else if (state->currentView == PLAYLIST_VIEW) { chosen_row += max_list_size - 1; chosen_row = (chosen_row >= unshuffled_playlist->count) ? unshuffled_playlist->count - 1 : chosen_row; trigger_refresh(); } else if (state->currentView == SEARCH_VIEW) { chosen_search_result_row += max_search_list_size - 1; chosen_search_result_row = (chosen_search_result_row >= get_search_results_count()) ? get_search_results_count() - 1 : chosen_search_result_row; start_search_iter += max_search_list_size - 1; trigger_refresh(); } } void flip_prev_page(void) { AppState *state = get_app_state(); if (state->currentView == LIBRARY_VIEW) { chosen_lib_row -= max_lib_list_size; start_lib_iter -= max_lib_list_size; trigger_refresh(); } else if (state->currentView == PLAYLIST_VIEW) { chosen_row -= max_list_size; chosen_row = (chosen_row > 0) ? chosen_row : 0; trigger_refresh(); } else if (state->currentView == SEARCH_VIEW) { chosen_search_result_row -= max_search_list_size; chosen_search_result_row = (chosen_search_result_row > 0) ? chosen_search_result_row : 0; start_search_iter -= max_search_list_size; trigger_refresh(); } } void scroll_next(void) { AppState *state = get_app_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); if (state->currentView == PLAYLIST_VIEW) { chosen_row++; chosen_row = (chosen_row >= unshuffled_playlist->count) ? unshuffled_playlist->count - 1 : chosen_row; trigger_refresh(); } else if (state->currentView == LIBRARY_VIEW) { previous_chosen_lib_row = chosen_lib_row; chosen_lib_row++; trigger_refresh(); } else if (state->currentView == SEARCH_VIEW) { chosen_search_result_row++; trigger_refresh(); } } void scroll_prev(void) { AppState *state = get_app_state(); if (state->currentView == PLAYLIST_VIEW) { chosen_row--; chosen_row = (chosen_row > 0) ? chosen_row : 0; trigger_refresh(); } else if (state->currentView == LIBRARY_VIEW) { previous_chosen_lib_row = chosen_lib_row; chosen_lib_row--; trigger_refresh(); } else if (state->currentView == SEARCH_VIEW) { chosen_search_result_row--; chosen_search_result_row = (chosen_search_result_row > 0) ? chosen_search_result_row : 0; trigger_refresh(); } } int get_row_within_bounds(int row) { PlayList *unshuffled_playlist = get_unshuffled_playlist(); if (row >= unshuffled_playlist->count) { row = unshuffled_playlist->count - 1; } if (row < 0) row = 0; return row; } int print_logo_and_adjustments(SongData *song_data, int term_width, UISettings *ui, int indentation) { int about_rows = print_logo(song_data, ui, 1, indentation + 2); apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); int row = about_rows; int col = indentation + 1; if (term_width > 52 && !ui->hideHelp) { printf("\033[%d;%dH", row, col); clear_rest_of_line(); printf("\033[%d;%dH", ++row, col + 1); printf(_(" Select:↑/↓ or k/j.")); printf(_(" Accept:%s."), get_binding_string(EVENT_ENQUEUE, true)); printf(_(" Clear:%s."), get_binding_string(EVENT_CLEARPLAYLIST, true)); printf("\033[%d;%dH", ++row, col); clear_rest_of_line(); printf("\033[%d;%dH", row, col + 1); #ifndef __APPLE__ printf(_(" Scroll:PgUp/PgDn.")); #else printf(_(" Scroll:Fn+↑/↓.")); #endif printf(_(" Remove:%s."), get_binding_string(EVENT_REMOVE, true)); printf(_(" Move songs:%s"), get_binding_string(EVENT_MOVESONGUP, true)); printf(_("/%s."), get_binding_string(EVENT_MOVESONGDOWN, true)); printf("\033[%d;%dH", ++row, col); clear_rest_of_line(); printf("\033[%d;%dH", ++row, col); clear_rest_of_line(); } return row; } void show_search(SongData *song_data, int *chosen_row) { int term_w, term_h; get_term_size(&term_w, &term_h); max_search_list_size = term_h - 2; AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); goto_first_line_first_row(); int col = indent + (indent / 4); if (ui->hideSideCover) col = indent; int about_rows = print_logo(song_data, ui, 1, col + 2); max_search_list_size -= about_rows; apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); int row = about_rows; printf("\033[%d;%dH", row, col + 2); clear_rest_of_line(); if (term_w > col + 38 && !ui->hideHelp) { printf("\033[%d;%dH", ++row, col + 2); printf(_(" Select:↑/↓.")); printf(_(" Enqueue:%s."), get_binding_string(EVENT_ENQUEUE, true)); printf(_(" Play:%s."), get_binding_string(EVENT_ENQUEUEANDPLAY, true)); printf("\033[%d;%dH", ++row, col + 2); clear_rest_of_line(); max_search_list_size -= 2; } printf("\033[%d;%dH", ++row, col); clear_rest_of_line(); display_search(row, col, max_search_list_size, chosen_row, start_search_iter); calc_and_print_last_row_and_error_row(); } void show_playlist(SongData *song_data, PlayList *list, int *chosen_song, int *chosen_node_id) { int term_w, term_h; get_term_size(&term_w, &term_h); max_list_size = term_h - 1; AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); int update_counter = get_update_counter(); if (get_is_long_name() && is_same_name_as_last_time && update_counter % scrolling_interval != 0) { increment_update_counter(); trigger_refresh(); return; } else cancel_refresh(); goto_first_line_first_row(); int row = 1; int col = indent + (indent / 4); if (ui->hideSideCover) col = indent; int about_rows = print_logo_and_adjustments(song_data, term_w, ui, col); max_list_size -= about_rows; row = about_rows; apply_color(ui->colorMode, ui->theme.header, ui->color); if (max_list_size > 0) { printf("\033[%d;%dH", row, col + 1); printf(_(" ─ PLAYLIST ─")); clear_rest_of_line(); } max_list_size -= 2; if (max_list_size > 0) display_playlist(row + 1, col + 1, list, max_list_size, chosen_song, chosen_node_id, state->uiState.resetPlaylistDisplay); calc_and_print_last_row_and_error_row(); } void reset_search_result(void) { chosen_search_result_row = 0; } void print_progress_bar(int row, int col, AppSettings *settings, UISettings *ui, int elapsed_bars, int num_progress_bars) { PixelData color = ui->color; ProgressBar *progress_bar = get_progress_bar(); progress_bar->row = row; progress_bar->col = col + 1; progress_bar->length = num_progress_bars; printf("\033[%d;%dH", row, col); printf(" "); for (int i = 0; i < num_progress_bars; i++) { if (i > elapsed_bars) { if (ui->colorMode == COLOR_MODE_ALBUM) { PixelData tmp = increase_luminosity(color, 50); printf("\033[38;2;%d;%d;%dm", tmp.r, tmp.g, tmp.b); apply_color(ui->colorMode, ui->theme.progress_empty, tmp); } else { apply_color(ui->colorMode, ui->theme.progress_empty, color); } if (i % 2 == 0) printf( "%s", settings->progressBarApproachingEvenChar); else printf("%s", settings->progressBarApproachingOddChar); continue; } if (i < elapsed_bars) { apply_color(ui->colorMode, ui->theme.progress_filled, color); if (i % 2 == 0) printf("%s", settings->progressBarElapsedEvenChar); else printf("%s", settings->progressBarElapsedOddChar); } else if (i == elapsed_bars) { apply_color(ui->colorMode, ui->theme.progress_elapsed, color); if (i % 2 == 0) printf("%s", settings->progressBarCurrentEvenChar); else printf("%s", settings->progressBarCurrentOddChar); } } clear_rest_of_line(); } void print_visualizer(int row, int col, int visualizer_width, AppSettings *settings, double elapsed_seconds) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); UIState *uis = &(state->uiState); int height = state->uiSettings.visualizer_height; int term_w, term_h; get_term_size(&term_w, &term_h); if (row + height + 2 > term_h) height -= (row + height + 1 - term_h); if (height < 2) return; if (ui->visualizerEnabled) { uis->num_progress_bars = (int)visualizer_width / 2; double duration = get_current_song_duration(); draw_spectrum_visualizer(row, col, height, visualizer_width); int elapsed_bars = calc_elapsed_bars(elapsed_seconds, duration, visualizer_width); print_progress_bar(row + height - 1, col, settings, ui, elapsed_bars, visualizer_width - 1); } } FileSystemEntry *get_chosen_dir(void) { return chosen_dir; } void set_chosen_dir(FileSystemEntry *entry) { AppState *state = get_app_state(); if (entry == NULL) { return; } if (entry->is_directory) { state->uiState.currentLibEntry = chosen_dir = entry; } } void reset_chosen_dir(void) { chosen_dir = NULL; } void apply_tree_item_color(UISettings *ui, int depth, PixelData track_color, PixelData enqueued_color, bool is_enqueued, bool is_playing) { if (depth <= 1) { apply_color(ui->colorMode, ui->theme.library_artist, enqueued_color); } else { if (ui->colorMode == COLOR_MODE_ALBUM || (ui->colorMode == COLOR_MODE_THEME && ui->theme.library_track.type == COLOR_TYPE_RGB)) apply_color(COLOR_MODE_ALBUM, ui->theme.library_track, track_color); else apply_color(ui->colorMode, ui->theme.library_track, track_color); } if (is_enqueued) { if (is_playing) { apply_color(ui->colorMode, ui->theme.library_playing, ui->color); } else { if (ui->colorMode == COLOR_MODE_ALBUM || (ui->colorMode == COLOR_MODE_THEME && ui->theme.library_enqueued.type == COLOR_TYPE_RGB)) apply_color(COLOR_MODE_ALBUM, ui->theme.library_enqueued, enqueued_color); else apply_color(ui->colorMode, ui->theme.library_enqueued, enqueued_color); } } } int display_tree(FileSystemEntry *root, int depth, int max_list_size, int max_name_width, int row, int col) { if (max_name_width < 0) max_name_width = 0; char dir_name[max_name_width + 1]; char filename[PATH_MAX + 1]; bool foundChosen = false; int is_playing = 0; int extra_indent = 0; AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); UIState *uis = &(state->uiState); Node *current = get_current_song(); if (current != NULL && (strcmp(current->song.file_path, root->full_path) == 0)) { is_playing = 1; } if (start_lib_iter < 0) start_lib_iter = 0; if (lib_iter >= start_lib_iter + max_list_size) { return false; } int threshold = start_lib_iter + (max_list_size + 1) / 2; if (chosen_lib_row > threshold) { start_lib_iter = chosen_lib_row - max_list_size / 2 + 1; } if (chosen_lib_row < 0) start_lib_iter = chosen_lib_row = lib_iter = 0; if (root == NULL) return false; PixelData row_color = {ui->default_color, ui->default_color, ui->default_color}; PixelData row_color2 = {ui->default_color, ui->default_color, ui->default_color}; PixelData rgb_track = {ui->default_color, ui->default_color, ui->default_color}; PixelData rgb_enqueued = {ui->default_color, ui->default_color, ui->default_color}; if (ui->colorMode == COLOR_MODE_THEME && ui->theme.library_track.type == COLOR_TYPE_RGB) { rgb_track = ui->theme.library_track.rgb; rgb_enqueued = ui->theme.library_enqueued.rgb; } else { rgb_enqueued = ui->color; } if (!(rgb_track.r == ui->default_color && rgb_track.g == ui->default_color && rgb_track.b == ui->default_color)) row_color = get_gradient_color(rgb_track, lib_iter - start_lib_iter, max_list_size, max_list_size / 2, 0.7f); if (!(rgb_enqueued.r == ui->default_color && rgb_enqueued.g == ui->default_color && rgb_enqueued.b == ui->default_color)) row_color2 = get_gradient_color(rgb_enqueued, lib_iter - start_lib_iter, max_list_size, max_list_size / 2, 0.7f); if (!(root->is_directory || (!root->is_directory && depth == 1) || (root->is_directory && depth == 0) || (chosen_dir != NULL && uis->allowChooseSongs && root->parent != NULL && (strcmp(root->parent->full_path, chosen_dir->full_path) == 0 || strcmp(root->full_path, chosen_dir->full_path) == 0)))) { return foundChosen; } if (depth >= 0) { if (state->uiState.currentLibEntry != NULL && state->uiState.currentLibEntry != last_entry && !state->uiState.currentLibEntry->is_directory && state->uiState.currentLibEntry->parent != NULL && state->uiState.currentLibEntry->parent->parent != NULL && state->uiState.currentLibEntry->parent == chosen_dir) { FileSystemEntry *tmpc = state->uiState.currentLibEntry->parent->children; lib_current_dir_song_count = 0; while (tmpc != NULL) { if (!tmpc->is_directory) lib_current_dir_song_count++; tmpc = tmpc->next; } last_entry = state->uiState.currentLibEntry; } if (lib_iter >= start_lib_iter) { apply_tree_item_color(ui, depth, row_color, row_color2, root->is_enqueued, is_playing); printf("\033[%d;%dH", row + lib_row_count, col); clear_rest_of_line(); if (depth >= 2) extra_indent += 2; // If more than two levels deep add an extra // indentation extra_indent += (depth - 2 <= 0) ? 0 : depth - 2; printf("\033[%d;%dH", row + lib_row_count, col + extra_indent); if (chosen_lib_row == lib_iter) { if (root->is_enqueued) { printf("\x1b[7m * "); } else { printf(" \x1b[7m "); } state->uiState.currentLibEntry = root; if (uis->allowChooseSongs == true && (chosen_dir == NULL || (state->uiState.currentLibEntry != NULL && state->uiState.currentLibEntry->parent != NULL && chosen_dir != NULL && !is_contained_within(state->uiState.currentLibEntry, chosen_dir) && strcmp(root->full_path, chosen_dir->full_path) != 0))) { uis->collapseView = true; trigger_refresh(); if (!uis->openedSubDir) { uis->allowChooseSongs = false; chosen_dir = NULL; } } foundChosen = true; } else { if (root->is_enqueued) { printf(" * "); } else { printf(" "); } } if (max_name_width < extra_indent) max_name_width = extra_indent; if (root->is_directory) { dir_name[0] = '\0'; if (strcmp(root->name, "root") == 0) snprintf(dir_name, max_name_width + 1 - extra_indent, "%s", _("─ MUSIC LIBRARY ─")); else snprintf(dir_name, max_name_width + 1 - extra_indent, "%s", root->name); char *upper_dir_name = string_to_upper(dir_name); if (depth == 1) { printf("%s", upper_dir_name); } else { printf("%s", dir_name); } if (strcmp(dir_name, "Warlord") == 0) fflush(stdout); free(upper_dir_name); } else { filename[0] = '\0'; is_same_name_as_last_time = (previous_chosen_lib_row == chosen_lib_row); if (foundChosen) { previous_chosen_lib_row = chosen_lib_row; } if (!is_same_name_as_last_time) { reset_name_scroll(); } if (foundChosen) { process_name_scroll(root->name, filename, max_name_width - extra_indent, is_same_name_as_last_time); } else { process_name(root->name, filename, max_name_width - extra_indent, true, true); } if (is_playing) { if (chosen_lib_row == lib_iter) { printf("\x1b[7m"); } } printf("└─ "); // Playlist if (path_ends_with(root->full_path, "m3u") || path_ends_with(root->full_path, "m3u8")) { printf("♫ "); max_name_width = max_name_width - 2; } if (is_playing && chosen_lib_row != lib_iter) { printf("\e[4m"); } printf("%s", filename); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->defaultColorRGB); lib_song_iter++; } lib_row_count++; } lib_iter++; } FileSystemEntry *child = root->children; while (child != NULL) { if (display_tree(child, depth + 1, max_list_size, max_name_width, row, col)) foundChosen = true; child = child->next; } return foundChosen; } size_t string_hash(const char *str) { if (str == NULL) return 0; size_t hash = 5381; int c; while ((c = *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } void print_side_cover(SongData *songdata) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); int term_w, term_h; get_term_size(&term_w, &term_h); if (state->currentView != last_view) redraw_side_cover = true; last_view = state->currentView; const char *current_ptr = songdata ? songdata->cover_art_path : NULL; if (current_ptr != last_cover_path_ptr) { last_cover_path_ptr = current_ptr; last_cover_path_hash = current_ptr ? string_hash(current_ptr) : (size_t)-1; redraw_side_cover = true; } else if (current_ptr != NULL) { size_t current_hash = string_hash(current_ptr); if (current_hash != last_cover_path_hash) { last_cover_path_hash = current_hash; redraw_side_cover = true; } } if (state->currentView == TRACK_VIEW) return; int cover_indent = (indent + (indent / 4)) / 8; int row = 2; int col = cover_indent + 3; int target_height = indent - cover_indent; if (redraw_side_cover) { clear_screen(); redraw_side_cover = false; if (ui->coverEnabled && term_w - 4 > indent / 2) { if (row < 2) row = 2; if (col < 1) col = 1; TermSize term_size; gint cell_width = -1, cell_height = -1; tty_init(); get_tty_size(&term_size); int max_height = term_h - 4; if (target_height > max_height) target_height = max_height; 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; } // Set default cell size for some terminals if (cell_width == -1 || cell_height == -1) { cell_width = 8; cell_height = 16; } float aspect_ratio_correction = (float)cell_height / (float)cell_width; int corrected_width = (int)(target_height * aspect_ratio_correction); row = term_h / 2 - (target_height / aspect_ratio_correction / 2); while (corrected_width > indent - cover_indent) { target_height--; corrected_width = (int)(target_height * aspect_ratio_correction); } if (target_height > MIN_COVER_SIZE && ui->hideSideCover != 1) { print_cover(row, col, target_height, false, songdata); } } } } void show_library(SongData *song_data, AppSettings *settings) { AppState *state = get_app_state(); bool refresh_triggered = is_refresh_triggered(); // For scrolling names, update every nth time if (get_is_long_name() && is_same_name_as_last_time && get_update_counter() % scrolling_interval != 0) { trigger_refresh(); return; } else cancel_refresh(); goto_first_line_first_row(); if (state->uiState.collapseView) { if (previous_chosen_lib_row < chosen_lib_row) { if (!state->uiState.openedSubDir) { chosen_lib_row -= lib_current_dir_song_count; lib_current_dir_song_count = 0; } else { chosen_lib_row -= state->uiState.numSongsAboveSubDir; state->uiState.openedSubDir = false; state->uiState.numSongsAboveSubDir = 0; state->uiState.collapseView = false; } } else { if (state->uiState.openedSubDir) { chosen_lib_row -= state->uiState.numSongsAboveSubDir; } lib_current_dir_song_count = 0; state->uiState.openedSubDir = false; state->uiState.numSongsAboveSubDir = 0; } state->uiState.collapseView = false; } UISettings *ui = &(state->uiSettings); lib_iter = 0; lib_song_iter = 0; start_lib_iter = 0; int term_w, term_h; get_term_size(&term_w, &term_h); int total_height = term_h; max_lib_list_size = total_height; int row = 1; int col = indent + (indent / 4); if (ui->hideSideCover) col = indent; int about_size = print_logo(song_data, ui, 1, col + 2); int max_name_width = term_w - 10 - indent - (indent / 4); if (ui->hideSideCover) max_name_width = term_w - 10 - indent; max_lib_list_size -= about_size + 2; apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); row = about_size + 1; col += 1; if (term_w > 67 && !ui->hideHelp) { max_lib_list_size -= 3; printf("\033[%d;%dH", row, col); clear_rest_of_line(); printf("\033[%d;%dH", row++, col + 1); printf(_(" Select:↑/↓.")); printf(_(" Enqueue/Dequeue:%s."), get_binding_string(EVENT_ENQUEUE, true)); printf(_(" Play:%s."), get_binding_string(EVENT_ENQUEUEANDPLAY, true)); clear_rest_of_line(); printf("\033[%d;%dH", row++, col + 1); clear_rest_of_line(); #ifndef __APPLE__ printf(_(" Scroll:PgUp/PgDn.")); #else printf(_(" Scroll:Fn+↑/↓.")); #endif printf(_(" Update:%s."), get_binding_string(EVENT_UPDATELIBRARY, true)); printf(_(" Sort:%s."), get_binding_string(EVENT_SORTLIBRARY, true)); clear_rest_of_line(); printf("\033[%d;%dH", row++, col); clear_rest_of_line(); } num_top_level_songs = 0; FileSystemEntry *library = get_library(); FileSystemEntry *tmp = library->children; while (tmp != NULL) { if (!tmp->is_directory) num_top_level_songs++; tmp = tmp->next; } bool foundChosen = false; lib_row_count = 0; if (max_lib_list_size <= 0) foundChosen = true; else foundChosen = display_tree(library, 0, max_lib_list_size, max_name_width, row, col); fflush(stdout); if (!foundChosen) { chosen_lib_row--; trigger_refresh(); } printf("\e[0m"); apply_color(COLOR_MODE_ALBUM, ui->theme.text, ui->defaultColorRGB); int empty_lines = max_lib_list_size - lib_row_count; for (int i = 0; i < empty_lines; i++) { printf("\033[%d;%dH", row + lib_row_count + i, col); clear_rest_of_line(); } calc_and_print_last_row_and_error_row(); if (!foundChosen && is_refresh_triggered()) { goto_first_line_first_row(); show_library(song_data, settings); } else if (refresh_triggered) print_side_cover(song_data); } int calc_visualizer_width() { int term_w, term_h; get_term_size(&term_w, &term_h); int visualizer_width = (ABSOLUTE_MIN_WIDTH > preferred_width) ? ABSOLUTE_MIN_WIDTH : preferred_width; visualizer_width = (visualizer_width < text_width && text_width < term_w - 2) ? text_width : visualizer_width; visualizer_width = (visualizer_width > term_w - 2) ? term_w - 2 : visualizer_width; visualizer_width -= 1; return visualizer_width; } void print_at(int row, int indent, const char *text, int max_width) { char buffer[1024]; size_t len = strlen(text); if (len > (size_t)max_width) len = max_width; // Safe copy of exactly len bytes memcpy(buffer, text, len); buffer[len] = '\0'; printf("\033[%d;%dH%s", row, indent, buffer); } void print_lyrics_page(UISettings *ui, int row, int col, double seconds, SongData *songdata, int height) { clear_rest_of_line(); if (!songdata) return; Lyrics *lyrics = songdata->lyrics; if (!lyrics || lyrics->count == 0) { printf("\033[%d;%dH", row, col); printf(_(" No lyrics available. Press %s to go back."), get_binding_string(EVENT_SHOWLYRICSPAGE, true)); return; } int limit = MIN((int)lyrics->count, height); int startat = 0; int current = 0; if (lyrics->isTimed) { for (int i = 0; i < (int)lyrics->count; i++) { if (lyrics->lines[i].timestamp >= seconds) { current = i - 1; if (i > limit && i > 0) startat = i - 1; // If the current line would have fallen out of view, start at the current line - 1 break; } } } int newlimit = startat + limit; if (newlimit > (int)lyrics->count) newlimit = (int)lyrics->count; if (startat < 0) startat = 0; if (startat >= (int)lyrics->count) startat = (int)lyrics->count - 1; for (int i = startat; i < newlimit; i++) { char linebuf[1024]; const char *text = lyrics->lines[i].text ? lyrics->lines[i].text : ""; strncpy(linebuf, text, sizeof(linebuf) - 1); linebuf[sizeof(linebuf) - 1] = '\0'; int length = (int)strnlen(linebuf, songdata->lyrics->max_length - 1); if (length + col > term_w) length = term_w - col; if (length < 0) length = 0; if (i == current && lyrics->isTimed) apply_color(ui->colorMode, ui->theme.nowplaying, ui->defaultColorRGB); else apply_color(ui->colorMode, ui->theme.trackview_lyrics, ui->color); print_at(row + i - startat, col, linebuf, length); clear_rest_of_line(); } } const char *get_lyrics_line(const Lyrics *lyrics, double elapsed_seconds) { if (!lyrics || lyrics->count == 0) return ""; static char line[1024]; line[0] = '\0'; double last_timestamp = -1.0; for (size_t i = 0; i < lyrics->count; i++) { double ts = lyrics->lines[i].timestamp; const char *text = lyrics->lines[i].text; if (elapsed_seconds < ts) break; if (ts == last_timestamp) { // Same timestamp → append strncat(line, " | ", sizeof(line) - strlen(line) - 1); strncat(line, text, sizeof(line) - strlen(line) - 1); } else { // New timestamp → start fresh snprintf(line, sizeof(line), "%s", text); last_timestamp = ts; } } return line; } void print_timestamped_lyrics(UISettings *ui, SongData *songdata, int row, int col, int term_w, double elapsed_seconds) { if (!songdata) return; if (!songdata->lyrics) return; if (songdata->lyrics->isTimed != 1) return; const char *line = get_lyrics_line(songdata->lyrics, elapsed_seconds); if (!line) return; static char prev_line[1024] = ""; if (line && line[0] != '\0' && (strcmp(line, prev_line) != 0)) { strncpy(prev_line, line, sizeof(prev_line) - 1); prev_line[sizeof(prev_line) - 1] = '\0'; int length = ((int)strnlen(line, songdata->lyrics->max_length - 1)); length -= col + length - term_w; apply_color(ui->colorMode, ui->theme.trackview_lyrics, ui->color); print_at(row, col, line, length); clear_rest_of_line(); } } void show_track_view_landscape(int height, int width, float aspect_ratio, AppSettings *settings, SongData *songdata, double elapsed_seconds) { AppState *state = get_app_state(); TagSettings *metadata = NULL; int avg_bit_rate = 0; int metadata_height = 4; int time_height = 1; if (songdata) { metadata = songdata->metadata; } int col = height * aspect_ratio; if (!state->uiSettings.coverEnabled) col = 1; int term_w, term_h; get_term_size(&term_w, &term_h); int visualizer_width = term_w - col; int row = height - metadata_height - time_height - state->uiSettings.visualizer_height - 3; if (row <= 1) row = 2; if (is_refresh_triggered()) { if (!chroma_is_started()) { clear_screen(); print_cover(2, 2, height - 2, false, songdata); } if (!state->uiState.showLyricsPage) { if (height > metadata_height) print_metadata(row, col, visualizer_width - 1, metadata, &(state->uiSettings)); } cancel_refresh(); } if (songdata) { ma_uint32 sample_rate; ma_format format; avg_bit_rate = songdata->avg_bit_rate; get_format_and_sample_rate(&format, &sample_rate); if (!state->uiState.showLyricsPage) { if (chroma_is_started()) { chroma_print_frame(2, 2, false); } if (height > metadata_height + time_height) print_time(row + 4, col, elapsed_seconds, sample_rate, avg_bit_rate, term_w - col); printf("\033[%d;%dH", row + metadata_height + 1, col); printf(" "); if (row > 0) print_timestamped_lyrics(&(state->uiSettings), songdata, row + metadata_height + 1, col + 1, term_w, elapsed_seconds); if (row > 0) print_visualizer(row + metadata_height + 2, col, visualizer_width, settings, elapsed_seconds); if (width - col > ABSOLUTE_MIN_WIDTH) { print_error_row(row + metadata_height + 2 + state->uiSettings.visualizer_height, col, &(state->uiSettings)); print_footer(row + metadata_height + 2 + state->uiSettings.visualizer_height + 1, col); } } else { print_now_playing(songdata, &(state->uiSettings), 2, col, term_w - indent); print_lyrics_page(&(state->uiSettings), 4, col, elapsed_seconds, songdata, height - 4); } } } void show_track_view_portrait(int height, AppSettings *settings, SongData *songdata, double elapsed_seconds) { AppState *state = get_app_state(); TagSettings *metadata = NULL; int avg_bit_rate = 0; int metadata_height = 4; int cover_row = 2; int row = height + 3; int col = indent; int visualizer_width = calc_visualizer_width(); if (is_refresh_triggered()) { if (songdata) { metadata = songdata->metadata; } clear_screen(); if (!state->uiState.showLyricsPage) { if (!chroma_is_started()) { clear_screen(); print_cover(cover_row, indent, preferred_height, true, songdata); printf("\n\n"); } print_metadata(row, col, visualizer_width - 1, metadata, &(state->uiSettings)); } cancel_refresh(); } if (songdata) { if (!state->uiState.showLyricsPage) { ma_uint32 sample_rate; ma_format format; avg_bit_rate = songdata->avg_bit_rate; if (chroma_is_started()) { chroma_print_frame(2, col + 1, true); } get_format_and_sample_rate(&format, &sample_rate); print_time(row + metadata_height, col, elapsed_seconds, sample_rate, avg_bit_rate, term_w - col); if (row > 0) print_timestamped_lyrics(&(state->uiSettings), songdata, row + metadata_height + 1, indent + 1, term_w, elapsed_seconds); print_visualizer(row + metadata_height + 2, col, visualizer_width + 1, settings, elapsed_seconds); } else { clear_screen(); printf("\n"); print_now_playing(songdata, &(state->uiSettings), 2, indent + 1, term_w - indent); int term_w, term_h; get_term_size(&term_w, &term_h); int lyrics_height = term_h - 5; if (lyrics_height > 0) print_lyrics_page(&(state->uiSettings), 4, col + 1, elapsed_seconds, songdata, lyrics_height); } } calc_and_print_last_row_and_error_row(); } static int lastHeight = 0; static time_t last_restart = 0; void show_track_view(int width, int height, AppSettings *settings, SongData *songdata, double elapsed_seconds, UISettings *ui) { time_t now = time(NULL); bool landscape_layout = false; float aspect = get_aspect_ratio(); if (aspect == 0.0f) aspect = 1.0f; int corrected_width = width / aspect; int cover_height = preferred_height; if (corrected_width > height * 2) { landscape_layout = true; cover_height = height - 2; } if (height != lastHeight && chroma_is_started() && now != last_restart) { last_restart = now; lastHeight = height; chroma_stop(); clear_screen(); chroma_start(cover_height); } if (songdata && ui->visualizations_instead_of_cover && (!chroma_is_started() || next_visualization_requested) && ui->coverEnabled) { if (has_chroma == -1) has_chroma = chroma_is_installed(); if (has_chroma == 1) { lastHeight = height; if (next_visualization_requested) { clear_screen(); chroma_set_next_preset(cover_height); trigger_refresh(); } else { clear_screen(); if (ui->chromaPreset >= 0) chroma_set_current_preset(ui->chromaPreset); chroma_start(cover_height); trigger_refresh(); } if (!chroma_is_started()) { ui->visualizations_instead_of_cover = false; trigger_refresh(); } } next_visualization_requested = false; } if (landscape_layout) { show_track_view_landscape(height, width, aspect, settings, songdata, elapsed_seconds); } else { show_track_view_portrait(preferred_height, settings, songdata, elapsed_seconds); } } int print_player(SongData *songdata, double elapsed_seconds) { AppState *state = get_app_state(); AppSettings *settings = get_app_settings(); UISettings *ui = &(state->uiSettings); UIState *uis = &(state->uiState); PlayList *unshuffled_playlist = get_unshuffled_playlist(); if (has_printed_error_message() && is_refresh_triggered()) clear_error_message(); if (!ui->uiEnabled) { return 0; } if (is_refresh_triggered()) { hide_cursor(); if (songdata != NULL && songdata->metadata != NULL && !songdata->hasErrors && (songdata->hasErrors < 1)) { ui->color.r = songdata->red; ui->color.g = songdata->green; ui->color.b = songdata->blue; if (ui->trackTitleAsWindowTitle) set_terminal_window_title( songdata->metadata->title); } else { if (state->currentView == TRACK_VIEW) { state->currentView = LIBRARY_VIEW; clear_screen(); } ui->color.r = ui->defaultColorRGB.r; ui->color.g = ui->defaultColorRGB.g; ui->color.b = ui->defaultColorRGB.b; if (ui->trackTitleAsWindowTitle) set_terminal_window_title("kew"); } calc_preferred_size(ui); calc_indent(songdata); } get_term_size(&term_w, &term_h); bool shouldRefresh = is_refresh_triggered() || state->uiState.isFastForwarding || state->uiState.isRewinding; if (state->currentView != PLAYLIST_VIEW) state->uiState.resetPlaylistDisplay = true; if ((state->currentView != TRACK_VIEW || is_refresh_triggered()) && chroma_is_started()) chroma_stop(); print_side_cover(songdata); if (state->currentView == HELP_VIEW && shouldRefresh) { show_key_bindings(songdata); save_cursor_position(); cancel_refresh(); fflush(stdout); } else if (state->currentView == PLAYLIST_VIEW && shouldRefresh) { show_playlist(songdata, unshuffled_playlist, &chosen_row, &(uis->chosen_node_id)); state->uiState.resetPlaylistDisplay = false; fflush(stdout); } else if (state->currentView == SEARCH_VIEW && shouldRefresh) { show_search(songdata, &chosen_search_result_row); cancel_refresh(); fflush(stdout); } else if (state->currentView == LIBRARY_VIEW && shouldRefresh) { show_library(songdata, settings); fflush(stdout); } else if (state->currentView == TRACK_VIEW) { show_track_view(term_w, term_h, settings, songdata, elapsed_seconds, ui); fflush(stdout); } return 0; } void show_help(void) { print_help(); } void save_library(void) { FileSystemEntry *library = get_library(); char *filepath = get_library_file_path(); reset_sort_library(); write_tree_to_binary(library, filepath); free(filepath); } void free_library(void) { FileSystemEntry *library = get_library(); if (library == NULL) return; free_tree(library); } int get_chosen_row(void) { return chosen_row; } void set_chosen_row(int row) { chosen_row = row; } void refresh_player() { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); int mutex_result = pthread_mutex_trylock(&(state->switch_mutex)); if (mutex_result != 0) { fprintf(stderr, "Failed to lock switch mutex.\n"); return; } if (ps->notifyPlaying) { ps->notifyPlaying = false; emit_string_property_changed("PlaybackStatus", "Playing"); } if (ps->notifySwitch) { ps->notifySwitch = false; notify_mpris_switch(get_current_song_data()); } if (should_refresh_player()) { print_player(get_current_song_data(), get_elapsed_seconds()); } pthread_mutex_unlock(&(state->switch_mutex)); } kew/src/ui/player_ui.h000066400000000000000000000027001512074754200152200ustar00rootroot00000000000000/** * @file player_ui.h * @brief Main player screen rendering. * * Displays current track info, progress bar, and playback status. * Acts as the central visual component of the terminal player. */ #ifndef PLAYER_H #define PLAYER_H #include "common/appstate.h" #include "data/directorytree.h" #include int print_logo_art(int row, int col, const UISettings *ui, bool centered, bool print_tag_line, bool use_gradient); int calc_indent_normal(void); int print_player(SongData *songdata, double elapsed_seconds); int get_footer_row(void); int get_footer_col(void); int get_indent(void); int print_about_for_version(SongData *songdata); int get_chosen_row(void); void flip_next_page(void); void flip_prev_page(void); void show_help(void); void set_chosen_dir(FileSystemEntry *entry); void set_current_as_chosen_dir(void); void scroll_next(void); void scroll_prev(void); void toggle_show_view(ViewState VIEW_TO_SHOW); void show_track(void); void save_library(void); void free_library(void); void reset_chosen_dir(void); void switch_to_next_view(void); void switch_to_previous_view(void); void reset_search_result(void); void set_chosen_row(int row); void trigger_redraw_side_cover(void); void refresh_player(); void set_track_title_as_window_title(void); char *get_library_file_path(void); bool init_theme(int argc, char *argv[]); FileSystemEntry *get_chosen_dir(void); void request_next_visualization(void); void request_stop_visualization(void); #endif kew/src/ui/playlist_ui.c000066400000000000000000000300451512074754200155630ustar00rootroot00000000000000/** * @file playlist_ui.c * @brief Playlist display and interaction layer. * * Renders the list of tracks in the current playlist and handles * navigation, selection, and editing within the playlist view. */ #include "playlist_ui.h" #include "common/common.h" #include "ops/playback_state.h" #include "ops/playback_system.h" #include "common/appstate.h" #include "common_ui.h" #include "data/song_loader.h" #include "ops/playlist_ops.h" #include "sys/mpris.h" #include "utils/term.h" #include "utils/utils.h" #include #include static const int MAX_TERM_WIDTH = 1000; static int start_iter = 0; static int previous_chosen_song = 0; static bool is_same_name_as_last_time = false; Node *determine_start_node(Node *head, int *found_at, int list_size) { if (found_at == NULL) { return head; } Node *node = head; Node *current = get_current_song(); Node *found_node = NULL; int num_songs = 0; *found_at = -1; while (node != NULL && num_songs <= list_size) { if (current != NULL && current->id == node->id) { *found_at = num_songs; found_node = node; break; } node = node->next; num_songs++; } return found_node ? found_node : head; } void prepare_playlist_string(Node *node, char *buffer, int buffer_size) { if (node == NULL || buffer == NULL || node->song.file_path == NULL || buffer_size <= 0) { if (buffer && buffer_size > 0) buffer[0] = '\0'; return; } if (strnlen(node->song.file_path, PATH_MAX) >= PATH_MAX) { buffer[0] = '\0'; return; } char file_path[PATH_MAX]; c_strcpy(file_path, node->song.file_path, sizeof(file_path)); char *last_slash = strrchr(file_path, '/'); size_t len = strnlen(file_path, sizeof(file_path)); if (last_slash != NULL && last_slash < file_path + len) { c_strcpy(buffer, last_slash + 1, buffer_size); buffer[buffer_size - 1] = '\0'; } else { // If no slash found or invalid pointer arithmetic, just copy // whole path safely or clear c_strcpy(buffer, file_path, buffer_size); buffer[buffer_size - 1] = '\0'; } } int display_playlist_items(int row, int col, Node *start_node, int start_iter, int max_list_size, int term_width, int chosen_song, int *chosen_node_id, UISettings *ui) { int num_printed_rows = 0; Node *node = start_node; int indent = col -1; if (term_width < 0 || term_width > MAX_TERM_WIDTH || indent < 0 || indent >= term_width) return 0; int max_name_width = term_width - indent - (indent / 4) - 10; if (max_name_width <= 0) return 0; unsigned char default_color = ui->default_color; PixelData row_color = {default_color, default_color, default_color}; PixelData row_color2 = {default_color, default_color, default_color}; char *buffer = malloc(NAME_MAX + 1); if (!buffer) return 0; char *filename = malloc(NAME_MAX + 1); if (!filename) { free(buffer); return 0; } for (int i = start_iter; node != NULL && i < start_iter + max_list_size; i++) { PixelData rgbRowNum = {default_color, default_color, default_color}; PixelData rgbTitle = {default_color, default_color, default_color}; if (ui->colorMode == COLOR_MODE_THEME && ui->theme.playlist_rownum.type == COLOR_TYPE_RGB) { rgbRowNum = ui->theme.playlist_rownum.rgb; rgbTitle = ui->theme.playlist_title.rgb; } else { rgbRowNum = ui->color; } if (!(rgbRowNum.r == default_color && rgbRowNum.g == default_color && rgbRowNum.b == default_color)) row_color = get_gradient_color(rgbRowNum, i - start_iter, max_list_size, max_list_size / 2, 0.7f); if (!(rgbTitle.r == default_color && rgbTitle.g == default_color && rgbTitle.b == default_color)) row_color2 = get_gradient_color(rgbTitle, i - start_iter, max_list_size, max_list_size / 2, 0.7f); prepare_playlist_string(node, buffer, NAME_MAX); if (buffer[0] != '\0') { if (ui->colorMode == COLOR_MODE_ALBUM || (ui->colorMode == COLOR_MODE_THEME && ui->theme.playlist_rownum.type == COLOR_TYPE_RGB)) apply_color(COLOR_MODE_ALBUM, ui->theme.playlist_rownum, row_color); else apply_color(ui->colorMode, ui->theme.playlist_rownum, row_color); printf("\033[%d;%dH", row + num_printed_rows, col); clear_rest_of_line(); printf(" %d. ", i + 1); if (ui->colorMode == COLOR_MODE_ALBUM || (ui->colorMode == COLOR_MODE_THEME && ui->theme.playlist_rownum.type == COLOR_TYPE_RGB)) apply_color(COLOR_MODE_ALBUM, ui->theme.playlist_title, row_color2); else apply_color(ui->colorMode, ui->theme.playlist_title, ui->defaultColorRGB); is_same_name_as_last_time = (previous_chosen_song == chosen_song); if (!is_same_name_as_last_time) { reset_name_scroll(); } filename[0] = '\0'; if (i == chosen_song) { previous_chosen_song = chosen_song; *chosen_node_id = node->id; process_name_scroll(buffer, filename, max_name_width, is_same_name_as_last_time); inverse_text(); } else { process_name(buffer, filename, max_name_width, true, true); } Node *current = get_current_song(); if (current != NULL && current->id == node->id) apply_color(ui->colorMode, ui->theme.playlist_playing, row_color); if (i + 1 < 10) printf(" "); if (current != NULL && current->id == node->id && i == chosen_song) { inverse_text(); } if (current != NULL && current->id == node->id && i != chosen_song) { printf("\e[4m"); } printf("%s", filename); num_printed_rows++; } node = node->next; reset_color(); } free(buffer); free(filename); return num_printed_rows; } void ensure_chosen_song_within_limits(int *chosen_song, PlayList *list) { if (list == NULL) { *chosen_song = 0; } else if (*chosen_song >= list->count) { *chosen_song = list->count - 1; } if (*chosen_song < 0) { *chosen_song = 0; } } int determine_playlist_start(int previous_start_iter, int found_at, int max_list_size, int *chosen_song, bool reset, bool end_of_list_reached) { int start_iter = 0; start_iter = (found_at > -1 && (found_at > start_iter + max_list_size)) ? found_at : start_iter; if (previous_start_iter <= found_at && found_at < previous_start_iter + max_list_size) start_iter = previous_start_iter; if (*chosen_song < start_iter) { start_iter = *chosen_song; } if (*chosen_song > start_iter + max_list_size - floor(max_list_size / 2)) { start_iter = *chosen_song - max_list_size + floor(max_list_size / 2); } if (reset && !end_of_list_reached) { if (found_at > max_list_size) start_iter = previous_start_iter = *chosen_song = found_at; else start_iter = *chosen_song = 0; } return start_iter; } void move_start_node_into_position(int found_at, Node **start_node) { // Go up to adjust the start_node for (int i = found_at; i > start_iter; i--) { if (i > 0 && (*start_node)->prev != NULL) *start_node = (*start_node)->prev; } // Go down to adjust the start_node for (int i = (found_at == -1) ? 0 : found_at; i < start_iter; i++) { if ((*start_node)->next != NULL) *start_node = (*start_node)->next; } } int display_playlist(int row, int col, PlayList *list, int max_list_size, int *chosen_song, int *chosen_node_id, bool reset) { AppState *state = get_app_state(); int term_width, termHeight; get_term_size(&term_width, &termHeight); UISettings *ui = &(state->uiSettings); int found_at = -1; Node *start_node = NULL; if (list != NULL) start_node = determine_start_node(list->head, &found_at, list->count); ensure_chosen_song_within_limits(chosen_song, list); start_iter = determine_playlist_start(start_iter, found_at, max_list_size, chosen_song, reset, audio_data.end_of_list_reached); move_start_node_into_position(found_at, &start_node); int printed_rows = display_playlist_items(row, col, start_node, start_iter, max_list_size, term_width, *chosen_song, chosen_node_id, ui); while (printed_rows <= max_list_size) { printf("\033[%d;%dH", row + printed_rows, col); clear_rest_of_line(); printed_rows++; } return printed_rows; } void set_end_of_list_reached(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); ps->loadedNextSong = false; ps->waitingForNext = true; audio_data.end_of_list_reached = true; audio_data.currentFileIndex = 0; audio_data.restart = true; ps->usingSongDataA = false; ps->loadingdata.loadA = true; clear_current_song(); playback_cleanup(); trigger_refresh(); if (is_repeat_list_enabled()) repeat_list(); else { emit_playback_stopped_mpris(); emit_metadata_changed("", "", "", "", "/org/mpris/MediaPlayer2/TrackList/NoTrack", NULL, 0); state->currentView = LIBRARY_VIEW; clear_screen(); } } kew/src/ui/playlist_ui.h000066400000000000000000000007411512074754200155700ustar00rootroot00000000000000/** * @file playlist_ui.h * @brief Playlist display and interaction layer. * * Renders the list of tracks in the current playlist and handles * navigation, selection, and editing within the playlist view. */ #ifndef PLAYLIST_UI_H #define PLAYLIST_UI_H #include "common/appstate.h" int display_playlist(int row, int col, PlayList *list, int max_list_size, int *chosen_song, int *chosen_node_id, bool reset); void set_end_of_list_reached(void); #endif kew/src/ui/queue_ui.c000066400000000000000000000272771512074754200150630ustar00rootroot00000000000000/** * @file queue_ui.c * @brief Handles high level enqueue * */ #include "queue_ui.h" #include "common/appstate.h" #include "common/common.h" #include "data/directorytree.h" #include "input.h" #include "player_ui.h" #include "search_ui.h" #include "ops/library_ops.h" #include "ops/playback_clock.h" #include "ops/playback_state.h" #include "ops/playback_system.h" #include "ops/playlist_ops.h" #include "ops/track_manager.h" #include "sys/mpris.h" #include "sys/sys_integration.h" #include "utils/utils.h" void determine_song_and_notify(void) { AppState *state = get_app_state(); SongData *current_song_data = NULL; bool isDeleted = determine_current_song_data(¤t_song_data); Node *current = get_current_song(); if (current_song_data && current) current->song.duration = current_song_data->duration; if (state->uiState.lastNotifiedId != current->id) { if (!isDeleted) notify_song_switch(current_song_data); } } void update_next_song_if_needed(void) { load_next_song(); determine_song_and_notify(); } void reset_list_after_dequeuing_playing_song(void) { AppState *state = get_app_state(); PlaybackState *ps = get_playback_state(); state->uiState.startFromTop = true; if (ps->lastPlayedId < 0) return; Node *node = find_selected_entry_by_id(get_playlist(), ps->lastPlayedId); if (get_current_song() == NULL && node == NULL) { ps->loadedNextSong = false; audio_data.end_of_list_reached = true; audio_data.restart = true; emit_metadata_changed("", "", "", "", "/org/mpris/MediaPlayer2/TrackList/NoTrack", NULL, 0); emit_playback_stopped_mpris(); playback_cleanup(); trigger_refresh(); switch_audio_implementation(); unload_song_a(); unload_song_b(); state->uiState.songWasRemoved = true; UserData *user_data = audio_data.pUserData; user_data->current_song_data = NULL; audio_data.currentFileIndex = 0; audio_data.restart = true; ps->waitingForNext = true; PlaybackState *ps = get_playback_state(); ps->loadingdata.loadA = true; ps->usingSongDataA = false; ma_data_source_uninit(&audio_data); audio_data.switchFiles = false; if (get_playlist()->count == 0) set_song_to_start_from(NULL); } } FileSystemEntry *enqueue_songs(FileSystemEntry *entry, FileSystemEntry **chosen_dir) { AppState *state = get_app_state(); FileSystemEntry *first_enqueued_entry = NULL; UIState *uis = &(state->uiState); PlaybackState *ps = get_playback_state(); bool has_enqueued = false; bool shuffle = false; if (entry != NULL) { if (entry->is_directory) { if (!has_song_children(entry) || entry->parent == NULL || ((*chosen_dir) != NULL && strcmp(entry->full_path, (*chosen_dir)->full_path) == 0)) { if (has_dequeued_children(entry)) { if (entry->parent == NULL) // Shuffle playlist if it's // the root shuffle = true; has_enqueued = enqueue_children(entry->children, &first_enqueued_entry); ps->nextSongNeedsRebuilding = true; } else { dequeue_children(entry); ps->nextSongNeedsRebuilding = true; } } if ((*chosen_dir) != NULL && entry->parent != NULL && is_contained_within(entry, (*chosen_dir)) && uis->allowChooseSongs == true) { // If the chosen directory is the same as the // entry's parent and it is open uis->openedSubDir = true; FileSystemEntry *tmpc = (*chosen_dir)->children; uis->numSongsAboveSubDir = 0; while (tmpc != NULL) { if (strcmp(entry->full_path, tmpc->full_path) == 0 || is_contained_within(entry, tmpc)) break; if (tmpc->is_directory == 0) uis->numSongsAboveSubDir++; tmpc = tmpc->next; } } AppState *state = get_app_state(); if (state->uiState.currentLibEntry && state->uiState.currentLibEntry->is_directory) *chosen_dir = state->uiState.currentLibEntry; if (uis->allowChooseSongs == true) { uis->collapseView = true; trigger_refresh(); } if (entry->parent != NULL) uis->allowChooseSongs = true; } else { if (!entry->is_enqueued) { set_next_song(NULL); ps->nextSongNeedsRebuilding = true; first_enqueued_entry = entry; enqueue_song(entry); set_childrens_queued_status_on_parents(entry->parent, true); has_enqueued = true; } else { set_next_song(NULL); ps->nextSongNeedsRebuilding = true; dequeue_song(entry); set_childrens_queued_status_on_parents(entry->parent, false); } } trigger_refresh(); } if (has_enqueued && first_enqueued_entry) { autostart_if_stopped(first_enqueued_entry->full_path); } if (shuffle) { PlayList *playlist = get_playlist(); shuffle_playlist(playlist); set_song_to_start_from(NULL); } else if (ps->nextSongNeedsRebuilding) { reshuffle_playlist(); } return first_enqueued_entry; } FileSystemEntry *enqueue(FileSystemEntry *entry) { AppState *state = get_app_state(); FileSystemEntry *first_enqueued_entry = NULL; PlaybackState *ps = get_playback_state(); if (audio_data.restart) { Node *last_song = find_selected_entry_by_id(get_playlist(), ps->lastPlayedId); state->uiState.startFromTop = false; if (last_song == NULL) { if (get_playlist()->tail != NULL) ps->lastPlayedId = get_playlist()->tail->id; else { ps->lastPlayedId = -1; state->uiState.startFromTop = true; } } } pthread_mutex_lock(&(get_playlist()->mutex)); FileSystemEntry *chosen_dir = get_chosen_dir(); first_enqueued_entry = enqueue_songs(entry, &chosen_dir); set_chosen_dir(chosen_dir); reset_list_after_dequeuing_playing_song(); pthread_mutex_unlock(&(get_playlist()->mutex)); return first_enqueued_entry; } Node* pick_random_node(Node* first) { int count = 0; Node* n = first; while (n) { count++; n = n->next; } int idx = rand() % count; n = first; while (idx--) n = n->next; return n; } void view_enqueue(bool play_immediately) { AppState *state = get_app_state(); PlayList *playlist = get_playlist(); PlaybackState *ps = get_playback_state(); PlayList *unshuffled_playlist = get_unshuffled_playlist(); FileSystemEntry *entry = NULL; Node *current_song = get_current_song(); Node *first_enqueued_node = NULL; bool canGoNext = (current_song != NULL && current_song->next != NULL); if (state->currentView == TRACK_VIEW) { if (current_song != NULL) { clear_and_play(current_song); } } if (state->currentView == PLAYLIST_VIEW) { if (is_digits_pressed() == 0) { playlist_play(playlist); } else { state->uiState.resetPlaylistDisplay = true; int song_number = get_number_from_string(get_digits_pressed()); reset_digits_pressed(); ps->nextSongNeedsRebuilding = false; skip_to_numbered_song(song_number); } } if (state->currentView == LIBRARY_VIEW || state->currentView == SEARCH_VIEW) { if (state->currentView == LIBRARY_VIEW) entry = state->uiState.currentLibEntry; else { entry = get_current_search_entry(); set_chosen_dir(get_current_search_entry()); } if (entry == NULL) return; // Enqueue playlist if (path_ends_with(entry->full_path, "m3u") || path_ends_with(entry->full_path, "m3u8")) { if (playlist != NULL) { first_enqueued_node = read_m3u_file(entry->full_path, playlist); deep_copy_play_list_onto_list(playlist, &unshuffled_playlist); if (state->uiSettings.shuffle_enabled) reshuffle_playlist(); } } else { FileSystemEntry *first_enqueued_entry = enqueue(entry); // Enqueue song if (first_enqueued_entry) first_enqueued_node = find_path_in_playlist(first_enqueued_entry->full_path, playlist); } } if (first_enqueued_node && state->uiSettings.shuffle_enabled) { Node *unshuffled_node = find_path_in_playlist(first_enqueued_node->song.file_path, unshuffled_playlist); if (unshuffled_node && unshuffled_node->next) { Node *rand = pick_random_node(unshuffled_node); first_enqueued_node = find_path_in_playlist(rand->song.file_path, playlist); } } if (first_enqueued_node && (play_immediately || is_stopped()) && playlist->count != 0) { clear_and_play(first_enqueued_node); } // Handle MPRIS can_go_next current_song = get_current_song(); bool couldGoNext = (current_song != NULL && current_song->next != NULL); if (canGoNext != couldGoNext) { emit_boolean_property_changed("can_go_next", couldGoNext); } } kew/src/ui/queue_ui.h000066400000000000000000000007461512074754200150600ustar00rootroot00000000000000/** * @file queue_ui.h * @brief Handles high level enqueue * */ #include "common/appstate.h" void determine_song_and_notify(void); void reset_list_after_dequeuing_playing_song(void); void view_enqueue(bool play_immediately); void handleGoToSong(void); void update_next_song_if_needed(void); FileSystemEntry *enqueue(FileSystemEntry *entry); FileSystemEntry *enqueue_songs(FileSystemEntry *entry, FileSystemEntry **chosen_dir); FileSystemEntry *libraryEnqueue(PlayList *playlist); kew/src/ui/search_ui.c000066400000000000000000000401461512074754200151720ustar00rootroot00000000000000/** * @file search_ui.c * @brief Search interface for tracks and artists. * * Provides UI and logic for querying the music library, filtering results, * and adding songs to playlists from search results. */ #include "search_ui.h" #include "common/appstate.h" #include "common/common.h" #include "common_ui.h" #include "data/directorytree.h" #include "data/playlist.h" #include "ui/queue_ui.h" #include "utils/term.h" #include "utils/utils.h" #include #include #define MAX_SEARCH_LEN 32 typedef struct SearchResult { FileSystemEntry *entry; struct FileSystemEntry *parent; int distance; int groupDistance; } SearchResult; // Global variables to store results static SearchResult *results = NULL; size_t results_count = 0; size_t results_capacity = 0; size_t terminal_height = 0; static int num_search_letters = 0; static int num_search_bytes = 0; static int min_search_letters = 1; static FileSystemEntry *current_search_entry = NULL; static char search_text[MAX_SEARCH_LEN * 4 + 1]; // Unicode can be 4 characters FileSystemEntry *get_current_search_entry(void) { return current_search_entry; } int get_search_results_count(void) { return results_count; } #define GROW_MARGIN 50 void realloc_results() { if (results_count >= results_capacity) { results_capacity = results_capacity == 0 ? 10 + GROW_MARGIN : results_capacity + GROW_MARGIN; results = realloc(results, results_capacity * sizeof(SearchResult)); } } void set_result_fields(FileSystemEntry *entry, int distance, FileSystemEntry *parent) { results[results_count].distance = distance; results[results_count].entry = entry; results[results_count].parent = parent; } bool is_duplicate(const FileSystemEntry *entry) { for (size_t i = 0; i < results_count; i++) { const FileSystemEntry *other = results[i].entry; if (!entry->is_directory) return false; if (entry == other) return true; } return false; } void add_result(FileSystemEntry *entry, int distance) { if (num_search_letters < min_search_letters) return; if (results_count > terminal_height * 10) return; if (entry->parent == NULL) // Root return; if (is_duplicate(entry)) return; realloc_results(); set_result_fields(entry, distance, NULL); results_count++; if (entry->is_directory) { if (entry->children && entry->parent != NULL && entry->parent->parent == NULL) { FileSystemEntry *child = entry->children; while (child) { if (child->is_directory) { if (results_count > terminal_height * 10) break; realloc_results(); set_result_fields(child, distance, entry); results_count++; } child = child->next; } } } } void collect_result(FileSystemEntry *entry, int distance) { add_result(entry, distance); } void free_search_results(void) { if (results != NULL) { free(results); results = NULL; } if (current_search_entry != NULL) current_search_entry = NULL; results_capacity = 0; results_count = 0; } void calculate_group_distances(void) { for (size_t i = 0; i < results_count; i++) { // Find top-level parent (entry with no parent, or root) FileSystemEntry *root = results[i].entry; while (root->parent != NULL) { root = root->parent; } // Find if this root appears in results, and use ITS distance // Otherwise use minimum distance among descendants int minDist = results[i].distance; for (size_t j = 0; j < results_count; j++) { FileSystemEntry *otherRoot = results[j].entry; while (otherRoot->parent != NULL) { otherRoot = otherRoot->parent; } if (otherRoot == root) { if (results[j].entry == root) { // The root itself is in results - use // its distance minDist = results[j].distance; break; } if (results[j].distance < minDist) { minDist = results[j].distance + 1; } } } // If root is in results, use only its distance for grouping // Otherwise use the best child distance results[i].groupDistance = minDist; } } static int ancestor_compare(const FileSystemEntry *A, const FileSystemEntry *B) { for (const FileSystemEntry *p = B->parent; p; p = p->parent) if (p == A) return -1; for (const FileSystemEntry *p = A->parent; p; p = p->parent) if (p == B) return 1; return 0; } int compare_results(const void *a, const void *b) { const SearchResult *A = a; const SearchResult *B = b; int rel = ancestor_compare(A->entry, B->entry); if (rel != 0) return rel; // Sort by best distance in the top-level group first if (A->groupDistance != B->groupDistance) return (A->groupDistance < B->groupDistance) ? -1 : 1; // If different parents, compare by hierarchy (path) if (A->entry->parent != B->entry->parent) { const FileSystemEntry *p_a = A->entry; const FileSystemEntry *p_b = B->entry; // Walk up to same depth int depth_a = 0, depthB = 0; for (const FileSystemEntry *p = p_a; p->parent; p = p->parent) depth_a++; for (const FileSystemEntry *p = p_b; p->parent; p = p->parent) depthB++; while (depth_a > depthB) { p_a = p_a->parent; depth_a--; } while (depthB > depth_a) { p_b = p_b->parent; depthB--; } // Walk up together to find where they diverge while (p_a->parent != p_b->parent) { p_a = p_a->parent; p_b = p_b->parent; } // Compare by name at divergence point int cmp = strcmp(p_a->name, p_b->name); if (cmp != 0) return cmp; } // Within same parent: directories first if (A->entry->is_directory != B->entry->is_directory) return A->entry->is_directory ? -1 : 1; // Then by individual distance if (A->distance != B->distance) return (A->distance < B->distance) ? -1 : 1; // Then by name int cmp = strcmp(A->entry->name, B->entry->name); if (cmp != 0) return cmp; return (A->entry < B->entry) ? -1 : (A->entry > B->entry); } void sort_search_results(void) { calculate_group_distances(); qsort(results, results_count, sizeof(SearchResult), compare_results); } void fuzzy_search(FileSystemEntry *root, int threshold) { int term_w, term_h; get_term_size(&term_w, &term_h); terminal_height = term_h; free_search_results(); if (num_search_letters > min_search_letters) { fuzzy_search_recursive(root, search_text, threshold, collect_result); } sort_search_results(); trigger_refresh(); } int display_search_box(int row, int col) { AppState *state = get_app_state(); UISettings *ui = &(state->uiSettings); apply_color(ui->colorMode, ui->theme.search_label, ui->color); printf("\033[%d;%dH", row, col); printf(_(" Search: ")); apply_color(ui->colorMode, ui->theme.search_query, ui->defaultColorRGB); // Save cursor position printf("%s", search_text); printf("\033[s"); printf("█"); clear_rest_of_line(); return 0; } int add_to_search_text(const char *str) { if (str == NULL) { return -1; } size_t len = strnlen(str, MAX_SEARCH_LEN); // Check if the string can fit into the search text buffer if (num_search_letters + len > MAX_SEARCH_LEN) { return 0; // Not enough space } // Add the string to the search text buffer for (size_t i = 0; i < len; i++) { search_text[num_search_bytes++] = str[i]; } search_text[num_search_bytes] = '\0'; // Null-terminate the buffer num_search_letters++; trigger_refresh(); return 0; } // Determine the number of bytes in the last UTF-8 character int get_last_char_bytes(const char *str, int len) { if (len == 0) return 0; int i = len - 1; while (i >= 0 && (str[i] & 0xC0) == 0x80) { i--; } return len - i; } // Remove the preceding character from the search text int remove_from_search_text(void) { if (num_search_letters == 0) return 0; // Determine the number of bytes to remove for the last character int last_char_bytes = get_last_char_bytes(search_text, num_search_bytes); if (last_char_bytes == 0) return 0; // Remove the character from the buffer num_search_bytes -= last_char_bytes; search_text[num_search_bytes] = '\0'; num_search_letters--; trigger_refresh(); return 0; } void apply_color_and_format(bool is_chosen, FileSystemEntry *entry, UISettings *ui, bool is_playing) { if (is_chosen) { current_search_entry = entry; if (entry->is_enqueued) { apply_color(ui->colorMode, is_playing ? ui->theme.search_playing : ui->theme.search_enqueued, ui->color); printf("\x1b[7m * "); } else { printf(" \x1b[7m "); } } else { if (entry->is_enqueued) { apply_color(ui->colorMode, is_playing ? ui->theme.search_playing : ui->theme.search_enqueued, ui->color); printf(" * "); if (is_playing) { printf("\e[4m"); } } else { if (entry->parent != NULL && entry->parent->parent == NULL) { apply_color(ui->colorMode, is_playing ? ui->theme.search_playing : ui->theme.library_artist, ui->color); } printf(" "); } } } FileSystemEntry *last_directory = NULL; int display_search_results(int row, int col, int max_list_size, int *chosen_row, int start_search_iter) { AppState *state = get_app_state(); int term_w, term_h; get_term_size(&term_w, &term_h); int max_name_width = term_w - col - 5; if (!state->uiSettings.hideSideCover) max_name_width -= col /4; char name[max_name_width + 1]; int printed_rows = 0; if (*chosen_row >= (int)results_count - 1) { *chosen_row = results_count - 1; } if (start_search_iter < 0) start_search_iter = 0; if (*chosen_row > start_search_iter + round(max_list_size / 2)) { start_search_iter = *chosen_row - round(max_list_size / 2) + 1; } if (*chosen_row < start_search_iter) start_search_iter = *chosen_row; if (*chosen_row < 0) start_search_iter = *chosen_row = 0; int name_width = max_name_width; int extra_indent = 0; UISettings *ui = &(state->uiSettings); for (size_t i = start_search_iter; i < results_count; i++) { if ((int)i >= max_list_size + start_search_iter - 1) break; apply_color(ui->colorMode, ui->theme.search_result, ui->defaultColorRGB); // Indent sub dirs if (results[i].parent != NULL) extra_indent = 2; else if (!results[i].entry->is_directory && (last_directory != NULL) && results[i].entry->parent_id == last_directory->id) extra_indent = 4; else extra_indent = 0; name_width = max_name_width - extra_indent; printf("\033[%d;%dH", row, col); clear_rest_of_line(); printf("\033[%d;%dH", row, col + extra_indent); bool is_chosen = (*chosen_row == (int)i); Node *current = get_current_song(); bool isCurrentSong = current != NULL && strcmp(current->song.file_path, results[i].entry->full_path) == 0; apply_color_and_format(is_chosen, results[i].entry, ui, isCurrentSong); name[0] = '\0'; if (results[i].entry->is_directory) { last_directory = results[i].entry; if (results[i].entry->parent != NULL && results[i].entry->parent->parent == NULL) { char *upper_dir_name = string_to_upper(results[i].entry->name); snprintf(name, name_width + 1, "%s", upper_dir_name); free(upper_dir_name); } else { snprintf(name, name_width + 1, "[%s]", results[i].entry->name); } } else { snprintf(name, name_width + 1, "%s [%s]", results[i].entry->name, (results[i].entry->parent != NULL ? results[i].entry->parent->name : "Root")); } printf("%s", name); row++; printed_rows++; } apply_color(ui->colorMode, ui->theme.help, ui->defaultColorRGB); while (printed_rows < max_list_size) { printf("\033[%d;%dH", row++, col); clear_rest_of_line(), printed_rows++; } return 0; } int display_search(int row, int col, int max_list_size, int *chosen_row, int start_search_iter) { display_search_box(row, col); printf("\033[%d;%dH", row+1, col); clear_rest_of_line(); display_search_results(row + 2, col, max_list_size, chosen_row, start_search_iter); return 0; } kew/src/ui/search_ui.h000066400000000000000000000011221512074754200151660ustar00rootroot00000000000000/** * @file search_ui.h * @brief Search interface for tracks and artists. * * Provides UI and logic for querying the music library, filtering results, * and adding songs to playlists from search results. */ #include "data/directorytree.h" int display_search(int row, int col, int max_list_size, int *chosen_row, int start_search_iter); int add_to_search_text(const char *str); int remove_from_search_text(void); int get_search_results_count(void); void free_search_results(void); void fuzzy_search(FileSystemEntry *root, int threshold); FileSystemEntry *get_current_search_entry(void); kew/src/ui/settings.c000066400000000000000000002525031512074754200150720ustar00rootroot00000000000000/** * @file settings.c * @brief Application settings management. * * Loads, saves, and applies configuration settings such as * playback behavior, UI preferences, and library paths. */ #include "settings.h" #include "common_ui.h" #include "common/events.h" #include "termbox2_input.h" #include "common/appstate.h" #include "ops/playback_state.h" #include "sound/volume.h" #include "utils/file.h" #include "utils/utils.h" #include #include #include #include #include #include #include #include #include #include #include const char SETTINGS_FILE[] = "kewrc"; const char STATE_FILE[] = "kewstaterc"; #define MAX_LINE 1024 #define MAX_KEY_BINDINGS 200 time_t last_time_app_ran; size_t keybinding_count = 0; typedef struct { const char *name; uint16_t code; } KeyMap; TBKeyBinding key_bindings[MAX_KEY_BINDINGS] = { {TB_KEY_SPACE, 0, 0, EVENT_PLAY_PAUSE, ""}, // Basic navigation {TB_KEY_TAB, 0, TB_MOD_SHIFT, EVENT_PREVVIEW, ""}, {TB_KEY_TAB, 0, 0, EVENT_NEXTVIEW, ""}, // volume {0, '+', 0, EVENT_VOLUME_UP, "+5%"}, {0, '=', 0, EVENT_VOLUME_UP, "+5%"}, {0, '-', 0, EVENT_VOLUME_DOWN, "-5%"}, // Tracks {0, 'h', 0, EVENT_PREV, ""}, {0, 'l', 0, EVENT_NEXT, ""}, {0, 'k', 0, EVENT_SCROLLUP, ""}, {0, 'j', 0, EVENT_SCROLLDOWN, ""}, // Controls {0, 'p', 0, EVENT_PLAY_PAUSE, ""}, {0, 'n', 0, EVENT_TOGGLENOTIFICATIONS, ""}, {0, 'v', 0, EVENT_TOGGLEVISUALIZER, ""}, {0, 'b', 0, EVENT_TOGGLEASCII, ""}, {0, 'r', 0, EVENT_TOGGLEREPEAT, ""}, {0, 'i', 0, EVENT_CYCLECOLORMODE, ""}, {0, 't', 0, EVENT_CYCLETHEMES, ""}, {0, 'c', 0, EVENT_CYCLEVISUALIZATION, ""}, {0, 's', 0, EVENT_SHUFFLE, ""}, {0, 'a', 0, EVENT_SEEKBACK, ""}, {0, 'd', 0, EVENT_SEEKFORWARD, ""}, {0, 'o', 0, EVENT_SORTLIBRARY, ""}, {0, 'm', 0, EVENT_SHOWLYRICSPAGE, ""}, {0, 's', TB_MOD_SHIFT, EVENT_STOP, ""}, // Playlist actions {0, 'x', 0, EVENT_EXPORTPLAYLIST, ""}, {0, '.', 0, EVENT_ADDTOFAVORITESPLAYLIST, ""}, {0, 'u', 0, EVENT_UPDATELIBRARY, ""}, {0, 'f', 0, EVENT_MOVESONGUP, ""}, {0, 'g', 0, EVENT_MOVESONGDOWN, ""}, {TB_KEY_ENTER, 0, 0, EVENT_ENQUEUE, ""}, {0, 'g', TB_MOD_SHIFT, EVENT_ENQUEUE, ""}, {TB_KEY_BACKSPACE, 0, 0, EVENT_CLEARPLAYLIST, ""}, {TB_KEY_ENTER, 0, TB_MOD_ALT, EVENT_ENQUEUEANDPLAY, ""}, // Hard navigation / arrows {TB_KEY_ARROW_LEFT, 0, 0, EVENT_PREV, ""}, {TB_KEY_ARROW_RIGHT, 0, 0, EVENT_NEXT, ""}, {TB_KEY_ARROW_UP, 0, 0, EVENT_SCROLLUP, ""}, {TB_KEY_ARROW_DOWN, 0, 0, EVENT_SCROLLDOWN, ""}, #if defined(__ANDROID__) || defined(__APPLE__) // Show Views macOS/Android {0, 'z', TB_MOD_SHIFT, EVENT_SHOWPLAYLIST, ""}, {0, 'x', TB_MOD_SHIFT, EVENT_SHOWLIBRARY, ""}, {0, 'c', TB_MOD_SHIFT, EVENT_SHOWTRACK, ""}, {0, 'v', TB_MOD_SHIFT, EVENT_SHOWSEARCH, ""}, {0, 'b', TB_MOD_SHIFT, EVENT_SHOWHELP, ""}, #else // Show Views {TB_KEY_F2, 0, 0, EVENT_SHOWPLAYLIST, ""}, {TB_KEY_F3, 0, 0, EVENT_SHOWLIBRARY, ""}, {TB_KEY_F4, 0, 0, EVENT_SHOWTRACK, ""}, {TB_KEY_F5, 0, 0, EVENT_SHOWSEARCH, ""}, {TB_KEY_F6, 0, 0, EVENT_SHOWHELP, ""}, #endif // Page navigation {TB_KEY_PGDN, 0, 0, EVENT_NEXTPAGE, ""}, {TB_KEY_PGUP, 0, 0, EVENT_PREVPAGE, ""}, // Remove {TB_KEY_DELETE, 0, 0, EVENT_REMOVE, ""}, // Mouse events {TB_KEY_MOUSE_MIDDLE, 0, 0, EVENT_ENQUEUEANDPLAY, ""}, {TB_KEY_MOUSE_RIGHT, 0, 0, EVENT_PLAY_PAUSE, ""}, {TB_KEY_MOUSE_WHEEL_DOWN, 0, 0, EVENT_SCROLLDOWN, ""}, {TB_KEY_MOUSE_WHEEL_UP, 0, 0, EVENT_SCROLLUP, ""}, {0, 'q', 0, EVENT_QUIT, ""}, {TB_KEY_ESC, 0, 0, EVENT_QUIT, ""}}; static const KeyMap key_map[] = { // Arrow keys {"Up", TB_KEY_ARROW_UP}, {"Down", TB_KEY_ARROW_DOWN}, {"Left", TB_KEY_ARROW_LEFT}, {"Right", TB_KEY_ARROW_RIGHT}, // Function keys {"F1", TB_KEY_F1}, {"F2", TB_KEY_F2}, {"F3", TB_KEY_F3}, {"F4", TB_KEY_F4}, {"F5", TB_KEY_F5}, {"F6", TB_KEY_F6}, {"F7", TB_KEY_F7}, {"F8", TB_KEY_F8}, {"F9", TB_KEY_F9}, {"F10", TB_KEY_F10}, {"F11", TB_KEY_F11}, {"F12", TB_KEY_F12}, // Navigation / editing {"Insert", TB_KEY_INSERT}, {"Ins", TB_KEY_INSERT}, {"Del", TB_KEY_DELETE}, {"Delete", TB_KEY_DELETE}, {"Home", TB_KEY_HOME}, {"End", TB_KEY_END}, {"PgUp", TB_KEY_PGUP}, {"PageUp", TB_KEY_PGUP}, {"PgDn", TB_KEY_PGDN}, {"PageDown", TB_KEY_PGDN}, {"BackTab", TB_KEY_BACK_TAB}, {"Tab", TB_KEY_TAB}, {"Backspace", TB_KEY_BACKSPACE}, {"Enter", TB_KEY_ENTER}, {"Esc", TB_KEY_ESC}, {"Escape", TB_KEY_ESC}, {"Space", TB_KEY_SPACE}, // Modifiers {"Shift", TB_MOD_SHIFT}, {"Alt", TB_MOD_ALT}, {"Ctrl", TB_MOD_CTRL}, // Mouse buttons {"mouseLeft", TB_KEY_MOUSE_LEFT}, {"mouseRight", TB_KEY_MOUSE_RIGHT}, {"mouseMiddle", TB_KEY_MOUSE_MIDDLE}, {"mouseRelease", TB_KEY_MOUSE_RELEASE}, {"mouseWheelUp", TB_KEY_MOUSE_WHEEL_UP}, {"mouseWheelDown", TB_KEY_MOUSE_WHEEL_DOWN}, // Symbols {"=", '='}, {"+", '+'}, {"-", '-'}, {"*", '*'}, {"/", '/'}, {"\\", '\\'}, {";", ';'}, {":", ':'}, {",", ','}, {".", '.'}, {"'", '\''}, {"\"", '\"'}, {"`", '`'}, {"~", '~'}, {"[", '['}, {"]", ']'}, {"(", '('}, {")", ')'}, {"<", '<'}, {">", '>'}, {"!", '!'}, {"@", '@'}, {"#", '#'}, {"$", '$'}, {"%", '%'}, {"^", '^'}, {"&", '&'}, {"|", '|'}, {"?", '?'}, {NULL, 0} // Sentinel }; const char *get_key_name(int key) { for (int i = 0; key_map[i].name != NULL; i++) { if (key_map[i].code == key) return key_map[i].name; } // Printable ASCII fallback static char buf[2]; if (key >= 32 && key <= 126) { buf[0] = (char)key; buf[1] = '\0'; return buf; } return "?"; } const char *get_modifier_string(uint8_t mods) { static char buf[64]; buf[0] = '\0'; int first = 1; if (mods & TB_MOD_CTRL) { strcat(buf, "Ctrl"); first = 0; } if (mods & TB_MOD_ALT) { if (!first) strcat(buf, "+"); strcat(buf, "Alt"); first = 0; } if (mods & TB_MOD_SHIFT) { if (!first) strcat(buf, "+"); strcat(buf, "Shft"); } return buf; } static bool key_str_already_added(const char *buf, const char *token) { char temp[256]; strncpy(temp, buf, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; char *part = strtok(temp, " or "); while (part != NULL) { if (strcmp(part, token) == 0) return true; part = strtok(NULL, " or "); } return false; } const char *get_binding_string(enum EventType event, bool find_only_one) { static char buf[256]; buf[0] = '\0'; int found = 0; for (int i = 0; i < MAX_KEY_BINDINGS; i++) { if (key_bindings[i].eventType != event) continue; const char *key_part = "?"; // Determine key name if (key_bindings[i].key != 0) key_part = get_key_name(key_bindings[i].key); else if (key_bindings[i].ch != 0) { static char cbuf[2]; cbuf[0] = (char)key_bindings[i].ch; cbuf[1] = '\0'; key_part = cbuf; } const char *mod_part = get_modifier_string(key_bindings[i].mods); // Build "Alt+Enter" style string for this binding char full_key[64]; if (mod_part[0] != '\0') snprintf(full_key, sizeof(full_key), "%s+%s", mod_part, key_part); else snprintf(full_key, sizeof(full_key), "%s", key_part); // Skip duplicate key names (e.g. "Space" vs " ") if (key_str_already_added(buf, full_key)) continue; // Append to output buffer if (found > 0) { if (find_only_one) return buf; strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); } strncat(buf, full_key, sizeof(buf) - strlen(buf) - 1); found++; } if (!found) snprintf(buf, sizeof(buf), "?"); return buf; } static uint16_t key_name_to_code(const char *name) { if (!name || !*name) return 0; for (int i = 0; key_map[i].name != NULL; i++) { if (strcmp(name, key_map[i].name) == 0) return key_map[i].code; } // Single printable char fallback if (strlen(name) == 1) return (uint16_t)(unsigned char)name[0]; return 0; } static const char *key_code_to_name(uint16_t code) { for (int i = 0; key_map[i].name != NULL; i++) { if (key_map[i].code == code) return key_map[i].name; } // If code is printable ASCII, return it as a string static char buf[2]; if (code >= 32 && code <= 126) { buf[0] = (char)code; buf[1] = '\0'; return buf; } return "Unknown"; } TBKeyBinding *get_key_bindings() { return key_bindings; } AppSettings init_settings(void) { AppState *state = get_app_state(); UserData *user_data = audio_data.pUserData; AppSettings settings; keybinding_count = NUM_DEFAULT_KEY_BINDINGS; get_config(&settings, &(state->uiSettings)); get_prefs(&settings, &(state->uiSettings)); user_data->replayGainCheckFirst = state->uiSettings.replayGainCheckFirst; return settings; } void free_key_value_pairs(KeyValuePair *pairs, int count) { if (pairs == NULL || count <= 0) { return; } for (size_t i = 0; i < (size_t)count; i++) { free(pairs[i].key); free(pairs[i].value); } free(pairs); } static int stricmp(const char *a, const char *b) { while (*a && *b) { int diff = tolower(*a) - tolower(*b); if (diff != 0) return diff; a++; b++; } return *a - *b; } typedef struct { const char *name; enum EventType event; } EventMap; static const EventMap event_map[] = { {"playPause", EVENT_PLAY_PAUSE}, {"volUp", EVENT_VOLUME_UP}, {"volDown", EVENT_VOLUME_DOWN}, {"nextSong", EVENT_NEXT}, {"prevSong", EVENT_PREV}, {"quit", EVENT_QUIT}, {"toggleRepeat", EVENT_TOGGLEREPEAT}, {"toggleVisualizer", EVENT_TOGGLEVISUALIZER}, {"toggleAscii", EVENT_TOGGLEASCII}, {"addToFavorites_playlist", EVENT_ADDTOFAVORITESPLAYLIST}, {"deleteFromMainPlaylist", EVENT_DELETEFROMMAINPLAYLIST}, {"exportPlaylist", EVENT_EXPORTPLAYLIST}, {"updateLibrary", EVENT_UPDATELIBRARY}, {"shuffle", EVENT_SHUFFLE}, {"keyPress", EVENT_KEY_PRESS}, {"showHelp", EVENT_SHOWHELP}, {"showPlaylist", EVENT_SHOWPLAYLIST}, {"showSearch", EVENT_SHOWSEARCH}, {"enqueue", EVENT_ENQUEUE}, {"gotoBeginningOfPlaylist", EVENT_GOTOBEGINNINGOFPLAYLIST}, {"gotoEndOfPlaylist", EVENT_GOTOENDOFPLAYLIST}, {"cycleColorMode", EVENT_CYCLECOLORMODE}, {"cycleVisualization", EVENT_CYCLEVISUALIZATION}, {"scrollDown", EVENT_SCROLLDOWN}, {"scrollUp", EVENT_SCROLLUP}, {"seekBack", EVENT_SEEKBACK}, {"seekForward", EVENT_SEEKFORWARD}, {"showLibrary", EVENT_SHOWLIBRARY}, {"showTrack", EVENT_SHOWTRACK}, {"nextPage", EVENT_NEXTPAGE}, {"prevPage", EVENT_PREVPAGE}, {"remove", EVENT_REMOVE}, {"search", EVENT_SEARCH}, {"nextView", EVENT_NEXTVIEW}, {"prevView", EVENT_PREVVIEW}, {"clearPlaylist", EVENT_CLEARPLAYLIST}, {"moveSongUp", EVENT_MOVESONGUP}, {"moveSongDown", EVENT_MOVESONGDOWN}, {"enqueueAndPlay", EVENT_ENQUEUEANDPLAY}, {"stop", EVENT_STOP}, {"sortLibrary", EVENT_SORTLIBRARY}, {"cycleThemes", EVENT_CYCLETHEMES}, {"toggleNotifications", EVENT_TOGGLENOTIFICATIONS}, {"showLyricsPage", EVENT_SHOWLYRICSPAGE}, {NULL, EVENT_NONE} // Sentinel }; static enum EventType parse_to_event(const char *name) { if (!name) return EVENT_NONE; for (int i = 0; event_map[i].name != NULL; i++) { if (stricmp(name, event_map[i].name) == 0) return event_map[i].event; } return EVENT_NONE; } static const char *event_to_string(enum EventType ev) { for (int i = 0; event_map[i].name != NULL; i++) { if (event_map[i].event == ev) return event_map[i].name; } return "EVENT_NONE"; } TBKeyBinding parse_binding(const char *binding_str, const char *event_str, const char *args_str) { TBKeyBinding kb = {0}; kb.eventType = parse_to_event(event_str); kb.args[0] = '\0'; if (args_str && *args_str) strncpy(kb.args, args_str, sizeof(kb.args) - 1); if (!binding_str || !*binding_str) return kb; char temp[64]; strncpy(temp, binding_str, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; char *p = temp; while (p && *p) { char *delim = strchr(p, '+'); // Next '+' or NULL size_t raw_len = delim ? (size_t)(delim - p) : strlen(p); char tokenbuf[64]; size_t token_len = 0; if (raw_len == 0) { // Empty between delimiters -> literal '+' token tokenbuf[0] = '+'; tokenbuf[1] = '\0'; } else { // Copy substring [p .. p+raw_len) and trim whitespace size_t s = 0; while (s < raw_len && isspace((unsigned char)p[s])) s++; size_t e = raw_len; while (e > s && isspace((unsigned char)p[e - 1])) e--; token_len = e - s; if (token_len >= sizeof(tokenbuf)) token_len = sizeof(tokenbuf) - 1; if (token_len > 0) memcpy(tokenbuf, p + s, token_len); tokenbuf[token_len] = '\0'; } // If tokenbuf is still empty (shouldn't happen), skip it if (tokenbuf[0] != '\0') { // Handle modifiers if (stricmp(tokenbuf, "Ctrl") == 0 || stricmp(tokenbuf, "LCtrl") == 0 || stricmp(tokenbuf, "RCtrl") == 0) { kb.mods |= TB_MOD_CTRL; } else if (stricmp(tokenbuf, "Alt") == 0 || stricmp(tokenbuf, "LAlt") == 0 || stricmp(tokenbuf, "RAlt") == 0) { kb.mods |= TB_MOD_ALT; } else if (stricmp(tokenbuf, "Shift") == 0 || stricmp(tokenbuf, "LShift") == 0 || stricmp(tokenbuf, "RShift") == 0) { kb.mods |= TB_MOD_SHIFT; } else if (strcmp(tokenbuf, "Space") == 0) { kb.key = TB_KEY_SPACE; kb.ch = 0; } else { // Normal key token (including "+" token) uint16_t code = key_name_to_code(tokenbuf); if (code >= 0x20 && code < 0x7f) { kb.key = 0; kb.ch = code; } else { kb.key = code; kb.ch = 0; } } } if (!delim) break; // Done p = delim + 1; // Advance past '+' } return kb; } void set_default_config(AppSettings *settings) { memset(settings, 0, sizeof(AppSettings)); c_strcpy(settings->coverEnabled, "1", sizeof(settings->coverEnabled)); c_strcpy(settings->allowNotifications, "1", sizeof(settings->allowNotifications)); c_strcpy(settings->coverAnsi, "0", sizeof(settings->coverAnsi)); c_strcpy(settings->quitAfterStopping, "0", sizeof(settings->quitAfterStopping)); c_strcpy(settings->hideGlimmeringText, "0", sizeof(settings->hideGlimmeringText)); c_strcpy(settings->mouseEnabled, "1", sizeof(settings->mouseEnabled)); c_strcpy(settings->replayGainCheckFirst, "0", sizeof(settings->replayGainCheckFirst)); c_strcpy(settings->visualizer_bar_width, "2", sizeof(settings->visualizer_bar_width)); c_strcpy(settings->visualizerBrailleMode, "0", sizeof(settings->visualizerBrailleMode)); c_strcpy(settings->progressBarElapsedEvenChar, "━", sizeof(settings->progressBarElapsedEvenChar)); c_strcpy(settings->progressBarElapsedOddChar, "━", sizeof(settings->progressBarElapsedOddChar)); c_strcpy(settings->progressBarApproachingEvenChar, "━", sizeof(settings->progressBarApproachingEvenChar)); c_strcpy(settings->progressBarApproachingOddChar, "━", sizeof(settings->progressBarApproachingOddChar)); c_strcpy(settings->progressBarCurrentEvenChar, "━", sizeof(settings->progressBarCurrentEvenChar)); c_strcpy(settings->progressBarCurrentOddChar, "━", sizeof(settings->progressBarCurrentOddChar)); c_strcpy(settings->saveRepeatShuffleSettings, "1", sizeof(settings->saveRepeatShuffleSettings)); c_strcpy(settings->trackTitleAsWindowTitle, "1", sizeof(settings->trackTitleAsWindowTitle)); #ifdef __APPLE__ // Visualizer looks wonky in default terminal but let's enable it // anyway. People need to switch c_strcpy(settings->visualizerEnabled, "1", sizeof(settings->visualizerEnabled)); c_strcpy(settings->colorMode, "0", sizeof(settings->colorMode)); #else c_strcpy(settings->visualizerEnabled, "1", sizeof(settings->visualizerEnabled)); c_strcpy(settings->colorMode, "1", sizeof(settings->colorMode)); #endif #ifdef __ANDROID__ c_strcpy(settings->hideLogo, "1", sizeof(settings->hideLogo)); #else c_strcpy(settings->hideLogo, "0", sizeof(settings->hideLogo)); #endif c_strcpy(settings->hideHelp, "0", sizeof(settings->hideHelp)); c_strcpy(settings->hideSideCover, "0", sizeof(settings->hideSideCover)); c_strcpy(settings->visualizer_height, "6", sizeof(settings->visualizer_height)); c_strcpy(settings->visualizer_color_type, "2", sizeof(settings->visualizer_color_type)); c_strcpy(settings->titleDelay, "9", sizeof(settings->titleDelay)); c_strcpy(settings->lastVolume, "100", sizeof(settings->lastVolume)); c_strcpy(settings->color, "6", sizeof(settings->color)); c_strcpy(settings->artistColor, "6", sizeof(settings->artistColor)); c_strcpy(settings->titleColor, "6", sizeof(settings->titleColor)); c_strcpy(settings->enqueued_color, "6", sizeof(settings->enqueued_color)); memcpy(settings->ansiTheme, "default", 8); } bool is_printable_ascii(const char *value) { if (value == NULL || value[0] == '\0' || value[1] != '\0') return false; // must be exactly one character unsigned char c = (unsigned char)value[0]; return (c >= 32 && c <= 126); // printable ASCII range } uint32_t utf8_to_codepoint(const char *s) { const unsigned char *u = (const unsigned char *)s; if (u[0] < 0x80) return u[0]; else if ((u[0] & 0xE0) == 0xC0) return ((u[0] & 0x1F) << 6) | (u[1] & 0x3F); else if ((u[0] & 0xF0) == 0xE0) return ((u[0] & 0x0F) << 12) | ((u[1] & 0x3F) << 6) | (u[2] & 0x3F); else if ((u[0] & 0xF8) == 0xF0) return ((u[0] & 0x07) << 18) | ((u[1] & 0x3F) << 12) | ((u[2] & 0x3F) << 6) | (u[3] & 0x3F); else return 0; // invalid } void remove_printable_key_binding(char *value) { for (size_t i = 0; i < keybinding_count; i++) { if (key_bindings[i].ch == utf8_to_codepoint(value) && key_bindings[i].mods == 0 && key_bindings[i].key == 0) { key_bindings[i].ch = 0; key_bindings[i].key = 0; key_bindings[i].eventType = EVENT_NONE; key_bindings[i].args[0] = '\0'; } } } void remove_special_key_binding(uint16_t value) { for (size_t i = 0; i < keybinding_count; i++) { if (key_bindings[i].key == value && key_bindings[i].mods == 0 && key_bindings[i].ch == 0) { key_bindings[i].key = 0; key_bindings[i].ch = 0; key_bindings[i].eventType = EVENT_NONE; key_bindings[i].args[0] = '\0'; } } } int convert_legacy_key(const char *s) { if (!s || !*s) return 0; if (strcmp(s, "[A") == 0) return TB_KEY_ARROW_UP; if (strcmp(s, "[B") == 0) return TB_KEY_ARROW_DOWN; if (strcmp(s, "[C") == 0) return TB_KEY_ARROW_RIGHT; if (strcmp(s, "[D") == 0) return TB_KEY_ARROW_LEFT; if (strcmp(s, "[H") == 0) return TB_KEY_HOME; if (strcmp(s, "[F") == 0) return TB_KEY_END; if (strcmp(s, "OP") == 0) return TB_KEY_F1; if (strcmp(s, "OQ") == 0) return TB_KEY_F2; if (strcmp(s, "OR") == 0) return TB_KEY_F3; if (strcmp(s, "OS") == 0) return TB_KEY_F4; if (strcmp(s, "[15~") == 0) return TB_KEY_F5; if (strcmp(s, "[17~") == 0) return TB_KEY_F6; if (strcmp(s, "[18~") == 0) return TB_KEY_F7; if (strcmp(s, "[19~") == 0) return TB_KEY_F8; if (strcmp(s, "[20~") == 0) return TB_KEY_F9; if (strcmp(s, "[21~") == 0) return TB_KEY_F10; if (strcmp(s, "[23~") == 0) return TB_KEY_F11; if (strcmp(s, "[24~") == 0) return TB_KEY_F12; if (strcmp(s, "[5~") == 0) return TB_KEY_PGUP; if (strcmp(s, "[6~") == 0) return TB_KEY_PGDN; if (strcmp(s, "[2~") == 0) return TB_KEY_INSERT; if (strcmp(s, "[3~") == 0) return TB_KEY_DELETE; return 0; } void add_legacy_key_binding(enum EventType event, char *value) { if (keybinding_count >= MAX_KEY_BINDINGS) return; TBKeyBinding kb = {0}; kb.eventType = event; // Printable single-char key if (is_printable_ascii(value)) { remove_printable_key_binding(value); kb.ch = utf8_to_codepoint(value); key_bindings[keybinding_count++] = kb; } // Escape sequence (starts with 0x1b) else { int tb_key = convert_legacy_key(value); if (tb_key == 0) { return; } remove_special_key_binding(tb_key); kb.key = tb_key; key_bindings[keybinding_count++] = kb; } } bool replace_key_binding(TBKeyBinding binding) { for (size_t i = 0; i < keybinding_count; i++) { if (key_bindings[i].key == binding.key && key_bindings[i].ch == binding.ch && key_bindings[i].mods == binding.mods) { key_bindings[i].eventType = binding.eventType; snprintf(key_bindings[i].args, sizeof(key_bindings[i].args), "%s", binding.args); return true; } } return false; } void add_key_binding(TBKeyBinding binding) { if (!replace_key_binding(binding)) key_bindings[keybinding_count++] = binding; } void add_legacy_mouse_binding(int mouseInputType, int mod, int action) { enum EventType event = get_mouse_action(action); TBKeyBinding kb = {mouseInputType, 0, mod, event, ""}; add_key_binding(kb); } static void trim_start(char **s) { while (**s && isspace((unsigned char)**s)) (*s)++; } static char *parse_field(char **s) { trim_start(s); if (!**s) return NULL; char buffer[256]; int idx = 0; int in_quotes = 0; if (**s == '"') { in_quotes = 1; (*s)++; } while (**s) { if (in_quotes) { if (**s == '"') { (*s)++; break; } if (**s == '\\') { (*s)++; if (**s) buffer[idx++] = **s++; } else buffer[idx++] = *(*s)++; } else { if (**s == ',') { (*s)++; break; } if (**s == '\\') { (*s)++; if (**s) buffer[idx++] = **s++; } else buffer[idx++] = *(*s)++; } if (idx >= (int)(sizeof(buffer) - 1)) break; // avoid overflow } buffer[idx] = '\0'; return strdup(buffer); // return a copy } void construct_app_settings(AppSettings *settings, KeyValuePair *pairs, int count) { if (pairs == NULL) { return; } bool foundCycleThemesSetting = false; for (int i = 0; i < count; i++) { KeyValuePair *pair = &pairs[i]; trim(pair->key, strlen(pair->key)); char *lowercase_key = string_to_lower(pair->key); if (strcmp(lowercase_key, "path") == 0) { snprintf(settings->path, sizeof(settings->path), "%s", pair->value); } else if (strcmp(lowercase_key, "theme") == 0) { snprintf(settings->theme, sizeof(settings->theme), "%s", pair->value); } else if (strcmp(lowercase_key, "chromapreset") == 0) { snprintf(settings->chromaPreset, sizeof(settings->chromaPreset), "%s", pair->value); } else if (strcmp(lowercase_key, "coverenabled") == 0) { snprintf(settings->coverEnabled, sizeof(settings->coverEnabled), "%s", pair->value); } else if (strcmp(lowercase_key, "coveransi") == 0) { snprintf(settings->coverAnsi, sizeof(settings->coverAnsi), "%s", pair->value); } else if (strcmp(lowercase_key, "visualizerenabled") == 0) { snprintf(settings->visualizerEnabled, sizeof(settings->visualizerEnabled), "%s", pair->value); } else if (strcmp(lowercase_key, "useconfigcolors") == 0) { snprintf(settings->useConfigColors, sizeof(settings->useConfigColors), "%s", pair->value); } else if (strcmp(lowercase_key, "visualizerheight") == 0) { snprintf(settings->visualizer_height, sizeof(settings->visualizer_height), "%s", pair->value); } else if (strcmp(lowercase_key, "visualizercolortype") == 0) { snprintf(settings->visualizer_color_type, sizeof(settings->visualizer_color_type), "%s", pair->value); } else if (strcmp(lowercase_key, "titledelay") == 0) { snprintf(settings->titleDelay, sizeof(settings->titleDelay), "%s", pair->value); } else if (strcmp(lowercase_key, "volumeup") == 0) { snprintf(settings->volumeUp, sizeof(settings->volumeUp), "%s", pair->value); add_legacy_key_binding(EVENT_VOLUME_UP, pair->value); } else if (strcmp(lowercase_key, "volumeupalt") == 0) { snprintf(settings->volumeUpAlt, sizeof(settings->volumeUpAlt), "%s", pair->value); add_legacy_key_binding(EVENT_VOLUME_UP, pair->value); } else if (strcmp(lowercase_key, "volumedown") == 0) { snprintf(settings->volumeDown, sizeof(settings->volumeDown), "%s", pair->value); add_legacy_key_binding(EVENT_VOLUME_DOWN, pair->value); } else if (strcmp(lowercase_key, "previoustrackalt") == 0) { snprintf(settings->previousTrackAlt, sizeof(settings->previousTrackAlt), "%s", pair->value); add_legacy_key_binding(EVENT_PREV, pair->value); } else if (strcmp(lowercase_key, "nexttrackalt") == 0) { snprintf(settings->nextTrackAlt, sizeof(settings->nextTrackAlt), "%s", pair->value); add_legacy_key_binding(EVENT_NEXT, pair->value); } else if (strcmp(lowercase_key, "scrollupalt") == 0) { snprintf(settings->scrollUpAlt, sizeof(settings->scrollUpAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SCROLLUP, pair->value); } else if (strcmp(lowercase_key, "scrolldownalt") == 0) { snprintf(settings->scrollDownAlt, sizeof(settings->scrollDownAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SCROLLDOWN, pair->value); } else if (strcmp(lowercase_key, "switchnumberedsong") == 0) { snprintf(settings->switchNumberedSong, sizeof(settings->switchNumberedSong), "%s", pair->value); add_legacy_key_binding(EVENT_ENQUEUE, pair->value); } else if (strcmp(lowercase_key, "togglepause") == 0) { snprintf(settings->toggle_pause, sizeof(settings->toggle_pause), "%s", pair->value); add_legacy_key_binding(EVENT_PLAY_PAUSE, pair->value); } else if (strcmp(lowercase_key, "togglecolorsderivedfrom") == 0) { snprintf(settings->cycleColorsDerivedFrom, sizeof(settings->cycleColorsDerivedFrom), "%s", pair->value); add_legacy_key_binding(EVENT_CYCLECOLORMODE, pair->value); } else if (strcmp(lowercase_key, "cyclethemes") == 0) { snprintf(settings->cycle_themes, sizeof(settings->cycle_themes), "%s", pair->value); foundCycleThemesSetting = true; add_legacy_key_binding(EVENT_CYCLETHEMES, pair->value); } else if (strcmp(lowercase_key, "togglenotifications") == 0) { snprintf(settings->toggle_notifications, sizeof(settings->toggle_notifications), "%s", pair->value); add_legacy_key_binding(EVENT_TOGGLENOTIFICATIONS, pair->value); } else if (strcmp(lowercase_key, "togglevisualizer") == 0) { snprintf(settings->toggle_visualizer, sizeof(settings->toggle_visualizer), "%s", pair->value); add_legacy_key_binding(EVENT_TOGGLEVISUALIZER, pair->value); } else if (strcmp(lowercase_key, "toggleascii") == 0) { snprintf(settings->toggle_ascii, sizeof(settings->toggle_ascii), "%s", pair->value); add_legacy_key_binding(EVENT_TOGGLEASCII, pair->value); } else if (strcmp(lowercase_key, "togglerepeat") == 0) { snprintf(settings->toggle_repeat, sizeof(settings->toggle_repeat), "%s", pair->value); add_legacy_key_binding(EVENT_TOGGLEREPEAT, pair->value); } else if (strcmp(lowercase_key, "toggleshuffle") == 0) { snprintf(settings->toggle_shuffle, sizeof(settings->toggle_shuffle), "%s", pair->value); add_legacy_key_binding(EVENT_SHUFFLE, pair->value); } else if (strcmp(lowercase_key, "seekbackward") == 0) { snprintf(settings->seekBackward, sizeof(settings->seekBackward), "%s", pair->value); add_legacy_key_binding(EVENT_SEEKBACK, pair->value); } else if (strcmp(lowercase_key, "seekforward") == 0) { snprintf(settings->seek_forward, sizeof(settings->seek_forward), "%s", pair->value); add_legacy_key_binding(EVENT_SEEKFORWARD, pair->value); } else if (strcmp(lowercase_key, "saveplaylist") == 0) { snprintf(settings->save_playlist, sizeof(settings->save_playlist), "%s", pair->value); add_legacy_key_binding(EVENT_EXPORTPLAYLIST, pair->value); } else if (strcmp(lowercase_key, "addtofavoritesplaylist") == 0) { snprintf(settings->add_to_favorites_playlist, sizeof(settings->add_to_favorites_playlist), "%s", pair->value); add_legacy_key_binding(EVENT_ADDTOFAVORITESPLAYLIST, pair->value); } else if (strcmp(lowercase_key, "lastvolume") == 0) { snprintf(settings->lastVolume, sizeof(settings->lastVolume), "%s", pair->value); } else if (strcmp(lowercase_key, "allownotifications") == 0) { snprintf(settings->allowNotifications, sizeof(settings->allowNotifications), "%s", pair->value); } else if (strcmp(lowercase_key, "colormode") == 0) { snprintf(settings->colorMode, sizeof(settings->colorMode), "%s", pair->value); } else if (strcmp(lowercase_key, "color") == 0) { snprintf(settings->color, sizeof(settings->color), "%s", pair->value); } else if (strcmp(lowercase_key, "artistcolor") == 0) { snprintf(settings->artistColor, sizeof(settings->artistColor), "%s", pair->value); } else if (strcmp(lowercase_key, "enqueuedcolor") == 0) { snprintf(settings->enqueued_color, sizeof(settings->enqueued_color), "%s", pair->value); } else if (strcmp(lowercase_key, "titlecolor") == 0) { snprintf(settings->titleColor, sizeof(settings->titleColor), "%s", pair->value); } else if (strcmp(lowercase_key, "mouseenabled") == 0) { snprintf(settings->mouseEnabled, sizeof(settings->mouseEnabled), "%s", pair->value); } else if (strcmp(lowercase_key, "repeatstate") == 0) { snprintf(settings->repeatState, sizeof(settings->repeatState), "%s", pair->value); } else if (strcmp(lowercase_key, "shuffleenabled") == 0) { snprintf(settings->shuffle_enabled, sizeof(settings->shuffle_enabled), "%s", pair->value); } else if (strcmp(lowercase_key, "saverepeatshufflesettings") == 0) { snprintf(settings->saveRepeatShuffleSettings, sizeof(settings->saveRepeatShuffleSettings), "%s", pair->value); } else if (strcmp(lowercase_key, "tracktitleaswindowtitle") == 0) { snprintf(settings->trackTitleAsWindowTitle, sizeof(settings->trackTitleAsWindowTitle), "%s", pair->value); } else if (strcmp(lowercase_key, "replaygaincheckfirst") == 0) { snprintf(settings->replayGainCheckFirst, sizeof(settings->replayGainCheckFirst), "%s", pair->value); } else if (strcmp(lowercase_key, "visualizerbarwidth") == 0) { snprintf(settings->visualizer_bar_width, sizeof(settings->visualizer_bar_width), "%s", pair->value); } else if (strcmp(lowercase_key, "visualizerbraillemode") == 0) { snprintf(settings->visualizerBrailleMode, sizeof(settings->visualizerBrailleMode), "%s", pair->value); } else if (strcmp(lowercase_key, "mouseleftclickaction") == 0) { snprintf(settings->mouseLeftClickAction, sizeof(settings->mouseLeftClickAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_LEFT, 0, get_number(pair->value)); } else if (strcmp(lowercase_key, "mousemiddleclickaction") == 0) { snprintf(settings->mouseMiddleClickAction, sizeof(settings->mouseMiddleClickAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_MIDDLE, 0, get_number(pair->value)); } else if (strcmp(lowercase_key, "mouserightclickaction") == 0) { snprintf(settings->mouseRightClickAction, sizeof(settings->mouseRightClickAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_RIGHT, 0, get_number(pair->value)); } else if (strcmp(lowercase_key, "mousescrollupaction") == 0) { snprintf(settings->mouseScrollUpAction, sizeof(settings->mouseScrollUpAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_WHEEL_UP, 0, get_number(pair->value)); } else if (strcmp(lowercase_key, "mousescrolldownaction") == 0) { snprintf(settings->mouseScrollDownAction, sizeof(settings->mouseScrollDownAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_WHEEL_DOWN, 0, get_number(pair->value)); } else if (strcmp(lowercase_key, "mousealtscrollupaction") == 0) { snprintf(settings->mouseAltScrollUpAction, sizeof(settings->mouseAltScrollUpAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_WHEEL_UP, TB_MOD_ALT, get_number(pair->value)); } else if (strcmp(lowercase_key, "mousealtscrolldownaction") == 0) { snprintf(settings->mouseAltScrollDownAction, sizeof(settings->mouseAltScrollDownAction), "%s", pair->value); add_legacy_mouse_binding(TB_KEY_MOUSE_WHEEL_DOWN, TB_MOD_ALT, get_number(pair->value)); } else if (strcmp(lowercase_key, "hidelogo") == 0) { snprintf(settings->hideLogo, sizeof(settings->hideLogo), "%s", pair->value); } else if (strcmp(lowercase_key, "hidehelp") == 0) { snprintf(settings->hideHelp, sizeof(settings->hideHelp), "%s", pair->value); } else if (strcmp(lowercase_key, "hidesidecover") == 0) { snprintf(settings->hideSideCover, sizeof(settings->hideSideCover), "%s", pair->value); } else if (strcmp(lowercase_key, "quitonstop") == 0) { snprintf(settings->quitAfterStopping, sizeof(settings->quitAfterStopping), "%s", pair->value); } else if (strcmp(lowercase_key, "hideglimmeringtext") == 0) { snprintf(settings->hideGlimmeringText, sizeof(settings->hideGlimmeringText), "%s", pair->value); } else if (strcmp(lowercase_key, "quit") == 0) { snprintf(settings->quit, sizeof(settings->quit), "%s", pair->value); add_legacy_key_binding(EVENT_QUIT, pair->value); } else if (strcmp(lowercase_key, "altquit") == 0) { snprintf(settings->altQuit, sizeof(settings->altQuit), "%s", pair->value); add_legacy_key_binding(EVENT_QUIT, pair->value); } else if (strcmp(lowercase_key, "prevpage") == 0) { snprintf(settings->prevPage, sizeof(settings->prevPage), "%s", pair->value); add_legacy_key_binding(EVENT_PREVPAGE, pair->value); } else if (strcmp(lowercase_key, "nextpage") == 0) { snprintf(settings->nextPage, sizeof(settings->nextPage), "%s", pair->value); add_legacy_key_binding(EVENT_NEXTPAGE, pair->value); } else if (strcmp(lowercase_key, "updatelibrary") == 0) { snprintf(settings->update_library, sizeof(settings->update_library), "%s", pair->value); add_legacy_key_binding(EVENT_UPDATELIBRARY, pair->value); } else if (strcmp(lowercase_key, "showplaylistalt") == 0) { if (strcmp(pair->value, "") != 0) // Don't set these to nothing snprintf(settings->showPlaylistAlt, sizeof(settings->showPlaylistAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWPLAYLIST, pair->value); } else if (strcmp(lowercase_key, "showlibraryalt") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->showLibraryAlt, sizeof(settings->showLibraryAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWLIBRARY, pair->value); } else if (strcmp(lowercase_key, "showtrackalt") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->showTrackAlt, sizeof(settings->showTrackAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWTRACK, pair->value); } else if (strcmp(lowercase_key, "showsearchalt") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->showSearchAlt, sizeof(settings->showSearchAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWSEARCH, pair->value); } else if (strcmp(lowercase_key, "showlyricspage") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->showLyricsPage, sizeof(settings->showLyricsPage), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWLYRICSPAGE, pair->value); } else if (strcmp(lowercase_key, "movesongup") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->move_song_up, sizeof(settings->move_song_up), "%s", pair->value); // Don't add this since this key 't' should be used for themes //add_legacy_key_binding(EVENT_MOVESONGUP, pair->value); } else if (strcmp(lowercase_key, "movesongdown") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->move_song_down, sizeof(settings->move_song_down), "%s", pair->value); add_legacy_key_binding(EVENT_MOVESONGDOWN, pair->value); } else if (strcmp(lowercase_key, "enqueueandplay") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->enqueueAndPlay, sizeof(settings->enqueueAndPlay), "%s", pair->value); add_legacy_key_binding(EVENT_ENQUEUEANDPLAY, pair->value); } else if (strcmp(lowercase_key, "sort") == 0) { if (strcmp(pair->value, "") != 0) snprintf(settings->sort_library, sizeof(settings->sort_library), "%s", pair->value); add_legacy_key_binding(EVENT_SORTLIBRARY, pair->value); } else if (strcmp(lowercase_key, "progressbarelapsedevenchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarElapsedEvenChar, sizeof(settings->progressBarElapsedEvenChar), "%s", pair->value); } else if (strcmp(lowercase_key, "progressbarelapsedoddchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarElapsedOddChar, sizeof(settings->progressBarElapsedOddChar), "%s", pair->value); } else if (strcmp(lowercase_key, "progressbarapproachingevenchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarApproachingEvenChar, sizeof(settings->progressBarApproachingEvenChar), "%s", pair->value); } else if (strcmp(lowercase_key, "progressbarapproachingoddchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarApproachingOddChar, sizeof( settings->progressBarApproachingOddChar), "%s", pair->value); } else if (strcmp(lowercase_key, "progressbarcurrentevenchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarCurrentEvenChar, sizeof(settings->progressBarCurrentEvenChar), "%s", pair->value); } else if (strcmp(lowercase_key, "progressbarcurrentoddchar") == 0) { if (strcmp(pair->value, "") != 0) snprintf( settings->progressBarCurrentOddChar, sizeof(settings->progressBarCurrentOddChar), "%s", pair->value); } else if (strcmp(lowercase_key, "showkeysalt") == 0 && strcmp(pair->value, "N") != 0) { // We need to prevent the previous key B or else config // files wont get updated to the new key N and B for // radio search on macOS if (strcmp(pair->value, "") != 0) snprintf(settings->showKeysAlt, sizeof(settings->showKeysAlt), "%s", pair->value); add_legacy_key_binding(EVENT_SHOWHELP, pair->value); } else if (strcmp(lowercase_key, "bind") == 0) { char value_copy[256]; strncpy(value_copy, pair->value, sizeof(value_copy)); value_copy[sizeof(value_copy) - 1] = '\0'; char *s = value_copy; char *binding_str = parse_field(&s); char *event_str = parse_field(&s); char *args_str = parse_field(&s); if (binding_str && event_str) { if (!args_str) args_str = ""; TBKeyBinding kb = parse_binding(binding_str, event_str, args_str); add_key_binding(kb); } if (binding_str) free(binding_str); if (event_str) free(event_str); if (args_str && args_str[0] != '\0') free(args_str); } free(lowercase_key); } free_key_value_pairs(pairs, count); if (!foundCycleThemesSetting) { // move_song_up is no longer t, it needs to be changed c_strcpy(settings->move_song_up, "f", sizeof(settings->move_song_up)); } } KeyValuePair *read_key_value_pairs(const char *file_path, int *count, time_t *last_time_app_ran) { FILE *file = fopen(file_path, "r"); if (file == NULL) { return NULL; } struct stat file_stat; if (stat(file_path, &file_stat) == -1) { return NULL; } // Save the modification time (mtime) of the file *last_time_app_ran = file_stat.st_mtime; KeyValuePair *pairs = NULL; int pair_count = 0; char line[256]; while (fgets(line, sizeof(line), file)) { // Remove trailing newline character if present line[strcspn(line, "\n")] = '\0'; char *comment = strchr(line, '#'); // Remove comments if (comment != NULL) *comment = '\0'; char *delimiter = strchr(line, '='); if (delimiter != NULL) { *delimiter = '\0'; char *value = delimiter + 1; pair_count++; pairs = realloc(pairs, pair_count * sizeof(KeyValuePair)); KeyValuePair *current_pair = &pairs[pair_count - 1]; current_pair->key = strdup(line); current_pair->value = strdup(value); } } fclose(file); *count = pair_count; return pairs; } const char *get_default_music_folder(void) { const char *home = get_home_path(); if (home != NULL) { static char music_path[PATH_MAX]; snprintf(music_path, sizeof(music_path), "%s/Music", home); return music_path; } else { return NULL; // Return NULL if XDG home is not found. } } int get_music_library_path(char *path) { char expanded_path[PATH_MAX]; if (path[0] != '\0' && path[0] != '\r') { if (expand_path(path, expanded_path) >= 0) { c_strcpy(path, expanded_path, sizeof(expanded_path)); } } return 0; } void map_settings_to_keys(AppSettings *settings, EventMapping *mappings) { mappings[0] = (EventMapping){settings->scrollUpAlt, EVENT_SCROLLUP}; mappings[1] = (EventMapping){settings->scrollDownAlt, EVENT_SCROLLDOWN}; mappings[2] = (EventMapping){settings->nextTrackAlt, EVENT_NEXT}; mappings[3] = (EventMapping){settings->previousTrackAlt, EVENT_PREV}; mappings[4] = (EventMapping){settings->volumeUp, EVENT_VOLUME_UP}; mappings[5] = (EventMapping){settings->volumeUpAlt, EVENT_VOLUME_UP}; mappings[6] = (EventMapping){settings->volumeDown, EVENT_VOLUME_DOWN}; mappings[7] = (EventMapping){settings->toggle_pause, EVENT_PLAY_PAUSE}; mappings[8] = (EventMapping){settings->quit, EVENT_QUIT}; mappings[9] = (EventMapping){settings->altQuit, EVENT_QUIT}; mappings[10] = (EventMapping){settings->toggle_shuffle, EVENT_SHUFFLE}; mappings[11] = (EventMapping){settings->toggle_visualizer, EVENT_TOGGLEVISUALIZER}; mappings[12] = (EventMapping){settings->toggle_ascii, EVENT_TOGGLEASCII}; mappings[13] = (EventMapping){settings->switchNumberedSong, EVENT_ENQUEUE}; mappings[14] = (EventMapping){settings->seekBackward, EVENT_SEEKBACK}; mappings[15] = (EventMapping){settings->seek_forward, EVENT_SEEKFORWARD}; mappings[16] = (EventMapping){settings->toggle_repeat, EVENT_TOGGLEREPEAT}; mappings[17] = (EventMapping){settings->save_playlist, EVENT_EXPORTPLAYLIST}; mappings[18] = (EventMapping){settings->cycleColorsDerivedFrom, EVENT_CYCLECOLORMODE}; mappings[19] = (EventMapping){settings->add_to_favorites_playlist, EVENT_ADDTOFAVORITESPLAYLIST}; mappings[20] = (EventMapping){settings->update_library, EVENT_UPDATELIBRARY}; mappings[21] = (EventMapping){settings->hardPlayPause, EVENT_PLAY_PAUSE}; mappings[22] = (EventMapping){settings->hardPrev, EVENT_PREV}; mappings[23] = (EventMapping){settings->hardNext, EVENT_NEXT}; mappings[24] = (EventMapping){settings->hardSwitchNumberedSong, EVENT_ENQUEUE}; mappings[25] = (EventMapping){settings->hardScrollUp, EVENT_SCROLLUP}; mappings[26] = (EventMapping){settings->hardScrollDown, EVENT_SCROLLDOWN}; mappings[27] = (EventMapping){settings->hardShowPlaylist, EVENT_SHOWPLAYLIST}; mappings[28] = (EventMapping){settings->hardShowPlaylistAlt, EVENT_SHOWPLAYLIST}; mappings[29] = (EventMapping){settings->showPlaylistAlt, EVENT_SHOWPLAYLIST}; mappings[30] = (EventMapping){settings->hardShowKeys, EVENT_SHOWHELP}; mappings[31] = (EventMapping){settings->hardShowKeysAlt, EVENT_SHOWHELP}; mappings[32] = (EventMapping){settings->showKeysAlt, EVENT_SHOWHELP}; mappings[33] = (EventMapping){settings->hardShowTrack, EVENT_SHOWTRACK}; mappings[34] = (EventMapping){settings->hardShowTrackAlt, EVENT_SHOWTRACK}; mappings[35] = (EventMapping){settings->showTrackAlt, EVENT_SHOWTRACK}; mappings[36] = (EventMapping){settings->hardShowLibrary, EVENT_SHOWLIBRARY}; mappings[37] = (EventMapping){settings->hardShowLibraryAlt, EVENT_SHOWLIBRARY}; mappings[38] = (EventMapping){settings->showLibraryAlt, EVENT_SHOWLIBRARY}; mappings[39] = (EventMapping){settings->hardShowSearch, EVENT_SHOWSEARCH}; mappings[40] = (EventMapping){settings->hardShowSearchAlt, EVENT_SHOWSEARCH}; mappings[41] = (EventMapping){settings->showSearchAlt, EVENT_SHOWSEARCH}; mappings[42] = (EventMapping){settings->nextPage, EVENT_NEXTPAGE}; mappings[43] = (EventMapping){settings->prevPage, EVENT_PREVPAGE}; mappings[44] = (EventMapping){settings->hardRemove, EVENT_REMOVE}; mappings[45] = (EventMapping){settings->hardRemove2, EVENT_REMOVE}; mappings[46] = (EventMapping){settings->nextView, EVENT_NEXTVIEW}; mappings[47] = (EventMapping){settings->prevView, EVENT_PREVVIEW}; mappings[55] = (EventMapping){settings->hardClearPlaylist, EVENT_CLEARPLAYLIST}; mappings[56] = (EventMapping){settings->move_song_up, EVENT_MOVESONGUP}; mappings[57] = (EventMapping){settings->move_song_down, EVENT_MOVESONGDOWN}; mappings[58] = (EventMapping){settings->enqueueAndPlay, EVENT_ENQUEUEANDPLAY}; mappings[59] = (EventMapping){settings->hardStop, EVENT_STOP}; mappings[60] = (EventMapping){settings->sort_library, EVENT_SORTLIBRARY}; mappings[61] = (EventMapping){settings->cycle_themes, EVENT_CYCLETHEMES}; mappings[62] = (EventMapping){settings->toggle_notifications, EVENT_TOGGLENOTIFICATIONS}; mappings[63] = (EventMapping){settings->showLyricsPage, EVENT_SHOWLYRICSPAGE}; } char *get_config_file_path(char *configdir) { size_t configdir_length = strnlen(configdir, PATH_MAX - 1); size_t settings_file_length = strnlen(SETTINGS_FILE, sizeof(SETTINGS_FILE) - 1); if (configdir_length + 1 + settings_file_length + 1 > PATH_MAX) { fprintf(stderr, "Error: File path exceeds maximum length.\n"); exit(1); } char *filepath = (char *)malloc(PATH_MAX); if (filepath == NULL) { perror("malloc"); exit(1); } int written = snprintf(filepath, PATH_MAX, "%s/%s", configdir, SETTINGS_FILE); if (written < 0 || written >= PATH_MAX) { fprintf(stderr, "Error: snprintf failed or filepath truncated.\n"); free(filepath); exit(1); } return filepath; } char *get_prefs_file_path(char *prefsdir) { size_t dir_length = strnlen(prefsdir, PATH_MAX - 1); size_t file_length = strnlen(STATE_FILE, sizeof(STATE_FILE) - 1); if (dir_length + 1 + file_length + 1 > PATH_MAX) { fprintf(stderr, "Error: File path exceeds maximum length.\n"); exit(1); } char *filepath = (char *)malloc(PATH_MAX); if (filepath == NULL) { perror("malloc"); exit(1); } int written = snprintf(filepath, PATH_MAX, "%s/%s", prefsdir, STATE_FILE); if (written < 0 || written >= PATH_MAX) { fprintf(stderr, "Error: snprintf failed or filepath truncated.\n"); free(filepath); exit(1); } return filepath; } int mkdir_p(const char *path, mode_t mode) { if (path == NULL) return -1; if (path[0] == '~') { // Just try a plain mkdir if there's a tilde if (mkdir(path, mode) == -1) { if (errno != EEXIST) return -1; } return 0; } char tmp[PATH_MAX]; char *p = NULL; size_t len; snprintf(tmp, sizeof(tmp), "%s", path); len = strnlen(tmp, PATH_MAX); if (len > 0 && tmp[len - 1] == '/') tmp[len - 1] = 0; for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = 0; if (mkdir(tmp, mode) == -1) { if (errno != EEXIST) return -1; } *p = '/'; } } if (mkdir(tmp, mode) == -1) { if (errno != EEXIST) return -1; } return 0; } void get_prefs(AppSettings *settings, UISettings *ui) { int pair_count; char *prefsdir = get_prefs_path(); setlocale(LC_ALL, ""); struct stat st = {0}; if (stat(prefsdir, &st) == -1) { if (mkdir_p(prefsdir, 0700) != 0) { perror("mkdir"); free(prefsdir); exit(1); } } char *filepath = get_prefs_file_path(prefsdir); KeyValuePair *pairs = read_key_value_pairs(filepath, &pair_count, &(ui->last_time_app_ran)); free(filepath); construct_app_settings(settings, pairs, pair_count); int tmp = get_number(settings->repeatState); if (tmp >= 0) ui->repeatState = tmp; if (settings->chromaPreset[0] != '\0') { tmp = get_number(settings->chromaPreset); if (tmp >= 0) ui->chromaPreset = tmp; } tmp = get_number(settings->colorMode); if (tmp >= 0 && tmp < 3) { ui->colorMode = tmp; } tmp = get_number(settings->lastVolume); if (tmp >= 0) set_volume(tmp); snprintf(ui->theme_name, sizeof(ui->theme_name), "%s", settings->theme); free(prefsdir); } void get_config(AppSettings *settings, UISettings *ui) { int pair_count; char *configdir = get_config_path(); setlocale(LC_ALL, ""); struct stat st = {0}; if (stat(configdir, &st) == -1) { if (mkdir_p(configdir, 0700) != 0) { perror("mkdir"); exit(1); } } set_default_config(settings); char *filepath = get_config_file_path(configdir); FILE *file = fopen(filepath, "r"); if (file == NULL) { set_config(settings, ui); } KeyValuePair *pairs = read_key_value_pairs(filepath, &pair_count, &(ui->last_time_app_ran)); free(filepath); construct_app_settings(settings, pairs, pair_count); free(configdir); } void set_prefs(AppSettings *settings, UISettings *ui) { // Create the file path char *prefsdir = get_prefs_path(); char *filepath = get_prefs_file_path(prefsdir); setlocale(LC_ALL, ""); FILE *file = fopen(filepath, "w"); if (file == NULL) { fprintf(stderr, "Error opening file: %s\n", filepath); free(filepath); free(prefsdir); return; } if (settings->allowNotifications[0] == '\0') ui->allowNotifications ? c_strcpy(settings->allowNotifications, "1", sizeof(settings->allowNotifications)) : c_strcpy(settings->allowNotifications, "0", sizeof(settings->allowNotifications)); if (settings->coverEnabled[0] == '\0') ui->coverEnabled ? c_strcpy(settings->coverEnabled, "1", sizeof(settings->coverEnabled)) : c_strcpy(settings->coverEnabled, "0", sizeof(settings->coverEnabled)); if (settings->coverAnsi[0] == '\0') ui->coverAnsi ? c_strcpy(settings->coverAnsi, "1", sizeof(settings->coverAnsi)) : c_strcpy(settings->coverAnsi, "0", sizeof(settings->coverAnsi)); if (settings->visualizerEnabled[0] == '\0') ui->visualizerEnabled ? c_strcpy(settings->visualizerEnabled, "1", sizeof(settings->visualizerEnabled)) : c_strcpy(settings->visualizerEnabled, "0", sizeof(settings->visualizerEnabled)); if (ui->saveRepeatShuffleSettings) { snprintf(settings->repeatState, sizeof(settings->repeatState), "%d", ui->repeatState); ui->shuffle_enabled ? c_strcpy(settings->shuffle_enabled, "1", sizeof(settings->shuffle_enabled)) : c_strcpy(settings->shuffle_enabled, "0", sizeof(settings->shuffle_enabled)); } if (settings->visualizer_color_type[0] == '\0') snprintf(settings->visualizer_color_type, sizeof(settings->visualizer_color_type), "%d", ui->visualizer_color_type); int current_volume = get_current_volume(); current_volume = (current_volume <= 0) ? 10 : current_volume; snprintf(settings->lastVolume, sizeof(settings->lastVolume), "%d", current_volume); fprintf(file, "\n[miscellaneous]\n\n"); fprintf(file, "allowNotifications=%s\n\n", settings->allowNotifications); if (ui->saveRepeatShuffleSettings) { fprintf(file, "repeatState=%s\n\n", settings->repeatState); fprintf(file, "shuffleEnabled=%s\n\n", settings->shuffle_enabled); } fprintf(file, "lastVolume=%s\n\n", settings->lastVolume); fprintf(file, "[track cover]\n\n"); fprintf(file, "coverAnsi=%s\n\n", settings->coverAnsi); fprintf(file, "[visualizer]\n\n"); fprintf(file, "visualizerEnabled=%s\n\n", settings->visualizerEnabled); fprintf(file, "[chroma]\n\n"); fprintf(file, "chromaPreset=%d\n\n", ui->chromaPreset); fprintf(file, "[colors]\n\n"); fprintf(file, "colorMode=%d\n\n", ui->colorMode); fprintf(file, "theme=%s\n\n", ui->theme_name); free(prefsdir); free(filepath); } static const char *key_binding_to_str(const TBKeyBinding kb) { static char buf[128]; buf[0] = '\0'; // Modifiers if (kb.mods & TB_MOD_CTRL) strcat(buf, "Ctrl+"); if (kb.mods & TB_MOD_ALT) strcat(buf, "Alt+"); if (kb.mods & TB_MOD_SHIFT) strcat(buf, "Shift+"); // Key if (kb.key != 0) { // Special key const char *key_name = key_code_to_name(kb.key); if (key_name) strcat(buf, key_name); else sprintf(buf + strlen(buf), "0x%x", kb.key); } else if (kb.ch != 0) { // Printable character size_t len = strlen(buf); buf[len] = (char)kb.ch; buf[len + 1] = '\0'; } else { strcat(buf, "Unknown"); } // Event const char *event_name = event_to_string(kb.eventType); // Final Format static char final[256]; if (kb.args[0]) snprintf(final, sizeof(final), "%s, %s, %s", buf, event_name, kb.args); else snprintf(final, sizeof(final), "%s, %s", buf, event_name); return final; } void set_config(AppSettings *settings, UISettings *ui) { // Create the file path char *configdir = get_config_path(); char *filepath = get_config_file_path(configdir); setlocale(LC_ALL, ""); FILE *file = fopen(filepath, "w"); if (file == NULL) { fprintf(stderr, "Error opening file: %s\n", filepath); free(filepath); free(configdir); return; } // Make sure strings are valid before writing settings to the file if (settings->allowNotifications[0] == '\0') ui->allowNotifications ? c_strcpy(settings->allowNotifications, "1", sizeof(settings->allowNotifications)) : c_strcpy(settings->allowNotifications, "0", sizeof(settings->allowNotifications)); if (settings->coverEnabled[0] == '\0') ui->coverEnabled ? c_strcpy(settings->coverEnabled, "1", sizeof(settings->coverEnabled)) : c_strcpy(settings->coverEnabled, "0", sizeof(settings->coverEnabled)); if (settings->coverAnsi[0] == '\0') ui->coverAnsi ? c_strcpy(settings->coverAnsi, "1", sizeof(settings->coverAnsi)) : c_strcpy(settings->coverAnsi, "0", sizeof(settings->coverAnsi)); if (settings->visualizerEnabled[0] == '\0') ui->visualizerEnabled ? c_strcpy(settings->visualizerEnabled, "1", sizeof(settings->visualizerEnabled)) : c_strcpy(settings->visualizerEnabled, "0", sizeof(settings->visualizerEnabled)); if (settings->quitAfterStopping[0] == '\0') ui->quitAfterStopping ? c_strcpy(settings->quitAfterStopping, "1", sizeof(settings->quitAfterStopping)) : c_strcpy(settings->quitAfterStopping, "0", sizeof(settings->quitAfterStopping)); if (settings->hideGlimmeringText[0] == '\0') ui->hideGlimmeringText ? c_strcpy(settings->hideGlimmeringText, "1", sizeof(settings->hideGlimmeringText)) : c_strcpy(settings->hideGlimmeringText, "0", sizeof(settings->hideGlimmeringText)); if (settings->mouseEnabled[0] == '\0') ui->mouseEnabled ? c_strcpy(settings->mouseEnabled, "1", sizeof(settings->mouseEnabled)) : c_strcpy(settings->mouseEnabled, "0", sizeof(settings->mouseEnabled)); if (settings->trackTitleAsWindowTitle[0] == '\0') ui->trackTitleAsWindowTitle ? c_strcpy(settings->trackTitleAsWindowTitle, "1", sizeof(settings->trackTitleAsWindowTitle)) : c_strcpy(settings->trackTitleAsWindowTitle, "0", sizeof(settings->trackTitleAsWindowTitle)); snprintf(settings->repeatState, sizeof(settings->repeatState), "%d", ui->repeatState); ui->shuffle_enabled ? c_strcpy(settings->shuffle_enabled, "1", sizeof(settings->shuffle_enabled)) : c_strcpy(settings->shuffle_enabled, "0", sizeof(settings->shuffle_enabled)); if (settings->visualizer_bar_width[0] == '\0') snprintf(settings->visualizer_bar_width, sizeof(settings->visualizer_bar_width), "%d", ui->visualizer_bar_mode); if (settings->visualizerBrailleMode[0] == '\0') ui->visualizerBrailleMode ? c_strcpy(settings->visualizerBrailleMode, "1", sizeof(settings->visualizerBrailleMode)) : c_strcpy(settings->visualizerBrailleMode, "0", sizeof(settings->visualizerBrailleMode)); if (settings->hideLogo[0] == '\0') ui->hideLogo ? c_strcpy(settings->hideLogo, "1", sizeof(settings->hideLogo)) : c_strcpy(settings->hideLogo, "0", sizeof(settings->hideLogo)); if (settings->hideHelp[0] == '\0') ui->hideHelp ? c_strcpy(settings->hideHelp, "1", sizeof(settings->hideHelp)) : c_strcpy(settings->hideHelp, "0", sizeof(settings->hideHelp)); if (settings->hideSideCover[0] == '\0') ui->hideSideCover ? c_strcpy(settings->hideSideCover, "1", sizeof(settings->hideSideCover)) : c_strcpy(settings->hideSideCover, "0", sizeof(settings->hideSideCover)); if (settings->visualizer_height[0] == '\0') snprintf(settings->visualizer_height, sizeof(settings->visualizer_height), "%d", ui->visualizer_height); if (settings->visualizer_color_type[0] == '\0') snprintf(settings->visualizer_color_type, sizeof(settings->visualizer_color_type), "%d", ui->visualizer_color_type); if (settings->titleDelay[0] == '\0') snprintf(settings->titleDelay, sizeof(settings->titleDelay), "%d", ui->titleDelay); if (settings->replayGainCheckFirst[0] == '\0') snprintf(settings->replayGainCheckFirst, sizeof(settings->replayGainCheckFirst), "%d", ui->replayGainCheckFirst); // Write the settings to the file fprintf(file, "\n[miscellaneous]\n\n"); fprintf(file, "path=%s\n", settings->path); fprintf(file, "allowNotifications=%s\n", settings->allowNotifications); fprintf(file, "hideLogo=%s\n", settings->hideLogo); fprintf(file, "hideHelp=%s\n", settings->hideHelp); fprintf(file, "hideSideCover=%s\n\n", settings->hideSideCover); fprintf(file, "# Delay when drawing title in track view, set to 0 to " "have no delay.\n"); fprintf(file, "titleDelay=%s\n\n", settings->titleDelay); fprintf(file, "# Same as '--quitonstop' flag, exits after playing the " "whole playlist.\n"); fprintf(file, "quitOnStop=%s\n\n", settings->quitAfterStopping); fprintf(file, "# Glimmering text on the bottom row.\n"); fprintf(file, "hideGlimmeringText=%s\n\n", settings->hideGlimmeringText); fprintf(file, "# Replay gain check first, can be either 0=track, " "1=album or 2=disabled.\n"); fprintf(file, "replayGainCheckFirst=%s\n\n", settings->replayGainCheckFirst); fprintf(file, "# Save Repeat and Shuffle Settings.\n"); fprintf(file, "saveRepeatShuffleSettings=%s\n\n", settings->saveRepeatShuffleSettings); fprintf(file, "repeatState=%s\n\n", settings->repeatState); fprintf(file, "shuffleEnabled=%s\n\n", settings->shuffle_enabled); fprintf(file, "# Set the window title to the title of the currently " "playing track\n"); fprintf(file, "trackTitleAsWindowTitle=%s\n\n", settings->trackTitleAsWindowTitle); fprintf(file, "\n[colors]\n\n"); fprintf(file, "# Theme's go in ~/.config/kew/themes (on Linux/FreeBSD/Android), \n"); fprintf(file, "# and ~/Library/Preferences/kew/themes (on macOS), \n"); fprintf(file, "theme=%s\n\n", settings->theme); fprintf(file, "# Color Mode is:\n"); fprintf(file, "# 0 = 16-bit color palette from default theme, \n"); fprintf(file, "# 1 = Colors derived from track cover, \n"); fprintf(file, "# 2 = Colors derived from TrueColor theme, \n\n"); fprintf(file, "# Color Mode:\n"); fprintf(file, "colorMode=%d\n\n", ui->colorMode); fprintf(file, "# Terminal color theme is default.theme in \n"); fprintf(file, "# ~/.config/kew/themes (on Linux/FreeBSD/Android), \n"); fprintf(file, "# and ~/Library/Preferences/kew/themes (on macOS).\n\n"); fprintf(file, "\n[track cover]\n\n"); fprintf(file, "coverEnabled=%s\n", settings->coverEnabled); fprintf(file, "coverAnsi=%s\n\n", settings->coverAnsi); fprintf(file, "\n[mouse]\n\n"); fprintf(file, "mouseEnabled=%s\n\n", settings->mouseEnabled); fprintf(file, "\n[visualizer]\n\n"); fprintf(file, "visualizerEnabled=%s\n", settings->visualizerEnabled); fprintf(file, "visualizerHeight=%s\n", settings->visualizer_height); fprintf(file, "visualizerBrailleMode=%s\n\n", settings->visualizerBrailleMode); fprintf(file, "# How colors are laid out in the spectrum visualizer. " "0=lighten, 1=brightness depending on bar height, " "2=reversed, 3=reversed darken.\n"); fprintf(file, "visualizerColorType=%s\n\n", settings->visualizer_color_type); fprintf(file, "# 0=Thin bars, 1=Bars twice the width, 2=Auto (depends " "on window size).\n"); fprintf(file, "visualizerBarWidth=%s\n\n", settings->visualizer_bar_width); fprintf(file, "\n[progress bar]\n\n"); fprintf(file, "# Progress bar in track view\n"); fprintf(file, "# The progress bar can be configured in many ways.\n"); fprintf(file, "# When copying the values below, be sure to include values " "that are empty spaces or things will get messed up.\n"); fprintf(file, "# Be sure to have the actual uncommented values last.\n"); fprintf( file, "# For instance use the below values for a pill muncher mode:\n\n"); fprintf(file, "#progressBarElapsedEvenChar= \n"); fprintf(file, "#progressBarElapsedOddChar= \n"); fprintf(file, "#progressBarApproachingEvenChar=•\n"); fprintf(file, "#progressBarApproachingOddChar=·\n"); fprintf(file, "#progressBarCurrentEvenChar=ᗧ\n"); fprintf(file, "#progressBarCurrentOddChar=ᗧ\n\n"); fprintf(file, "# To have a thick line: \n\n"); fprintf(file, "#progressBarElapsedEvenChar=━\n"); fprintf(file, "#progressBarElapsedOddChar=━\n"); fprintf(file, "#progressBarApproachingEvenChar=━\n"); fprintf(file, "#progressBarApproachingOddChar=━\n"); fprintf(file, "#progressBarCurrentEvenChar=━\n"); fprintf(file, "#progressBarCurrentOddChar=━\n\n"); fprintf(file, "# To have dots (the original): \n\n"); fprintf(file, "#progressBarElapsedEvenChar=■\n"); fprintf(file, "#progressBarElapsedOddChar= \n"); fprintf(file, "#progressBarApproachingEvenChar==\n"); fprintf(file, "#progressBarApproachingOddChar= \n"); fprintf(file, "#progressBarCurrentEvenChar=■\n"); fprintf(file, "#progressBarCurrentOddChar= \n\n"); fprintf(file, "# Current values: \n\n"); fprintf(file, "progressBarElapsedEvenChar=%s\n", settings->progressBarElapsedEvenChar); fprintf(file, "progressBarElapsedOddChar=%s\n", settings->progressBarElapsedOddChar); fprintf(file, "progressBarApproachingEvenChar=%s\n", settings->progressBarApproachingEvenChar); fprintf(file, "progressBarApproachingOddChar=%s\n", settings->progressBarApproachingOddChar); fprintf(file, "progressBarCurrentEvenChar=%s\n", settings->progressBarCurrentEvenChar); fprintf(file, "progressBarCurrentOddChar=%s\n\n", settings->progressBarCurrentOddChar); fprintf(file, "\n[key bindings]\n\n"); for (size_t i = 0; i < keybinding_count; i++) { fprintf(file, "bind = "); fprintf(file, "%s\n", key_binding_to_str(key_bindings[i])); } fprintf(file, "\n"); fclose(file); free(filepath); free(configdir); } int update_rc(const char *path, const char *key, const char *value) { FILE *file = fopen(path, "r"); if (!file) { perror("fopen"); return -1; } char **lines = NULL; size_t num_lines = 0; char line[MAX_LINE]; int found = 0; while (fgets(line, sizeof(line), file)) { // Remove trailing newline line[strcspn(line, "\n")] = '\0'; char *eq = strchr(line, '='); char *newline; if (eq) { *eq = '\0'; char *existing_key = line; char *existing_value = eq + 1; if (strcmp(existing_key, key) == 0) { // Replace value size_t needed = strlen(key) + strlen(value) + 2; newline = malloc(needed); if (!newline) { fclose(file); return -1; } snprintf(newline, needed, "%s=%s", key, value); found = 1; } else { // Keep original line newline = malloc(strlen(line) + strlen(existing_value) + 2); snprintf(newline, strlen(line) + strlen(existing_value) + 2, "%s=%s", existing_key, existing_value); } } else { // Line without '=' or empty line newline = strdup(line); } lines = realloc(lines, sizeof(char *) * (num_lines + 1)); lines[num_lines++] = newline; } fclose(file); if (!found) { // Append new key=value size_t needed = strlen(key) + strlen(value) + 2; char *newline = malloc(needed); snprintf(newline, needed, "%s=%s", key, value); lines = realloc(lines, sizeof(char *) * (num_lines + 1)); lines[num_lines++] = newline; } // Write back file = fopen(path, "w"); if (!file) { perror("fopen"); return -1; } for (size_t i = 0; i < num_lines; i++) { fprintf(file, "%s\n", lines[i]); free(lines[i]); } free(lines); fclose(file); return 0; } void set_path(const char *path) { char *configdir = NULL; char *config_file_path = NULL; configdir = get_config_path(); config_file_path = get_config_file_path(configdir); update_rc(config_file_path, "path", path); if (configdir != NULL) free(configdir); if (config_file_path != NULL) free(config_file_path); } kew/src/ui/settings.h000066400000000000000000000016101512074754200150660ustar00rootroot00000000000000/* * @file settings.h * @brief Application settings management. * * Loads, saves, and applies configuration settings such as * playback behavior, UI preferences, and library paths. */ #ifndef SETTINGS_H #define SETTINGS_H #include "common/appstate.h" #include "common/events.h" #define NUM_DEFAULT_KEY_BINDINGS 52 extern size_t keybinding_count; TBKeyBinding *get_key_bindings(); void get_config(AppSettings *settings, UISettings *ui); void get_prefs(AppSettings *settings, UISettings *ui); void set_config(AppSettings *settings, UISettings *ui); void set_prefs(AppSettings *settings, UISettings *ui); void set_path(const char *path); void map_settings_to_keys(AppSettings *settings, EventMapping *mappings); int update_rc(const char *path, const char *key, const char *value); const char *get_binding_string(enum EventType event, bool find_only_one); AppSettings init_settings(void); #endif kew/src/ui/termbox2_input.h000066400000000000000000003066011512074754200162170ustar00rootroot00000000000000/* MIT License Copyright (c) 2010-2020 nsf 2015-2024 Adam Saponara 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. IMPORTANT: This is based on termbox2. The original source has been modified to keep only the input handling. It is adapted for working with the music player kew, which handles setting non-blocking mode itself. */ #ifndef TERMBOX_H_INCL #define TERMBOX_H_INCL #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PATH_MAX #define TB_PATH_MAX PATH_MAX #else #define TB_PATH_MAX 4096 #endif #ifdef __cplusplus extern "C" { #endif // __ffi_start #define TB_VERSION_STR "2.5.0" /* The following compile-time options are supported: * * TB_OPT_ATTR_W: Integer width of fg and bg attributes. Valid values * (assuming system support) are 16, 32, and 64. (See * uintattr_t). 32 or 64 enables output mode * TB_OUTPUT_TRUECOLOR. 64 enables additional style * attributes. (See tb_set_output_mode.) Larger values * consume more memory in exchange for more features. * Defaults to 16. * * TB_OPT_EGC: If set, enable extended grapheme cluster support * (tb_extend_cell, tb_set_cell_ex). Consumes more memory. * Defaults off. * * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the * largest string that can be sent in one call to tb_print* * and tb_send* functions. Defaults to 4096. * * TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64. * * TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set. */ #if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts /* Ensure consistent compile-time options when using as a shared library */ #undef TB_OPT_ATTR_W #undef TB_OPT_EGC #undef TB_OPT_PRINTF_BUF #undef TB_OPT_READ_BUF #define TB_OPT_ATTR_W 64 #define TB_OPT_EGC #endif /* Ensure sane `TB_OPT_ATTR_W` (16, 32, or 64) */ #if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16 #elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32 #elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64 #else #undef TB_OPT_ATTR_W #if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag. #define TB_OPT_ATTR_W 32 #else #define TB_OPT_ATTR_W 16 #endif #endif /* ASCII key constants (`tb_event.key`) */ #define TB_KEY_CTRL_TILDE 0x00 #define TB_KEY_CTRL_2 0x00 // clash with `CTRL_TILDE` #define TB_KEY_CTRL_A 0x01 #define TB_KEY_CTRL_B 0x02 #define TB_KEY_CTRL_C 0x03 #define TB_KEY_CTRL_D 0x04 #define TB_KEY_CTRL_E 0x05 #define TB_KEY_CTRL_F 0x06 #define TB_KEY_CTRL_G 0x07 #define TB_KEY_BACKSPACE 0x08 #define TB_KEY_CTRL_H 0x08 // clash with `CTRL_BACKSPACE` #define TB_KEY_TAB 0x09 #define TB_KEY_CTRL_I 0x09 // clash with `TAB` #define TB_KEY_CTRL_J 0x0a #define TB_KEY_CTRL_K 0x0b #define TB_KEY_CTRL_L 0x0c #define TB_KEY_ENTER 0x0d #define TB_KEY_CTRL_M 0x0d // clash with `ENTER` #define TB_KEY_CTRL_N 0x0e #define TB_KEY_CTRL_O 0x0f #define TB_KEY_CTRL_P 0x10 #define TB_KEY_CTRL_Q 0x11 #define TB_KEY_CTRL_R 0x12 #define TB_KEY_CTRL_S 0x13 #define TB_KEY_CTRL_T 0x14 #define TB_KEY_CTRL_U 0x15 #define TB_KEY_CTRL_V 0x16 #define TB_KEY_CTRL_W 0x17 #define TB_KEY_CTRL_X 0x18 #define TB_KEY_CTRL_Y 0x19 #define TB_KEY_CTRL_Z 0x1a #define TB_KEY_ESC 0x1b #define TB_KEY_CTRL_LSQ_BRACKET 0x1b // clash with 'ESC' #define TB_KEY_CTRL_3 0x1b // clash with 'ESC' #define TB_KEY_CTRL_4 0x1c #define TB_KEY_CTRL_BACKSLASH 0x1c // clash with 'CTRL_4' #define TB_KEY_CTRL_5 0x1d #define TB_KEY_CTRL_RSQ_BRACKET 0x1d // clash with 'CTRL_5' #define TB_KEY_CTRL_6 0x1e #define TB_KEY_CTRL_7 0x1f #define TB_KEY_CTRL_SLASH 0x1f // clash with 'CTRL_7' #define TB_KEY_CTRL_UNDERSCORE 0x1f // clash with 'CTRL_7' #define TB_KEY_SPACE 0x20 #define TB_KEY_BACKSPACE2 0x7f #define TB_KEY_CTRL_8 0x7f // clash with 'BACKSPACE2' #define tb_key_i(i) 0xffff - (i) /* Terminal-dependent key constants (`tb_event.key`) and terminfo caps */ /* BEGIN codegen h */ /* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:47 +0000 */ #define TB_KEY_F1 (0xffff - 0) #define TB_KEY_F2 (0xffff - 1) #define TB_KEY_F3 (0xffff - 2) #define TB_KEY_F4 (0xffff - 3) #define TB_KEY_F5 (0xffff - 4) #define TB_KEY_F6 (0xffff - 5) #define TB_KEY_F7 (0xffff - 6) #define TB_KEY_F8 (0xffff - 7) #define TB_KEY_F9 (0xffff - 8) #define TB_KEY_F10 (0xffff - 9) #define TB_KEY_F11 (0xffff - 10) #define TB_KEY_F12 (0xffff - 11) #define TB_KEY_INSERT (0xffff - 12) #define TB_KEY_DELETE (0xffff - 13) #define TB_KEY_HOME (0xffff - 14) #define TB_KEY_END (0xffff - 15) #define TB_KEY_PGUP (0xffff - 16) #define TB_KEY_PGDN (0xffff - 17) #define TB_KEY_ARROW_UP (0xffff - 18) #define TB_KEY_ARROW_DOWN (0xffff - 19) #define TB_KEY_ARROW_LEFT (0xffff - 20) #define TB_KEY_ARROW_RIGHT (0xffff - 21) #define TB_KEY_BACK_TAB (0xffff - 22) #define TB_KEY_MOUSE_LEFT (0xffff - 23) #define TB_KEY_MOUSE_RIGHT (0xffff - 24) #define TB_KEY_MOUSE_MIDDLE (0xffff - 25) #define TB_KEY_MOUSE_RELEASE (0xffff - 26) #define TB_KEY_MOUSE_WHEEL_UP (0xffff - 27) #define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28) #define TB_CAP_F1 0 #define TB_CAP_F2 1 #define TB_CAP_F3 2 #define TB_CAP_F4 3 #define TB_CAP_F5 4 #define TB_CAP_F6 5 #define TB_CAP_F7 6 #define TB_CAP_F8 7 #define TB_CAP_F9 8 #define TB_CAP_F10 9 #define TB_CAP_F11 10 #define TB_CAP_F12 11 #define TB_CAP_INSERT 12 #define TB_CAP_DELETE 13 #define TB_CAP_HOME 14 #define TB_CAP_END 15 #define TB_CAP_PGUP 16 #define TB_CAP_PGDN 17 #define TB_CAP_ARROW_UP 18 #define TB_CAP_ARROW_DOWN 19 #define TB_CAP_ARROW_LEFT 20 #define TB_CAP_ARROW_RIGHT 21 #define TB_CAP_BACK_TAB 22 #define TB_CAP__COUNT_KEYS 23 #define TB_CAP_ENTER_CA 23 #define TB_CAP_EXIT_CA 24 #define TB_CAP_SHOW_CURSOR 25 #define TB_CAP_HIDE_CURSOR 26 #define TB_CAP_CLEAR_SCREEN 27 #define TB_CAP_SGR0 28 #define TB_CAP_UNDERLINE 29 #define TB_CAP_BOLD 30 #define TB_CAP_BLINK 31 #define TB_CAP_ITALIC 32 #define TB_CAP_REVERSE 33 #define TB_CAP_ENTER_KEYPAD 34 #define TB_CAP_EXIT_KEYPAD 35 #define TB_CAP_DIM 36 #define TB_CAP_INVISIBLE 37 #define TB_CAP__COUNT 38 /* END codegen h */ /* Some hard-coded caps */ #define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" #define TB_HARDCAP_EXIT_MOUSE "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" #define TB_HARDCAP_STRIKEOUT "\x1b[9m" #define TB_HARDCAP_UNDERLINE_2 "\x1b[21m" #define TB_HARDCAP_OVERLINE "\x1b[53m" /* Colors (numeric) and attributes (bitwise) (`tb_cell.fg`, `tb_cell.bg`) */ #define TB_DEFAULT 0x0000 #define TB_BLACK 0x0001 #define TB_RED 0x0002 #define TB_GREEN 0x0003 #define TB_YELLOW 0x0004 #define TB_BLUE 0x0005 #define TB_MAGENTA 0x0006 #define TB_CYAN 0x0007 #define TB_WHITE 0x0008 #if TB_OPT_ATTR_W == 16 #define TB_BOLD 0x0100 #define TB_UNDERLINE 0x0200 #define TB_REVERSE 0x0400 #define TB_ITALIC 0x0800 #define TB_BLINK 0x1000 #define TB_HI_BLACK 0x2000 #define TB_BRIGHT 0x4000 #define TB_DIM 0x8000 #define TB_256_BLACK TB_HI_BLACK // `TB_256_BLACK` is deprecated #else // `TB_OPT_ATTR_W` is 32 or 64 #define TB_BOLD 0x01000000 #define TB_UNDERLINE 0x02000000 #define TB_REVERSE 0x04000000 #define TB_ITALIC 0x08000000 #define TB_BLINK 0x10000000 #define TB_HI_BLACK 0x20000000 #define TB_BRIGHT 0x40000000 #define TB_DIM 0x80000000 #define TB_TRUECOLOR_BOLD TB_BOLD // `TB_TRUECOLOR_*` is deprecated #define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE #define TB_TRUECOLOR_REVERSE TB_REVERSE #define TB_TRUECOLOR_ITALIC TB_ITALIC #define TB_TRUECOLOR_BLINK TB_BLINK #define TB_TRUECOLOR_BLACK TB_HI_BLACK #endif #if TB_OPT_ATTR_W == 64 #define TB_STRIKEOUT 0x0000000100000000 #define TB_UNDERLINE_2 0x0000000200000000 #define TB_OVERLINE 0x0000000400000000 #define TB_INVISIBLE 0x0000000800000000 #endif /* Event types (`tb_event.type`) */ #define TB_EVENT_KEY 1 #define TB_EVENT_RESIZE 2 #define TB_EVENT_MOUSE 3 /* Key modifiers (bitwise) (`tb_event.mod`) */ #define TB_MOD_ALT 1 #define TB_MOD_CTRL 2 #define TB_MOD_SHIFT 4 #define TB_MOD_MOTION 8 /* Input modes (bitwise) (`tb_set_input_mode`) */ #define TB_INPUT_CURRENT 0 #define TB_INPUT_ESC 1 #define TB_INPUT_ALT 2 #define TB_INPUT_MOUSE 4 /* Output modes (`tb_set_output_mode`) */ #define TB_OUTPUT_CURRENT 0 #define TB_OUTPUT_NORMAL 1 #define TB_OUTPUT_256 2 #define TB_OUTPUT_216 3 #define TB_OUTPUT_GRAYSCALE 4 #if TB_OPT_ATTR_W >= 32 #define TB_OUTPUT_TRUECOLOR 5 #endif /* Common function return values unless otherwise noted. * * Library behavior is undefined after receiving `TB_ERR_MEM`. Callers may * attempt reinitializing by freeing memory, invoking `tb_shutdown`, then * `tb_init`. */ #define TB_OK 0 #define TB_ERR -1 #define TB_ERR_NEED_MORE -2 #define TB_ERR_INIT_ALREADY -3 #define TB_ERR_INIT_OPEN -4 #define TB_ERR_MEM -5 #define TB_ERR_NO_EVENT -6 #define TB_ERR_NO_TERM -7 #define TB_ERR_NOT_INIT -8 #define TB_ERR_OUT_OF_BOUNDS -9 #define TB_ERR_READ -10 #define TB_ERR_RESIZE_IOCTL -11 #define TB_ERR_RESIZE_PIPE -12 #define TB_ERR_RESIZE_SIGACTION -13 #define TB_ERR_POLL -14 #define TB_ERR_TCGETATTR -15 #define TB_ERR_TCSETATTR -16 #define TB_ERR_UNSUPPORTED_TERM -17 #define TB_ERR_RESIZE_WRITE -18 #define TB_ERR_RESIZE_POLL -19 #define TB_ERR_RESIZE_READ -20 #define TB_ERR_RESIZE_SSCANF -21 #define TB_ERR_CAP_COLLISION -22 #define TB_ERR_SELECT TB_ERR_POLL #define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL /* Deprecated. Function types to be used with `tb_set_func`. */ #define TB_FUNC_EXTRACT_PRE 0 #define TB_FUNC_EXTRACT_POST 1 /* Define this to set the size of the buffer used in `tb_printf` * and `tb_sendf` */ #ifndef TB_OPT_PRINTF_BUF #define TB_OPT_PRINTF_BUF 4096 #endif /* Define this to set the size of the read buffer used when reading * from the tty */ #ifndef TB_OPT_READ_BUF #define TB_OPT_READ_BUF 64 #endif /* Define this for limited back compat with termbox v1 */ #ifdef TB_OPT_V1_COMPAT #define tb_change_cell tb_set_cell #define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg) #define tb_set_clear_attributes tb_set_clear_attrs #define tb_select_input_mode tb_set_input_mode #define tb_select_output_mode tb_set_output_mode #endif /* Define these to swap in a different allocator */ #ifndef tb_malloc #define tb_malloc malloc #define tb_realloc realloc #define tb_free free #endif #if TB_OPT_ATTR_W == 64 typedef uint64_t uintattr_t; #elif TB_OPT_ATTR_W == 32 typedef uint32_t uintattr_t; #else // 16 typedef uint16_t uintattr_t; #endif /* A cell in a 2d grid representing the terminal screen. * * The terminal screen is represented as 2d array of cells. The structure is * optimized for dealing with single-width (`wcwidth==1`) Unicode codepoints, * however some support for grapheme clusters (e.g., combining diacritical * marks) and wide codepoints (e.g., Hiragana) is provided through `ech`, * `nech`, and `cech` via `tb_set_cell_ex`. `ech` is only valid when `nech>0`, * otherwise `ch` is used. * * For non-single-width codepoints, given `N=wcwidth(ch)/wcswidth(ech)`: * * when `N==0`: termbox forces a single-width cell. Callers should avoid this * if aiming to render text accurately. Callers may use * `tb_set_cell_ex` or `tb_print*` to render `N==0` combining * characters. * * when `N>1`: termbox zeroes out the following `N-1` cells and skips sending * them to the tty. So, e.g., if the caller sets `x=0,y=0` to an * `N==2` codepoint, the caller's next set should be at `x=2,y=0`. * Anything set at `x=1,y=0` will be ignored. If there are not * enough columns remaining on the line to render `N` width, spaces * are sent instead. * * See `tb_present` for implementation. */ struct tb_cell { uint32_t ch; // a Unicode codepoint uintattr_t fg; // bitwise foreground attributes uintattr_t bg; // bitwise background attributes #ifdef TB_OPT_EGC uint32_t *ech; // a grapheme cluster of Unicode codepoints, 0-terminated size_t nech; // num elements in ech, 0 means use ch instead of ech size_t cech; // num elements allocated for ech #endif }; /* An incoming event from the tty. * * Given the event type, the following fields are relevant: * * when `TB_EVENT_KEY`: `key` xor `ch` (one will be zero) and `mod`. Note * there is overlap between `TB_MOD_CTRL` and * `TB_KEY_CTRL_*`. `TB_MOD_CTRL` and `TB_MOD_SHIFT` are * only set as modifiers to `TB_KEY_ARROW_*`. * * when `TB_EVENT_RESIZE`: `w` and `h` * * when `TB_EVENT_MOUSE`: `key` (`TB_KEY_MOUSE_*`), `x`, and `y` */ struct tb_event { uint8_t type; // one of `TB_EVENT_*` constants uint8_t mod; // bitwise `TB_MOD_*` constants uint16_t key; // one of `TB_KEY_*` constants uint32_t ch; // a Unicode codepoint int32_t w; // resize width int32_t h; // resize height int32_t x; // mouse x int32_t y; // mouse y }; /* Initialize the termbox library. This function should be called before any * other functions. `tb_init` is equivalent to `tb_init_file("/dev/tty")`. After * successful initialization, the library must be finalized using `tb_shutdown`. */ int tb_init(void); int tb_init_file(const char *path); int tb_init_fd(int ttyfd); int tb_init_rwfd(int rfd, int wfd); int tb_shutdown(void); int tb_get_input_fd(void); int tb_get_output_fd(void); /* Return the size of the internal back buffer (which is the same as terminal's * window size in rows and columns). The internal buffer can be resized after * `tb_clear` or `tb_present` calls. Both dimensions have an unspecified * negative value when called before `tb_init` or after `tb_shutdown`. */ /* Clear the internal back buffer using `TB_DEFAULT` or the attributes set by * `tb_set_clear_attrs`. */ int tb_clear(void); /* Synchronize the internal back buffer with the terminal by writing to tty. */ //int tb_present(void); /* Clear the internal front buffer effectively forcing a complete re-render of * the back buffer to the tty. It is not necessary to call this under normal * circumstances. */ int tb_invalidate(void); /* Set the position of the cursor. Upper-left cell is (0, 0). */ int tb_set_cursor(int cx, int cy); int tb_hide_cursor(void); /* Set cell contents in the internal back buffer at the specified position. * * Use `tb_set_cell_ex` for rendering grapheme clusters (e.g., combining * diacritical marks). * * Calling `tb_set_cell(x, y, ch, fg, bg)` is equivalent to * `tb_set_cell_ex(x, y, &ch, 1, fg, bg)`. * * `tb_extend_cell` is a shortcut for appending 1 codepoint to `tb_cell.ech`. * * Non-printable (`iswprint(3)`) codepoints are replaced with `U+FFFD` at render * time. */ int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, uintattr_t bg); int tb_extend_cell(int x, int y, uint32_t ch); /* Set the input mode. Termbox has two input modes: * * 1. `TB_INPUT_ESC` * When escape (`\x1b`) is in the buffer and there's no match for an escape * sequence, a key event for `TB_KEY_ESC` is returned. * * 2. `TB_INPUT_ALT` * When escape (`\x1b`) is in the buffer and there's no match for an escape * sequence, the next keyboard event is returned with a `TB_MOD_ALT` * modifier. * * You can also apply `TB_INPUT_MOUSE` via bitwise OR operation to either of the * modes (e.g., `TB_INPUT_ESC | TB_INPUT_MOUSE`) to receive `TB_EVENT_MOUSE` * events. If none of the main two modes were set, but the mouse mode was, * `TB_INPUT_ESC` is used. If for some reason you've decided to use * `TB_INPUT_ESC | TB_INPUT_ALT`, it will behave as if only `TB_INPUT_ESC` was * selected. * * If mode is `TB_INPUT_CURRENT`, return the current input mode. * * The default input mode is `TB_INPUT_ESC`. */ int tb_set_input_mode(int mode); /* Set the output mode. Termbox has multiple output modes: * * 1. `TB_OUTPUT_NORMAL` => [0..8] * * This mode provides 8 different colors: * `TB_BLACK`, `TB_RED`, `TB_GREEN`, `TB_YELLOW`, * `TB_BLUE`, `TB_MAGENTA`, `TB_CYAN`, `TB_WHITE` * * Plus `TB_DEFAULT` which skips sending a color code (i.e., uses the * terminal's default color). * * Colors (including `TB_DEFAULT`) may be bitwise OR'd with attributes: * `TB_BOLD`, `TB_UNDERLINE`, `TB_REVERSE`, `TB_ITALIC`, `TB_BLINK`, * `TB_BRIGHT`, `TB_DIM` * * The following style attributes are also available if compiled with * `TB_OPT_ATTR_W` set to 64: * `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE` * * As in all modes, the value 0 is interpreted as `TB_DEFAULT` for * convenience. * * Some notes: `TB_REVERSE` and `TB_BRIGHT` can be applied as either `fg` or * `bg` attributes for the same effect. The rest of the attributes apply to * `fg` only and are ignored as `bg` attributes. * * Example usage: `tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED)` * * 2. `TB_OUTPUT_256` => [0..255] + `TB_HI_BLACK` * * In this mode you get 256 distinct colors (plus default): * 0x00 (1): `TB_DEFAULT` * `TB_HI_BLACK` (1): `TB_BLACK` in `TB_OUTPUT_NORMAL` * 0x01..0x07 (7): the next 7 colors as in `TB_OUTPUT_NORMAL` * 0x08..0x0f (8): bright versions of the above * 0x10..0xe7 (216): 216 different colors * 0xe8..0xff (24): 24 different shades of gray * * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in * `TB_OUTPUT_NORMAL`. * * Note `TB_HI_BLACK` must be used for black, as 0x00 represents default. * * 3. `TB_OUTPUT_216` => [0..216] * * This mode supports the 216-color range of `TB_OUTPUT_256` only, but you * don't need to provide an offset: * 0x00 (1): `TB_DEFAULT` * 0x01..0xd8 (216): 216 different colors * * 4. `TB_OUTPUT_GRAYSCALE` => [0..24] * * This mode supports the 24-color range of `TB_OUTPUT_256` only, but you * don't need to provide an offset: * 0x00 (1): `TB_DEFAULT` * 0x01..0x18 (24): 24 different shades of gray * * 5. `TB_OUTPUT_TRUECOLOR` => [0x000000..0xffffff] + `TB_HI_BLACK` * * This mode provides 24-bit color on supported terminals. The format is * 0xRRGGBB. * * All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in * `TB_OUTPUT_NORMAL`. * * Note `TB_HI_BLACK` must be used for black, as 0x000000 represents default. * * To use the terminal default color (i.e., to not send an escape code), pass * `TB_DEFAULT`. For convenience, the value 0 is interpreted as `TB_DEFAULT` in * all modes. * * Note, cell attributes persist after switching output modes. Any translation * between, for example, `TB_OUTPUT_NORMAL`'s `TB_RED` and * `TB_OUTPUT_TRUECOLOR`'s 0xff0000 must be performed by the caller. Also note * that cells previously rendered in one mode may persist unchanged until the * front buffer is cleared (such as after a resize event) at which point it will * be re-interpreted and flushed according to the current mode. Callers may * invoke `tb_invalidate` if it is desirable to immediately re-interpret and * flush the entire screen according to the current mode. * * Note, not all terminals support all output modes, especially beyond * `TB_OUTPUT_NORMAL`. There is also no very reliable way to determine color * support dynamically. If portability is desired, callers are recommended to * use `TB_OUTPUT_NORMAL` or make output mode end-user configurable. The same * advice applies to style attributes. * * If mode is `TB_OUTPUT_CURRENT`, return the current output mode. * * The default output mode is `TB_OUTPUT_NORMAL`. */ int tb_set_output_mode(int mode); /* Wait for an event up to `timeout_ms` milliseconds and populate `event` with * it. If no event is available within the timeout period, `TB_ERR_NO_EVENT` * is returned. On a resize event, the underlying `select(2)` call may be * interrupted, yielding a return code of `TB_ERR_POLL`. In this case, you may * check `errno` via `tb_last_errno`. If it's `EINTR`, you may elect to ignore * that and call `tb_peek_event` again. */ int tb_peek_event(struct tb_event *event, int timeout_ms); /* Same as `tb_peek_event` except no timeout. */ int tb_poll_event(struct tb_event *event); /* Internal termbox fds that can be used with `poll(2)`, `select(2)`, etc. * externally. Callers must invoke `tb_poll_event` or `tb_peek_event` if * fds become readable. */ int tb_get_fds(int *ttyfd, int *resizefd); /* Print and printf functions. Specify param `out_w` to determine width of * printed string. Strings are interpreted as UTF-8. * * Non-printable characters (`iswprint(3)`) and truncated UTF-8 byte sequences * are replaced with U+FFFD. * * Newlines (`\n`) are supported with the caveat that `out_w` will return the * width of the string as if it were on a single line. * * If the starting coordinate is out of bounds, `TB_ERR_OUT_OF_BOUNDS` is * returned. If the starting coordinate is in bounds, but goes out of bounds, * then the out-of-bounds portions of the string are ignored. * * For finer control, use `tb_set_cell`. */ int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *str); int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *fmt, ...); /* Send raw bytes to terminal. */ int tb_send(const char *buf, size_t nbuf); int tb_sendf(const char *fmt, ...); /* Deprecated. Set custom callbacks. `fn_type` is one of `TB_FUNC_*` constants, * `fn` is a compatible function pointer, or NULL to clear. * * `TB_FUNC_EXTRACT_PRE`: * If specified, invoke this function BEFORE termbox tries to extract any * escape sequences from the input buffer. * * `TB_FUNC_EXTRACT_POST`: * If specified, invoke this function AFTER termbox tries (and fails) to * extract any escape sequences from the input buffer. */ int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); /* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */ int tb_utf8_char_length(char c); /* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint. * * If `c` is an empty C string, return 0. `out` is left unchanged. * * If a null byte is encountered in the middle of the codepoint, return a * negative number indicating how many bytes were processed. `out` is left * unchanged. * * Otherwise, return byte length of codepoint (1-6). */ int tb_utf8_char_to_unicode(uint32_t *out, const char *c); /* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence. * * `out` must be char[7] or greater. Return byte length of codepoint (1-6). */ int tb_utf8_unicode_to_char(char *out, uint32_t c); /* Library utility functions */ int tb_last_errno(void); const char *tb_strerror(int err); struct tb_cell *tb_cell_buffer(void); // Deprecated int tb_has_truecolor(void); int tb_has_egc(void); int tb_attr_width(void); const char *tb_version(void); /* Deprecation notice! * * The following will be removed in version 3.x (ABI version 3): * * TB_256_BLACK (use TB_HI_BLACK) * TB_OPT_TRUECOLOR (use TB_OPT_ATTR_W) * TB_TRUECOLOR_BOLD (use TB_BOLD) * TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE) * TB_TRUECOLOR_REVERSE (use TB_REVERSE) * TB_TRUECOLOR_ITALIC (use TB_ITALICe) * TB_TRUECOLOR_BLINK (use TB_BLINK) * TB_TRUECOLOR_BLACK (use TB_HI_BLACK) * tb_cell_buffer * tb_set_func * TB_FUNC_EXTRACT_PRE * TB_FUNC_EXTRACT_POST */ #ifdef __cplusplus } #endif #endif // TERMBOX_H_INCL #ifdef TB_IMPL #define if_err_return(rv, expr) \ if (((rv) = (expr)) != TB_OK) \ return (rv) #define if_err_break(rv, expr) \ if (((rv) = (expr)) != TB_OK) \ break #define if_ok_return(rv, expr) \ if (((rv) = (expr)) == TB_OK) \ return (rv) #define if_ok_or_need_more_return(rv, expr) \ if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) \ return (rv) #define if_not_init_return() \ if (!global.initialized) \ return TB_ERR_NOT_INIT #define snprintf_or_return(rv, str, sz, fmt, ...) \ do { \ (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__); \ if ((rv) < 0 || (rv) >= (int)(sz)) \ return TB_ERR; \ } while (0) struct bytebuf_t { char *buf; size_t len; size_t cap; }; struct cellbuf_t { int width; int height; struct tb_cell *cells; }; struct cap_trie_t { char c; struct cap_trie_t *children; size_t nchildren; int is_leaf; uint16_t key; uint8_t mod; }; struct tb_global_t { int ttyfd; int rfd; int wfd; int ttyfd_open; int resize_pipefd[2]; int width; int height; int cursor_x; int cursor_y; int last_x; int last_y; uintattr_t fg; uintattr_t bg; uintattr_t last_fg; uintattr_t last_bg; int input_mode; int output_mode; char *terminfo; size_t nterminfo; const char *caps[TB_CAP__COUNT]; struct cap_trie_t cap_trie; struct bytebuf_t in; struct bytebuf_t out; struct cellbuf_t back; struct cellbuf_t front; struct termios orig_tios; int has_orig_tios; int last_errno; int initialized; int (*fn_extract_esc_pre)(struct tb_event *, size_t *); int (*fn_extract_esc_post)(struct tb_event *, size_t *); int esc_pending; char errbuf[1024]; }; static struct tb_global_t global = {0}; /* BEGIN codegen c */ /* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:48 +0000 */ static const int16_t terminfo_cap_indexes[] = { 66, // kf1 (TB_CAP_F1) 68, // kf2 (TB_CAP_F2) 69, // kf3 (TB_CAP_F3) 70, // kf4 (TB_CAP_F4) 71, // kf5 (TB_CAP_F5) 72, // kf6 (TB_CAP_F6) 73, // kf7 (TB_CAP_F7) 74, // kf8 (TB_CAP_F8) 75, // kf9 (TB_CAP_F9) 67, // kf10 (TB_CAP_F10) 216, // kf11 (TB_CAP_F11) 217, // kf12 (TB_CAP_F12) 77, // kich1 (TB_CAP_INSERT) 59, // kdch1 (TB_CAP_DELETE) 76, // khome (TB_CAP_HOME) 164, // kend (TB_CAP_END) 82, // kpp (TB_CAP_PGUP) 81, // knp (TB_CAP_PGDN) 87, // kcuu1 (TB_CAP_ARROW_UP) 61, // kcud1 (TB_CAP_ARROW_DOWN) 79, // kcub1 (TB_CAP_ARROW_LEFT) 83, // kcuf1 (TB_CAP_ARROW_RIGHT) 148, // kcbt (TB_CAP_BACK_TAB) 28, // smcup (TB_CAP_ENTER_CA) 40, // rmcup (TB_CAP_EXIT_CA) 16, // cnorm (TB_CAP_SHOW_CURSOR) 13, // civis (TB_CAP_HIDE_CURSOR) 5, // clear (TB_CAP_CLEAR_SCREEN) 39, // sgr0 (TB_CAP_SGR0) 36, // smul (TB_CAP_UNDERLINE) 27, // bold (TB_CAP_BOLD) 26, // blink (TB_CAP_BLINK) 311, // sitm (TB_CAP_ITALIC) 34, // rev (TB_CAP_REVERSE) 89, // smkx (TB_CAP_ENTER_KEYPAD) 88, // rmkx (TB_CAP_EXIT_KEYPAD) 30, // dim (TB_CAP_DIM) 32, // invis (TB_CAP_INVISIBLE) }; // xterm static const char *xterm_caps[] = { "\033OP", // kf1 (TB_CAP_F1) "\033OQ", // kf2 (TB_CAP_F2) "\033OR", // kf3 (TB_CAP_F3) "\033OS", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033OH", // khome (TB_CAP_HOME) "\033OF", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033OA", // kcuu1 (TB_CAP_ARROW_UP) "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA) "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA) "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033(B\033[m", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "\033[3m", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) "\033[2m", // dim (TB_CAP_DIM) "\033[8m", // invis (TB_CAP_INVISIBLE) }; // linux static const char *linux_caps[] = { "\033[[A", // kf1 (TB_CAP_F1) "\033[[B", // kf2 (TB_CAP_F2) "\033[[C", // kf3 (TB_CAP_F3) "\033[[D", // kf4 (TB_CAP_F4) "\033[[E", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[1~", // khome (TB_CAP_HOME) "\033[4~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033\011", // kcbt (TB_CAP_BACK_TAB) "", // smcup (TB_CAP_ENTER_CA) "", // rmcup (TB_CAP_EXIT_CA) "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "", // smkx (TB_CAP_ENTER_KEYPAD) "", // rmkx (TB_CAP_EXIT_KEYPAD) "\033[2m", // dim (TB_CAP_DIM) "", // invis (TB_CAP_INVISIBLE) }; // screen static const char *screen_caps[] = { "\033OP", // kf1 (TB_CAP_F1) "\033OQ", // kf2 (TB_CAP_F2) "\033OR", // kf3 (TB_CAP_F3) "\033OS", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[1~", // khome (TB_CAP_HOME) "\033[4~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033OA", // kcuu1 (TB_CAP_ARROW_UP) "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h", // smcup (TB_CAP_ENTER_CA) "\033[?1049l", // rmcup (TB_CAP_EXIT_CA) "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) "\033[2m", // dim (TB_CAP_DIM) "", // invis (TB_CAP_INVISIBLE) }; // rxvt-256color static const char *rxvt_256color_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) "", // dim (TB_CAP_DIM) "", // invis (TB_CAP_INVISIBLE) }; // rxvt-unicode static const char *rxvt_unicode_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h", // smcup (TB_CAP_ENTER_CA) "\033[r\033[?1049l", // rmcup (TB_CAP_EXIT_CA) "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\033(B", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "\033[3m", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) "", // dim (TB_CAP_DIM) "", // invis (TB_CAP_INVISIBLE) }; // Eterm static const char *eterm_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "", // kcbt (TB_CAP_BACK_TAB) "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "", // smkx (TB_CAP_ENTER_KEYPAD) "", // rmkx (TB_CAP_EXIT_KEYPAD) "", // dim (TB_CAP_DIM) "", // invis (TB_CAP_INVISIBLE) }; static struct { const char *name; const char **caps; const char *alias; } builtin_terms[] = { {"xterm", xterm_caps, ""}, {"xterm-256color", xterm_caps, ""}, {"linux", linux_caps, ""}, {"screen", screen_caps, "tmux"}, {"rxvt-256color", rxvt_256color_caps, ""}, {"rxvt-unicode", rxvt_unicode_caps, "rxvt"}, {"Eterm", eterm_caps, ""}, {NULL, NULL, NULL}, }; /* END codegen c */ static struct { const char *cap; const uint16_t key; const uint8_t mod; } builtin_mod_caps[] = { // xterm arrows {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT}, {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT}, {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL}, {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT}, {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT}, {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL}, {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT}, {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT}, {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL}, {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT}, {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT}, {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL}, {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, // xterm keys {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT}, {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT}, {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL}, {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT}, {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT}, {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL}, {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT}, {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT}, {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL}, {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT}, {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT}, {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL}, {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT}, {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT}, {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL}, {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT}, {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT}, {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL}, {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT}, {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT}, {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL}, {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT}, {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT}, {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL}, {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT}, {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT}, {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL}, {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT}, {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT}, {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL}, {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[1;8S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT}, {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT}, {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL}, {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[15;8~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT}, {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT}, {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL}, {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[17;8~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT}, {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT}, {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL}, {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[18;8~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT}, {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT}, {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL}, {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[19;8~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT}, {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT}, {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL}, {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[20;8~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT}, {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT}, {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL}, {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[21;8~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT}, {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT}, {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL}, {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[23;8~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT}, {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT}, {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL}, {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[24;8~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, // rxvt arrows {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT}, {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT}, {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL}, {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT}, {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT}, {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL}, {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT}, {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT}, {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL}, {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT}, {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT}, {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL}, {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT}, // rxvt keys {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT}, {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT}, {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL}, {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT}, {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL}, {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT}, {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT}, {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL}, {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT}, {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT}, {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL}, {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT}, {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT}, {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL}, {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT}, {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT}, {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL}, {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT}, {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT}, {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL}, {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT}, {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT}, {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL}, {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT}, {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT}, {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL}, {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT}, {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT}, {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL}, {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT}, {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT}, {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL}, {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT}, {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT}, {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL}, {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT}, {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT}, {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL}, {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT}, {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT}, {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL}, {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT}, {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT}, {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL}, {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT}, {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT}, {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL}, {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT}, {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT}, {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL}, {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT}, {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT}, {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL}, {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT}, {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT}, // linux console/putty arrows {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT}, {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT}, {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT}, {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT}, // more putty arrows {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL}, {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL}, {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL}, {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT}, {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL}, {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT}, {NULL, 0, 0}, }; static const unsigned char utf8_length[256] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1}; static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01}; static int tb_reset(void); static int init_term_caps(void); static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod); static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, size_t *depth); static int cap_trie_deinit(struct cap_trie_t *node); static int parse_terminfo_caps(void); static int tb_deinit(void); static int wait_event(struct tb_event *event, int timeout); static int extract_event(struct tb_event *event); static int extract_esc(struct tb_event *event); static int extract_esc_user(struct tb_event *event, int is_post); static int load_builtin_caps(void); static int init_cap_trie(void); static int update_term_size(void); static int update_term_size_via_esc(void); static int extract_esc_mouse(struct tb_event *event); static int load_terminfo(void); static int load_terminfo_from_path(const char *path, const char *term); static int read_terminfo_path(const char *path); static int cell_free(struct tb_cell *cell); static int cellbuf_free(struct cellbuf_t *c); static int bytebuf_puts(struct bytebuf_t *b, const char *str); static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr); static int bytebuf_shift(struct bytebuf_t *b, size_t n); static int bytebuf_flush(struct bytebuf_t *b, int fd); static int bytebuf_reserve(struct bytebuf_t *b, size_t sz); static int bytebuf_free(struct bytebuf_t *b); static const char *get_terminfo_string(int16_t str_offsets_pos, int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len, int16_t str_index); int tb_init(void) { return tb_init_file("/dev/tty"); } int tb_init_file(const char *path) { if (global.initialized) return TB_ERR_INIT_ALREADY; int ttyfd = open(path, O_RDWR); if (ttyfd < 0) { // Fallback: use stdin/stdout if /dev/tty fails ttyfd = STDIN_FILENO; if (!isatty(ttyfd)) { global.last_errno = errno; return TB_ERR_INIT_OPEN; } global.ttyfd_open = 0; // we didn’t open a new fd } else { global.ttyfd_open = 1; } global.input_mode &= TB_INPUT_ESC; return tb_init_fd(ttyfd); } int tb_shutdown(void) { if_not_init_return(); tb_deinit(); return TB_OK; } int tb_init_fd(int ttyfd) { return tb_init_rwfd(ttyfd, ttyfd); } int tb_init_rwfd(int rfd, int wfd) { int rv; tb_reset(); global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1; global.rfd = rfd; global.wfd = wfd; do { // Skip termios setup; we manage raw/nonblocking mode ourselves // if_err_break(rv, init_term_attrs()); if_err_break(rv, init_term_caps()); // load key escape sequences if_err_break(rv, init_cap_trie()); // map escape sequences // Skip resize handler // if_err_break(rv, init_resize_handler()); if_err_break(rv, update_term_size()); // still needed for mouse + resize global.initialized = 1; } while (0); if (rv != TB_OK) { tb_deinit(); } return rv; } int tb_get_input_fd(void) { return global.rfd; } int tb_get_output_fd(void) { return global.wfd; } static int load_builtin_caps(void) { int i, j; const char *term = getenv("TERM"); if (!term) { return TB_ERR_NO_TERM; } // Check for exact TERM match for (i = 0; builtin_terms[i].name != NULL; i++) { if (strcmp(term, builtin_terms[i].name) == 0) { for (j = 0; j < TB_CAP__COUNT; j++) { global.caps[j] = builtin_terms[i].caps[j]; } return TB_OK; } } // Check for partial TERM or alias match for (i = 0; builtin_terms[i].name != NULL; i++) { if (strstr(term, builtin_terms[i].name) != NULL || (*(builtin_terms[i].alias) != '\0' && strstr(term, builtin_terms[i].alias) != NULL)) { for (j = 0; j < TB_CAP__COUNT; j++) { global.caps[j] = builtin_terms[i].caps[j]; } return TB_OK; } } return TB_ERR_UNSUPPORTED_TERM; } static int tb_deinit(void) { // Remove all cursor / screen reset / mouse / SGR codes // Remove SIGWINCH handler // Free the data buffers cellbuf_free(&global.back); cellbuf_free(&global.front); bytebuf_free(&global.in); bytebuf_free(&global.out); if (global.terminfo) tb_free(global.terminfo); cap_trie_deinit(&global.cap_trie); tb_reset(); // reset global struct return TB_OK; } int tb_set_input_mode(int mode) { if (!global.initialized) return -1; if (mode == TB_INPUT_CURRENT) return global.input_mode; global.input_mode = mode; return 0; } int tb_peek_event(struct tb_event *event, int timeout_ms) { if (!global.initialized) return TB_ERR_NOT_INIT; return wait_event(event, timeout_ms); } int tb_poll_event(struct tb_event *event) { if (!global.initialized) return TB_ERR_NOT_INIT; return wait_event(event, -1); } static int tb_reset(void) { int ttyfd_open = global.ttyfd_open; memset(&global, 0, sizeof(global)); global.ttyfd = -1; global.rfd = -1; global.wfd = -1; global.ttyfd_open = ttyfd_open; global.resize_pipefd[0] = -1; global.resize_pipefd[1] = -1; global.width = -1; global.height = -1; global.cursor_x = -1; global.cursor_y = -1; global.last_x = -1; global.last_y = -1; global.fg = TB_DEFAULT; global.bg = TB_DEFAULT; global.last_fg = ~global.fg; global.last_bg = ~global.bg; global.input_mode &= TB_INPUT_ESC; global.output_mode = TB_OUTPUT_NORMAL; return TB_OK; } static int bytebuf_flush(struct bytebuf_t *b, int fd) { if (b->len <= 0) { return TB_OK; } ssize_t write_rv = write(fd, b->buf, b->len); if (write_rv < 0 || (size_t)write_rv != b->len) { // Note, errno will be 0 on partial write global.last_errno = errno; return TB_ERR; } b->len = 0; return TB_OK; } static int bytebuf_free(struct bytebuf_t *b) { if (b->buf) { tb_free(b->buf); } memset(b, 0, sizeof(*b)); return TB_OK; } static int cap_trie_deinit(struct cap_trie_t *node) { size_t j; for (j = 0; j < node->nchildren; j++) { cap_trie_deinit(&node->children[j]); } if (node->children) { tb_free(node->children); } memset(node, 0, sizeof(*node)); return TB_OK; } static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) { struct cap_trie_t *next, *node = &global.cap_trie; size_t i, j; if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps for (i = 0; cap[i] != '\0'; i++) { char c = cap[i]; next = NULL; // Check if c is already a child of node for (j = 0; j < node->nchildren; j++) { if (node->children[j].c == c) { next = &node->children[j]; break; } } if (!next) { // We need to add a new child to node node->nchildren += 1; node->children = (struct cap_trie_t *)tb_realloc(node->children, sizeof(*node) * node->nchildren); if (!node->children) { return TB_ERR_MEM; } next = &node->children[node->nchildren - 1]; memset(next, 0, sizeof(*next)); next->c = c; } // Continue node = next; } if (node->is_leaf) { // Already a leaf here return TB_ERR_CAP_COLLISION; } node->is_leaf = 1; node->key = key; node->mod = mod; return TB_OK; } static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) { if (b->cap >= sz) { return TB_OK; } size_t newcap = b->cap > 0 ? b->cap : 1; while (newcap < sz) { newcap *= 2; } char *newbuf; if (b->buf) { newbuf = (char *)tb_realloc(b->buf, newcap); } else { newbuf = (char *)tb_malloc(newcap); } if (!newbuf) { return TB_ERR_MEM; } b->buf = newbuf; b->cap = newcap; return TB_OK; } int tb_utf8_char_length(char c) { return utf8_length[(unsigned char)c]; } int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { if (*c == '\0') return 0; int i; unsigned char len = tb_utf8_char_length(*c); unsigned char mask = utf8_mask[len - 1]; uint32_t result = c[0] & mask; for (i = 1; i < len && c[i] != '\0'; ++i) { result <<= 6; result |= c[i] & 0x3f; } if (i != len) return i * -1; *out = result; return (int)len; } int tb_utf8_unicode_to_char(char *out, uint32_t c) { int len = 0; int first; if (c < 0x80) { first = 0; len = 1; } else if (c < 0x800) { first = 0xc0; len = 2; } else if (c < 0x10000) { first = 0xe0; len = 3; } else if (c < 0x200000) { first = 0xf0; len = 4; } else if (c < 0x4000000) { first = 0xf8; len = 5; } else { first = 0xfc; len = 6; } for (int i = len - 1; i > 0; --i) { out[i] = (c & 0x3f) | 0x80; c >>= 6; } out[0] = c | first; out[len] = '\0'; return len; } // ----------------------------------------------------------------------------- // Core event reading // ----------------------------------------------------------------------------- static int wait_event(struct tb_event *event, int timeout_ms) { int rv; char buf[TB_OPT_READ_BUF]; memset(event, 0, sizeof(*event)); // Try to extract an event from the existing buffer first rv = extract_event(event); if (rv == TB_OK) return TB_OK; // Setup select timeout fd_set fds; struct timeval tv; struct timeval *ptv = NULL; if (timeout_ms >= 0) { tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; ptv = &tv; } while (1) { FD_ZERO(&fds); FD_SET(global.rfd, &fds); int maxfd = global.rfd; int sel = select(maxfd + 1, &fds, NULL, NULL, ptv); if (sel < 0) { if (errno == EINTR) continue; global.last_errno = errno; return TB_ERR_POLL; } if (sel == 0) return TB_ERR_NO_EVENT; // Read terminal input if ready if (FD_ISSET(global.rfd, &fds)) { ssize_t n = read(global.rfd, buf, sizeof(buf)); if (n < 0) { global.last_errno = errno; return TB_ERR_READ; } if (n > 0) { bytebuf_nputs(&global.in, buf, n); } } // Extract an event from buffer rv = extract_event(event); if (rv == TB_OK) return TB_OK; // If non-blocking and no event, return if (timeout_ms >= 0) return TB_ERR_NO_EVENT; } } static int extract_event(struct tb_event *event) { struct bytebuf_t *in = &global.in; if (in->len == 0) { // Handle pending ESC from previous incomplete sequence if (global.esc_pending) { event->type = TB_EVENT_KEY; event->key = TB_KEY_ESC; event->ch = 0; event->mod = 0; global.esc_pending = 0; return TB_OK; } return TB_ERR; } if (in->buf[0] == '\n') in->buf[0] = '\r'; // Handle ESC sequences if (in->buf[0] == '\x1b') { // Try to parse full escape sequence int rv = extract_esc(event); if (rv == TB_OK) { global.esc_pending = 0; return TB_OK; } // If not recognized, treat as ESC key if ((global.input_mode & TB_INPUT_ESC) && rv == TB_ERR_NEED_MORE) { event->type = TB_EVENT_KEY; event->key = TB_KEY_ESC; event->ch = 0; event->mod = 0; bytebuf_shift(in, 1); return TB_OK; } // Possibly Alt + key if more bytes follow if (in->len > 1) { event->mod |= TB_MOD_ALT; bytebuf_shift(in, 1); return extract_event(event); } if (in->len >= 6 && in->buf[0] == '\x1b' && in->buf[1] == '[' && in->buf[2] == '<') { int btn, x, y; char type; if (sscanf(in->buf, "\x1b[<%d;%d;%d%c", &btn, &x, &y, &type) == 4) { event->type = TB_EVENT_MOUSE; event->key = btn; // button code event->x = x - 1; // terminal coordinates are 1-based event->y = y - 1; event->mod = 0; // parse modifiers if needed bytebuf_shift(in, strlen((char *)in->buf)); // shift consumed bytes return TB_OK; } } return TB_ERR; } if (in->buf[0] == ' ' || in->buf[0] == TB_KEY_SPACE) { event->type = TB_EVENT_KEY; event->key = TB_KEY_SPACE; event->ch = 0; event->mod = 0; bytebuf_shift(in, 1); return TB_OK; } // Backspace if (in->buf[0] == 0x7f || in->buf[0] == TB_KEY_BACKSPACE2) { event->type = TB_EVENT_KEY; event->key = TB_KEY_BACKSPACE; event->ch = 0; event->mod = 0; bytebuf_shift(in, 1); return TB_OK; } // Enter \n if (((uint8_t)in->buf[0] == '\n' || (uint8_t)in->buf[0] == '\r') && event->mod == 0) { event->type = TB_EVENT_KEY; event->key = TB_KEY_ENTER; event->ch = 0; event->mod = 0; bytebuf_shift(in, 1); return TB_OK; } // Control keys (Ctrl + key) uint8_t c = in->buf[0]; if (c < 0x20) { event->type = TB_EVENT_KEY; event->key = c; event->ch = 0; // Only set CTRL if it's not an Alt combo and not a special key like Enter/Tab/ESC if (!(event->mod & TB_MOD_ALT) && c != '\t' && c != '\n' && c != '\r' && c != '\033') { event->mod |= TB_MOD_CTRL; } bytebuf_shift(in, 1); return TB_OK; } // UTF-8 printable keys int len = tb_utf8_char_length(in->buf[0]); if ((size_t)len <= in->len) { int ulen = tb_utf8_char_to_unicode(&event->ch, in->buf); if (ulen < 0) { bytebuf_shift(in, 1); return TB_ERR; } event->type = TB_EVENT_KEY; event->key = 0; bytebuf_shift(in, ulen); return TB_OK; } return TB_ERR; } static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, size_t *depth) { struct cap_trie_t *next, *node = &global.cap_trie; size_t i, j; *last = node; *depth = 0; for (i = 0; i < nbuf; i++) { char c = buf[i]; next = NULL; // Find c in node.children for (j = 0; j < node->nchildren; j++) { if (node->children[j].c == c) { next = &node->children[j]; break; } } if (!next) { // Not found return TB_OK; } node = next; *last = node; *depth += 1; if (node->is_leaf && node->nchildren < 1) { break; } } return TB_OK; } static int init_term_caps(void) { cap_trie_add("\033[A", TB_KEY_ARROW_UP, 0); cap_trie_add("\033[B", TB_KEY_ARROW_DOWN, 0); cap_trie_add("\033[C", TB_KEY_ARROW_RIGHT, 0); cap_trie_add("\033[D", TB_KEY_ARROW_LEFT, 0); cap_trie_add("\033[Z", TB_KEY_TAB, TB_MOD_SHIFT); // shift+tab cap_trie_add("\033\t", TB_KEY_TAB, TB_MOD_SHIFT); cap_trie_add("\033[H", TB_KEY_HOME, 0); cap_trie_add("\033[F", TB_KEY_END, 0); cap_trie_add("\033[2~", TB_KEY_INSERT, 0); cap_trie_add("\033[3~", TB_KEY_DELETE, 0); cap_trie_add("\033[5~", TB_KEY_PGUP, 0); cap_trie_add("\033[6~", TB_KEY_PGDN, 0); cap_trie_add("\033OP", TB_KEY_F1, 0); // xterm cap_trie_add("\033OQ", TB_KEY_F2, 0); cap_trie_add("\033OR", TB_KEY_F3, 0); cap_trie_add("\033OS", TB_KEY_F4, 0); cap_trie_add("\033[[A", TB_KEY_F1, 0); // linux cap_trie_add("\033[[B", TB_KEY_F2, 0); cap_trie_add("\033[[C", TB_KEY_F3, 0); cap_trie_add("\033[[D", TB_KEY_F4, 0); cap_trie_add("\033[[E", TB_KEY_F5, 0); cap_trie_add("\033[11~", TB_KEY_F1, 0); // rxvt / urxvt cap_trie_add("\033[12~", TB_KEY_F2, 0); cap_trie_add("\033[13~", TB_KEY_F3, 0); cap_trie_add("\033[14~", TB_KEY_F4, 0); cap_trie_add("\033[15~", TB_KEY_F5, 0); cap_trie_add("\033[17~", TB_KEY_F6, 0); cap_trie_add("\033[18~", TB_KEY_F7, 0); cap_trie_add("\033[19~", TB_KEY_F8, 0); cap_trie_add("\033[20~", TB_KEY_F9, 0); cap_trie_add("\033[21~", TB_KEY_F10, 0); cap_trie_add("\033[23~", TB_KEY_F11, 0); cap_trie_add("\033[24~", TB_KEY_F12, 0); // enable mouse click + SGR reporting bytebuf_puts(&global.out, "\033[?1002h"); // button tracking (press+release+drag) bytebuf_puts(&global.out, "\033[?1006h"); // SGR mode bytebuf_puts(&global.out, "\033[?1000l"); // turn OFF X10 (buggy on many terms) bytebuf_flush(&global.out, global.wfd); if (load_terminfo() == TB_OK) { return parse_terminfo_caps(); } return load_builtin_caps(); } static int init_cap_trie(void) { int rv, i; // Add caps from terminfo or built-in // // Collisions are expected as some terminfo entries have dupes. (For // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap // in TB_CAP_* index order will win. // // TODO: Reorder TB_CAP_* so more critical caps come first. for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { rv = cap_trie_add(global.caps[i], tb_key_i(i), 0); if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; } // Add built-in mod caps // // Collisions are OK here as well. This can happen if global.caps collides // with builtin_mod_caps. It is desirable to give precedence to global.caps // here. for (i = 0; builtin_mod_caps[i].cap != NULL; i++) { rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key, builtin_mod_caps[i].mod); if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; } return TB_OK; } static int update_term_size(void) { int rv, ioctl_errno; if (global.ttyfd < 0) { return TB_OK; } struct winsize sz; memset(&sz, 0, sizeof(sz)); // Try ioctl TIOCGWINSZ if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) { global.width = sz.ws_col; global.height = sz.ws_row; return TB_OK; } ioctl_errno = errno; // Try >cursor(9999,9999), >u7, b->len) { n = b->len; } size_t nmove = b->len - n; memmove(b->buf, b->buf + n, nmove); b->len -= n; return TB_OK; } static int bytebuf_puts(struct bytebuf_t *b, const char *str) { if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps return bytebuf_nputs(b, str, (size_t)strlen(str)); } static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) { int rv; if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1)); memcpy(b->buf + b->len, str, nstr); b->len += nstr; b->buf[b->len] = '\0'; return TB_OK; } static int extract_esc_cap(struct tb_event *event) { int rv; struct bytebuf_t *in = &global.in; struct cap_trie_t *node; size_t depth; if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth)); if (node->is_leaf) { // Found a leaf node event->type = TB_EVENT_KEY; event->ch = 0; event->key = node->key; event->mod = node->mod; bytebuf_shift(in, depth); return TB_OK; } else if (node->nchildren > 0 && in->len <= depth) { // Found a branch node (not enough input) return TB_ERR_NEED_MORE; } return TB_ERR; } static int extract_esc_user(struct tb_event *event, int is_post) { int rv; size_t consumed = 0; struct bytebuf_t *in = &global.in; int (*fn)(struct tb_event *, size_t *); fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre; if (!fn) { return TB_ERR; } rv = fn(event, &consumed); if (rv == TB_OK) { bytebuf_shift(in, consumed); } if_ok_or_need_more_return(rv, rv); return TB_ERR; } static int extract_esc_mouse(struct tb_event *event) { struct bytebuf_t *in = &global.in; enum { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; const char *cmp[TYPE_MAX] = {// // X10 mouse encoding, the simplest one // \x1b [ M Cb Cx Cy [TYPE_VT200] = "\x1b[M", // xterm 1006 extended mode or urxvt 1015 extended mode // xterm: \x1b [ < Cb ; Cx ; Cy (M or m) [TYPE_1006] = "\x1b[<", // urxvt: \x1b [ Cb ; Cx ; Cy M [TYPE_1015] = "\x1b["}; int type = 0; int ret = TB_ERR; // Unrolled at compile-time (probably) for (; type < TYPE_MAX; type++) { size_t size = strlen(cmp[type]); if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) { break; } } if (type == TYPE_MAX) { ret = TB_ERR; // No match return ret; } size_t buf_shift = 0; switch (type) { case TYPE_VT200: if (in->len >= 6) { int b = in->buf[3] - 0x20; int fail = 0; switch (b & 3) { case 0: event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP : TB_KEY_MOUSE_LEFT; break; case 1: event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN : TB_KEY_MOUSE_MIDDLE; break; case 2: event->key = TB_KEY_MOUSE_RIGHT; break; case 3: event->key = TB_KEY_MOUSE_RELEASE; break; default: ret = TB_ERR; fail = 1; break; } if (!fail) { if ((b & 32) != 0) { event->mod |= TB_MOD_MOTION; } // the coord is 1,1 for upper left event->x = ((uint8_t)in->buf[4]) - 0x21; event->y = ((uint8_t)in->buf[5]) - 0x21; ret = TB_OK; } buf_shift = 6; } break; case TYPE_1006: // fallthrough case TYPE_1015: { size_t index_fail = (size_t)-1; enum { FIRST_M = 0, FIRST_SEMICOLON, LAST_SEMICOLON, FIRST_LAST_MAX }; size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail, index_fail}; int m_is_capital = 0; for (size_t i = 0; i < in->len; i++) { if (in->buf[i] == ';') { if (indices[FIRST_SEMICOLON] == index_fail) { indices[FIRST_SEMICOLON] = i; } else { indices[LAST_SEMICOLON] = i; } } else if (indices[FIRST_M] == index_fail) { if (in->buf[i] == 'm' || in->buf[i] == 'M') { m_is_capital = (in->buf[i] == 'M'); indices[FIRST_M] = i; } } } if (indices[FIRST_M] == index_fail || indices[FIRST_SEMICOLON] == index_fail || indices[LAST_SEMICOLON] == index_fail) { ret = TB_ERR; } else { int start = (type == TYPE_1015 ? 2 : 3); unsigned n1 = strtoul(&in->buf[start], NULL, 10); unsigned n2 = strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); unsigned n3 = strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10); if (type == TYPE_1015) { n1 -= 0x20; } int fail = 0; switch (n1 & 3) { case 0: event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP : TB_KEY_MOUSE_LEFT; break; case 1: event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN : TB_KEY_MOUSE_MIDDLE; break; case 2: event->key = TB_KEY_MOUSE_RIGHT; break; case 3: event->key = TB_KEY_MOUSE_RELEASE; break; default: ret = TB_ERR; fail = 1; break; } buf_shift = in->len; if (!fail) { if (!m_is_capital) { // on xterm mouse release is signaled by lowercase m event->key = TB_KEY_MOUSE_RELEASE; } if ((n1 & 32) != 0) { event->mod |= TB_MOD_MOTION; } event->x = ((uint8_t)n2) - 1; event->y = ((uint8_t)n3) - 1; ret = TB_OK; } } } break; case TYPE_MAX: ret = TB_ERR; } if (buf_shift > 0) { bytebuf_shift(in, buf_shift); } if (ret == TB_OK) { event->type = TB_EVENT_MOUSE; } return ret; } static const char *get_terminfo_string(int16_t str_offsets_pos, int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len, int16_t str_index) { const int str_byte_index = (int)str_index * (int)sizeof(int16_t); if (str_byte_index >= (int)str_offsets_len * (int)sizeof(int16_t)) { // An offset beyond the table indicates absent // See `convert_strings` in tinfo `read_entry.c` return ""; } const int16_t *str_offset = (int16_t *)(global.terminfo + (int)str_offsets_pos + str_byte_index); if ((char *)str_offset >= global.terminfo + global.nterminfo) { // str_offset points beyond end of entry // Truncated/corrupt terminfo entry? return NULL; } if (*str_offset < 0 || *str_offset >= str_table_len) { // A negative offset indicates absent // An offset beyond the table indicates absent // See `convert_strings` in tinfo `read_entry.c` return ""; } if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { // string points beyond end of entry // Truncated/corrupt terminfo entry? return NULL; } return ( const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset); } static int cellbuf_free(struct cellbuf_t *c) { if (c->cells) { int i; for (i = 0; i < c->width * c->height; i++) { cell_free(&c->cells[i]); } tb_free(c->cells); } memset(c, 0, sizeof(*c)); return TB_OK; } static int load_terminfo(void) { int rv; char tmp[TB_PATH_MAX]; // See terminfo(5) "Fetching Compiled Descriptions" for a description of // this behavior. Some of these paths are compile-time ncurses options, so // best guesses are used here. const char *term = getenv("TERM"); if (!term) { return TB_ERR; } // If TERMINFO is set, try that directory and stop const char *terminfo = getenv("TERMINFO"); if (terminfo) { return load_terminfo_from_path(terminfo, term); } // Next try ~/.terminfo const char *home = getenv("HOME"); if (home) { snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home); if_ok_return(rv, load_terminfo_from_path(tmp, term)); } // Next try TERMINFO_DIRS // // Note, empty entries are supposed to be interpretted as the "compiled-in // default", which is of course system-dependent. Previously /etc/terminfo // was used here. Let's skip empty entries altogether rather than give // precedence to a guess, and check common paths after this loop. const char *dirs = getenv("TERMINFO_DIRS"); if (dirs) { snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs); char *dir = strtok(tmp, ":"); while (dir) { const char *cdir = dir; if (*cdir != '\0') { if_ok_return(rv, load_terminfo_from_path(cdir, term)); } dir = strtok(NULL, ":"); } } #ifdef TB_TERMINFO_DIR if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term)); #endif if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/local/share/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term)); return TB_ERR; } static int load_terminfo_from_path(const char *path, const char *term) { int rv; char tmp[TB_PATH_MAX]; // Look for term at this terminfo location, e.g., /x/xterm snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); if_ok_return(rv, read_terminfo_path(tmp)); #ifdef __APPLE__ // Try the Darwin equivalent path, e.g., /78/xterm snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); return read_terminfo_path(tmp); #endif return TB_ERR; } static int read_terminfo_path(const char *path) { FILE *fp = fopen(path, "rb"); if (!fp) { return TB_ERR; } struct stat st; if (fstat(fileno(fp), &st) != 0) { fclose(fp); return TB_ERR; } size_t fsize = st.st_size; char *data = (char *)tb_malloc(fsize); if (!data) { fclose(fp); return TB_ERR; } if (fread(data, 1, fsize, fp) != fsize) { fclose(fp); tb_free(data); return TB_ERR; } global.terminfo = data; global.nterminfo = fsize; fclose(fp); return TB_OK; } static int parse_terminfo_caps(void) { // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a // description of this behavior. // Ensure there's at least a header's worth of data if (global.nterminfo < 6) { return TB_ERR; } int16_t *header = (int16_t *)global.terminfo; // header[0] the magic number (octal 0432 or 01036) // header[1] the size, in bytes, of the names section // header[2] the number of bytes in the boolean section // header[3] the number of short integers in the numbers section // header[4] the number of offsets (short integers) in the strings section // header[5] the size, in bytes, of the string table // Legacy ints are 16-bit, extended ints are 32-bit const int bytes_per_int = header[0] == 01036 ? 4 // 32-bit : 2; // 16-bit // > Between the boolean section and the number section, a null byte will be // > inserted, if necessary, to ensure that the number section begins on an // > even byte const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0; const int pos_str_offsets = (6 * sizeof(int16_t)) // header (12 bytes) + header[1] // length of names section + header[2] // length of boolean section + align_offset + (header[3] * bytes_per_int); // length of numbers section const int pos_str_table = pos_str_offsets + (header[4] * sizeof(int16_t)); // length of string offsets table // Load caps int i; for (i = 0; i < TB_CAP__COUNT; i++) { const char *cap = get_terminfo_string(pos_str_offsets, header[4], pos_str_table, header[5], terminfo_cap_indexes[i]); if (!cap) { // Something is not right return TB_ERR; } global.caps[i] = cap; } return TB_OK; } static int cell_free(struct tb_cell *cell) { #ifdef TB_OPT_EGC if (cell->ech) { tb_free(cell->ech); } #endif memset(cell, 0, sizeof(*cell)); return TB_OK; } // ESC/mouse parsing (simplified, does not use bytebuf) static int extract_esc(struct tb_event *event) { int rv; if_ok_or_need_more_return(rv, extract_esc_user(event, 0)); if_ok_or_need_more_return(rv, extract_esc_cap(event)); // handles arrows/F-keys if_ok_or_need_more_return(rv, extract_esc_mouse(event)); // handles mouse if_ok_or_need_more_return(rv, extract_esc_user(event, 1)); return TB_ERR; } #endif // TB_IMPL kew/src/ui/visuals.c000066400000000000000000000521071512074754200147160ustar00rootroot00000000000000/** * @file visuals.c * @brief Audio visualization rendering. * * Implements a spectrum visualizer that react * to playback data. */ #include "common/appstate.h" #include "common_ui.h" #include "visuals.h" #include "common/appstate.h" #include "sound/playback.h" #include "sound/audiobuffer.h" #include "utils/term.h" #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define MAX_BARS 26 // Counting 1/3 octave per bar, 50hz-10000hz range static float *fft_input = NULL; static fftwf_complex *fft_output = NULL; static ma_format format = ma_format_unknown; static ma_uint32 sample_rate = 0; static float bar_height[MAX_BARS] = {0.0f}; static float display_magnitudes[MAX_BARS] = {0.0f}; static float magnitudes[MAX_BARS] = {0.0f}; static float dBFloor = -60.0f; static float dBCeil = -18.0f; static float emphasis = 1.3f; static float fast_attack = 0.6f; static float decay = 0.14f; static float slow_attack = 0.15f; static int visualizer_bar_mode = 2; static int max_thin_bars_in_auto_mode = 20; static int prev_fft_size = 0; void clear_magnitudes(int num_bars, float *magnitudes) { for (int i = 0; i < num_bars; i++) { magnitudes[i] = 0.0f; } } void apply_blackman_harris(float *fft_input, int buffer_size) { if (!fft_input || buffer_size < 2) // Must be at least 2 to avoid division by zero return; const float alpha0 = 0.35875f; const float alpha1 = 0.48829f; const float alpha2 = 0.14128f; const float alpha3 = 0.01168f; float denom = (float)(buffer_size - 1); for (int i = 0; i < buffer_size; i++) { float fraction = (float)i / denom; float window = alpha0 - alpha1 * cosf(2.0f * M_PI * fraction) + alpha2 * cosf(4.0f * M_PI * fraction) - alpha3 * cosf(6.0f * M_PI * fraction); fft_input[i] *= window; } } // Fill center freqs for 1/3-octave bands, given min/max freq and num_bands void compute_band_centers(float min_freq, float sample_rate, int num_bands, float *center_freqs) { if (!center_freqs || num_bands <= 0 || min_freq <= 0 || sample_rate <= 0) return; float nyquist = sample_rate * 0.5f; float octave_fraction = 1.0f / 3.0f; // 1/3 octave float factor = powf(2.0f, octave_fraction); float f = min_freq; // Ensure we don't exceed the Nyquist frequency for (int i = 0; i < num_bands; i++) { if (f > nyquist) { center_freqs[i] = nyquist; // Clamp remaining bands at Nyquist } else { center_freqs[i] = f; f *= factor; // Safeguard against overflow in case 'f' grows too // large if (f > nyquist) { f = nyquist; } } } } void fill_eq_bands(const fftwf_complex *fft_output, int buffer_size, float sample_rate, float *band_db, int num_bands, const float *center_freqs) { // Basic input checks if (!fft_output || !band_db || !center_freqs || buffer_size <= 0 || num_bands <= 0 || sample_rate <= 0.0f) return; // Guard against potential overflow in bin count computation if (buffer_size > INT_MAX - 1) return; int num_bins = buffer_size / 2 + 1; // Safe bin_spacing computation float bin_spacing = sample_rate / (float)buffer_size; if (bin_spacing <= 0.0f || !isfinite(bin_spacing)) return; // Prevent division by zero in normalization float norm_factor = (float)buffer_size; if (norm_factor <= 0.0f) return; // Frequency window width for 1/3-octave bands const float width = powf(2.0f, 1.0f / 6.0f); // +/-1/6 octave // Pink noise correction const float correction_per_octave = 3.0f; const float max_freq_for_correction = 10000.0f; const float nyquist = sample_rate * 0.5f; // Make sure reference_freq is safe float reference_freq = fmaxf(center_freqs[0], 1.0f); if (!isfinite(reference_freq) || reference_freq <= 0.0f) reference_freq = 1.0f; for (int i = 0; i < num_bands; i++) { float center = center_freqs[i]; if (!isfinite(center) || center <= 0.0f || center > nyquist) { band_db[i] = -INFINITY; continue; } float lo = center / width; float hi = center * width; // Avoid integer overflows in bin index computation int bin_lo = (int)ceilf(lo / bin_spacing); int bin_hi = (int)floorf(hi / bin_spacing); bin_lo = (bin_lo < 0) ? 0 : bin_lo; bin_hi = (bin_hi >= num_bins) ? num_bins - 1 : bin_hi; bin_hi = (bin_hi < bin_lo) ? bin_lo : bin_hi; float sum_sq = 0.0f; int count = 0; for (int k = bin_lo; k <= bin_hi; k++) { if (k < 0 || k >= num_bins) continue; float real = fft_output[k][0] / norm_factor; float imag = fft_output[k][1] / norm_factor; float mag = sqrtf(real * real + imag * imag); sum_sq += mag * mag; count++; } float rms = (count > 0) ? sqrtf(sum_sq / count) : 1e-9f; // Small nonzero floor band_db[i] = 20.0f * log10f(rms); // Pink noise EQ compensation float freq = fminf(center, max_freq_for_correction); float octaves_above_ref = log2f(freq / reference_freq); float correction = fmaxf(octaves_above_ref, 0.0f) * correction_per_octave; band_db[i] += correction; } } int normalize_audio_samples(const void *audio_buffer, float *fft_input, int buffer_size, int bit_depth) { if (bit_depth == 8) { const uint8_t *buf = (const uint8_t *)audio_buffer; for (int i = 0; i < buffer_size; ++i) fft_input[i] = ((float)buf[i] - 127.0f) / 128.0f; } else if (bit_depth == 16) { const int16_t *buf = (const int16_t *)audio_buffer; for (int i = 0; i < buffer_size; ++i) fft_input[i] = (float)buf[i] / 32768.0f; } else if (bit_depth == 24) { const uint8_t *buf = (const uint8_t *)audio_buffer; for (int i = 0; i < buffer_size; ++i) { int32_t sample = unpack_s24(buf + i * 3); fft_input[i] = (float)sample / 8388608.0f; } } else if (bit_depth == 32) { const float *buf = (const float *)audio_buffer; for (int i = 0; i < buffer_size; ++i) fft_input[i] = buf[i]; } else { // Unsupported bit depth return -1; } return 0; } void calc_magnitudes(int height, int num_bars, void *audio_buffer, int bit_depth, float *fft_input, fftwf_complex *fft_output, int fft_size, float *magnitudes, fftwf_plan plan, float *display_magnitudes) { // Only execute when we get the signal that we have enough samples // (fft_size) if (!is_buffer_ready()) return; if (!audio_buffer) { fprintf(stderr, "Audio buffer is NULL.\n"); return; } set_buffer_ready(false); normalize_audio_samples(audio_buffer, fft_input, fft_size, bit_depth); // Apply Blackman Harris window function apply_blackman_harris(fft_input, fft_size); // Compute fast fourier transform fftwf_execute(plan); // Clear previous magnitudes clear_magnitudes(MAX_BARS, magnitudes); float center_freqs[num_bars]; float min_freq = 25.0f; float audible_half = 10000.0f; float max_freq = fmin(audible_half, 0.5f * sample_rate); float octave_fraction = 1.0f / 3.0f; int used_bars = floor(log2(max_freq / min_freq) / octave_fraction) + 1; // How many bars are actually in use, given we // increase with 1/3 octave per bar // Compute center frequencies for EQ bands compute_band_centers(min_freq, max_freq, num_bars, center_freqs); // Fill magnitudes for EQ bands from FFT output fill_eq_bands(fft_output, fft_size, sample_rate, magnitudes, num_bars, center_freqs); // Map magnitudes (in dB) to bar heights with gating and emphasis // (pow/gated) for (int i = 0; i < used_bars; ++i) { float db = magnitudes[i]; if (db < dBFloor) db = dBFloor; if (db > dBCeil) db = dBCeil; float ratio = (db - dBFloor) / (dBCeil - dBFloor); ratio = powf(ratio, emphasis); if (ratio < 0.1f) bar_height[i] = 0.0f; // Gate out tiny bars else bar_height[i] = ratio * height; } float snap_threshold = 0.2f * height; // Smoothly update display magnitudes with attack/decay and snap // threshold for (int i = 0; i < used_bars; ++i) { float current = display_magnitudes[i]; float target = bar_height[i]; float delta = target - current; if (delta > snap_threshold) display_magnitudes[i] += delta * fast_attack; // SNAP on big hits else if (delta > 0) display_magnitudes[i] += delta * slow_attack; else display_magnitudes[i] += delta * decay; } } char *upward_motion_chars_block[] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; char *upward_motion_chars_braille[] = {" ", "⣀", "⣀", "⣤", "⣤", "⣶", "⣶", "⣿", "⣿"}; char *inbetween_chars_rising[] = {" ", "⣠", "⣠", "⣴", "⣴", "⣾", "⣾", "⣿", "⣿"}; char *inbetween_chars_falling[] = {" ", "⡀", "⡀", "⣄", "⣄", "⣦", "⣦", "⣷", "⣷"}; char *get_upward_motion_char(int level, bool braille) { if (level < 0 || level > 8) { level = 8; } if (braille) return upward_motion_chars_braille[level]; else return upward_motion_chars_block[level]; } char *get_inbetweend_motion_char(float magnitude_prev, float magnitude_next, int prev, int next) { if (prev < 0) prev = 0; if (prev > 8) prev = 8; if (next < 0) next = 0; if (next > 8) next = 8; if (magnitude_next > magnitude_prev) return inbetween_chars_rising[prev]; else if (magnitude_next < magnitude_prev) return inbetween_chars_falling[prev]; else return upward_motion_chars_braille[prev]; } char *get_inbetween_char(float prev, float next) { int first_decimal_digit = (int)(fmod(prev * 10, 10)); int second_decimal_digit = (int)(fmod(next * 10, 10)); return get_inbetweend_motion_char(prev, next, first_decimal_digit, second_decimal_digit); } int get_bit_depth(ma_format format) { if (format == ma_format_unknown) return -1; int bit_depth = 32; switch (format) { case ma_format_u8: bit_depth = 8; break; case ma_format_s16: bit_depth = 16; break; case ma_format_s24: bit_depth = 24; break; case ma_format_f32: case ma_format_s32: bit_depth = 32; break; default: break; } return bit_depth; } void print_spectrum(int row, int col, UISettings *ui, int height, int num_bars, int visualizer_width, float *magnitudes) { PixelData color = {0, 0, 0}; if (ui->colorMode == COLOR_MODE_ALBUM) { color.r = ui->color.r; color.g = ui->color.g; color.b = ui->color.b; } else if (ui->colorMode == COLOR_MODE_THEME && ui->theme.trackview_visualizer.type == COLOR_TYPE_RGB) { color.r = ui->theme.trackview_visualizer.rgb.r; color.g = ui->theme.trackview_visualizer.rgb.g; color.b = ui->theme.trackview_visualizer.rgb.b; } int visualizer_color_type = ui->visualizer_color_type; bool brailleMode = ui->visualizerBrailleMode; PixelData tmp; bool is_playing = !(pb_is_paused() || pb_is_stopped()); for (int j = height; j > 0 && !is_playing; j--) { printf("\033[%d;%dH", row, col); clear_rest_of_line(); } for (int j = height; j > 0 && is_playing; j--) { printf("\033[%d;%dH", row + height - j, col); if (color.r != 0 || color.g != 0 || color.b != 0) { if ((visualizer_color_type == 0 || visualizer_color_type == 2 || visualizer_color_type == 3)) { if (visualizer_color_type == 0) { tmp = increase_luminosity( color, round(j * height * 4)); } else if (visualizer_color_type == 2) { tmp = increase_luminosity( color, round((height - j) * height * 4)); } else if (visualizer_color_type == 3) { tmp = get_gradient_color(color, j, height, 1, 0.6f); } } } if (ui->colorMode == COLOR_MODE_ALBUM) printf("\033[38;2;%d;%d;%dm", tmp.r, tmp.g, tmp.b); else if (ui->theme.trackview_visualizer.type == COLOR_TYPE_RGB) { printf("\033[38;2;%d;%d;%dm", tmp.r, tmp.g, tmp.b); } else apply_color(ui->colorMode, ui->theme.trackview_visualizer, tmp); for (int i = 0; i < num_bars; i++) { if (ui->colorMode != COLOR_MODE_DEFAULT && visualizer_color_type == 1) { tmp = (PixelData){ color.r / 2, color.g / 2, color.b / 2}; // Make colors half as bright before // increasing brightness tmp = increase_luminosity( tmp, round(magnitudes[i] * 10 * 4)); printf("\033[38;2;%d;%d;%dm", tmp.r, tmp.g, tmp.b); } if (i == 0 && brailleMode) { printf(" "); } else if (i > 0 && brailleMode) { if (magnitudes[i - 1] >= j) { printf("%s", get_upward_motion_char( 10, brailleMode)); } else if (magnitudes[i - 1] + 1 >= j) { printf("%s", get_inbetween_char( magnitudes[i - 1], magnitudes[i])); } else { printf(" "); } } if (!brailleMode) { printf(" "); } if (magnitudes[i] >= j) { printf("%s", get_upward_motion_char(10, brailleMode)); if (visualizer_bar_mode == 1 || (visualizer_bar_mode == 2 && visualizer_width > max_thin_bars_in_auto_mode)) printf("%s", get_upward_motion_char( 10, brailleMode)); } else if (magnitudes[i] + 1 >= j) { int first_decimal_digit = (int)(fmod(magnitudes[i] * 10, 10)); printf("%s", get_upward_motion_char(first_decimal_digit, brailleMode)); if (visualizer_bar_mode == 1 || (visualizer_bar_mode == 2 && visualizer_width > max_thin_bars_in_auto_mode)) printf("%s", get_upward_motion_char( first_decimal_digit, brailleMode)); } else { printf(" "); if (visualizer_bar_mode == 1 || (visualizer_bar_mode == 2 && visualizer_width > max_thin_bars_in_auto_mode)) printf(" "); } } } fflush(stdout); } void free_visuals(void) { if (fft_input != NULL) { free(fft_input); fft_input = NULL; } if (fft_output != NULL) { fftwf_free(fft_output); fft_output = NULL; } } void draw_spectrum_visualizer(int row, int col, int height, int width) { AppState *state = get_app_state(); int num_bars = state->uiState.num_progress_bars; int visualizer_width = state->uiState.num_progress_bars; visualizer_bar_mode = state->uiSettings.visualizer_bar_mode; int bar_width = 2; if (visualizer_bar_mode == 1 || (visualizer_bar_mode == 2 && num_bars > max_thin_bars_in_auto_mode)) { num_bars *= 0.67f; bar_width = 3; } if (num_bars > MAX_BARS) num_bars = MAX_BARS; // Center the visualizer int extra_cols = width - (num_bars * bar_width); col += extra_cols / 2; height -= 1; if (height <= 0 || num_bars <= 0) { return; } int fft_size = get_fft_size(); if (fft_size != prev_fft_size) { free_visuals(); memset(display_magnitudes, 0, sizeof(display_magnitudes)); fft_input = (float *)malloc(sizeof(float) * fft_size); if (fft_input == NULL) { for (int i = 0; i <= height; i++) { printf("\n"); } return; } fft_output = (fftwf_complex *)fftwf_malloc( sizeof(fftwf_complex) * fft_size); if (fft_output == NULL) { fftwf_free(fft_input); fft_input = NULL; for (int i = 0; i <= height; i++) { printf("\n"); } return; } prev_fft_size = fft_size; } fftwf_plan plan = fftwf_plan_dft_r2c_1d(fft_size, fft_input, fft_output, FFTW_ESTIMATE); get_current_format_and_sample_rate(&format, &sample_rate); int bit_depth = get_bit_depth(format); calc_magnitudes(height, num_bars, get_audio_buffer(), bit_depth, fft_input, fft_output, fft_size, magnitudes, plan, display_magnitudes); print_spectrum(row, col, &(state->uiSettings), height, num_bars, visualizer_width, display_magnitudes); fftwf_destroy_plan(plan); } kew/src/ui/visuals.h000066400000000000000000000004071512074754200147170ustar00rootroot00000000000000 /** * @file visuals.h * @brief Audio visualization rendering. * * Implements a spectrum visualizer that react * to playback data. */ void init_visuals(void); void free_visuals(void); void draw_spectrum_visualizer(int row, int col, int height, int width); kew/src/utils/000077500000000000000000000000001512074754200136025ustar00rootroot00000000000000kew/src/utils/cache.c000066400000000000000000000047511512074754200150200ustar00rootroot00000000000000#define _XOPEN_SOURCE 700 /** * @file cache.c * @brief Disk cache management for metadata and library data. * * Handles caching of song metadata, album art, and library indexes * to improve startup and search performance. */ #include "cache.h" #include #include #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif Cache *create_cache() { Cache *cache = malloc(sizeof(Cache)); if (cache == NULL) { fprintf(stderr, "create_cache: malloc\n"); return NULL; } cache->head = NULL; return cache; } void add_to_cache(Cache *cache, const char *file_path) { if (cache == NULL) { fprintf(stderr, "Cache is null.\n"); return; } if (file_path == NULL || *file_path == '\0') { fprintf(stderr, "Invalid file_path.\n"); return; } if (strnlen(file_path, PATH_MAX + 1) >= PATH_MAX) { fprintf(stderr, "File path too long.\n"); return; } CacheNode *new_node = malloc(sizeof(CacheNode)); if (new_node == NULL) { fprintf(stderr, "add_to_cache: malloc\n"); return; } new_node->file_path = strdup(file_path); if (new_node->file_path == NULL) { fprintf(stderr, "add_to_cache: strdup\n"); free(new_node); // prevent memory leak return; } new_node->next = cache->head; cache->head = new_node; } void delete_cache(Cache *cache) { if (cache == NULL) { fprintf(stderr, "delete_cache: Cache is null.\n"); return; } CacheNode *current = cache->head; while (current != NULL) { CacheNode *tmp = current; current = current->next; free(tmp->file_path); free(tmp); } free(cache); } bool exists_in_cache(Cache *cache, char *file_path) { if (file_path == NULL) return false; if (cache == NULL) { fprintf(stderr, "exists_in_cache: Cache is null.\n"); return false; } CacheNode *current = cache->head; while (current != NULL) { if (strcmp(file_path, current->file_path) == 0) { return true; } current = current->next; } return false; } kew/src/utils/cache.h000066400000000000000000000011221512074754200150120ustar00rootroot00000000000000/** * @file cache.h * @brief Disk cache management for metadata and library data. * * Handles caching of song metadata, album art, and library indexes * to improve startup and search performance. */ #ifndef CACHE_H #define CACHE_H #include typedef struct CacheNode { char *file_path; struct CacheNode *next; } CacheNode; typedef struct Cache { CacheNode *head; } Cache; Cache *create_cache(void); void add_to_cache(Cache *cache, const char *file_path); void delete_cache(Cache *cache); bool exists_in_cache(Cache *cache, char *file_path); #endif kew/src/utils/file.c000066400000000000000000000345341512074754200146760ustar00rootroot00000000000000/** * @file file.c * @brief File and directory utilities. * * Provides wrappers around file I/O, path manipulation, * and safe filesystem access used throughout the app. */ #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include "file.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_RECURSION_DEPTH 64 const char *get_temp_dir() { const char *tmpdir = getenv("TMPDIR"); if (tmpdir != NULL) { return tmpdir; // Use TMPDIR if set (common on Android/Termux) } tmpdir = getenv("TEMP"); if (tmpdir != NULL) { return tmpdir; } // Fallback to /tmp on Unix-like systems return "/tmp"; } void get_directory_from_path(const char *path, char *directory) { if (!path || !directory) return; size_t len = strnlen(path, PATH_MAX); char *tmp = malloc(len + 1); if (!tmp) { fprintf(stderr, "Out of memory while processing path\n"); return; } memcpy(tmp, path, len + 1); // dirname() may modify the buffer, so we keep it in tmp char *dir = dirname(tmp); // Copy the result to the caller‑supplied buffer safely strncpy(directory, dir, PATH_MAX - 1); directory[PATH_MAX - 1] = '\0'; // Ensure null termination /// Ensure a trailing '/' size_t dlen = strnlen(directory, PATH_MAX); if (dlen > 0 && directory[dlen - 1] != '/' && dlen + 1 < PATH_MAX) { directory[dlen] = '/'; directory[dlen + 1] = '\0'; } free(tmp); } int exists_file(const char *fname) { if (fname == NULL || fname[0] == '\0') return -1; FILE *file; if ((file = fopen(fname, "r"))) { fclose(file); return 1; } return -1; } int is_directory(const char *path) { DIR *dir = opendir(path); if (dir) { closedir(dir); return 1; } else { if (errno == ENOENT) { return -1; } return 0; } } int directory_exists(const char *path) { char expanded[PATH_MAX]; expand_path(path, expanded); DIR *dir = opendir(expanded); if (dir) { closedir(dir); return 1; } return 0; } // Traverse a directory tree and search for a given file or directory int walker(const char *start_path, const char *low_case_searching, char *result, const char *allowed_extensions, enum SearchType search_type, bool exact_search, int depth) { if (depth > MAX_RECURSION_DEPTH) { fprintf(stderr, "Maximum recursion depth exceeded\n"); return 1; } if (!start_path || !low_case_searching || !result || !allowed_extensions) { fprintf(stderr, "Invalid arguments to walker\n"); return 1; } struct stat path_stat; if (stat(start_path, &path_stat) != 0) { fprintf(stderr, "Cannot stat path '%s': %s\n", start_path, strerror(errno)); return 1; } if (!S_ISDIR(path_stat.st_mode)) { // Not a directory, stop here return 1; } DIR *d = opendir(start_path); if (!d) { fprintf(stderr, "Failed to open directory '%s': %s\n", start_path, strerror(errno)); return 1; } regex_t regex; if (regcomp(®ex, allowed_extensions, REG_EXTENDED) != 0) { fprintf(stderr, "Failed to compile regex\n"); closedir(d); return 1; } bool found = false; struct dirent *entry; char ext[100] = {0}; while ((entry = readdir(d)) != NULL) { // Skip . and .. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // Build full path for entry char entry_path[PATH_MAX]; if (snprintf(entry_path, sizeof(entry_path), "%s/%s", start_path, entry->d_name) >= (int)sizeof(entry_path)) { fprintf(stderr, "Path too long: %s/%s\n", start_path, entry->d_name); continue; } if (stat(entry_path, &path_stat) != 0) { // Can't stat, skip continue; } if (S_ISDIR(path_stat.st_mode)) { // Directory handling char *folded_name = g_utf8_casefold(entry->d_name, -1); if (!folded_name) continue; bool nameMatch = exact_search ? (strcasecmp(folded_name, low_case_searching) == 0) : (c_strcasestr(folded_name, low_case_searching, PATH_MAX) != NULL); free(folded_name); if (nameMatch && search_type != FileOnly && search_type != SearchPlayList) { strncpy(result, entry_path, PATH_MAX - 1); result[PATH_MAX - 1] = '\0'; found = true; break; } // Recurse into subdirectory if (walker(entry_path, low_case_searching, result, allowed_extensions, search_type, exact_search, depth + 1) == 0) { found = true; break; } } else { // File handling if (search_type == DirOnly) continue; if (strlen(entry->d_name) <= 4) continue; extract_extension(entry->d_name, sizeof(ext) - 1, ext); if (match_regex(®ex, ext) != 0) continue; char *folded_name = g_utf8_casefold(entry->d_name, -1); if (!folded_name) continue; bool nameMatch = exact_search ? (strcasecmp(folded_name, low_case_searching) == 0) : (c_strcasestr(folded_name, low_case_searching, PATH_MAX) != NULL); free(folded_name); if (nameMatch) { strncpy(result, entry_path, PATH_MAX - 1); result[PATH_MAX - 1] = '\0'; found = true; break; } } } regfree(®ex); closedir(d); return found ? 0 : 1; } int expand_path(const char *input_path, char *expanded_path) { if (input_path[0] == '\0' || input_path[0] == '\r') return -1; expanded_path[0] = '\0'; if (input_path[0] == '~') // Check if input_path starts with '~' { const char *home_dir; if (input_path[1] == '/' || input_path[1] == '\0') // Handle "~/" { home_dir = getenv("HOME"); if (home_dir == NULL) { return -1; // Unable to retrieve home directory } input_path++; // Skip '~' character } else // Handle "~username/" { const char *username = input_path + 1; const char *slash = strchr(username, '/'); if (slash == NULL) { const struct passwd *pw = getpwnam(username); if (pw == NULL) { return -1; // Unable to retrieve user directory } home_dir = pw->pw_dir; input_path = ""; // Empty path component after '~username' } else { size_t usernameLen = slash - username; const struct passwd *pw = getpwuid(getuid()); if (pw == NULL) { return -1; // Unable to retrieve user directory } home_dir = pw->pw_dir; input_path += usernameLen + 1; // Skip '~username/' component } } size_t homeDirLen = strnlen(home_dir, PATH_MAX); size_t inputPathLen = strnlen(input_path, PATH_MAX); if (homeDirLen + inputPathLen >= PATH_MAX) { return -1; // Expanded path exceeds maximum length } c_strcpy(expanded_path, home_dir, PATH_MAX); snprintf(expanded_path + homeDirLen, PATH_MAX - homeDirLen, "%s", input_path); } else // Handle if path is not prefixed with '~' { if (realpath(input_path, expanded_path) == NULL) { return -1; // Unable to expand the path } } return 0; // Path expansion successful } void collapse_path(const char *input, char *output) { if (!input || !output) return; size_t in_len = strlen(input); /* Quick copy for empty input */ if (in_len == 0) { output[0] = '\0'; return; } /* Resolve current user's home (prefer $HOME, fallback to getpwuid) */ const char *home = getenv("HOME"); if (!home) { struct passwd *pw = getpwuid(getuid()); if (pw) home = pw->pw_dir; } if (home) { size_t home_len = strlen(home); if (in_len >= home_len && strncmp(input, home, home_len) == 0 && (input[home_len] == '/' || input[home_len] == '\0')) { /* Collapse to ~ or ~/rest */ if (input[home_len] == '\0') { snprintf(output, PATH_MAX, "~"); } else { snprintf(output, PATH_MAX, "~%s", input + home_len); } return; } } #if !defined(__ANDROID__) /* Check other users' home dirs (e.g. /home/alice -> ~alice) */ /* We'll iterate passwd entries and look for a pw_dir that is a prefix */ struct passwd *pw; /* Iterate over passwd database */ setpwent(); while ((pw = getpwent()) != NULL) { if (!pw->pw_dir) continue; size_t dlen = strlen(pw->pw_dir); if (in_len >= dlen && strncmp(input, pw->pw_dir, dlen) == 0 && (input[dlen] == '/' || input[dlen] == '\0')) { /* Found a match for this user's home */ if (input[dlen] == '\0') { /* exact match */ snprintf(output, PATH_MAX, "~%s", pw->pw_name); } else { snprintf(output, PATH_MAX, "~%s%s", pw->pw_name, input + dlen); } endpwent(); return; } } endpwent(); #endif /* No match — copy unchanged */ snprintf(output, PATH_MAX, "%s", input); } int create_directory(const char *path) { struct stat st; // Check if directory already exists if (stat(path, &st) == 0) { if (S_ISDIR(st.st_mode)) return 0; // Directory already exists else return -1; // Path exists but is not a directory } // Directory does not exist, so create it if (mkdir(path, 0700) == 0) return 1; // Directory created successfully return -1; // Failed to create directory } int delete_file(const char *file_path) { if (remove(file_path) == 0) { return 0; } else { return -1; } } int is_in_temp_dir(const char *path) { const char *tmp_dir = getenv("TMPDIR"); static char tmpdir_buf[PATH_MAX + 2]; if (tmp_dir == NULL || strnlen(tmp_dir, PATH_MAX) >= PATH_MAX) tmp_dir = "/tmp"; size_t len = strlen(tmp_dir); strncpy(tmpdir_buf, tmp_dir, PATH_MAX); tmpdir_buf[PATH_MAX] = '\0'; if (len == 0 || tmpdir_buf[len - 1] != '/') { tmpdir_buf[len] = '/'; tmpdir_buf[len + 1] = '\0'; } return path_starts_with(path, tmpdir_buf); } void generate_temp_file_path(char *file_path, const char *prefix, const char *suffix) { const char *tmp_dir = getenv("TMPDIR"); if (tmp_dir == NULL || strnlen(tmp_dir, PATH_MAX) >= PATH_MAX) { tmp_dir = "/tmp"; } struct passwd *pw = getpwuid(getuid()); const char *username = pw ? pw->pw_name : "unknown"; char dir_path[PATH_MAX]; snprintf(dir_path, sizeof(dir_path), "%s/kew", tmp_dir); create_directory(dir_path); snprintf(dir_path, sizeof(dir_path), "%s/kew/%s", tmp_dir, username); create_directory(dir_path); char random_string[7]; for (int i = 0; i < 6; ++i) { random_string[i] = 'a' + rand() % 26; } random_string[6] = '\0'; int written = snprintf(file_path, PATH_MAX, "%s/%s%.6s%s", dir_path, prefix, random_string, suffix); if (written < 0 || written >= PATH_MAX) { file_path[0] = '\0'; } } kew/src/utils/file.h000066400000000000000000000024201512074754200146700ustar00rootroot00000000000000/** * @file file.h * @brief File and directory utilities. * * Provides wrappers around file I/O, path manipulation, * and safe filesystem access used throughout the app. */ #ifndef FILE_H #define FILE_H #include #define __USE_GNU #ifndef PATH_MAX #define PATH_MAX 4096 #endif #define MUSIC_FILE_EXTENSIONS "(m4a|aac|mp3|ogg|flac|wav|opus|webm)$" #define AUDIO_EXTENSIONS "(m4a|aac|mp3|ogg|flac|wav|opus|webm|m3u|m3u8)$" enum SearchType { SearchAny = 0, DirOnly = 1, FileOnly = 2, SearchPlayList = 3, ReturnAllSongs = 4 }; const char *get_temp_dir(); void get_directory_from_path(const char *path, char *directory); void collapse_path(const char *input, char *output); void generate_temp_file_path(char *file_path, const char *prefix, const char *suffix); int directory_exists(const char *path); int is_directory(const char *path); int walker(const char *start_path, const char *searching, char *result, const char *allowed_extensions, enum SearchType search_type, bool exact_search, int depth); int expand_path(const char *input_path, char *expanded_path); int create_directory(const char *path); int delete_file(const char *file_path); int is_in_temp_dir(const char *path); int exists_file(const char *fname); #endif kew/src/utils/term.c000066400000000000000000000210521512074754200147150ustar00rootroot00000000000000/** * @file term.c * @brief Terminal manipulation utilities. * * Handles terminal capabilities (color, cursor movement, screen clearing), * and provides a lightweight abstraction for TUI rendering. */ #include "term.h" #include #include #include #include #include #include #include #include #include #include #include static const int MAX_TERMINAL_ROWS = 9999; static struct termios orig_termios; static int termios_saved = 0; void set_terminal_color(int color) { /* - 0: Black - 1: Red - 2: Green - 3: Yellow - 4: Blue - 5: Magenta - 6: Cyan - 7: White - 8: Bright Black (Gray) - 9: Bright Red - 10: Bright Green - 11: Bright Yellow - 12: Bright Blue - 13: Bright Magenta - 14: Bright Cyan - 15: Bright White */ if (color < -1 || color > 15) color = 7; // default to white if (color == -1) { // Default foreground printf("\033[39m"); } else if (color < 8) { // Normal colors (30–37) printf("\033[0;3%dm", color); } else { // Bright colors (90–97) printf("\033[0;9%dm", color - 8); } } void set_text_color_RGB(int r, int g, int b) { if (r < 0 || r > 255) r = 255; if (g < 0 || g > 255) g = 255; if (b < 0 || b > 255) b = 255; printf("\033[0;38;2;%03u;%03u;%03um", (unsigned int)r, (unsigned int)g, (unsigned int)b); } void get_term_size(int *width, int *height) { struct winsize w; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1 || w.ws_row == 0 || w.ws_col == 0) { // Fallback for non-interactive environments (like Homebrew tests) *height = 24; // default terminal height *width = 80; // default terminal width return; } *height = (int)w.ws_row; *width = (int)w.ws_col; } void set_nonblocking_mode(void) { struct termios ttystate; tcgetattr(STDIN_FILENO, &orig_termios); // save original termios_saved = 1; ttystate = orig_termios; ttystate.c_lflag &= ~(ICANON | ECHO); // non-canonical, no echo // ISIG left intact to handle Ctrl-C etc. ttystate.c_cc[VMIN] = 0; // return immediately ttystate.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSANOW, &ttystate); tcflush(STDIN_FILENO, TCIFLUSH); // flush any leftover input } void restore_terminal_mode(void) { if (termios_saved) { tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); tcflush(STDIN_FILENO, TCIFLUSH); } } void save_cursor_position(void) { printf("\033[s"); } void restore_cursor_position(void) { printf("\033[u"); } void set_default_text_color(void) { printf("\033[0m"); } void hide_cursor(void) { printf("\033[?25l"); } void show_cursor(void) { printf("\033[?25h"); fflush(stdout); } void clear_rest_of_screen(void) { printf("\033[J"); } void clear_line(void) { printf("\033[2K"); } void clear_rest_of_line(void) { printf("\033[K"); } void clear_screen(void) { printf("\033[3J"); // Clear scrollback buffer printf("\033[2J\033[3J\033[H"); // Move cursor to top-left and clear // screen and scrollback buffer fflush(stdout); } void goto_first_line_first_row(void) { printf("\033[H"); } void enable_scrolling(void) { printf("\033[?7h"); } void disable_terminal_line_input(void) { setvbuf(stdout, NULL, _IOFBF, BUFSIZ); } void set_raw_input_mode(void) { struct termios term; tcgetattr(STDIN_FILENO, &term); term.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSAFLUSH, &term); } void enable_input_buffering() { struct termios term; tcgetattr(STDIN_FILENO, &term); term.c_lflag |= ICANON | ECHO; tcsetattr(STDIN_FILENO, TCSAFLUSH, &term); } void cursor_jump(int num_rows) { if (num_rows < 0 || num_rows > MAX_TERMINAL_ROWS) return; printf("\033[%dA", num_rows); printf("\033[0m"); } void cursor_jump_down(int num_rows) { if (num_rows < 0 || num_rows > MAX_TERMINAL_ROWS) return; printf("\033[%dB", num_rows); } int read_input_sequence(char *seq, size_t seq_size) { if (seq == NULL || seq_size < 2) // Buffer needs at least 1 byte + null terminator return 0; char c; ssize_t bytes_read = read(STDIN_FILENO, &c, 1); if (bytes_read <= 0) return 0; // ASCII character (single byte, no continuation bytes) if ((c & 0x80) == 0) { if (seq_size < 2) // Make sure there's space for the null terminator return 0; seq[0] = c; seq[1] = '\0'; return 1; } // Determine the length of the UTF-8 sequence and validate the first // byte int additional_bytes; if ((c & 0xE0) == 0xC0) additional_bytes = 1; // 2-byte sequence else if ((c & 0xF0) == 0xE0) additional_bytes = 2; // 3-byte sequence else if ((c & 0xF8) == 0xF0) additional_bytes = 3; // 4-byte sequence else return 0; // Invalid UTF-8 start byte if ((size_t)(additional_bytes + 1) >= seq_size) return 0; seq[0] = c; // Read the continuation bytes bytes_read = read(STDIN_FILENO, &seq[1], additional_bytes); if (bytes_read != additional_bytes) return 0; // Validate continuation bytes (0x80 <= byte <= 0xBF) for (int i = 1; i <= additional_bytes; ++i) { if ((seq[i] & 0xC0) != 0x80) return 0; // Invalid continuation byte } // Null terminate the string seq[additional_bytes + 1] = '\0'; return additional_bytes + 1; // Return the total length including the null terminator } int is_input_available(void) { fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); if (ret < 0) { return 0; } int result = (ret > 0) && (FD_ISSET(STDIN_FILENO, &fds)); return result; } int get_indentation(int text_width) { int term_w, term_h; get_term_size(&term_w, &term_h); if (text_width <= 0 || term_w <= 0) { return 0; } if (text_width >= term_w) { text_width = term_w; } int available_space = term_w - text_width; int indent = (available_space / 2) + 1; return indent; } void enter_alternate_screen_buffer(void) { // Enter alternate screen buffer printf("\033[?1049h"); } void exit_alternate_screen_buffer(void) { // Exit alternate screen buffer printf("\033[?1049l"); } void enable_terminal_mouse_buttons(void) { // Enable program to accept mouse input as codes printf("\033[?1002h\033[?1006h"); } void disable_terminal_mouse_buttons(void) { // Disable ALL mouse modes that might have been enabled // Some terminals auto-enable 1004 when SGR mouse mode is on. // VTE (Guake) will not clear it unless explicitly disabled. printf("\033[?1000l"); // X10 mouse printf("\033[?1001l"); // Highlight tracking printf("\033[?1002l"); // Button tracking printf("\033[?1003l"); // Any-motion tracking printf("\033[?1004l"); // Focus events (VTE uses this!) printf("\033[?1006l"); // SGR mouse mode printf("\033[?1015l"); // urxvt mouse mode (rare but harmless) fflush(stdout); } void set_terminal_window_title(char *title) { // Only change window title, no icon printf("\033]2;%s\007", title); } void save_terminal_window_title(void) { // Save terminal window title on the stack printf("\033[22;0t"); } void restore_terminal_window_title(void) { // Restore terminal window title from the stack printf("\033[23;0t"); } kew/src/utils/term.h000066400000000000000000000030371512074754200147250ustar00rootroot00000000000000/** * @file term.h * @brief Terminal manipulation utilities. * * Handles terminal capabilities (color, cursor movement, screen clearing), * and provides a lightweight abstraction for TUI rendering. */ #ifndef TERM_H #include #define TERM_H #ifndef __USE_POSIX #define __USE_POSIX #endif #ifdef __GNU__ #define _BSD_SOURCE #endif int get_indentation(int text_width); int is_input_available(void); int read_input_sequence(char *seq, size_t seq_size); void set_terminal_color(int color); void set_text_color_RGB(int r, int g, int b); void get_term_size(int *width, int *height); void set_nonblocking_mode(void); void restore_terminal_mode(void); void set_default_text_color(void); void save_cursor_position(void); void restore_cursor_position(void); void hide_cursor(void); void show_cursor(void); void clear_rest_of_screen(void); void enable_scrolling(void); void clear_line(void); void clear_rest_of_line(void); void goto_first_line_first_row(void); void init_resize(void); void disable_terminal_line_input(void); void set_raw_input_mode(void); void enable_input_buffering(void); void cursor_jump(int num_rows); void cursor_jump_down(int num_rows); void clear_screen(void); void enter_alternate_screen_buffer(void); void exit_alternate_screen_buffer(void); void enable_terminal_mouse_buttons(void); void disable_terminal_mouse_buttons(void); void set_terminal_window_title(char *title); void save_terminal_window_title(void); void restore_terminal_window_title(void); void saveOriginalTerminalMode(void); void restore_terminal_mode(void); #endif kew/src/utils/utils.c000066400000000000000000000445441512074754200151210ustar00rootroot00000000000000/** * @file utils.c * @brief General-purpose utility functions and helpers. * * Provides miscellaneous helpers used across modules such as * string operations, math functions, or logging utilities. */ #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ defined(__NetBSD__) #include // For uint32_t #include // For arc4random uint32_t arc4random_uniform(uint32_t upper_bound); int get_random_number(int min, int max) { return min + arc4random_uniform(max - min + 1); } #else #include #include int get_random_number(int min, int max) { static int seeded = 0; if (!seeded) { srand(time( NULL)); seeded = 1; } return min + (rand() % (max - min + 1)); } #endif void c_sleep(int milliseconds) { struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = (milliseconds % 1000) * 1000000; if (ts.tv_nsec >= 1000000000) { ts.tv_sec += ts.tv_nsec / 1000000000; ts.tv_nsec %= 1000000000; } nanosleep(&ts, NULL); } void c_usleep(int microseconds) { if (microseconds < 0 || microseconds > 100000000) { return; } struct timespec ts; ts.tv_sec = microseconds / 1000000; ts.tv_nsec = (microseconds % 1000000) * 1000; nanosleep(&ts, NULL); } void c_strcpy(char *dest, const char *src, size_t dest_size) { if (dest && src && dest_size > 0) { size_t src_length = strnlen(src, dest_size - 1); if (src_length >= dest_size) src_length = dest_size - 1; memcpy(dest, src, src_length); dest[src_length] = '\0'; } else if (dest && dest_size > 0) { dest[0] = '\0'; } } gint64 get_length_in_micro_sec(double duration) { return floor(llround(duration * G_USEC_PER_SEC)); } char *string_to_lower(const char *str) { if (str == NULL) { return NULL; } size_t length = strnlen(str, PATH_MAX); return g_utf8_strdown(str, length); } char *string_to_upper(const char *str) { if (str == NULL) { return NULL; } size_t length = strnlen(str, PATH_MAX); return g_utf8_strup(str, length); } char *c_strcasestr(const char *haystack, const char *needle, int max_scan_len) { if (!haystack || !needle || max_scan_len <= 0) return NULL; size_t needle_len = strnlen(needle, max_scan_len); if (needle_len == 0) return (char *)haystack; size_t haystack_len = strnlen(haystack, max_scan_len); if (needle_len > haystack_len) return NULL; for (size_t i = 0; i <= haystack_len - needle_len; i++) { if (strncasecmp(&haystack[i], needle, needle_len) == 0) { return (char *)(haystack + i); } } return NULL; } int match_regex(const regex_t *regex, const char *ext) { if (regex == NULL || ext == NULL) { fprintf(stderr, "Invalid arguments\n"); return 1; } regmatch_t pmatch[1]; int ret = regexec(regex, ext, 1, pmatch, 0); if (ret == REG_NOMATCH) { return 1; } else if (ret == 0) { return 0; } else { fprintf(stderr, "match_regex: Regex match failed"); return 1; } } bool is_valid_utf8(const char *str, size_t len) { size_t i = 0; while (i < len) { unsigned char c = str[i]; if (c <= 0x7F) // 1-byte ASCII character { i++; } else if ((c & 0xE0) == 0xC0) // 2-byte sequence { if (i + 1 >= len || (str[i + 1] & 0xC0) != 0x80) return false; i += 2; } else if ((c & 0xF0) == 0xE0) // 3-byte sequence { if (i + 2 >= len || (str[i + 1] & 0xC0) != 0x80 || (str[i + 2] & 0xC0) != 0x80) return false; i += 3; } else if ((c & 0xF8) == 0xF0) // 4-byte sequence { if (i + 3 >= len || (str[i + 1] & 0xC0) != 0x80 || (str[i + 2] & 0xC0) != 0x80 || (str[i + 3] & 0xC0) != 0x80) return false; i += 4; } else { return false; // Invalid UTF-8 } } return true; } void extract_extension(const char *filename, size_t ext_size, char *ext) { if (!filename || !ext || ext_size == 0) { if (ext && ext_size > 0) ext[0] = '\0'; return; } size_t length = strnlen(filename, PATH_MAX); // Find the last '.' character in the filename const char *dot = NULL; for (size_t i = 0; i < length; i++) { if (filename[i] == '.') { dot = &filename[i]; } } // If no dot was found, there's no extension if (!dot || dot == filename + length - 1) { ext[0] = '\0'; // No extension found return; } size_t i = 0, j = 0; size_t dot_pos = dot - filename + 1; // Copy the extension while checking for UTF-8 validity while (dot_pos + i < length && filename[dot_pos + i] != '\0' && j < ext_size - 1) { size_t char_size = 1; // Default to 1 byte (ASCII) unsigned char c = filename[dot_pos + i]; if ((c & 0x80) != 0) // Check if the character is multi-byte { if ((c & 0xE0) == 0xC0) // 2-byte sequence char_size = 2; else if ((c & 0xF0) == 0xE0) // 3-byte sequence char_size = 3; else if ((c & 0xF8) == 0xF0) // 4-byte sequence char_size = 4; else { break; // Invalid UTF-8 start byte } } // Ensure we don't overflow the destination buffer if (j + char_size >= ext_size) break; // Check if the character is valid UTF-8 if (is_valid_utf8(&filename[dot_pos + i], char_size)) { // Copy the character to the extension buffer memcpy(ext + j, filename + dot_pos + i, char_size); j += char_size; i += char_size; } else { break; // Invalid UTF-8, stop copying } } // Null-terminate the extension ext[j] = '\0'; } int path_ends_with(const char *str, const char *suffix) { size_t length = strnlen(str, PATH_MAX); size_t suffixLength = strnlen(suffix, PATH_MAX); if (suffixLength > length) { return 0; } const char *str_suffix = str + (length - suffixLength); return strcmp(str_suffix, suffix) == 0; } int path_starts_with(const char *str, const char *prefix) { size_t length = strnlen(str, PATH_MAX); size_t prefixLength = strnlen(prefix, PATH_MAX); if (prefixLength > length) { return 0; } return strncmp(str, prefix, prefixLength) == 0; } void trim(char *str, int max_len) { if (!str || max_len <= 0) { return; } // Find start (skip leading whitespace) char *start = str; while (*start && isspace(*start)) { start++; } // Handle case where string is all whitespace or empty size_t len = strnlen(start, max_len - (start - str)); if (len == 0) { str[0] = '\0'; return; } // Find end (skip trailing whitespace) char *end = start + len - 1; while (end >= start && isspace(*end)) { end--; } // Null terminate *(end + 1) = '\0'; // Move trimmed string to beginning if needed if (start != str) { size_t trimmed_len = end - start + 1; memmove(str, start, trimmed_len + 1); // +1 for null terminator } } const char *get_home_path(void) { struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_dir) { return pw->pw_dir; } return NULL; } char *get_config_path(void) { char *config_path = malloc(PATH_MAX); if (!config_path) return NULL; const char *xdg_config = getenv("XDG_CONFIG_HOME"); if (xdg_config) { snprintf(config_path, PATH_MAX, "%s/kew", xdg_config); } else { const char *home = get_home_path(); if (home) { #ifdef __APPLE__ snprintf(config_path, PATH_MAX, "%s/Library/Preferences/kew", home); #else snprintf(config_path, PATH_MAX, "%s/.config/kew", home); #endif } else { struct passwd *pw = getpwuid(getuid()); if (pw) { #ifdef __APPLE__ snprintf(config_path, PATH_MAX, "%s/Library/Preferences/kew", pw->pw_dir); #else snprintf(config_path, PATH_MAX, "%s/.config/kew", pw->pw_dir); #endif } else { free(config_path); return NULL; } } } return config_path; } char *get_prefs_path(void) { char *prefs_path = malloc(PATH_MAX); if (!prefs_path) return NULL; const char *xdg_state = getenv("XDG_STATE_HOME"); if (xdg_state) { snprintf(prefs_path, PATH_MAX, "%s", xdg_state); } else { const char *home = get_home_path(); if (home) { #ifdef __APPLE__ snprintf(prefs_path, PATH_MAX, "%s/Library/Application Support", home); #else snprintf(prefs_path, PATH_MAX, "%s/.local/state", home); #endif } else { struct passwd *pw = getpwuid(getuid()); if (pw) { #ifdef __APPLE__ snprintf(prefs_path, PATH_MAX, "%s/Library/Application Support", pw->pw_dir); #else snprintf(prefs_path, PATH_MAX, "%s/.local/state", pw->pw_dir); #endif } else { free(prefs_path); return NULL; } } } return prefs_path; } bool is_valid_filename(const char *filename) { // Check for path traversal patterns if (strstr(filename, "..") != NULL) { return false; } // Check for path separators (works for UTF-8) if (strchr(filename, '/') != NULL || strchr(filename, '\\') != NULL) { return false; } // Don't allow starting with dot (hidden files) if (filename[0] == '.') { return false; } // Allow everything else (including UTF-8 Chinese characters) return true; } char *get_file_path(const char *filename) { if (filename == NULL || !is_valid_filename(filename)) { return NULL; } if (filename[0] == '.') { return NULL; } char *configdir = get_config_path(); if (configdir == NULL) { return NULL; } size_t configdir_length = strnlen(configdir, PATH_MAX); size_t filename_length = strnlen(filename, PATH_MAX); size_t filepath_length = configdir_length + 1 + filename_length + 1; if (filepath_length > PATH_MAX) { free(configdir); return NULL; } char *filepath = (char *)malloc(filepath_length); if (filepath == NULL) { free(configdir); return NULL; } snprintf(filepath, filepath_length, "%s/%s", configdir, filename); free(configdir); return filepath; } void remove_unneeded_chars(char *str, int length) { // Do not remove characters if filename only contains digits bool stringContainsLetters = false; for (int i = 0; str[i] != '\0'; i++) { if (!isdigit(str[i])) { stringContainsLetters = true; } } if (!stringContainsLetters) { return; } for (int i = 0; i < 3 && str[i] != '\0' && str[i] != ' '; i++) { if (isdigit(str[i]) || str[i] == '.' || str[i] == '-' || str[i] == ' ') { int j; for (j = i; str[j] != '\0'; j++) { str[j] = str[j + 1]; } str[j] = '\0'; i--; // Decrement i to re-check the current index length--; } } // Remove hyphens and underscores from filename for (int i = 0; str[i] != '\0'; i++) { // Only remove if there are no spaces around if ((str[i] == '-' || str[i] == '_') && (i > 0 && i < length && str[i - 1] != ' ' && str[i + 1] != ' ')) { str[i] = ' '; } } } void shorten_string(char *str, size_t max_length) { size_t length = strnlen(str, max_length + 2); if (length > max_length) { str[max_length] = '\0'; } } void print_blank_spaces(int num_spaces) { if (num_spaces < 1) return; printf("%*s", num_spaces, " "); } int get_number(const char *str) { char *endptr; long value = strtol(str, &endptr, 10); if (value < INT_MIN || value > INT_MAX) { return 0; } return (int)value; } float get_float(const char *str) { char *endptr; float value = strtof(str, &endptr); if (str == endptr) { return 0.0f; } if (isnan(value) || isinf(value) || value < -FLT_MAX || value > FLT_MAX) { return 0.0f; } return value; } int copy_file(const char *src, const char *dst) { // Validate inputs if (!src || !dst) { return -1; } // Check if source and destination are the same struct stat src_stat, dst_stat; if (stat(src, &src_stat) != 0) { return -1; } // Don't copy if destination exists and is the same file (same inode) if (stat(dst, &dst_stat) == 0) { if (src_stat.st_dev == dst_stat.st_dev && src_stat.st_ino == dst_stat.st_ino) { return -1; // Same file } } // Don't copy directories, symlinks, or special files if (!S_ISREG(src_stat.st_mode)) { return -1; } // Check file size is reasonable (prevent copying huge files) if (src_stat.st_size > 10 * 1024 * 1024) { // 10MB limit for theme files return -1; } // Open source file int src_fd = open(src, O_RDONLY); if (src_fd < 0) { return -1; } // Create destination with user read/write permissions int dst_fd = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0600); if (dst_fd < 0) { // If file exists, try to open it (but don't use O_EXCL) dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (dst_fd < 0) { close(src_fd); return -1; } } // Copy data in chunks char buffer[8192]; ssize_t bytes_read, bytes_written; ssize_t total_written = 0; while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) { bytes_written = write(dst_fd, buffer, bytes_read); if (bytes_written != bytes_read) { close(src_fd); close(dst_fd); unlink(dst); // Remove partial file on error return -1; } total_written += bytes_written; // Sanity check: make sure we're not writing more than expected if (total_written > src_stat.st_size) { close(src_fd); close(dst_fd); unlink(dst); return -1; } } if (bytes_read < 0) { close(src_fd); close(dst_fd); unlink(dst); // Remove partial file on error return -1; } // Sync to disk before closing if (fsync(dst_fd) != 0) { close(src_fd); close(dst_fd); unlink(dst); return -1; } close(src_fd); close(dst_fd); return 0; } int get_number_from_string(const char *str) { char *endptr; long value = strtol(str, &endptr, 10); if (*endptr != '\0') { return 0; } if (value < 0 || value > INT_MAX) { return 0; } return (int)value; } kew/src/utils/utils.h000066400000000000000000000031021512074754200151070ustar00rootroot00000000000000/** * @file utils.h * @brief General-purpose utility functions and helpers. * * Provides miscellaneous helpers used across modules such as * string operations, math functions, or logging utilities. */ #ifndef UTILS_H #define UTILS_H #include #include #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #ifndef __USE_POSIX #define __USE_POSIX #endif #ifndef PATH_MAX #define PATH_MAX 4096 #endif int get_random_number(int min, int max); int get_number(const char *str); int copy_file(const char *src, const char *dst); int get_number_from_string(const char *str); int match_regex(const regex_t *regex, const char *ext); int path_ends_with(const char *str, const char *suffix); int path_starts_with(const char *str, const char *prefix); char *get_file_path(const char *filename); char *string_to_upper(const char *str); char *string_to_lower(const char *str); char *utf8_strstr(const char *haystack, const char *needle); char *c_strcasestr(const char *haystack, const char *needle, int max_scan_len); char *get_config_path(void); char *get_prefs_path(void); const char *get_home_path(void); void c_sleep(int milliseconds); void c_usleep(int microseconds); void c_strcpy(char *dest, const char *src, size_t dest_size); void extract_extension(const char *filename, size_t num_chars, char *ext); void trim(char *str, int max_len); void remove_unneeded_chars(char *str, int length); void shorten_string(char *str, size_t max_length); void print_blank_spaces(int num_spaces); float get_float(const char *str); gint64 get_length_in_micro_sec(double duration); #endif kew/themes/000077500000000000000000000000001512074754200131405ustar00rootroot00000000000000kew/themes/army.theme000066400000000000000000000022731512074754200151400ustar00rootroot00000000000000# ============================================ # ARMY # ============================================ [theme] name=Army author=Ravachol # Core colors accent=#a89074 text=#d6cbb8 textDim=#b59d82 textMuted=#786b5a # General Colors logo=#786b5a header=#786b5a footer=#786b5a help=#bca890 link=#b59d82 nowplaying=#a89074 # Playlist View playlist.rownum=#786b5a playlist.title=#d6cbb8 playlist.playing=#b8a085 # Track View trackview.title=#d6cbb8 trackview.artist=#a89074 trackview.album=#b59d82 trackview.year=#b59d82 trackview.time=#bca890 trackview.visualizer=#2b2721 trackview.lyrics=#d6cbb8 # Library View library.artist=#786b5a library.album=#c8bfad library.track=#c8bfad library.enqueued=#8a8a70 library.playing=#b8a085 # Search search.label=#786b5a search.query=#a89074 search.result=#d6cbb8 search.enqueued=#8a8a70 search.playing=#b8a085 # Progress progress.filled=#786b5a progress.empty=#1f1d1a progress.elapsed=#3a3630 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/bunker.theme000066400000000000000000000022771512074754200154620ustar00rootroot00000000000000# ============================================ # BUNKER # ============================================ [theme] name=Bunker author=Ravachol # Core colors accent=#7c8c9e text=#c8d3dc textDim=#8b99a8 textMuted=#5a6b7d # General Colors logo=#5a6b7d header=#5a6b7d footer=#5a6b7d help=#94a7bc link=#a8b8c8 nowplaying=#7c8c9e # Playlist View playlist.rownum=#5a6b7d playlist.title=#c8d3dc playlist.playing=#9daec0 # Track View trackview.title=#c8d3dc trackview.artist=#7c8c9e trackview.album=#8b99a8 trackview.year=#8b99a8 trackview.time=#94a7bc trackview.visualizer=#2a3441 trackview.lyrics=#c8d3dc # Library View library.artist=#5a6b7d library.album=#b4c1ce library.track=#b4c1ce library.enqueued=#8b99a8 library.playing=#9daec0 # Search search.label=#5a6b7d search.query=#7c8c9e search.result=#c8d3dc search.enqueued=#8b99a8 search.playing=#9daec0 # Progress progress.filled=#3d4a58 progress.empty=#1f2830 progress.elapsed=#5a6b7d # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/catpuccin.theme000066400000000000000000000027631512074754200161450ustar00rootroot00000000000000# ============================================ # 2. CATPPUCCIN MOCHA (Warm & Cozy) # ============================================ [theme] name=Catppuccin Mocha author=pixel-peeper # Core colors (Catppuccin Mocha palette) accent=#b4befe # Lavender text=#cdd6f4 # Text textDim=#a6adc8 # Subtext1 textMuted=#6c7086 # Subtext0 # General Colors logo=#b4befe # Lavender header=#b4befe # Lavender footer=#1e1e2e # Base help=#a6adc8 # Subtext1 link=#89b4fa # Blue nowplaying=#b4befe # Lavender # Playlist View playlist.rownum=#6c7086 # Subtext0 playlist.title=#cdd6f4 # Text playlist.playing=#b4befe # Lavender # Track View trackview.title=#cdd6f4 # Text trackview.artist=#a6adc8 # Subtext1 trackview.album=#89b4fa # Blue trackview.year=#6c7086 # Subtext0 trackview.time=#b4befe # Lavender trackview.visualizer=#a6adc8 # Subtext1 trackview.lyrics=#cdd6f4 # Text # Library View library.artist=#b4befe # Lavender library.album=#a6adc8 # Subtext1 library.track=#cdd6f4 # Text library.enqueued=#a6adc8 # Subtext1 library.playing=#b4befe # Lavender # Search search.label=#89b4fa # Blue search.query=#cdd6f4 # Text search.result=#cdd6f4 # Text search.enqueued=#a6adc8 # Subtext1 search.playing=#b4befe # Lavender # Progress progress.filled=#b4befe # Lavender progress.empty=#313244 # Surface1 progress.elapsed=#b4befe # Lavender # Status status.info=-1 # Default foreground status.warning=#f9e2af # Yellow status.error=#f38ba8 # Red status.success=#a6e3a1 # Green kew/themes/cyberpunk.theme000066400000000000000000000023251512074754200161700ustar00rootroot00000000000000# ============================================ # 1. CYBERPUNK (Neon Purple) # ============================================ [theme] name=Cyberpunk author=Ravachol # Core colors accent=#6957ce text=#e0e0ff textDim=#5994ce textMuted=#3a4e93 # General Colors logo=#5658b6 header=#2a2e5a footer=#2a2e5a help=#5994ce link=#ff00ff nowplaying=#ff0080 # Playlist View playlist.rownum=#3a4e93 playlist.title=#b8b8ff playlist.playing=#6957ce # Track View trackview.title=#e0e0ff trackview.artist=#5994ce trackview.album=#5658b6 trackview.year=#3a4e93 trackview.time=#6957ce trackview.visualizer=#5994ce trackview.lyrics=#e0e0ff # Library View library.artist=#6957ce library.album=#5994ce library.track=#b8b8ff library.enqueued=#5994ce library.playing=#6957ce # Search search.label=#5658b6 search.query=#e0e0ff search.result=#b8b8ff search.enqueued=#5994ce search.playing=#6957ce # Progress progress.filled=#6957ce progress.empty=#1a1e3a progress.elapsed=#6957ce # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/default.theme000066400000000000000000000037261512074754200156200ustar00rootroot00000000000000# Default kew Theme # # ANSI 16-Color Palette Theme # This theme will be used if you choose terminal profile colors (press i in kew to cycle color settings) # # The available colors are: # # 0: Black # 1: Red # 2: Green # 3: Yellow # 4: Blue # 5: Magenta # 6: Cyan # 7: White # 8: Bright Black (Gray) # 9: Bright Red # 10: Bright Green # 11: Bright Yellow # 12: Bright Blue # 13: Bright Magenta # 14: Bright Cyan # 15: Bright White # -1: Default terminal foreground name=Default 16-color author=Ravachol # Core Colors accent=13 # Bright Magenta text=-1 # Default terminal foreground textDim=8 # Bright Black / Gray textMuted=7 # White (dim) # General Colors logo=6 # Cyan header=6 # Cyan footer=#787878 # The old school gray, this works on most terminals help=6 # Cyan link=14 # Bright Cyan nowplaying=-1 # Default foreground # Playlist View playlist.rownum=8 # Bright Black / Gray playlist.title=-1 # Default foreground playlist.playing=6 # Cyan # Track View trackview.title=-1 # Default foreground trackview.artist=-1 # Default foreground trackview.album=-1 # Default foreground trackview.year=-1 # Default foreground trackview.time=-1 # Default foreground trackview.visualizer=-1 # Default foreground trackview.lyrics=-1 # Default foreground # Library View library.artist=6 # Cyan library.album=6 # Cyan library.track=-1 # Default foreground library.enqueued=6 # Cyan library.playing=6 # Cyan # Search search.label=6 # Cyan search.query=-1 # Default foreground search.result=-1 # Default foreground search.enqueued=14 # Bright Cyan search.playing=6 # Cyan # Progress progress.filled=8 # Bright Black / Gray progress.empty=0 # Black progress.elapsed=8 # Bright Black / Gray # Status status.info=-1 # Default foreground status.warning=11 # Bright Yellow status.error=9 # Bright Red status.success=10 # Bright Green kew/themes/forest.theme000066400000000000000000000023171512074754200154710ustar00rootroot00000000000000# ============================================ # FOREST (Natural Green) # ============================================ [theme] name=Forest author=Ravachol # Core colors accent=#5fb3a1 text=#d8ebe4 textDim=#9dc4bc textMuted=#4a7c70 # General Colors logo=#4a7c70 header=#4a7c70 footer=#4a7c70 help=#8fbc94 link=#87b96b nowplaying=#e76f51 # Playlist View playlist.rownum=#4a7c70 playlist.title=#d8ebe4 playlist.playing=#5fb3a1 # Track View trackview.title=#d8ebe4 trackview.artist=#5fb3a1 trackview.album=#8fbc94 trackview.year=#8fbc94 trackview.time=#f4a261 trackview.visualizer=#2d4a43 trackview.lyrics=#d8ebe4 # Library View library.artist=#4a7c70 library.album=#c8dcd1 library.track=#c8dcd1 library.enqueued=#87b96b library.playing=#5fb3a1 # Search search.label=#4a7c70 search.query=#5fb3a1 search.result=#d8ebe4 search.enqueued=#87b96b search.playing=#5fb3a1 # Progress progress.filled=#2d4a43 progress.empty=#1a2e29 progress.elapsed=#6a9a8a # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/gruvbox.theme000066400000000000000000000023161512074754200156620ustar00rootroot00000000000000# ============================================ # GRUVBOX (Warm Retro) # ============================================ [theme] name=Gruvbox author=Ravachol # Core colors accent=#fabd2f text=#dbc4a1 textDim=#d5c4a1 textMuted=#928374 # General Colors logo=#928374 header=#928374 footer=#928374 help=#83a598 link=#98971a nowplaying=#d79921 # Playlist View playlist.rownum=#928374 playlist.title=#dbc4a1 playlist.playing=#ebdbb2 # Track View trackview.title=#ebdbb2 trackview.artist=#bdae93 trackview.album=#a89984 trackview.year=#a89984 trackview.time=#a89984 trackview.visualizer=#665c54 trackview.lyrics=#ebdbb2 # Library View library.artist=#928374 library.album=#dbc4a1 library.track=#dbc4a1 library.enqueued=#98971a library.playing=#b8bb26 # Search search.label=#928374 search.query=#fe8019 search.result=#dbc4a1 search.enqueued=#98971a search.playing=#b8bb26 # Progress progress.filled=#665c54 progress.empty=#3c3836 progress.elapsed=#a89984 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/gruvboxlight.theme000066400000000000000000000023401512074754200167070ustar00rootroot00000000000000# ============================================ # GRUVBOX LIGHT (Warm Retro Light) # ============================================ [theme] name=Gruvbox Light author=Ravachol # Core colors accent=#d65d0e text=#3c3836 textDim=#504945 textMuted=#7c6f64 # General Colors logo=#7c6f64 header=#7c6f64 footer=#7c6f64 help=#076678 link=#79740e nowplaying=#b57614 # Playlist View playlist.rownum=#7c6f64 playlist.title=#3c3836 playlist.playing=#af3a03 # Track View trackview.title=#282828 trackview.artist=#9d0006 trackview.album=#665c54 trackview.year=#665c54 trackview.time=#665c54 trackview.visualizer=#d5c4a1 trackview.lyrics=#282828 # Library View library.artist=#7c6f64 library.album=#3c3836 library.track=#3c3836 library.enqueued=#79740e library.playing=#8f3f71 # Search search.label=#7c6f64 search.query=#af3a03 search.result=#3c3836 search.enqueued=#79740e search.playing=#8f3f71 # Progress progress.filled=#d5c4a1 progress.empty=#fbf1c7 progress.elapsed=#665c54 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/kew-theme-pack1.txt000066400000000000000000000006651512074754200165730ustar00rootroot00000000000000kew themes pack 1. Included in this pack: Dark Themes marianatrench monochrome gruvbox synthwave midnight forest tokyonight bunker army purple pewter kew cyberpunk Light Themes solarizedlight gruvboxlight paper To apply, run: kew theme Or cycle by pressing 't'. These should be available under /themes here, in case you need to restore them to defaults: https://codeberg.org/ravachol/kew https://github.com/ravachol/kew kew/themes/kew-theme-pack2.txt000066400000000000000000000004341512074754200165660ustar00rootroot00000000000000kew themes pack 2. Included in this pack: neutral catpucchin To apply, run: kew theme Or cycle by pressing 't'. These should be available under /themes here, in case you need to restore them to defaults: https://codeberg.org/ravachol/kew https://github.com/ravachol/kew kew/themes/marianatrench.theme000066400000000000000000000023171512074754200170030ustar00rootroot00000000000000# ============================================ # MARIANA TRENCH # ============================================ [theme] name=Mariana Trench author=Ravachol # Core colors accent=#7a92a8 text=#c8d5de textDim=#8599ab textMuted=#5a6e7d # General Colors logo=#5a6e7d header=#5a6e7d footer=#5a6e7d help=#92a5b5 link=#8599ab nowplaying=#7a92a8 # Playlist View playlist.rownum=#5a6e7d playlist.title=#c8d5de playlist.playing=#8fa4b6 # Track View trackview.title=#c8d5de trackview.artist=#7a92a8 trackview.album=#8599ab trackview.year=#8599ab trackview.time=#92a5b5 trackview.visualizer=#212c35 trackview.lyrics=#c8d5de # Library View library.artist=#5a6e7d library.album=#b5c4ce library.track=#b5c4ce library.enqueued=#7a9487 library.playing=#8fa4b6 # Search search.label=#5a6e7d search.query=#7a92a8 search.result=#c8d5de search.enqueued=#7a9487 search.playing=#8fa4b6 # Progress progress.filled=#2f3d48 progress.empty=#1a2329 progress.elapsed=#2f3d48 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/midnight.theme000066400000000000000000000023221512074754200157660ustar00rootroot00000000000000============================================ 7. MIDNIGHT (Pure Minimalism) ============================================ [theme] name=Midnight author=Ravachol # Core colors accent=#ffffff text=#cccccc textDim=#888888 textMuted=#444444 # General Colors logo=#444444 header=#444444 footer=#444444 help=#888888 link=#aaaaaa nowplaying=#ffffff # Playlist View playlist.rownum=#444444 playlist.title=#cccccc playlist.playing=#ffffff # Track View trackview.title=#ffffff trackview.artist=#ffffff trackview.album=#888888 trackview.year=#888888 trackview.time=#cccccc trackview.visualizer=#222222 trackview.lyrics=#ffffff # Library View library.artist=#ffffff library.album=#888888 library.track=#cccccc library.enqueued=#aaaaaa library.playing=#ffffff # Search search.label=#888888 search.query=#cccccc search.result=#cccccc search.enqueued=#aaaaaa search.playing=#ffffff # Progress progress.filled=#ffffff progress.empty=#222222 progress.elapsed=#ffffff # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/monochrome.theme000066400000000000000000000023321512074754200163320ustar00rootroot00000000000000# ============================================ # MONOCHROME (Distraction-Free) # ============================================ [theme] name=Monochrome author=Ravachol # Core colors accent=#ffffff text=#e0e0e0 textDim=#b0b0b0 textMuted=#808080 # General Colors logo=#808080 header=#808080 footer=#808080 help=#c0c0c0 link=#b0b0b0 nowplaying=#ffffff # Playlist View playlist.rownum=#808080 playlist.title=#f0f0f0 playlist.playing=#ffffff # Track View trackview.title=#f0f0f0 trackview.artist=#ffffff trackview.album=#c0c0c0 trackview.year=#c0c0c0 trackview.time=#d0d0d0 trackview.visualizer=#f0f0f0 trackview.lyrics=#f0f0f0 # Library View library.artist=#808080 library.album=#e0e0e0 library.track=#e0e0e0 library.enqueued=#b0b0b0 library.playing=#ffffff # Search search.label=#808080 search.query=#ffffff search.result=#e0e0e0 search.enqueued=#b0b0b0 search.playing=#ffffff # Progress progress.filled=#202020 progress.empty=#101010 progress.elapsed=#707070 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/neutral.theme000066400000000000000000000014471512074754200156440ustar00rootroot00000000000000# Neutral kew Theme # # This theme uses only the default text color # name=Neutral author=Ravachol # Core Colors accent=-1 text=-1 textDim=-1 textMuted=-1 # General Colors logo=-1 header=-1 footer=-1 help=-1 link=-1 nowplaying=-1 # Playlist View playlist.rownum=-1 playlist.title=-1 playlist.playing=-1 # Track View trackview.title=-1 trackview.artist=-1 trackview.album=-1 trackview.year=-1 trackview.time=-1 trackview.visualizer=-1 trackview.lyrics=-1 # Library View library.artist=-1 library.album=-1 library.track=-1 library.enqueued=-1 library.playing=-1 # Search search.label=-1 search.query=-1 search.result=-1 search.enqueued=-1 search.playing=-1 # Progress progress.filled=-1 progress.empty=0 progress.elapsed=-1 # Status status.info=-1 status.warning=-1 status.error=-1 status.success=-1 kew/themes/paper.theme000066400000000000000000000023201512074754200152700ustar00rootroot00000000000000# ============================================ # PAPER (Soft Light Theme) # ============================================ [theme] name=Paper author=Ravachol # Core colors accent=#6b7f96 text=#5a5a5a textDim=#707070 textMuted=#8a8a8a # General Colors logo=#8a8a8a header=#8a8a8a footer=#8a8a8a help=#6b8f8f link=#7a8a70 nowplaying=#6b7f96 # Playlist View playlist.rownum=#8a8a8a playlist.title=#4a4a4a playlist.playing=#8a7a6b # Track View trackview.title=#4a4a4a trackview.artist=#6b7f96 trackview.album=#7a8a8a trackview.year=#7a8a8a trackview.time=#7a8a8a trackview.visualizer=#e8e8e8 trackview.lyrics=#4a4a4a # Library View library.artist=#8a8a8a library.album=#5a5a5a library.track=#5a5a5a library.enqueued=#7a8a70 library.playing=#8a7a6b # Search search.label=#8a8a8a search.query=#6b7f96 search.result=#5a5a5a search.enqueued=#7a8a70 search.playing=#8a7a6b # Progress progress.filled=#d0d0d0 progress.empty=#f5f5f5 progress.elapsed=#8a8a8a # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/pewter.theme000066400000000000000000000023221512074754200154710ustar00rootroot00000000000000# ============================================ # PEWTER (Blue-Gray Subtle) # ============================================ [theme] name=Pewter author=Ravachol # Core colors accent=#7a8a96 text=#c5ced6 textDim=#8d9ba5 textMuted=#556169 # General Colors logo=#556169 header=#556169 footer=#556169 help=#94a1ab link=#8d9ba5 nowplaying=#7a8a96 # Playlist View playlist.rownum=#556169 playlist.title=#c5ced6 playlist.playing=#8c9aa5 # Track View trackview.title=#c5ced6 trackview.artist=#7a8a96 trackview.album=#8d9ba5 trackview.year=#8d9ba5 trackview.time=#94a1ab trackview.visualizer=#1f262b trackview.lyrics=#c5ced6 # Library View library.artist=#556169 library.album=#b0b9c1 library.track=#b0b9c1 library.enqueued=#7a8f7f library.playing=#8c9aa5 # Search search.label=#556169 search.query=#7a8a96 search.result=#c5ced6 search.enqueued=#7a8f7f search.playing=#8c9aa5 # Progress progress.filled=#2d363c progress.empty=#181e22 progress.elapsed=#556169 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/purple.theme000066400000000000000000000022771512074754200155030ustar00rootroot00000000000000# ============================================ # Purple # ============================================ [theme] name=Purple author=Ravachol # Core colors accent=#9d8ba8 text=#d8d0dc textDim=#a896b0 textMuted=#6d5f78 # General Colors logo=#6d5f78 header=#6d5f78 footer=#6d5f78 help=#b1a0bc link=#a896b0 nowplaying=#9d8ba8 # Playlist View playlist.rownum=#6d5f78 playlist.title=#d8d0dc playlist.playing=#b09cbd # Track View trackview.title=#d8d0dc trackview.artist=#9d8ba8 trackview.album=#a896b0 trackview.year=#a896b0 trackview.time=#b09cbd trackview.visualizer=#2a2531 trackview.lyrics=#d8d0dc # Library View library.artist=#6d5f78 library.album=#c5bcc9 library.track=#c5bcc9 library.enqueued=#8a9a87 library.playing=#b09cbd # Search search.label=#6d5f78 search.query=#9d8ba8 search.result=#d8d0dc search.enqueued=#8a9a87 search.playing=#b09cbd # Progress progress.filled=#362f3d progress.empty=#1d1921 progress.elapsed=#6d5f78 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/ravachol.theme000066400000000000000000000022771512074754200157730ustar00rootroot00000000000000# ============================================ # RAVACHOL'S THEME # ============================================ [theme] name=Ravachol's Theme author=Ravachol # Core colors accent=#e6607a text=#e0e0e0 textDim=#e6607a textMuted=#de2b4d # General Colors logo=#de2b4d header=#de2b4d footer=#5c5c5c help=#787878 link=#e6607a nowplaying=#e6607a # Playlist View playlist.rownum=#de2b4d playlist.title=#e0e0e0 playlist.playing=#e6607a # Track View trackview.title=#de2b4d trackview.artist=-1 trackview.album=-1 trackview.year=-1 trackview.time=-1 trackview.visualizer=#de2b4d trackview.lyrics=#e0e0e0 # Library View library.artist=#de2b4d library.album=#e0e0e0 library.track=#e0e0e0 library.enqueued=#d82953 library.playing=#e6607a # Search search.label=#de2b4d search.query=#e6607a search.result=#e0e0e0 search.enqueued=#d82953 search.playing=#e6607a # Progress progress.filled=#de2b4d progress.empty=#e6607a progress.elapsed=#de2b4d # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/solarizedlight.theme000066400000000000000000000023471512074754200172160ustar00rootroot00000000000000# ============================================ # SOLARIZED LIGHT (High Contrast Light) # ============================================ [theme] name=Solarized Light author=Ravachol # Core colors accent=#268bd2 text=#657b83 textDim=#839496 textMuted=#93a1a1 # General Colors logo=#93a1a1 header=#93a1a1 footer=#93a1a1 help=#2aa198 link=#859900 nowplaying=#268bd2 # Playlist View playlist.rownum=#93a1a1 playlist.title=#586e75 playlist.playing=#cb4b16 # Track View trackview.title=#586e75 trackview.artist=#268bd2 trackview.album=#2aa198 trackview.year=#2aa198 trackview.time=#b58900 trackview.visualizer=#eee8d5 trackview.lyrics=#586e75 # Library View library.artist=#93a1a1 library.album=#657b83 library.track=#657b83 library.enqueued=#859900 library.playing=#cb4b16 # Search search.label=#93a1a1 search.query=#268bd2 search.result=#657b83 search.enqueued=#859900 search.playing=#cb4b16 # Progress progress.filled=#eee8d5 progress.empty=#fdf6e3 progress.elapsed=#93a1a1 # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/synthwave.theme000066400000000000000000000023241512074754200162150ustar00rootroot00000000000000# ============================================ # 2. SYNTHWAVE (Retro 80s) # ============================================ [theme] name=Synthwave author=Ravachol # Core colors accent=#ff6ec7 text=#9a9a9a textDim=#bd93f9 textMuted=#6272a4 # General Colors logo=#6272a4 header=#6272a4 footer=#6272a4 help=#8be9fd link=#ff6ec7 nowplaying=#ff6ec7 # Playlist View playlist.rownum=#6272a4 playlist.title=#9a9a9a playlist.playing=#ff6ec7 # Track View trackview.title=#ff6ec7 trackview.artist=#bd93f9 trackview.album=#bd93f9 trackview.year=#bd93f9 trackview.time=#44475a trackview.visualizer=#44475a trackview.lyrics=#ff6ec7 # Library View library.artist=#6272a4 library.album=#bd93f9 library.track=#9a9a9a library.enqueued=#bd93f9 library.playing=#ff6ec7 # Search search.label=#6272a4 search.query=#50fa7b search.result=#9a9a9a search.enqueued=#bd93f9 search.playing=#ff6ec7 # Progress progress.filled=#ff6ec7 progress.empty=#44475a progress.elapsed=#ffb86c # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals kew/themes/tokyonight.theme000066400000000000000000000023321512074754200163630ustar00rootroot00000000000000# ============================================ # 4. TOKYO NIGHT (Modern Dark) # ============================================ [theme] name=Tokyo Night author=Ravachol # Core colors accent=#7aa2f7 text=#c0caf5 textDim=#9aa5ce textMuted=#565f89 # General Colors logo=#565f89 header=#565f89 footer=#565f89 help=#7dcfff link=#9ece6a nowplaying=#ff9e64 # Playlist View playlist.rownum=#565f89 playlist.title=#c0caf5 playlist.playing=#7aa2f7 # Track View trackview.title=#c0caf5 trackview.artist=#7aa2f7 trackview.album=#bb9af7 trackview.year=#bb9af7 trackview.time=#7dcfff trackview.visualizer=#7aa2f7 trackview.lyrics=#c0caf5 # Library View library.artist=#7aa2f7 library.album=#bb9af7 library.track=#a9b1d6 library.enqueued=#7dcfff library.playing=#7aa2f7 # Search search.label=#bb9af7 search.query=#c0caf5 search.result=#a9b1d6 search.enqueued=#7dcfff search.playing=#7aa2f7 # Progress progress.filled=#7aa2f7 progress.empty=#292e42 progress.elapsed=#7dcfff # Status status.info=-1 # Default foreground status.warning=#787878 # The old school gray, this works on most terminals status.error=#787878 # The old school gray, this works on most terminals status.success=#787878 # The old school gray, this works on most terminals

\bu bI1D4 Cr+ eDM${P%v$-[kt6Iet IEPFX~hJ ZiYI,,_ qfRJPBeOZ%DTح1ɾ&r z` !mZlXj2ĩ[ XxZENS+I[N6IgL!Xx O$܄q8D p:RN=Ģ,亨ONY"RfO\H{Ns$sڴɑȌ Jt_Z\|+NqZ*r>Q &äU EBS5Y5;ui" |uG|k:Y^GsS;`a6b'b/v1 `󤴈wFʥضGG$ EM,C1RA?a j]13ЎTÄ@aFH/b?T(sTGD6d7wow[]TG{qosRIrwLCԨYhKj:DXĠD -@,V,S A͛C͛/?ěCt>B<㢩TS)gjE J`U5j`v tKLuCP9 `I7CpA8@|##!qjr^R@wD *TH0|ҲkItt@l@j4JhCJXb˜./vqqD7 He/loCh"x} `Hc=51227;wPHpCE۝Dyr4 %~WeqaZ Wpnt 5^R&YmUaG{\v H|> kNEJ[ gR [@_A[,%_$a ,KQp\EL)TeK+U`,6e `@OD4gMG*iSOY) .u傖Dl +h6A#.@BrU_wF%tFAA )x.j|_(WCLfM,@o^U jpPqODn߿k-Y ]\]t?IԂq,-ʟAk !l0s{vσ&*2!9 ŶO a. ,( nĞ" *p/N%,o{?X r۔©B1PD2E"'Ȳ4J!kE,/ &BZG KfH!Bԡ 2IXj61 TPxAM47GS%E Pҫs 8?TSaT8DWҮ5[:Ya-|emg)\jnh'V d6r8נ&Vka) ((.d*={KE Zw b B OHNcwJ,Fh\l Ddzmt4h<Rg|Vˁ BZk\])]4P)X-O BM! Nb)("Y]%8LL`."9+r/  (s hW"zDI# '.@YZp5x/VH₠l \1fStJM8  UCdЕbVȁ3g|p %Q:^:82Ħ [S>@i ! H⁀g%[A 8h u* x hY>,qQrHP  >P(D:}<*t(j<"PAo` i"@SxDp EO2@   QǒZpBvX򱛰d,|= T9HhrOu8=zA@Z ~gYJ@xԣE8m;ВVdk 5\`WRI xH`J d)%i!E\)dntp=~CS  ^ @b-(L5pYCA13/!n8/KEbE!*2pƲJ*dzt@DJw"DMYXX*dZq /?T@Qk~VC!hE yK@+޲fVbvj8`P 1JYT7ІyN9/VȲDi,uhc"Uj C1!M"5! ͕^~A`#f =(xŃ[U@ a>4S!:QB4HW" l?έiJQ ~Y籕4#[#^ ) -JcU5'neJµ&a)uXI~BRm= Jb񧘀bJ~^Nm$>괻dk>XhpDN¦ź#58ԪƮdb^ʬ϶0PVjRr A6HuU UB`m{ۉ$%\BG.rġ l?Q vȨB9SE%AlM\$4p5?2$bTiC1$IpyP NE}횲e%BESQEov UTQP}KBԝP4 /KQFb' 0.T1A?de_~f  2x@od @[NbE$*<\Rq{SъcnPCykf(n >GJRJVj70PA8%`>&*̖!wD]}wmE״{Ԁؘ29ͽ *ͮDb%C᭴F\ZU(yV~Ғ,|",nX~߭"m{oj F[P/+_WA` XX`qD6%%TnK4t 39 I1? %Z_ "Ti`Jhf(Oh Biij)r%"@,S$ < b" pbbńh)"AlTyy2h;` q`b/ b `"ךc) a8g1o%-pv:%(Js< Fs> qt dJDI!nj"z\JΦrJ% -~-@,%nj4 a A.R HFbxrR 1P~"!]Z~"XRhxδdyh"* *7,leI < 쀴ܣ("@`"(Dd<ÍB:c*DX\ YCޞj\G{熖)JbI"R+')t+)$iډ4e+ppDנ~qvZ x/o.5Rd .*^*EBODM%av %@ czH!̯lt MnTl>z!R@-N: a@"s78ΐV FE1BkE bZ@J~`h"D)Jn}*2ɂ+ +$*B aBk^IAY ΋E]l'.*"&$h#f{BrĩI'xE4"TLՂJPr2+Ķ(Z1AkI])EI Mh+A̴(:+"(K t%'%5s%!"a!GsBB#bH[4A$xìNf"LT\"R"|(Ws`X"$ P²K FaIWYBQ|TL̒_b-m%XҦ .b.U/CFxA0X w4)%r-4,%P$& ba4S"$fGJ B{Wm&6-#ts)O%~X8_`K 3:+4Ev.#"Rec%B8q*v-9*CTbhAp)CT,'A# BD(Lmqi?@;24h;""jJ(hJl,`_ps).olTꔎrc~$"(I-IA( ntWR@LUVLgװ$fm6LN!,2vW%N!#T>/.D&(R0%,zE"zWcWU<%sr|tq)ueoHMĪ-$Y}}q}7xW B(2EӪ*"B j*Dhȼ `*X TpxU"|I Vcڤ*"%Ёocj3Jv<ݏA k AbHD1Jug~Gp+lR!0WE%.'/k _f7V ' '` "# 8  PA X%FcF"%&oP/.DfpjPoY!"6c~=@C# g, DhharS"*\Bvl%P!|9q\g)7-քbCP, ڭ9K  6B}B~LdDȅL'΂2$;Dv2䰋 19m) X M!G5'Lίt3] d$iY&.X+'%Կ vT rjP|n-g{Y[MF"nU.Cra^_'^4-Uj:^Q v*:8V˜ R&*u"xC%<>0bI*r[BZѪ]d{E}XBUB `%2~8bb"#.! D<eEq"0  !2L\Q})&B<#L" !>U!48Ty$4}8fPal)(`N %9@mV4Jk tܾ N&%b1=+=&<V⸌D0[ZFy$`U| _ x G܉] /[ J % aM8 A5eKB4;T kroCKr4"̴*GdVhVlipn<dÒBIoĺЄOO${2@K <`qK`籧6%BR4Ͻ*VBfRb0"}'. $U$BZ@,!%{8U-k>jB(ߔ :V:f%]!.ȠxChG%|SUE*r%AdT0!'#4u hCYx`%5|Ȝ 9 "f9a0Z  b>5[u_P_CW=D~5^MHaIxՃYC!(`Z&"e4@!֐0!Y^A L \0!P59P=bRNIeT#g;s5Y f'&1QO DxT)O ٦K2@Ԛ i1Ii"(f{ PSA͗htpi hZ\(&zdAႭ^ krApՁ lKl k0,@H1_ua"E -$@OٚV@}uEd"ZBB.* UgOHMcђ۶M /0ؖVSԄL[ЄFM mq} L"߫ 1E3*z#d%OP IA,("]!HJ]ApUZ?5Z( ]\(!Ѽ[/3Ud?uҍ(3b#8pWhȭ HM#G0o anw]vS0 u!I s(QXHY @ Gw͇$[|5mldO 4D| JW ?]__$?!h$hK4TY! oAaׂ(A4A^· QB5APMLG10LF&d;>B$0$I24y!*]!,I`H: ^?K/#[V Q5G"D4{`ab  94ҮULu82P)AH$zAH}Ѵg?5"I$( 칋mn .PRgؒ(Z " e(6VTIA@FN[F2 (>ԥbUA@(Ik&e P"  w^Xk4f(4)\V')]!ZDF#@XBg2b,  3|9>f ؖ(MT*4A/eXLLzϤAO C^ĀPdvV<%Y.%GEyyz0B:O> gr>fHXi$WPB8q𜣳I_1 n_KpXvF0-شñi!5ZϜ" `!|rIᎇ-"Plj$ 0 GB;ݡ`K -t:2-BAј)?cf@ff.HEz3>Hrr6A1A #4!4C\p]FIYzBSgZZk^$1OtOdNh JDuT"2=gpxQhE$QKSph22AR%&*!%D&Y)QR o7!0Z$H3x)_:50Hvb7bBQ(`acAT FvaYU "@w?53Sa%t}| :z wG@|F=AXHj\ zQ9U2T=>DRT[@@,U0xjP/ SY gʇ%k(@uV"(h]L&,ˡ _CՎ-aVe%1KQuQ `#]b b r. gM!f1Pc(01"!(@s`bV0@"R٢c%[PI _@GUcP]߅` 2Wb;_{eYkkfb]+;31Hg# W&VR j)n4mi**l("|Hm\mQj)ݱDA)mn۫Զ_eԊlU2\hy.GAa*r5F![hw") )+(GIb?wC!21;!u mGY9'uwipf_~ ,9@ˬar)ȌosF\tS'I1fpa\tpAc&ª@m _a =@;uyv[`Q}Sy4!Kq峰@P}P0A\z[H aEY`7llxvCDу3aOLPbA,M99oqzh1m/x@gPPl   {$ BG,2}XgapG1ݡ qFW7S=ѩ1P8|m4R*6 Rl|e"nؿY(: dnfh-¬!o<,x|)+"E02 r-ٓ ,v]()OrQB&b~9X8}ڨ~eRگ! $MĠwr&sm~mv۰ܮnXq7Mm׍3Q O_ati-\e,:/Bw H@5WPoڻYT=s0cB1>9C0qd1SEP ї8+m4,1m0cӖe3= CmCqS.–95>%?Bc̻P=q8j7w8n.8c@q 0ϕZB@#o0.j pKI" m18 D&aܹI%g|rsT3ZsA9t@E.y^W,>e3q:&B]BR&WP'DB"pTDt0P-%m@m_16-0#RTI*HVNJ=drrnTNtd]MTr>$1JN~nzt+\$KpFtI@E{%]AE@ =0m"- ?"G/+QBQ ZpFM!b;3gUl&2Y3W@'~uݞUKcyZuCU::PD  T@5 `+l+b^PUv?2m!D!AYerηAj%!@xPP\e*K6 9c,{nU?5UQ| *w2 v f uER>u$E`#(P@D&aD P'M<( XqbB9P5Ga,Od(Y Iu" H#D9A (ŚL#d-SF\Xmج~ Esb^i$.u 0LpLk`,q 3K-~XQqo*;@< ?"t CAөʩ"%n>CVq%:HQG/4aR(#SM@'R(b<:@ȤR; -9빳K7i"Ψ2H-&j4!c/ % 0HLyLJ{TƨC,PHO 4xIbYA 6 W:s^2^yр`7T]Bh& )oyȃG fܩR&b:{m6'fw7YC N +]]@""i$P {Crk;)Z^Q`Vn? d P`Z䕝̟eSH^p{jH#%L . -| JȄ1![`N,$ nA:qnk r}-&6C(.Xwq[B!={I*Nɠ#8y;5W돤q{?8*W&+Z$ή"}# G?M^#` x@&HI&Bk8U{`薏悝=AP,A[l2/ 5 %E#hxCPLPgPC$bxD$&p&d %$GBj0$pJT=AU $4P**D% ,l v/PrLd ϡAńP'N!J_9JMh9BKn'4$@P2 >S*$!CM$t}rEF a JErRˉ*Pmr**gmݡ:@.ꤷr]>M*RK.܄VbEjD[.R%Az:0KQN*@AGBdO':QsK&Ntbr5[A{(} ;dq" ߗfմ uCQZ!. ic4zBC[:Eq#\ GG6'F|+i $pmHX]VdiNh яFU.'hddA.Q@Ye RC 1L* T:"%$LLͼ>Zq  q(!rn\MlM׼^ ʷ L2|M N>*K N\NlN!8:b $Ԙ(N` tҨh  _2ȁTϘdE pK9 "t-48=;eT909 X33"`*_&_bYP'I&0lT(bxixaQj! Y}:Ony,@9Xp"Lwy`L('RتAjOH`jE+=)ӸLS2X|*x"5fR j iY7坉SD9'BМ8+RR'GHW䫼B,ҫjU7 0cD8Jď٩W0t0`tX>!ᡕ4'V|xl*9" H#б0bʳ .״AxWD׹$Rr;V2{ ҊЁ{Ҋ-֚Xಯ`WN 3{k?-i/ Ј= i5 EZM1N y8pʋ0c-1(5hs ɝ@ʕ@ Q3 )PߨL:2q2h\y\) 0\ L>s`C^ Fu827)4bdohݐ@Ao[J`Ӑ]Ut<j1+?*8 a=IN~  qST_xU5F8] _` ]C83y(JcK\` *uxPPuh2qa Ab`̗!ܛ#0}:`%p9u `Ⴈֺp;a+ K$ uTzx1 C9_`/K%PC2"XBb e[V$E$b$/$( Q1ѓ+,ޒ Bqwf (Va 0ƒ9QN,h.<*g(1OYC6B DC&@g G fA;B8݆Q5}i ]ސ]HL(NEğEt zٗzXLd` DANX=`jP|!kKe MȆeRLx"#9kx]^\ jqŊtF`_>Ʒߤ `,zHNyV @qi`ojԉ&S銈0CsזMޥӉh  Ѐ 8艺ٞy~" \dmɣL9M2Å>_>x2@!vF!|o#1pDe<) /Npq#Vq?qbMXqq?s qq rKPρv".Mx,A.J;#UzO7g dH҈+rB:B@m C 8A" 1޹h -Nè-@ ..["[D 0+:艊O=Ȅŀ!(rc 6@>Y9 ' *)O MH{R. (x"ӐӺ)%aW#+ju35Xըkc:PЧ8L`FTw! )eAwv=(Veqd$n8UHU͗J'(`,xGdcahT HrHr)B,^R t "PWQ0 (HH*mO >]fYzi1eK)Vm[eX .+h:/J1*y?7A k .}.F -Չc!#/oZw3\9|M$4kbX ^eS2m2IrHBXB߈7фR ~5> @`lESR_sq&VLxxw ? -Or='qb/b(?,`A9b􀀣]e 騦Ĝ(s1g%D(U.L|ME$'` iڴѺ+w.]'%Fw @Ob`^P#aa˝vg5pE^R1AL kǝ'{ ɗXWs݌1~ou 2ÀIIT2%09H h{K7zZyd9FFw!P7ҟ«p}hw}GS.=enϢ-hE/Q 4nNoU h@O ̔>r),k1^r"( aHc84r` JD! [Bb2_h0 WCMB H#"1 2|'B1rb h+ްU"/1bcc~SSVbD^ԁj8x#e@n% (#W ,ܥ~z;;:0n1{ #Cy5DX 0 6݌Syմ'0"b]WDj ֥W&gH )0JкhJo$91D[la '(%y%`"S# ةi]D+XiЀ1/D@/NZ#'B蟡ʂ" _cY&.#Ȟ1.dAˈIH]L9 b0U!8_Ī4'P}"(D9^39!Q#<E\ͦQ^]M,u>ˍB^ODx'.i!JҐ+9/N?R"Qx3x{5]^ $@ ԩX(Hx'_^|#a`F6yދsinnVYZO"<@hHhTħW-qux֘{՘Ukn ^DDV@i@wI4ouVDD`p?tu}Jvg cűQ^ HF?9Ɓ Iż$}XЗN5GxZ6DgaE[[Zda%d76FU4+iB\襥N0†܅ܼBȌ%P]=6̃('8 `(CuJ(۠eGb E)z ԡlJLjTBZf_>īaDD ˘DpjļFߝM@I-IšAFK䅆@A*Si%]te>]DD?MdԂFFj\da@{Ȥ] Pҧ@H1H4|"mDI<FlADp@ٝvJI[F<-FtjjwMEE?6[ǽFG6MD̍DЫB˘^jEjgulӑ)c:^TbD H HeNJ D,JlŢGII>kh U}nj Ɖai4ɝ0^kAl7 A8hI gaJY?haD-k^ɲ(i@_DO>iBDz@lZ)1rX$ ,pF̲ }2)T,qUx]D9E@JVJG}\%@eϜE,`fdMɅj 1C 8򪯾V'WPًx\ VFdAPqR:LjD``d1ͻ`LU ?8M *ГYΠ:~a Ϧ7t x`H ADaޣn=?1%8F<$!FGqD^"f`(CD#$P0Y8I܎УT2 2"1^|n( ܉@PP*y31 b7Ǥ@# _DĐ833BfeOֳ<>kJtc&3F:D$#>34"B;J@ՎCsrEkFstG|WfhJ8NkC)" e_ĵ0%9RdӨ.ȨND6sh9ƛH$ES^@tZx/,)jWN9zb_pDx5Ed*WFP,$4AnSI"D BhcK QCvT1q@XR u|@MXa,xY!6DFdSofGY`&V:\3UbcbTT_EFDp/pSaqI'QANE=\kNօab H\۵ *xKGAA&+=LT_vY[q^?Rqt]d?ѩX,qOܪ_~bE^eILQAD>hq$K$CԑcTt9PK3iִy3y9DDJCbO M4te4)Ő)4JQkƭ`ˆNk].'@H8:'Ed-\pP`Q=;FZ4 EK8@Z I̞ 0@xtBb 4N l:ja|`] 9S1_qQ(gjH.;fИmv6 16t 1#UQ_iPi&`inT10@rT@7\×<?nq" ŰF >ɂY4Ǝ AD,0!)LXz%HX aI4h ;C Qb&7$rK A&{ׄꠧE` (LR>sҚ2 3CSpQq2RLE|Mgb5Z)u P$&\eե‚uĘX=vT^m}h)㟈p *$B%tuIBq}xkb! {TހX|]7${5^B%~*I!ZdM>U^iZ/X.JHB\hb bj8&i| hB#K⋘I@e,1q:$ I*Ӿd.bk)& k# HqO wYbaǤ񡉨Z+.z%Kahz#dwGa(ϫqD]K>,@: :H(J,Ztp{@A*IX‰#h$Dz5S p pd #ʤWt$-EJQ-YG"QX`pQl#A>X/F^7B` 4|ƙLDSH6#%I2C@LN H`/Fq@<@("Fr4 醄s` <&!saf1ꁮiQb@\P(`ɀp y8Eh>B`R($.$KT)\*4F,4JD)(/In(%tfO"R!XX+]S2A8'r%' \S -΁rD!7@׈(&iyf1У\z~@8^E ,4VFB3D\,Ԕ8JFSSvfj2ՓW=kPLZ FbFv$!!K@ޚ]\…T q@<0".PX9C08 !i"G1T@ùHlD|# 4gHNEp {(H<ّ{BÉ-{UQGX#Ӏ; O5ˮ8/@Ci{ Mk<;f$$e+(@(!Qd>q4 T=E$)FRĐ/ OCꀀnR@I &"%ѶHãX J0$a фofAH<  @x1Js|)F0 @"yT$6EEmH@*h|s0h+5r Uu`Oe%J_Pd ̪,E6H4I1H`F) p(F)XYRJ`#eddѶ%&VڟDt)A{UDIQL+{{FM:EZIݱD ,E ZM_5Md2H<)|%VI!Xi X]EEY%<ȄVC-Y sZLx "/hQyYID)FXBA>a| &0B ב7^2$ zvj#HE)[{23^BL~v[^z)@EP!4/mmA8fjVo\@ E7V$ vGb߽[S^F?KB@?ʀ '~y)McMwb۩H$H &D 5md#L@A#ҢG)L(©QZ0UdC| NO rjNpO"54)D< )8 *P6bX8xL^0UP;pYaW 0f`SP&&%M0T ""0Z@#!A_""?1&FB+%"H]:bn /dqA@^*w1PְcyQ#갨ldQ%Food%@&gMV.$ˎ&&f `h J( 9 bknuBu%`Aj&AR @ɤ) /* *bxH l%N ='C2HB" oj&#"kbn2g `G(BjFqlulKw\F1 .#& ^c j(2—|XDCnщq2;"@(2`h <*JD5"4C5!"x#(.^> > /&!#%r/<0 xZ' Z!(MDJo mH$@8n}2:J8*hBb 3[q(/#:J̅=A>kh mD(rj cF&@ Ph`vD0v#((#e*  f$`I N , Ά $Cl-W)# XlM)# -42!F+A8n Dxx$8mtE ~T&-90J;VUY/߼ڞVX* OT+ ޠ*vF7&A1JcX%Y-ײ7$~.w%XDXd~"mۊބ"%"%R@{-R1z= Wƒ}mP: *B=Hk1^evW;BB* mg"r'®dbhc D##M(pbxȞ*Rys"y>! !b !,Db BbXBU i|&""."}}zT_`HH(⃲@0],cAB^ѫ^&&88vk9 ʡBaa! f0j9*/>D `9M}S(' s| )t-)2gH>]wsR ih 3i?D]`Žv%8"@xI+Dmꑤ DeX C@AB^hG*f[ >$f PMT2P7MS"*[*t\U^|'N\LgFUONi|1e*&E&ãfEz$RLTD҂":N rE5dwOh >Rھ#^_u0m07_@Br&&e>q.C@Bȕ_ (CJ ^ffA9Avg `H0\B 2La3jȱG0GB Y6(+=8!Ǜ5La>2lǖ#5N  g>ߗ-|p?DIXH̄"$$LPƄ4^9&ge&`ΩX4e '5&Ӊ(x1 #E`;'DPΗ& l ɜ0Tro]$_,^OQEl.Wlhӫ/DboxŁڢgʄO&0xV߀d!Ȑ jBSlP|TP`"NPBa~GH=I @U.$xL{8]b/h#XL* , I'i^B !X  K4J't0 |xK`(HF6gQSZhܳ2$x1FЌj = y8\ ,qohDgd"MQ}&S6Q m:׻ux9©vQmyڣ6GZƐ<Łs(55E(. ?(,]v7q`c8cL$jԴ>xͳ9 xD!$?n} 7=ֶ}墘&Bgmo4[rA0@U4Z X%+rfDLŨ&NPBZ2{"1W`<Gޙ$$uUCE m4¤=^aiCII< C^#8PͦTuz#NMJ@T0+! s$;/(v-m'Ίp ttK' Yz tQD@&?3<LVw' h 9s#!~34#Gc;(s 9Lb $C1;y<$=ͣsf"P>FQF&!gj=X>)G#5@{@| E6 G9jiZiDnrOJؗy]J=]D5CBr DBf9)($WdYdF5PzEЛ0FqEV9ԉHY$R YrO63Aa#Y虞깞 9KG0UyiW jiBJ$I%wyJwHM4A0,YB!]ufg #EQNipcQbM6p+Q z) UlQ_p,Wp POr,OO0DP !PxdoOTOp$tS WfU@@SR95XFQ!T@T=qTqqTJWMT7eV43V aqsu0Gp ySSUJ3<5.TL!|J/./6RY$:Ϊ @pzP-P! ,TF w lq/}nE0 $PZ7U[с[[ [ e 7p,F@]ӭ[ A|c !wDPA,#`dv'@_#vwrr@fp_f&LFz|#J!2:y5c4 R8^T!bΈDM N3!Q~3#o3f#`b2Q;3= ?eR6PeBeZ3+eosg0y"P Xv0Vp&$1r抅d;#H@$Ta;x#.GGk͋vɣAqb ~]ق]Ah#DT00'`FʨN6vaQq`(ʖmSmvLֶqa  8L7Ka(&,b>PCPQ; 5kٰP}hFM#6P3,7rr*5.wt16+7qB5AytO[M) yy4*s kdJvj7vSoW'nԑ9 1<$p"$< 9ޔ2Lq! f D*f MaWztr<EI8*$*Sqrlur\(oգD!9Ah9 {+1PlI,o;Qr} o-l, '}c q 1 gA%TBPZYoFF0Yc۔S}hatTx=74,30 ZPjf0 q0X/<W a/Rp ,, k P503 m44 0İ $ } 討H>]ÎU<7-[,+bL5!7Ldj&B&!#O/!#i%:s3!@,Km@ Q<491歽0Z=F( Y3: q>?$-aags]9h1d rYD[i;_험!"tvNGM4y>.G) l -ᙡ 2)D6DCAdP/ꞮC^>'$EQo[tFG[E  !dԌ볆qDI w.dk DmZGz3ax*IbA' ⤅t3БR ,Gv\mB74arpM 9D1M&Jy py<:N3qktDTJrY4k_^:}"p7H́[ l`4 cz֥qAxp;U8*Xpc*M3Ȩ:~J:!zrթa%0oPa@@}B1Y'.(Z( ad(cchd9HՍh|jvW @a /᳠&( % Gȵ H`!%& ꃜ$ 낒!Zb 3m*8`j+f [3cp'0|lo:M4h6k(ч/H 'xDg$aI:VDjBK-( 3T|O~<݃@I "hI^Ǡqv9^#K.$H :m gjF㿃D6z&1([bt舀 t4%=4jIQǔ4<0s$Ϋ P" 8ԡI6$ * Sx>*Dg"8bAhV,WFVF.RaA&76ΎiģG=$qA`XIr[ s\*ݬ RD %B l,+I  O3xkA(ʒ$$;H4(R/czI \QKt"GAq >} 'p Ԍ \蚍u!ڔ6੃T5ʪDOӘ=(} TVjrAdpV6^0L@F-)[Wev+^Pu ˙R3r, C@ "zpQDv81Ԇd3PAXV|aa6i3 J3D0.hSP`_MyL <RXgTWiI]gV Eyc. p+ub.PBSjV+ȏAkj)=eJ$җ%(ey: 2N@&$턷-T,c,bŮ]vw4Ϗ O&ElM$TݝvOr:H {K!BX_y~<`#/R]!THx!:Lra<³:=/R`t 0UbU?AjV1T{R ZTd2XsQ1-0x_IB&WKIUj^$nVܗT=s{^d6 s0d$AH dr:4EA> 1(d"aHW]ECL;pF1YF`?# C=\A/v`(Iʡ$ \m!a"DBn|( O) `aX9paC(o .Le_9nm#LKEWne% @B~O⇂"pBh)P|.ʰ\DXP=x`LƥwNP@?8b Scx%EȱdX#h𰩓&Mx$IEAA = "{ H=}AԜHU,!#U\%P'IrP8`Hh6)rУ7 c%;cNL8s.` ^FA~ X bQ  .I @`p9; x ȏ ꋶ`>81@@R>1ُ) x  菠QR1Sã r+ QA2 C&)( !4 7t828Cв6%%Ѳ;Lo:(B萲b,S0C8I:T1r +`RP8'.ɂÞp#1#,#ؖ(갺R0ZED(K) ! _ 5a';z K]ܨ= @>`Љ{R))*@(=r.2{XȂhQ1gQc(Sx5AQV H:\F/!n9-ZZᚿaȁFJO;)$Gډ)HBp`+6Qp\!)T/X`(t;C+#QRX II.- t F S#A yērMD$I.ͱp Z!ȕp29"b Ғ# 6C뙑52zѠ#+ ]>%0$T#H q]#\AyxW4D(K(A,1KC,P" 0%Z3hZW % 1b2JtaL}K/D0TRytXNܟ9U.m ɨLj[!\456[!(@C3;=Ӯ&=S qzhL jl2ƃ=P'vӐ" N {v|@I),|DکVdž,5 F94~Xe0qPH~)I4H\ ɋR1C,⪛Κ..͢8Z4VSS.[Dh YפזA<"qpDr Xz( -aِy[ |XϚ*ϒTڀ5Xx-(šё59)c3m" R(Ҳ6kMm/ F#R +N 7Zکu1^%"r '0Ȱ&a!Ppi n #)1Dھۿe7.y@w b7j[p6d" \\mͺ܉ ؕ٥ڕ]5 < =#-(PuXocoSD1[!`Q`$4L_0186h˃@$`8/8a߂6XxXh'0C(m_k <DZ=+djjDD:ГL%܎PE )u3 ᷂X2U:S6:5Π7Da08:H#ndaHh\EFa7F69cA&\ Ba 3WpQ܂PG }x`9PPɳ1f \ nZej0|-VsҨϻDH">"Ҡed,00 Oً\ P ӥhPAs[j > @h 88<3@{V@/(J+`{+Bv `  !4 ?j ~ RDwƐAp QŠeXAľiŲIΝݲBT$_4]4?.~]g 2 ׼`B.8cR1C+=] IȑǃN]ICݗG8EN# i-栋# =kniԃFnj=G,BFf Ĥ  @FWda@F#d;-qnFV`-O;sun{`]Xi"LTV^ g@#̓@Ւ,le=4kc cf*s闠)meDQPgLᙊF| &b),v-Y j?v l v?vt_Zrw|-;v%\gЌ:tg!ԌϹY)KI]Oa64j1σ$ #80 qJ"ݕ/?/4b#CyǙǙ5F$:$ lDNo ϑ Rt̂!Z )htg9l #8b&l2`g m .l)uTD)ʧ =XHm)7"P- r-}Wp\Qhy^hPwx悠DnɑU *DgB֑8߳hKo%TXP.l,UP锰7::qBh A  ࠅ Zp b C5rT(С 7T(P?%L8C z|aA5i_сxAD=a ;K 50|ң.D @ڸr}R!tW[.l0ؠ]ÑK*;٤^.wH9s 93̏>ȫFزgӮm6ܴS(|iB 8HlǓ<"? Sd>Iطs;'`y8V! L/z:B^!1ǜ[5 * ~BjGUA(Vg"T!8"%x")ƕht qhQqC9L " x]Ep F!TA9BS]!?/BbM4c()i)&VP)!*Ky%DN :Pڀr›@O?]Bu-:PFF],b?$L]̱s`h[M :@w'l 'Cc} p'B&* Q 0  1;A{l$#A@t@ >plҴQ:œ*D\v.F@Ɇ@Ҥw1 v(?2?0 $5 :$( ]sloD簲B@xpI@VāeqkwugL?,TCg[vF@?Ld ys#PAP`)-A@/!0Qr/"!`Jv-^GE|C+۪u~3Q 4:~+ hWxsy@-U QdqKU˛BjD<J/f>!%ck  I]X h"qY Tf 5@lTKH* fQ k`+ "A<v7"KrN7P)Wb= ; 8bP( / . dcl$YMbL4TGUH?%GG08ZZ,.99bNjDx()BRLGQ1BnH 8O!"%(C0d #l&B0[&Nj4M|a [xləJDէAP5Ah7HE!K?7DEfSǰ́HEBB0 Ĭ`Ot/)XΣl*Z*p%CtH|= O5m5,\"c!e+ Y Gb."!SF!D-Kj* ɵ47hU md*`;3By:B"LX@@O`ٶ&0ueN[(S14cs]r209:K8Z| h8LF}Ss"@)q jFl?ݍ`wB PC-HV*`ĺ}^C97Q2wmHt>CWPDX( R`V81$!Eamb8xn FݻQ4 " \86 @HG3>.rI4k@r!܅!5peCNy;F<.4@{丬(AX2ʁH!0|i O8c_yZP%#|ނw)qb</$"L6[5̽3k[qs-S߁m"% CHq&?9m`.J !<+q\(9Ѓ.$|&IJ$ձ|N:ԣ.SV:ֳs^:.f?;Ӯn;.ӽv;~;/?<3<#/S<3s3>/Sֿ>s>/??ӯ?/ӿ??  &. 6> FN V^ fn v~  Ơ ֠   !aе.!6a;F"a@ bf!BF~aR!׉a4&a.!!!ܱX߱aa" b!".a b#$N$$[%%2O&&nb'~'a)"*bađbу+","="+^b".,+b-0 c11c26#)@4F#5N9A2"Ja=6Sy== 9C:#;bʡ~F>c$=`8"@CdA$=>EV;΀)rFv\$+ Lr d\;Lb@R,סkБ>z-h \?Gy(iWFPrQFp%XH%1X?`P UP:DdYFh%&d&&t"8`E }VXDq` ?x",H)8(H~i 5.+@*Y4t(pdf1IArĤRr|*=w9S5OA?Q4`69=#@PЫ'TSIם1M=?5Yd?o,s{h RAB_GODDxb΂ ;@,JOmhK (Yn*K@P*Qzd|? R>"_8 F~R%5i*(3NGqjfɄp9fY-#oPUu+\j{kbSBKuc‹]X+BtcHc W *,<=ej_RR =B˹+h4a Yf[{rMib >'0@ K0R8xmWҍ&֒|OM~z IBT (pZ4ptTjJII㿚xq|/DSRKv!@~5a0șP@"dFzrQK-(`q)GwfL1m܂)x_%QB 8lN#\)KI@( B#M*3R)?@m2A0?$HE.5p$id= FnM+2ib'-Ȳ@,)[%@ *Ϩڷ? K#I q4#؀@6X!@}tb$cl p@A| $ro"oDxxuפHO8Ϲ؝;>ЁA5-`uB1͌5XJZљn^χ{`MBJAPǫbC ;$]W&9{Zر2_b]gbfmG2H$u>%GOқ~˶w2! yg Ͻwu y4tXE+5d%ԧ I=2}x% LBLP@'LLҚP*OVR-@soZ"$@]Dq $A(9W`-b 5 BA@%.Ԕ$R!a`814*as uTCMa}v-P(O5P((փAOHAx1X, ZZ@W@!ӂ=w&"_.nAe P}!A2!.z!" M1&0Rd#8##q  1B')o er%%q6/+j))a)mQFo >rqP(Xv*QW0x(UWBb؉! ("BiP'Rg+rvb)AB"Fn2@!@ CP Ӧ;.'135tĖ0;^5Vs115!\-2wdyə[iv I{!qc%z<͸$%!P !$Ou @@NE`XTE%A /0!\mQMZ4?Oa!ib " b $`*bF- p'Hh$FPj 99P$vgSfC:L""P' bFjTi @H' n*hG(q%j @*!),T3)wN4DPu(%vP,]!t L5MaM M_a`c3Q-TAR|%fOz"N[pT> n֔z[a`JTy`1TS%%%1(qbҪajRa1:;l%(& ęG:p@@[Y tYFieV u gYࣕfFgFk#xCO3NA%QhF#dSq& ډ"RnjB&Q*Ga"j,kG[Y:?r)&o!Ps{o֗=7mz>=D9}focm:$Qf0- 3r< 'l$b3$ rgYS[|g=P)r/jIǬaþ՚4LSgtÀB4VG6U8NLeGkeWS4\vJ(Hw7K}D+TdJH2VƣGkx'gA ƥYFy  ǵbȜhGҺ̢vV\qr C{0 > bzV"\ɔʪʬʜt#K\eQ%| bu}'0A}E$ ~1A~~WQ#8+cTI _Q$GRvT_HaqP ڋ`iAy!+M4(cc spa%J  ȵv&EpѫJ9y!A*ŽvFfy,D&HWZ&) %a"0ǥ$&ժHa}ؐjR,%*6ZTq\OiIِY0: l|I9/b-C9B5*ur$q*pwB .-47rMR|#}M6CcmY A.2]U} \ݖݲ g==ٙStK$p9@둕<4!(i(: =M,:--:=;ȣ:Ǝ ssWޚ+>{V<'q2w6`9'`X\ jgF I q%0CfIbDa)2&! l e,pi9/\L>  c[.) DhV,Qjh-&Ѽ)zGGݘ׻ m;ɛPTk3C`Yz[j^"+~8^M@I0J 05s!8kJ.DTU^Qt zQ5^'dT,Qѩ\^QuP%3JT a65S%V+EJ`k'Ntw^~",sJWt ' =C[ɐY)?{I;[)?|_.{(YerT*<7A_$ko|T BTVR [%Q§p o*QfA_ Z0%d[:.2SXF:L!;| U+_T+?<B UK Q{(`p>h$qЃG><`Xa= D}ETiRBASTY{rpЁT @&f8AZ2=8_iƤٻ.Ejma}`jAd}>p0ɏ=;Alq1250 *w`[F!4Bz]tb;f oiZ?+TǗw (SB>dAp%HZH$ #$-<(bF!  MFo1)Ãb|A,܉ !9!by. kkx1G-Ir)dh" ǯsDRj:roK9礓:5ϱ=S;߫3QP P< ASO?5TQG[@Te:!&X!N nȁ `X` T"+:b̸D,ٴHr:<(yW5IEi߃~I$cIf ,WD*2:xxj/`x0 D'qRbAhܴ,uE&ٕH+DOR'i HpH<knT8`d2S 0#p)Y8XDk9 F>p8sN^鑇Y %葓:Ğ5c'!`_ d=_b'~ gtչ'r\H QE4X@>ԗ< r񒃑" AF>Gt x g`{B ^L.45fgTs ' T.x 8&B@ x !BSAlCAZ 34Y4ġ4+ 8Npb@b"t$@#xFtFK/(@a[bP( >v3OZG9t"T 9"Ad5`'a,@X6C| b4vP҄0ƂaY[Z!CHa$d4h*=%Sx@Z2< @TJ<rl@*_9<}C8* 6P0CJPCiF…u \`9!P(N;C /48DF;4L9d@# Cp<AL(6RS%.r@![a{ìYKvxAa qOՅSҝ#DIbU}{0O:@[ Nȍ @-""!$^[?0BybK% ;D,( "Ȃ,18El>J=W(pQx "^@^Pl;ĝ0\(㙋9 y h(ʈe}˙;y*() JWh KI!ʃ87iJXA800 zњ4(!WHؠʉÌ'h!Ba&3Bˆ:jZ'G10OdR#/@7Z17=;*#B H Aj  3P8P$r @+10"x(0PZS P"ڏOB4Rb uI  h03R>AD# S  hH3e,?Oy 9\SX0!4l1] 3>A㌎ʎKR,@JB΍JNJJ 14 Q8ȩjz,H?4YjDžH*y[p臃!*;ź`.3i^:W:MݏdGʅ ^Bg鞼a2`S" Q@ ^ $.VX"'(o<CH5UcSF!/@_; jShLKI/ڵX10Htx@džq.HtH_0GJ 5@\pr S6Eeˁxx /8frsƀ$Bu/-8#(@p2{"5([A ( z'4 9+ Dd633ز(@G| $R2E/3)?C &[?fi\C xMą\. @!Ph!!( u@؅؃-Iܱ8YIbƖ6x6AH(s206fje}[_bMMr[ SxMS?]7𭽱@`u`Wy=/ ܼC<ū|8Ne A7PU%VՈW95^c)/Fb c}{XPV 띙Ú,9Lk| g@.H^8(qރJ0EjWF&z {:0dRİ 栀 _PS^GeU}Z(>Ѫ*q&ˎc D=fC(ehҐ x>@k Pf +/Xخ dVȒ^[2Xyu@6JNEJ$A֌, a '7@[F5]{AH|-Y^7ŕy蘏A酨ii&| !ycw03Zkp "doc騖jP7C+lC07QjC,^(C@ k{lX,8!6aÅc<֩EAi֤ʍDp5 H (4 M`EH`b`p! R( |8G^*K)2S KS)˳lN qˣ@j{ lL4^L!I+Vu@Q,˻$Ki YԐM>,z! (~;ēJ2q"8`" ؔ#tF)p2pL Ao(xpWD?.u# R 5m>' mi' EU,N2OW%nP4huYE 3/y`;{*: x8s)􃐬9xׅm}˫{x X/ \8m]'d gE&F"Nd߃MZ@ 0?Er]< [\&C |%j8 ""8XCČ/xpbeՋAM?KO8#oA RσRL@_;Xq%ǫRDpaB<>PM3x n3lb7" " p1x(.^\"l_#Pɖm1G43!@ً!h 1lm)7xAeGnWx[1 7c·`w|bA =`F<0wdb^~WUv^F=\(Vf^]_lm Ab*#pYEµfiW Ij\itID؅ IDQq& T"5Qn?hM Q[ĦRs~CmFv Zz[ZYdl`| (UZbٖ^GQTGQ7\>]* PD-@DAjbaEAh$? pOpt s+uP B 9>acO3 d?BD$CDךѹ rrO #A9$3WBTI*Q EDOdlp&+zM?pDqO[4f!mq4K-mӬfXRumXpn.و1t?&QV"'f 柹vA l&7bd? Bo)idQ5=Zm)\`#S?])h-*$`0^mD></G.%/]~SGyHgF]/qg=>_õ>˯>!y!֐@>81!/x55tCeWDxq@m~ 3 rH傋## S) 3᰸5x3"_T@P! y2N|">݆mx2RyLx09˱"n<)n|#(9ұ e'[ EdB"J-."E 2ED~Z PM nGJe#;(<$ 1"4 $P Oxf[Hv8Fd`AL0Gha @/)?$P#! .aq?.#tf]&-xk|iTS#Y@q`\"ůvw5~>F%tκ`EK3BUjHH32d1j'f(qG |Au]U@/_"3>P!+/%h'^ѐ7|!,U_! QȾ0TZAvI@ huF(a ƥڧt$pvJ$qQ!,?F@#A?g3\཈Y lNh$3? .,]ў,cFdQJ7XQMJD'ID"=z2YIP  i $&"ӄac T/|1hKD' [8YI(#tGx#B"¡ `g Lk9 hs0Tqx<យQn|d@ӯhg:~dgFH@PH=QVMBчl|479 vs#)S 8.zΡWQdъqLGy`XHQD !=YNDKiD6LVQ \lp]nIuD!AXupbԀN@IFdD#e^%ՙAdD _)U=pGeM i4]eL@%BFXeT낋t `OAc83C7WPX PMFH1FbPfDk1(C4I+f9hĉƳ4bI;M4NNkį F4b8rpģD".g(DC,D:CdI4NuFegDqn4s$Cb>MrOGa{gɝ 0POF8gt?m Zcd#G-ANcq 4"DֺX5DYVĸ <ЊL̂KCBgP $Obp(t%?0BX" k`GEcbFT^F(zEHԦ0!v+)UdbMF`j, Eh6]Peh"qH6?kk@s%jE6wE up/zPƄ5|@p+L-~_yH8dHDIƥO`Qnx,hQD,)K Sŧ,bPz1 HZ+ɹJ.6dX:q LT ,_;'I!.D MCjmBe*T|i\9Kbw$DIMM&x50=E88pq% øX<8aoWOŜce YB蘐deBc<= hZbgRlZFq JZud.搱sYgyA"pT;i/J@L'1Iz!$&)% >$㽂CJ~ <'aH.#86Tax*?^ta4&y< UMrÅhY\C ю,&`'> :4"6I&QlY Aٖ. !/ftЮ&Ueq\U@'@9Ph 82}X@pR!.\ KHa,L!#QLj#.`w_-K:D9- 6ke+xK]8B@V K!B<HH`h8T n rZv4dx? bA 7ockaFۿ+p@ R i$wSH A$eyʨC|> qz!Ia @*ouh pAH`rAnݼ2,0Y竱I沸DR Pc@#XF tY#>KBi".#fcg%xLlvC l%,%3$*J'v4ڈ$XI! L$Z# BZ szqa3Vvm[ju$!' g" Er0^ {*h\n \( ր}g2 JJJ,:!p$`& <(!` N*B bI b~!XBjTR*Bā4!$@ N< D ‰.$ 뒭 (> .#+6"ţLJ|aP(!^@bB |A&ARh1baHH bLVM f`"P0)>m H `" "ц>*%0,!$P!E讏("aګkrE6DlOtb 5QE2¨>'r `jrcZ*D`Xj6'Lʔ@L+eЖo-ZH/ɛV. 24$ F.P:DQHb&ߖJAVd)!LQ g$@tQp= s$>%DK "#zD !<11&:ൔkکZ3^뷺6 sNEZ@f8@"Pz\đ2 O:NEX{6BGb_(L`.R-¢&+Q/1*b){/u5(!W1*l?Vs7+|f'x &e.CVӎ2D T4ԕ`E"@c_!2-pY*#iɒ!(lInt؉-ldZ#uh6i .# `!'K KͨVp#cBp ̀l!IDx O[9o4"K`AL <ѷ $vg">9fwn.S˾.Aof)hɠ3c(/K9rL2"$/L)Pm|~)Z@O\XP_ ?2~(]S]yDnprЖ$,DM <2?@4&/ِ0/#sQִ1|P.8p?9?ygBySxF40 q҂PufE DxAHj1" &zE#.A|s0B@4!ùUx)QK4(iX{qvAʸ-  "qG ɐxAqn>8 @G7 0='{IP87!đW^6o!(L>6iIH՝|TZY8:dtOd@/_ƕkq@6;kK:.+[eyv>Z#YiP7‚۩h`tUL"Ď!e*n+A@z@K۴Ժ1()E+v{qfBNQKCvqca҇ !vBM[ Ìy@pǒ0 w(lPl+ GW|4o“@K^@tY: 6.bGF=DP}G v&Hgω}m1|!p;LqczZ7M11my-8<k~?LsxHNegJs-'0(ܟd)}Cl!$"7dbs>r7te|; D[jUsd*1e[&[J}mISf{P1x#q{pq A !Q Q; !%pr r?5/];AFGE!(qтMEKx A `! mq90Fe#a;*[Um7.0t7;-[G0\!-2tQ)$w67.8Ypdr0P<~(ϓ(AaXE8ʱLdh߱O7B?q7=qtsF77ݲ~m3U+N2}!3W](ZSS  GP/<c$5AV6wwF73s(uT1]+Oc9wna$})Q/WuX!+_54"C0)$ ~b$:0c+" Iւ?a5c&8gZ=273>7JUg5iL6A)Q1`0gGVqP9RT=+6&w΀f?Af/19'J (`)f?0=W_qq @ =0=6Ru٘(T7 /.ДzQc'g3Ge#0 w<' r9rdaٛOEZ C5 _f0>2H!-[}d*B10)1FI X# P a @ $s=zsX~j<09~$8'ۇhZk?'-41APZvԞP 0}WOOG=NGģ)Q)0 B*IQ 1DjBVocbQER'RmQ?lQj }r1 1~hgovs771SL7;ޢS7<p7 8kA :gscTuQ;ՋPks}\QV'PnzZ%Sxzu#!7\J\ qW3]*! Ї 9yCXзrsQ9C9,z:4u yMP?q @v[ XJ+P:7B{aH?ՓCS: cUf'WosF<Y)P _YYVA2.&b(!T;"Xp@Py<#))iř(eo<|'P'Qיٹ֝B(fsQ~ehXI<{7Qvjp[:9hculٱo5qfEej- nQ@P2n16|&m#aQCdwp ȁ,Tt< @arO<[ȃP;!zpxfz1 5<2 <;_汌fI:==ehbTu##l;!(Xp :me@9{fwx!*ЗDWVz ECOtv7;^Y pIE, R[nΐ;e.0;=30)Aw|QqF:#)JpP#YtOԚqa_ z6!GN2L10j#QL'p7:s~),. Sq R>O ]Sx Wq ]IOLJ:OUL 0!Gk^X1 Qk8EhR%nS8HQ#wH2OcAwQ>oe2T[x69I6zGEwS W964/06 Yyz0B12Q |YN4F7yꁔn%|~aJfϚr1GA,i/Ğ<Xjgp{Pj톟@1 [OnwX# gc0 {!#1?Z^$(!ޟߩ=nKd;t^vCؤ35>7i$m>~ [GA hc)!`ŲDq )`7+Z4g^CL[re]_|i_ $XpCA$!A B80b"hG=G * ^\0K YJd_@P 5XI(F;PS1oF%Bp(UCyPjCEwBV54lP4/𠇈|&Ä̌*Ņ%O\ٲȗ5oxpfe2N<Ŗ Krfݺ "XNQp@^ \o}o-<3$vBb> ` &>wŏ'/Q%d &`~nDꉠsp@NShk@tA#P3Bp+0/p$lꢪ(hAD pX:@9RH/*hb9ɂn|9P袢.R/ `Oˉ!"HJ! -!tϾmKDh4Q$QH/qMzҁN*ńH3L5Qxah r Gڄb ^)GF9 +yF_ґopE@E,b NJ!s r/&r7I9.]`t !} G^,n&"0ށ3=ʉcPɁ2Z FwŦ;) ^.{\z N㥾DgQ%2D缄:> 2#J\E@ 4p&raX*<"(@qKB߅sbHΘ ;)&...|4z h  r9Ջ* -D!'kkw$}"yIcwfư-c?CX.!;xޔ!KW>Cԏ%$R2)$HOclD6 bi5 i ?2@pB:)LN"Ƞ @^=G,4dv`#fTatt[x!L-"Nry9ABUrR8|%b3 b)م"R f!:lDw2 b!,81RL"v%7 D (@Dq p 34Ѐ Te'OQ򖢜.9Daa@@Q# iŁT4A&$ `R!!1l' ? 4p`?~T}?r ph "8@!2$ A- !`@5 p0!`'j7!A3Ζ47Y# `M?VNkq\;P^cNiδ']Kg:kXĈFo1^oDDhrc'{ٛ.ڊuR`_kv]s'vvڨ!?ށ) қtl! *AVF=,MaT;=n ]t)TEޓLF [frps  @ROU` ~1TD.NiQYM`#MhC%?P/.aΟ5O1A/ulu)e.cx} XLhl?\!1I!h29!!aC'1WP9@# EFрp. C /I˨@^=Xs)䙈 }QYU;8wtHx`h*V)Agxi9xAcYе㿑幉؛YP\ (@w6Y q u݁BV{  ! r9 ^Ԩҩ09N|p2O䠈@\ىQ9{̬p6 EA1St# Z|@q,o$9 > 9؀6 )7{ 7 > Jlp +~W^,= +sD5Bj(dL+jhH6"I8.F s uRX";TR(##'Ʉœ`1QB4G;2MYjʌ̒4NK2`SrClzwQjS:J&1Q4&K%H K+d*=0x& CH R)`08*` Τ/$9yḬXˁd30 BK@ZbC  * 8|2%&*iMH21ذz@ST( hb ""XʰP+9:-p!ӏr!=ژnB( LY 퉈"Sƒ/K+−p$+R=H/"}τΤNY 5eP<@τJAM:pEՈzh$mRӚI;L 1&PPz* #/ʪ 1Q* j@dPςT0M h g4 b!6C/c3K8pt_|ЁF D8@C褧`&6˂ؤ@B+"PmR#Q$vBŪxCUE[L̶$ِT F[f%s0w8M%XM x5ś0Dۼ9Hȱ97R7 6z6{勳Eůd &6390Sy·}i[`q O6IHj C 8s[ܻHhC<0ۀ\\uՄs }8 ۮ8T]Pdݎ[֘I6^-ށ([U0E#?^^@:x+#jX 9_-_aݽ5_m_^UO*J˨&IȁhD3|.M:Z>-I Ѓd(` P˒H+Y4!f,cq# `̳PR:L"Dk#O%RA aLh0,Ruv42bYátVinKp* j]@(Ť@)2a80 Ϝ(OI& cYy/ Zg% KINM:* .010/3/7zaJdPU xXЄ8/d%f%3A+/ ],Q7]&Z 0!39'=pIѯ`L9_ߚa ɠBSRb RPD_ކ9] @6l֤8  4Xl  vN]P *€!X-qz1_o@ZO~B}r; Vi>τF% 0}WDp TvNeW # ~[@Yf> !=AIrx 4ȤP%gN"NnntBN\f4E'T p(jloL~]W߅dL',pgH gm4 %Ѕ ʬ|u艄0ۮ _zI=O%  @|p}5&J7HwN"9!6H!/ŭa; 6^ _~(`X|W@Y ]'J-Jҕ Pq_۾/ u'p "Lp!ÆB(q"Ep< !x8(MUEɱ R#Lu#dpP K bM[ 1 @  { ?U`oȢ.buԓq|?VY{_Beđ@r$s,E@ .@0)s\?% BB .],~#9pw'TaA@;2Ux8YlAU=%SUT AyW0jA߫j@A@+{2E\[|AA|0FJ>zP$%W IUjRZ@5nSf/"%WAhH9VoҪU[|;[lFj+BL1UUP! 2D $A4żPZH\)@ ]tG-}$q$A&R=$5-43ridumX(/GC% M@4q Nd {A~Q od̂tuL[Bڥ+TS eAwjUph;.TDhJOR8!2p l^Q>0X @H#PA[ZDŽ,0@vf̳h &~`ۀ S|' Հ¼:jkJpjn&qVT=Tg';A.APX0Ů)OXː9 @=d[^D{kߓB6ɯA{80+Q.0C8‘+l c80;`=9 CPTQ@ŭ5='qdAf+G&ըСB-8K |07WHsc.9`v[ Rw2\z+^zL^<$0) !L){&z [Ji•@7Jf8- s7-oBh"/H-T]"7#h)PNEs †5!x Qfq4=HX=Q jlH)jJXv%N{1icCvh0F꾄DJ8\IBU^UO9"JHV5)Aw~ w.x2!aP7ts z YOИ]َ=~8U OɆ^_gN"eWҀR`"JAU2dC 4zOn_9,l'=A\&4n=#[OG\=VWhCC1!px,>X 胛\jYBPvV!?tARݷy B I|Q5H dۂH !A2f8ZRW S ֚iT?jтH;mO \|T E^pOEldA8I)tPC5tN-4C5Aț͎ጠALVEA@P,Aȁ8IsNHN.|@M(\?tN NE[DN\}G D D FʱBAMH ԏNLyE0DX%&h@PL] fdK>fqtnyhҜyȊI EcyBAH9cJGREK|Gg:N4A*j%$/Z4kP,5r$S38g-N?BĎ3T AT[`@(,d[eE?H7T(jX;GB}4DAy@AXsr Ce B#\2D SİpFY>\ϘZREԾB O ?pAg7c&~l 0ED=[qM5kZv^@fClrD&KmmT^KsiD! (gP0\"uu6fv&$S p9(~sܷt׷zw%pI{(͸"[좄iSH[C` RwHtxb'? ࡥC$t)BdĆbJ!$XiRK&OM@J?*z* !:VbY>u*zn\J$iۆPC'@=NX'CE6XXnC=SXPdDE:CԦm0"*ȿyPDPJA}]U?`u5hϙw͝ 5TpgW^qtTj G)MC& /("kj*\ɧ*%6ʀH"(Q8 \DO;ݢнxz{BkHt(IGT1 shnG$`J$h( !6 zH v s|& P!d1$: ((,#2JOjhjȃ3QAц&(pib4 g %:A `$:34Z9.A ID5Ptf]ouӱ=1JhUIW`ME!y}`˟ M JL䐃ūK Xc(fH K7nɪ: Ͳ0%ʀ2ࠅeZz jZ"‹Pl,g>j( 3rcz(h!A˷}ۡ28UV`%b#!Li8lp궫)/!K) ևNBPw‹a9|U"ZSr]*˿x'Xh[8/r uRP4P='X|h}ʓ"Q"043C^,v"'>]Cth5lc!PI`p Es>P: w5!O=;a wiCx-ˁ 3?DlWĸ$@~qjF#Pﳋ"- 2!Q:C#ݣ-Q0IbN(i+ <9hӲ@-J9H Hc0@W<<&UL p,hB!4HU`|aMz`ETҐ QJ!HHc6?>$6t$_3LYka@2f y p*B6ao-ǜ",HjThQܷτ&p`R†LtͨԲP~L9@#^^!R4*Y$ :D?ɩOl0P_cË ҖQM@O"cV?6HjCT\(dHOq*4eMۺvP` @ rC"A|k%"%PlH0Ze V?0|Bc.L5d!"҅<Ķ(=O92 p%pbö.Q!6OP LhKV>S`8t5#p] X >5HaIjZ!~@{vrt L8Ճ'<8(O*[=ËV(FKLaD"b;f:Y@JYX jPo) Š=@%8(Ё"H"8# 0‰!X_S暨!ox $@{A@gvM!O0 ͐=)j^?, qP ˆH rsܐy 09V x!*'1$a!H(̓&bvhꁆ쌆csQ3F > >ȫ}-de'_=>ڏ>w4 AA 2!HARM3֊IbXBE* ch@P"B!@@x0nhYE YodL5=2ߎsT@f8COE8,+HB0P&0C20u<&/Iݶp(*OFRm,lV*oekSw vm3RwE_i(d.,dsᒛ2IƴSsUtCrēP)]VNuNY7J! Gcn?)E$༠I!Q+6(/Ƞp5}Ȉ9Blot#!OGG! ,p! N5!J4P(!8CvDC)$ Z|B X0D"P@#",2", FP ` %Eh _& ,!>@QLB!\'"TCz&Rs" QQe[6gLf`F5| (JE"! J*vpXFZ\XzE"! LO&&"CaFeFe}hTH!b|g$!R RC PA cFv/* B*e^jcLh6 *10ff ccFf=$ڦ3R&hR"j!:|x.Oјȭ y K&&m'[ 2bb{İgF|Dp. ,bguR"{R)VB!bL ~""B!! ^,("2!6!!hN!Qn \0!O ZmD. 5‏$4MC]x;~!(_@3}7%B-&kt)L 5"B(bP"b"殔*b0.n/T P9 4:*((^c7*5bICV‰mĉC@88"44I &<@C1v`#4.V%_)”XO.t д$g",J!!$K&6KTԬ)O T*ĢJ8} #R}¬lJ"a"PS@M S"'>U i5Qv!"G6aU"8̯DRUX?MX%J42d#c:%ZZUd `W$ j!X`\Ո|]ߵ8e! 5ŠZ. @ZT $!ae1 cܫsJV\!rpduĥ(NbRP/9b9TB#>@Fu' 1!p&@/̴)t(NN$c*a:c!i(X} %"?J>tNK" bȹ,)@Qn1k%}'0$p !LbCE."*#:VuӨuѼCoE"lZms\F%">6h{BOE'*$'r'4v7'2t|[ag*re @ "4a`_j'@)!,*`&2 6{Prsb7#(Ng@M90xd[ ǥ\|G`(p !E.i֦"Ɔ#cgQm/©ʆbXf8J]#;m/@f"o ~fmeV2 )1n94cH!"A,GO#,0*ֈ U"()%i<)[&"lsB!(m'3]Eۛf0ef1Q|"z'/VR( .`TΣG/##["=D˓=E"Z'nh>j*Kn 2$ݿT,- -*)0,aHlnD&I/V K!cܭb}"KeA\˙*:Gb6$sB#<.[ )L;MUy^!GToTkzf}r秞^rU",U"2U"j>ìVS'dja^מ"Xʪ^`,`uV| bJ"<$+ G0/~h:HPD)᳍"rׅcɁ.mRăKt9{48#@$\4 A(2#Ҍ8tTB ?O'V`. k%%Krr S!q\vU4{Uͤ+Œ`F!zAYb:{:\8@+Eeku`e 2̱I% "Ô ^>BxPKX0@=Eh(>GQFPBt(%WTBAWEYmTD[FfT@_LRS4AB HRS@mKfSS#[xOTTU?TZWQfd$ձ!heIɦdQ5UVjXT'&%Y]/e@Pu!]mxPdka1(A)F)R٦vIg,f۱6kےȊ?$Bv jZְƸlz,R@R8=Ei&ADd)Fp8 /]eU MЧu_A|mpE" :-08 Eor(QIwͣlMQFt\ӜQS[ۖ4AQOhegj!FyYKf?ϴfw2'(Ȧ2@VC ?*|l8A3<|$`Cw=15빳90ĕ~MSPDEGg(S4TYIG2G-PШ: {ƽ1EMCi~>[M+bĂltV'BGΑ @ư4jDZH7Ld? HmFʱ‚h.C1:@hx σ "0 "ző)M[a=ZPdlrŌȆ+QH's5M~#9 HF#jU6DQ*jMRȔ@@[ں(@RZFb5.6HAH\lJ6 4)$^y,#L动DDҁ?"QZ+:0H8ҊHRNb.sO<mZ%?c MDc}p^2ՁGFIo.Ky4AP0ta0&4p?/E"hCvh0zud1H*ba%Ú>=&䆶yF'. HBZI<FքN FMr"C5fRA ";ɾQ:`Ce̘ѭfQ<@i+"jf75+r02Q\in3FqHddC2AAQM 8@ SFAg ^8b%`mfK>Zd'b$V$4\_aD& x&XQ,79#-vS m [ o5eFc붜F͉\ Y9qcH<E<Q1XȮƪ֏0AkXu-&< 7Om8-g.KpzH8$%[H3ҋ;ْ"PL*Sy}(Bj ݨ_ up.gf>L1xЉ<!khQd`ҺA} ղPM RԨNuU$%gs+^-k!Qȴ_NVMbاAa+PTp 1`rf` V POѦ'gwdU(b @Fr-h >H^^m0Qؚq3i"sP<#?@_&#] @H$,DdBPBw C# 9APa.C@Ґ@#DZrpEGrӫrp>I6vT$5-xМ0Il p$d ,\`VWyOV3iA66z@n3kŠ*.8 kK|`dcPm x-A0(ٰHAh!+ j, H#ad< sn y{&C-!g+ h L[f2RP?"`#9"? !$f`''bk/a$fI'Y 3)12#5zWT[% FnY'R3"S2*!& i20B^Q"H%kܤp#'@"4Kaa@&PaBc)G`2+"(ASXf|X4)f?fJ2&/a)]^]LF΢,0[p", 4#1ppU'OZROp;; @mn$sE*pUEӑhjSVcwpb1!c*EU^E y| tTC{9\źzP#K7O&Mcp2a41JYIK7xXY! T vV ;Yq-䠕7`8bR cnU@]\H5,p\J]Ʋ]1b!RV!\ZcSᲐ%tt]sB*F+@ C[jv`21Vd&1+9%-Raz`AY"s'A _+Ԉ+·HgkjzdUlEq-%dkdb^?^k:;[1 ]`V-\f^`hQfKjaz;Ė h;fϺi6V‘ih`F;gGI]a(ktI{j%uZWmcpMamMC@wG?${l!GOJtvw`mS' p9&/q'anpS1;6"ALG >Iܓ0WPIq]mZ']'ӿPsQ DhJwPv1嶩yC Pn/ly,u MRi\ D0Jw1se{ywLqx@[rhzhsp^'w,״b<l|{'7EaDaC řT$Uqq8qp"<&x%86 DI qv1$X qkqYv #R2l!$5HT&avZ bZ2PX"&O(?b&QzR""LApM;,VDp 'bV!"!'QshP3Q" P X5Hقm8(8%ׇ_bJp+ )aD 4Ňղ+uhJ*yKf`-#r)]Ih[-b7;'N0,+V CHᶾub!JR` jD.CtcJ P]!Qq4DJ;"`QP5$CGHE{,|RLM!^.aydO@ MaM8P`3px6p` @7`8U7w7A w#8a7 M9Tcz28Z8 X[/ 801tnY;=ИA{>󚮓Y9M֖Aؒ?!=~C YS SDq:Din`CJPAJ8Ūl C_0 c,@9l QN'ic?J8S lQy{a;^Xa(3S+RHV!|LV!4Soak%kv[>cT~Lq6]r MuO8qX7X` uq5I~iZ`a)`qQЕ%PYpYp#q]_((+",5OӰ ܠ! >,8@~RȐIC2Ǖ[|90f* 4 (0' 9/D p0q1C7(ԡ4D3!guf'A +Ё m܁2>4#-x -H|Rs̛=4fѥMF ; H'玔dmv09;%[<ܿw&Iyqխ_Ǟ]vݫ JAB^<$dm.H׻ Lz<wjhm9=0;0B '09;醿<HA<L@H␶0H!$H#Ej$G*!rz:>rJ'd(֨#3L1$GQ;쨯n8 XЩ:AK, )2-<2&."Ia-n!BRmO1,!=j@ 9+@jt N"8hGbJB =:n"+9 ⒔@Q*O 9<-b4R!z}Gz:`_*JDzp+t7)7H"ƃDyh9E1++)`"> $*,8I8 *Y0H(C /zcBhi%nTJ l^{kMimD \"`X.cI F?p x pd^`4-g ə/v\Ts;܃RY-§h9H"dvF E}5京A %4(4PIFE( AEB )3ZIe?T8`h B@REK8!1]K,?$P &7-i0` H)9 {bJe[TAt`p> "uqg;R8d!BX@VT Hi.L$(D6d4 WV78(0QXK)p4ǀ;8\sg$詂!g2<2xeI=9V9K0s G=zt\/a![x !g8FЧPe?&!Y $d@N ,ȪH2bO9Y,bEk:a4Hhs","9qY MJ?*u' S ЯPQc0$ PBK$e -"<: +b\y) +p%F&&HQB4P»r\JGh~ YRGt @&%6< #JRbMXӿrJ `̜JĎLec~)8@s<Ȥ1HC.< 8ŴfYpR-84s ;eG&yƛiCl.BOlk)YHV$ūg]T :$l $_@0;\!TPW p&@xMwZ߅$1?[돸8 hCcIW < yXOT4#CɈ,fC=4IyG)z^?"%!"p(D[suVtc~GG&q 8piODq zZ" SQѦd=Q\cs| 0|q}Ԁи? {GHYguPN. %_~䫀2@H 0+!D h]DB4tH:C @kCkB~p,$(w L8Ȅ(R`;<=N)KӒQ(X8DI(T6PpJ A?4)P)`)`ЀechҀ:j耞hJS*{+]g2ǒҧB|YhhR8 ڥjK@/8Jx<܍:DRX,WRꭁd.P5pmXXB .Hi؃P؂+JZe mXՉմ`ŪANAYDQtE/P=0VcqrIzZH}+RH5Hц da 6Z66R0GP3%8DC2 *1%֐21,:~r-)0 )73\+\W3ܨ3 O$Q}'1&݁ 33xڝ]s + sٺ=>܌W̚p7pׇރ^g.'Ex^<ُ͇@BRT_}^S5 :{]a80Zcڵ @ h_A:0!% @j8Rl-V1X?̒F@ĻbC·^:CݘQrPh+;,a!P) P;؅qsU9鸏k&k%$]-9ra, 9Kc@V(::чv9:++Br9jkŞ֬VD% X/1&hQ%$(b;ᎰÃ٘YٰK߈H;ڌZũph ;K {M8h#f"Y X2P 0[308&CÂX FT^.{ggO:1Ӎ%,߫ COJ f牦h{Cxh: h  藆i1 |Yi"cxHp٘adi&A?L7;/!驦j"jN>(&j*߆FVꐄa8j_xj!/(;Cш-Y `sV`N?@ĉ5eyP{iFFHƍ^ƃsi6=!mV9TSah1#8FtȖR"Y&n g\m|9a.xHh tƇF2ƁhGaf1ikI4{1N9BuLPBIz9oblZ).:h)yϡL7Հ)R0Ѣ@ "DY)Z2S>;?3 A+)!ڈ0" 8BO#zu[%ޣ@fuФH7t '#8p C:Չ(Q0̘*^ϮMhQa` !8 "Ki3AR -[jw Ċhexxw12 ZQh ixknyԚ= '. Z7tBSWZ1v)c6f[윚"hGկ&3Zx`ne xkHTX`x (ih+ֱ&7ҝ&>s l. :F9DgVwW@Q??BXP,cO<`[ & Ų́}'xߜm犮Ҙ.7~݌L~s.]XKԾ_PPߎ|P4낔} _pQ@pK0H(hxkjh6x[s5zG 'u $H0Ȗ5/F7_#iQǏ?c˗2gD K#IigFeyS$<&5yFF%ZjM:Y^%)VdL7֜VRgsi]^Rgɝ%v߷Ii٪K.ʖ/Mbg Ɂ乐 .l5&[\։?뜼4wI(YKo\)&fQA%<5{ A[e-'ءDBAxD4AsP"nay54PxՁMJadG 8ҍ ggS1b 2FsZFbQ<TRcY&lpnF264BAǨPgyZsc)xؤDZSH& k ËӦN:뭿tNQ7)3dQg{On~<;U+<[S:UC={=?>>髿>>?????(< 2| #( R 3 r C(&!(!F<"%2N|"()RV"-r^"(1f<#Ө5n|#(9ұv#=~# )A<$"E2|$$#)IR$&3Mr$(C)Q<%*SU|%,c)YҲ%.s]򲗾%0)a<&2e2|&4)iRּ&6mr&8)q<':өu|'<)v^4<}Ai =(BЅ2}(D#*щR}(F3|dhEяvt iLzRT(_K3ZҘrޣEgS:m)T5QE8ԥ2,o[HS*VS@PV:Pjզ~u#]`Xi"qk:Ҩi+ٺk*Mg`,k/ ["kc`e-K4Kt`fCvjSuziY+[vrm+r[NB-ky h)*a\*ɭġjFv*MmaB5E_u۾c/y+v/~;X*N.j?xp ޙ! ,Cf HE"z]1<2C_,A0p F8`ˁ X\r XEc4PF n`x_M&ҿ nlI裉F[ x2 GȢaW1R'aKg,qॖp*@(>-`ۈ9g-@͝C-sU%BN*A5i@v5'r Pc֒5%2 "3n4N)Ahw-8=,d pa_ ' i.6 4P=KsncQWL PZ1pJR(5Saa+f#"P#S $@<T@pX^7W \!RUgA&l(pr`u0cq1`1G!C3!e$PeiEESPnf+fbАE^2!d8J'V=%Z1GaG,2+pY1"13`MYIS ;sIIaaGQMI`QJP!!O k=)-ᝰo؝)@MŔJܴHJMwTGFJ-q c7`3P_tRXk/8OE"4 _A: t'Q1yo12"W7^R1!CwEyem(h.w7-EwFLåJujGXeprT9{:zfeggB̂nz6 wx<;X:sP|_pXwHEW'(49MJX*бaHRIzau}_PB(UR\Y9M(3Ġӥ*]qrJNL)^1 kJ8̚7\ `_9`y8uiv kIB1v}V!6b-V u9FL@rbi47R &򙉑A2۰K6d)b4kdRv 0t^)Wofm 𛪢*"McsFќp$ eTvt :A:`*r#a.i5VF(Ws lr`96.ɖ2vwjѥl%jj+0`x&DRi0BK2Cj͒;jR&6ec&-l"`-P9+m{5n{mӤJ8!m2xwk'RkqDUS׼qDj|& pԋtF HTZGtĎ|s-s9gT u"@ Qgvt1+L(|'r)cWv;QG<$ SK7EÒnkuq-!j;Cxy*>@P~q'3!q-taɰ& p>P~pi!{oqx% 2a^m 1B|y#!t27, ":, l_ŇKq 5GgB-uőP"]p椊Cn ܹKTSA9U&+j=RpB`bā  R B Ex ?>0&H5dX! !X)BM>UTUZ]1kС:MdgX$ŭMö-Iw޽j;N<5d`rFXbƍ ^|}^ Yhx[G|5ϠIƝ[fG$3$S)ID b#8sB fѩG:\ ^"Nz[/*ҬB?dA0B qCO0á-rЉMRn(r-!( a('BQ ."[Tyι,jhn.;ZۮRfLs5aꗨdH.Y(K*HЂx/D q , | 0':B%К](Q3" jVPa*ԅrM @((4+fMŢecդd+'+ w5X8.#0_cx` (eGXhЂ,hb Y"RY* :rd*)*1.] *(FX֊="Et" h^`9fFwF` Ś,埵#邾X*H u!'pjop(xKr[" 4*x b5"Hu@=o Rh HLE.Z9-D]B٫S6qүA\k<ʰNP$gK5d>vS7Iz@"ݰztyC„߲+Tw—? fӖ&ވ/x ´텯X}E?z3ffȱB(`scHj4vA~_6 )`"SQA!H`{/q E{s3O*HAyާsLT# !]?)O*A?cb D;2"bYas"*sIx>Y T(3H(,1He>@$"8" *AZd@j"\3.KaBR.0)pQv89( e&@s07˿q  r()<ak`}$PyyX@\?=`cxꚠu5{#7"qSwQ1@"GѐxKl^ ) "s j <2.{kpyZ&2 ҉(YZ0  I X1P 2@ xzȂpHyHy'B)~4֑fƈX0F0sLթZ&(7gʪȜ/c&PFڛ}ۛ{"r{ ߱'9HȨXƢ$JV8݀Y j!E BlԲ!;2#?*P5_j`pAs$Fh[ K!d!\l tOrdq)P*$IsM\ZJ,`ZëRQewJ.@=XŸ@L Rx @Z]0^l^@&LO fňː[l)@<ЅXz(OID@P@XЈJTzr"*haU(U2Qm` IYba0=PL[Iب*pN@P"# zRN xIy ~{&,zNѹ` 9 ;63ڼj8,iL>-/ zъF" Tخ[  ;;5Ȁ?T p.B`XKшZ6 1" j*-0PnM]X+ s 0w0l0s] Q.+@X-0͓ l% -jaJ{EIՕR  qC(O D8(p7(6X̴2#83ljAb'3(Ē9c4@#'t\MBcP"O3ͥÅͨݤ,!zM`ϐH%L _z#;R<˻ \3,\# ϹCrOTԞlL%]8ð axAH| (=] h˧ !RZ /tUM^Q7v$R @Тq@c8'%x 0r!Х\-_K;˨ Е4 ]_8;ܲQ<`4+DJk͛( +B԰r`g `` N#aǨS:Xav:0|8fP Sj;ȨH:bS6#F0Ѻ/q8 xXe sbкza$&26c{"Xd"HXhq.  Lә3?@A =n!<8G̅XX=jA$T d*:$Y|;9̎9CdY`X^>:_̞QE'~A-[!š肀h ̂PP8؄E܂P}c"Ȃ x}Q&w,j*EP( - ېd dߖlF|8ѵb0J"l ,gzF=xQ'B,~H }4TJqzӁݿ@e2I! RBbGɮgDzdE FXndsTɜtqo|}v C"(u-S +[`M RMO.JSq#8 Ͱ9 @-dYȂt<,̡d#A]ȑn(&X$:a\;u㨆 iOÆhZ\r?+4TZW@u'`zĵ+K3 Ie"8JΈkX!ȂMh!M"7ɮZקHsǘq p!qh= ᬗ"ψVjdX /I2aP=S+#M ЂPw#]e*Bɐ.Qr>m8SSu ˧fw8 D(N01/ۖ4:H2GOnVRY_(de`rx@_(ЅV10y\ߪb ﲴcyɛ.~A#XY& *,SJ-ǭO i h-"/( 񭬗3M).h_ Ufzjl2N B;`K^O`jgj|mvio Ϗn1%)ײr n9RwhTq Xx-xWӨ Xؚh:@=/&{!,"~Jc~LحxaGKY}h W?FgРd!G 43!aI,8@8rdF^OHk1vFhR$'?=O1=Sdx16xF ( 2VdNp),v2eelyr] d[|P/nj>y9᠇-Z>s80݁|z#"pGj@ g]mW{!A=xjdЄcth0!G! aYA\Go ]Aucity9*K:$QJYcT>9tՕ^eT}=ј=g> mjY䖳Y)%u]'}' z MGSų|uBpץYSÛ)fs:*z*AZD?F]o`C<]2 2›)T j,J;-~&c{~LrZX)jժ..;/E rM`Gs4A9\x& SD-xE-r&? H?DR*ń:*,J6(?!" 0Rcw Ki3!sG*+-CW\R@@u]>[AIM]lG|QPqPmPE2G-23 C'b[ {yswbBg>j:"›[ Ȑb pC $x?4s?SA`Uÿ׈5BL4-,@9acA4}Rc2dKAt}rqA4_r(s9~ ? ̡aK5gAA,DPl?vFK S@0aN ޔqt?>cc 2?𿴼B#Ђ) # WĎV&=ArD Z^_ c !"!@h t?2`l8` |@2K˘R|=42D m.ʿғA"4& &CDLWɘi 8F A`=ʅ>I?pA8LAi} ؅DYNK D?p`D]VC r3ODJ<.("i Љd넎V#.QSqDaXF pA (LS^1F8HGTMFI#+hޓ9~Iq]FYTxHrԝu]$=<M2!@lĚ Zan!i42AI=-,iN-IR*-d _]WGqW[VZ֎H]K73YHe1aXCqԍgboQrBMuWKXv_[2FJLAHyś~U xAHgO ĉHon}%GpcC4PL+ nAC==4p&NF ADddQAx@DB"}PA,WM?&DsFG'#\ڬ&uZ"qqpo4opA&t+|1Nb̀?}q$]1[ @x>N0(E*5 IB6jvV1t $$jXj$A "TLX/FzTc& aeWC@yL@?|w (CNYi| yV%=giYu R9bKI2}o&ZfEҙ ffTIXۣ!5ANERSykG揾]8[%HDeq^Xud)Ȇ$Tȁdm fw@ҀƄ@@{Di /y\|AXniHn%\^FmDgFT%J!FKZ[` *E\_Yho\pK 0 +jɣT3, 'ݶˎ0sD81ޅ:KMUp?1S9º*|.otaG15Bɭ,m4MAxA$A#pA"e;@0C9t( O \Fl]D p?L-o @1(SD3h, ADTpI]e h !2K'SXADhRCZpBnn"օ^ 6\VaY8 P0#XCKOM LiX&4GHAhKqD-4 f4IhIUHԐfPc8iݱJCTegTqr6m<=+_)Cq%A;U֬$W#,Umkq|@HKz .,^O`+{q̔ 'co`68pL4UC 1]^h"h@fi@p/DRA84(aE3TC1Rf]"rWgd+S[CCv))Q5 NEEnLApF$CA0(_BKSrwm-G)C 6F覊 jyXCdAC .&,J(ϙ CmHPت m0 CA%1Qy`i<+P7C)H~sD/FZ612E4\lZk\;Y,bdQq 9ه)ZarSæ=ýx+uqx0p,"g/z g,m`e_ |HlAA(A$)NCΔ'^x* ץXB4:cܺCQXyVJFjCH~~Fhr pBDhAL0<l3lOvSvovps T;UضH|Ll)G0BYruk vEe fFs'u2X HYD1x h@؀Bʆā0~E|DK QK B/G@povgvK8`&y s3Oa /p:d?̃ 01CO>ȀX <oB=گ}ΐ Ab<XAX 4D9We=>ثgXNI%p5c_>g34ghA$}FC?đ8\&Al"w$1?޺W NgH,@ssDAwnN=c5zi3֡HȢH4"{ۙJ?[36r1@_$K`Æ tF04VX/s  #yϑ;,UpWgSOBjէ/xPԿ~'l 8f!5*ѪCMMxv蠃I@t!4Ux(C d1\H̋p8G9{dKY3tGF'n)6׭ <>`2CnxǁG\'i 9`D`Kws-`۠uOǹ$f0 (`>:  xBC4( s!:ͲS! 1.D.Gg4؊ h; @`BϞARˊrp.\Ρ 5RD6Y'(`N6SOy/t9 OϺ<͋"k ?R 4Ɇ@*=\)L(,]Bj xp'X)[)`E` `r`Mpi9, +NEK2n+",h"Tl(S̚2B@8ol3N#Asз (w $KoVr!T\n"dNhnyD+p! ܫ nK &)X B(Yx:BuQ(h"^!NHƬ$@j+͎~H  A ̼N(u,Í`RjLL`cj.@26 )$4Dd-Hl b' "v'cqnng3?!- (`@)L'&'D.N&, )#*`*?r (}JЙ$11I0%/2uc2RwD3?xy0*tBxL"r4*ąaA˞,)܉⊝s6Rb6sHFxԁd753:As7yBž b(!*Љ') ›M/s?GA0F!6?!H3hA *jP.S&x84CA,"#@4EUtEYTJ<!% c B SLrJL d nѩb} 4@Ԫ!e"B`LA7J,#! -j?A&$.[C|` N OfBD!V/4Z N*4 ," joI ^'#0$-*U#d#P@H`(j  6 #%*G;P!AVcc,BKqsZ!Tef )a+AfrNZ d^ V% !4@Öf6 s9-e! e3#\4<  bD_+)¢!P pHC_,&L`k6ĵ*^&Gܖ#"I:B4:B"ゔ-u;I2۶ HN(: 6MDH26Cp#&)44nfl3kÄ;n -$ux-֠ƍPC:,.Rd9~9xrcbk1> 1&Gv*A#;d7C/HM; brWdʃ!R#'ƓW55B{a vaه{[4/ˆ (ӑ⊏$|"חw 8@?{RjB-^a 5z/CJ$jB&F8A+>Xh x[Ŏe~N R΄o-x!ob^ hu%E<7 @ #X`  J nG40A$Re]<TBL!)Dĩ) 4dtn :1иnHd@ t ' P!Y< agH6" P V'x0fxe2eh (4xyR?$`x-IR b`4mH H:OΧnLf(@bYĞ Q`d `hfɬ5 <06e3sCBC2x\3? bx.wd 0D~pMGy)X #OV-8WrpPr';wɌk*xT:C " D6|DEbusDTC" \!P!HP J001J!,.a'\fB!0r>  yⓛ좞yVJDn'g3h#'Ƙ A$a @WC )T⊸1l2OJ)dn&!ZB=t A`.<o2^?@.vaVؒ>4`2Y B'R~vL*c$^a! ])BX;8 'bHjWK22p!HJ!!.+OuA.<8DΖ"rb@-4R- ws:r\3B#8$n+is C<o\Z' 3O䙌x59IaҚ<}FZp=p 'C'6k5 :s:;[ ')h֍|@.DbFjAT<+N!3wh֤7ݻ}З]ݹZ/VދoN]LJK.i >.ӥ| Ź!Dה'^4&RjGuNyT-ق.^uVr˵DAaȰy`k#R!4?`Co V8ԃp&]4n c e .P=|nM0)@B.*C!j[j!5@ ` $zh BLK6Tl#ÈW"M]$Uae\/0v g? `V&y&Y3+XFiu"]. Wh# b"^MEUB_F&HC^v$Xj dUyߝbY:@ʾDхH@Z5h ϳ  ?!; {α8 ^DH(D^`O_L5X/t6+Jt>@gP ߃W­IDxfAD b`|XZV+pǢ?&DŁ|lLmOܹ)@ɚɢfx.]ʝXe'GVDSH IR0ID?2HtN H?- ϧW5Hd>WsW ?$$N6gjxȨ5ek1լtLq\SqSARgA&Ȕ$W-+58A 8 Q<ӥEK SH̸R85iw'51p^&=q%MǀÒÔ 8S5 R(YLP :)OH4`F?5|J\"M}~} X*e `UpԂ-&|}?ig3Eb1BUhf\:!M< 8C:pU2 S 2)^$^j<Y+ND&i興p< IӁHC&Ɉ %$`,QHܲU+N<W KCySũpAn 'E/}lY'9ϔ{˭tYBA`PY@;Ruw RC#+=FLOS$i;{]؏{QC?Y|et r;`S EYwP oY/PgMZ܌a/0hm j\rf+Vң 59ą VNj%|sM /gj5 +B^6%MWmS0ڔn"qb)PvU;ŀ Kx…} w)0ΦO0cx#NZ\ò:*jQ(XEE kZxD.2QbFX4R}N6v]d9Jc[20WEj|4ٺA^2<]]I6fQ ,8UCJ]i eQ,8AEAge5c*)qG"LZ W],@ h@mHR"1K/JwgɩqDNV߻b|"W&yQ|2,@IĶpD PrC E8 L"q*NJOR3Eq~YedE>%2K {vTq8؍ N! R %+e$N(%)#Dj Yyʅ=0 / =$#q՛CNΡa<?ah -QrBj0)JzP&Ba` p_筡m_,ߊ5k-GuyrӖ J"y LlK#*rn UD%b:ԅ$ 룴-l1aʶYyY1yM:Q"dU#FbG"&69K$EHG5&g|Orf+q[9ߧA)lVfre@fS,@1lAQ +v8KQ,LAD$HNR6*!ЃX1 ׀DcBSQ0Xa)M`Sf1 -BtWLAtK'tQpXWb).0rA% @ .` "p2"PKSSpu Siq<1H1# aLQ+M""WL}uBPKœJWJ=nn#pg;O 558 5a'8J͆Hs$(tH0|/B(pA20k(p8(d0ctbMM8 K5, a KIӆJ#84u'iA3#`PT&"b$fuAwT]5#WazSy9A?n<>C)4?=u ҕ+rfUE"AD#0D BSUf?ɑ 샗5Cؑq>S>[.?HPYVB]3BԆZ3_MwD4GED"4 )(#q8_P8_;Ɩ%$[P$sP$@"ёd')9NI P 6M65htx R8у_YKS8*@TSQ[ȋ^"dhLWA-^Xd{ǛM<" PO88d*18x0,SgHx$0lx\s"Wl@v_>,%/bZӑYD$3$=r"+!#\ڳ2&ecUE\V%E$%9:}f{U/[llKi"&UAkR@%[JAZ2X$_%&Jׁ!]eCe%糗mYvY}ʪU':\1]Jf4^@٪tooE_%__ʬQ[HN/3pTiH@g)6c@ccMOBkXb+֬骮{cINP8bfzt>74%ARQ۳K|A]iX˰  ^ihpnjrJ=,P=U2='hWqh+,.0k@"BkV³;eCd)-lQ8KXC~ف8T(8h(A]VAoN+bynةnM1 \K<8i&8" 30% 5 0*0OM+p2/<?1WBN+@j RTpx! uC9h;FY$M ,=G*JD%uOO(iO npFrNw/=a!+QqE1M/LA>qťgyׁyah!+;Cq1:EA/yWvNVѿlujf"R`|Q!;ɡ5āLdeʂgpH'$_V(Q'UGZR?Ba{рW!|e@zyA idfjG[Ydn*a%X3تX] $݂p 1:+*SP!Ր"T a 8Bu#"$OR7ALGJk6,$5:P BaUa1'I يgxX<B*QW͗XFC67 83 Ʊ0Ya1<[JĜ,B9;rf#8&853,CpN#"A7+8B.+Q(Ggv'xrýrRdL+Fy!&ebTO"1<6RG7>^tn, "l0A_ $\!v|paa":zm!y;yu)M@PA PAc h B~Dqa ^2.u$xRcN>"T˅KGJJ,_U1Ju*ą^6,!:K[j6%v?" O &* ~ /b톹;7:e̙M%o撜Ao ѩ + Ygձe<ٙC_i ZeA /TLNS44N GQ&~!W8ܥ.D9}r<m"bϣi 8Bh6ƀJ*-5,j* *$J#˷7gHh `$a!hG? e,R#Urj-FQatbra%.ryhzf~^)KbĐ%Q y` sz&<@y|35325T9R%3 Pl4I6x!JU[u*ۥB8%ӯMMrpXzPm;{y\qLTB8yE'H#<,ZdM:氋Ct^ W@v1.8=.o?}!\W*5t' ҉cc[*#R.(uIѐrG<hiD~PRDAafDٝJ;!.*M#id₠l 6@j--(d `, QRH+Ƈ+q?剢a(EO;?˘`B2 <>z|E0$1"bl4f5ˎlI5,d4Ɍ擘4$7xc@JXRe-_9Bbn t p3/x#A/[&Sdf3iɛtB F46Skvr |f9yNts6{q!`*> m :yO|SgF0[Ȉr(L 8Br/$ .! $N+?%CgH?W` rK `ʜ\A#[hF'hrjw8gG%@ D3- v27̭P`*<H*5v:YKvbW쒙Ǝ[j@)q3" 7PS"LЪ0,#x؈QF )&J$K<$ٹ_ +Ѓ*GQbV-XՏP; ",agzRd9Y?3ߺ$_"J$ѯ|$;$3v!~3 0 v?A0e-_B&R)D# IpS-)HHd⽒u@Te%Hi\$atY)S0 (UB($!+AB+y@@@XO8ZcTjYpj ]gf,jrO_ȥ!$8@t M'ַ4s>\h%( "Q%ihlbOΎ٫1ҝL4uh=;6|$ /+_4K&-y'}fJMΖ&4?_؝uk_#*yhؒX5ȉH 7PqZ +P<ȁ[QI o!RƍiD8G5 `G =RǛшtĈ ҈%ڹ>B= _cG^RY>`[d*OJS2%IpIIII` ɮ&8# J\ċP5̾8 S=+̎ˊ-3 ۈ8@N !ȳ(@Z@ }@ = ?402=!;Pj̵SJ.է ǠjMdH[HY ϸvl!VDW$yNِ@IÅ)R-7 H072CpJ + Q 3+Ԉr`tȴ ؃x2JQC8Cy8TX͸UPkEb&qPf` aQ#ыmEy;ƭ%D. ';1hK Qf%<] B6YYЕ:P%hx$fՈ 8={Չ+46 fɃX˼;}]gٓkD:a \MDO%]?_<=H%QIl1ǗɕcvRIÐ a#@BT*Hř'#1_ ,>>SX29HQ?јb .~_$$sU|% $ S# )k  ((Bב M=؂l_iA<:a,]̛b%мf.Ʉ(C+-N, G(BDH|9``98hM\d#H,y$30XH(37@- ZWr0o E@u MYcq QP8^P?e"RU aZ];ZQ zڑڏ[.;fʑ#ﺍ'jwdFh)#> &J qԡ#O pGX DH &᪀v+%ֈx!6Έ5!=M =ʈֈ-*H.b v-|[r r s=Rؑ&ngT#ɡI|6i>JljjfjcTjj'jjkk[:+@NJ< Qd`FkaK@*=LŤ+쓻 }L -vj*/D($ʔނ8=H%d(;XMtAmvK@[c|NxcـXzj+K\gE$à@8̇ldF^} }Ź?W)ݍP,$^'' [ /'p" C">l!ŃT0,4Gs&"ɒ&OtI%- %dI֡0W x@:2nScr |J 8UQ<0NpL/ Vv  דYέO u(V "p. Bʿc4r lP@!}sga ZZH YDSpqƛ/q(" F=e'4)kdQ  /E*!PPVǦ@Lϧa5 HwDSah@f#uChPI+ #(Brz E$ІHȮCPaؐʌQ 81l(\pЃH TUCuV5a\H“@ xèЩ!%z|`+)| (c`d+h@of,Y1䀨18 Z< eZ BK)|fbAYZB" \໧0C9#x[m#h@]R4ՉDP*-&0T ː 6W Dl0RZItl1<`,* N!"p$ $g|2N=Ic; ^W҆!<`~iI^ "!TERݹBȿeHZ8EOJtH%$JeE($[M4rWhG#>HeO)f!mwab$4RKႜU'$ u{"Ozb('Dc7NAH(Bs˃9*цBFo@g %-3\אuέ)IZy=!T1MPS2C!`6謯; !v 0eAYV[Uc-@̺V7pL(1,p!PF J:PChi'RZ2BI r; WwCpa4x1s+ɩF@tj0tDqC"GQ!0Z$:K:P}"Z?9sUgX!T%f?  Xy:ӣ>R&}[/+B0}@W Ů߽26T9<+G҃ A Ɵ~6vwUo do??i-e_$hQ$ A,^['C $=$i-B$@!d¸@@B,! j=͖4VM1 T8mA @2`C JvMCG(t0XZg(3 Dz`Z}0aIb @`VAaA?0D-]ΡF pC\OCahXx$@\0BLqE3HWtS3vlٯ"(!kBv؉{vpÉ*U"mְeKo[SŹw]`b%O{ ϧ~ Qn:sT m?Г0R)-Xb֎&XQ Y/0\]|Yv*,C'Ⱦ3hB9H  !3g.I$H  &E41 XPHh=ؤ"\!,T \BX &eɇh +)R.S aȷT'gXtVx#,'qLq\ GXKU*5ҬBI8ʈ(OH_)ԑڅ%fIJbA k׮EmhVG OooD`j>3(DZy`H,, t #3 8έ&9(4e!YW$ViHㆪ[cx&{螳qfi|؆2"` @  '+3~).*@xí0 XEP)KgP*pDBh "͑ !^S2o :@OaV"pb\= s yO*kZ~Lo 믋\#XBJTQ @Җ* H@"`S*d4l '3KYfS$p." TTjMG CLgP6GT,)D`X>YԐB@MAV"&ډ%R/6!*z'@$a-DѐTjTMhkpAU_k!@QMkM$n<.M:ݚ'cP՞)CbjDr.ig٪Vs"yl$!ؕHB \$%# 5QMQf9P-e0"!zt'BiMR::k:xm*2Sq,)K>=<,c)hXw &A[i(0rJ^6!ufIS@ /78R0Ih(~ Y"#YE2"cvFy~ ikHF%oxEGi 22d5G :66Ɇp50]SltVB# E .'d FNW&<45 Dِ r½5MK?П3`ج&D6 HH*ktXbM"]p"%`.8)bFB]c!Ǝ`>F!/Jy ܁7X9akv>})_b/: -)а\ @\j&X@|bd'H P"$q#! @*! nXbmQ }Hm7a)&Hg !" B~* 5wz}@.^$z$@>3# sp >zE"/*7@^R@!uXPQgd/&%qI%Kt:sj!*'Fq(E2#+zH&6%zbtbGz^dwD¿ "Gx.:> !>J@@#"s-:"&n)xF.&MƍA.n2,nH4ekL ><4/8!d]ޣ4}b4%H\cH~K85#XBެx&.o|.Z0b'0&  !a%F4)0%Dx7Ac40M=YBX\DI *E=rz *EX Z̯Lp2&Qj"aL 3>@جHdVީ)GiOAR?}&CP@*'&!JlKe(,j∄^*δKm"MJ| jBb ^⨨K4AbA,NDL3"Nx'jnB'f&R$`f ]~h!@ulBPȴKJ bO_uf%ghddYUUhBʁ&Ǩp'%Qs122NFKR2T5Xkj]b 5"B34 @%pk ^ȵvB9',.A&șJSaӑaݎӖIuB>5tN AP CքHtADgiE?&bI8iVa=ң<ۊɈmnfZߩHTtIcIY"J'cp h1%݀'(vtQ7(.4%6n!+8A /$T#**g"&xCA6[{\q2{箤#8.vld_Έu :>2J`!p2BD.s~/0,s7،ӵ442aOc1gB9 2v1XX/#9l"l"ҺC /:jЋ4Q2ؒ/m>CA'A 'B|kB8o>$8cY/\EuDv8ys$f٘2*Df`a$@"btA*mk%9|` $Яaʎ(_•}Ĺ&d@l T:Jr e\cM'/WSOEE 'PEUnYJ wOVʣm%=R@zEy9' AlNdR"Yzdc^1RZ#`]uzA whSS@+1 Lߠ)8&z"M7l|hO,a*ˌ܃K{_q냹-ɫBcDCc]C?VV1"p-%u HF-J G1޵'f=&"`dy*| RC@18MBE~$ud!/j/l])lD68Mk"&_/?D*$ !Xb?(~/ވ8&&ޖ%& WHп<D ( ,ҰRi! |(K.c ā.^\h=<H?>)ӤġSL,al٘2Cm>dAft <+!yB@Gdo}1!iE& /Yӧ˂5+\ON^ dpG?N#Ovb d_SE'QD"LufMw6\ l(ުH)HY]QUOMtC9 A'?[t?W$pCPU\U t?I4>tLYfsz3@MKc s4E_@M߁ Kwǝ@s?@B@1 Tf dɝUC$$M {6A HD#$I4*$J1"J{eQ*eE i Jbjf[[aJ ')KP\]@Ԑ&@G.s?,sX@S%)M+Ct#2~d@"f6 - ?q ?,h?. XP@ 1/`8o@*@#qf :]Gc@Xi?1#_vqz :<=1!{r g(A`|RR#Dx?" 44-s*@",ю)ZM(*!@}J8gV-(@RrN_, &/5#D ͏t> 6PpTnq<),mKh I#}$⁆>w *hI XRxT`"D]T2Ft`cҙEvUZJ5f.!**@ȉH)TQn9tZ&AQfJFc]s @>}=Tql dؕ ]؀#Gx=bNbX9oS:2hȑY.:2Yl5gܒA\1R <!>4)@h1Chҷ`_Z2:9&@4%MҷzBAhۓ1Aq+4X ND.)bR r`@9Xp@qWK@#9@Vb Tmt0/ϣ@I6 ڴD1lO!B1pOP!o5Tr1 )3`}"AEm#a98Ai#8Scݟ4]lB># bS4ٞxQ%M o  :ԍ2c /F@e葊c|/D1ٷ ѭsi6Ѐ=L,( i&LKMD~w ձфL״3# ` }H2bhX@!7-$0Y&àiƺ=ݰd~M *O; {O?Z%};߁.Iok؛?6V5~w_w_y5A``.22 Ʋaa 5Itb1I+H%+11!d-0d51Kӡdd()h]"+07#[$i. ~]8%aHwTXC"X!4q14e9\x#1"C1X'9Byt:jooV&/QWU1C&<eYhG%^2_%!!%|p6rRTvo()q,V"(MG!0qRr2yδ) )x蘎*.1HBb>J&oӲ9Y>Q,2a,II$xJ1a->"9's6! zv B1njΨRh'2$y8:R|oG35s39' i#gOI^Z9s_ I4Q6Z6!j_4~7K3R4d*O73ycx#t3sqcx " P  }6P3ned9K{5P15pIj `@ %/1S|S|Q #=w<-:aCg/a.! qq()z.1&!!8%egf9riw[R!+%U)udRZ]?.E1Q%w I9]|1 jHq}h.՟8%_h(F]2QyX{x00E}4 pP q UcP AuqK?"6{2c;)mJ2A8FkGˡazK/0 (P%G{WeW#611Txw2.f)g!i}=#}g(ۓ"5A.Q jhA855Z1uj҅PXd-QV <zpBfsl>Ag!ʿg )y}QҀKqVW7"Θ)Hq| (qZ{&#vw9x!HGrR4<#TOJ0S,UyrZR9FQeVtVR&O e<ufkvx4av |NhǫR9l(kmvK!g@ {m|\-R U2{{W˴{,&#qc@zK!y# )5qհ0@}YF[g[. eYLuCD9l!L.^D5C5ap5\<VRD[B^ce`ow0 Avz+|Π!@cK .ш; ay5\ʃ1f<1 p P)jh\IXz[k es*0Aa һB"C$2[(m'gBZhT[1Y[d.!QʰY JtO$mXx|1RQ% %(&@PH%ZE&1#'(BR"R$\r"Le}"HK< rP=pxfSp%͈*N\QH2-(̾}ؽ-@0Y>HGI2AghPQ' j"E5y(Xݒętex&- ]ħ1t,.>)C11eG[+|(f&#A3Z4e4G_@T1 8 3g[\6rn(iy|=>ww3s@EP 5 53P<eq+,gC# ? ó|3Go x`a>I>>y'1K1 ce. ܺ1q )4pH` "rpC)Y[c߂2״J)V0DM ?U10 naeqp36*`;KwrKҜgJޑˤ+(`\G ~T}VbNnXK#_ti)U'Zp1D d%LH9}f[@t_:Cd$pBPb ưo NaBR'HȒaT8DL -!hxk LHTb gdK ;' (6(; ;o-7j,̍#̄'V 6%K S*VL̬,؜ۚG5 *L]*( (mF6 ,w d?R2q8p`~m uT1B @H7I J.*0{@62C hDL`M?Ka;"bg(%6щO-&N3pۣ@%t6'٢8x4MdSE@iVc8G:Q8d%0  ev.fL&Jxċ=&!#ђd&5IG!cB@9Ɂtobrd#ua9jc4r t8rpf09L>JWwD$>ߤ*Y)UUMnvӛg8M 1?̐ 4@O.V!FX@1͝$^ 8r9 $ TP@(ϒcQ3+"UBo&c &d?'?.zD.tlF<&$9SF~! K(y.2Y 7*" D$ɧʨ|"<8lY0t1w uFjJ#PʴT˵LN$V:%X0|%Xb8<8K$Ddd| <&kz6lYhɔ#LMKXB@J:FD9>9Y|%Ú X'?Ɂ-#P2«R*Nƒ MȆ8I股*͠.cr8's )qH xsZM9+`s'`pNh"ZG WP]PQ0"l<cÓ1zih8LSx+`ݭL;|҈;z+@<㓀  @#UB)Յ{p CX"h Qm01phkUVFWt H4f*1XTU4402rUHr]n04,K,@+ W(ypm Xͦ{"4pk hl[|܀Sm!Y'Y" `X  JI1ۛ# ѓ"R6)ҶEQЀЀá39 Vܷ:pfpǸ (\Ue `\pÅ[{ [M"zX/gQ8OR ی E)N9 p.ak<| D7-DY>wŎ`Ⱥwi8'"(/A}4aBJ@"Y| 4agR$nUL;H[MLM!"֦5s} 35ʓf4+"|"y@U1<5|%(Y+IuQ__iC6<>!B:ވ|8F,„0(! ahIPHeؽ:np^hS ^g Д J[U4ۄpT[Vdekۣ8ŵC۽p(LlA0fl, `d&ǥgsyELܻXz]Ug% ^`(xhݍ]%F HY4sJpJuGS?6~NȅX!-S; piG1.G0C EـFH둓qĞ :Iz Y.V&l c6~b?@A'B7CGDWEgFwGHIJKLMNOPQ'R7u8TSnUwfu4Z[uuVlF^uab7vFdegvvgghv^vk?lnpWwuqGnrOusGnP`vwwuyz{|}_vo~mz0GJGU|o&mƤxx'xgKx'1oywZ4Gw"Tvnytxv7U哾.z/uwxX?VG'xtJz_o"php{G{pz$_gۮ{Ȁ|v2{O˿؁_|G|Z7|t|"z˟yM%|bz} 7߇m1'|F/}n1٧?۟:%o}ߦ`'J8V8J@~g_Jׯ|#FWraeX7~s}̾~O/>N~g{_|4*X!Ĉ'Rh"ƌ7r#Ȑ"G,i}*WlJ! ,)i H^%=X𯎆%xE8# 2@Yp%(N,,|)pEMBnAG#tҁ_ȜI8pE.u2v D)GA$P+pm[3\UP$m0X2 6QtiQ5,2 Na .6rYU3?}[7 -0F q-8LyG >U!(t.XOA {QRA.ܔ e@FLbVpVL0/@hE$ɱ{r@"QE.+ If/E9NI.yH-""{1I ()LOYf9PAt!A|)BY+&?֑գݳ??Q`?/ @ ϥ*AS@ @ D@t#/ZЄ7P} _첁 @= @PAxGn@`AkP[B@: GdI>/W ^D+=0]I"MA qA!Tİ)*#KA=P}P`(nBttVq$xA!4" BHSe `@x4h]3y="xF{yϙA$dA>nٜxn`p?&l>Ai[zB^@-D{A2#abpdOY&0db!l9ROpք~TsȂ(yW-8@!>J$J?(K\d ۹>KLHA.R$1dZw!P ^c<A&\3 !BN+eD2L:)uhCvoDpMD"<4І* T x Ђv!5Hk({" " WF'S r@ Z4`lqF* s4b6 N0f)MթrNy"wlOS GKAZQ5gh`B\TFW.Xmqt۱FH60%(Wk 2P zqM2@1s @3:XHMwN9iArnHMːnR3e˺9a3ERRMMEN8Ϲh .Z9 ;2 2^6>VC,@Zj'kԀ;Ϻַ{Yv zQk^UD髙 RDxϻ~s+elY߾:XWuJ.񐏼'O,Iwcj'$#8'BAFnGTJ"-`E+V.rH@F5{k.ݝq%G`ܵ@")8 ?ܿlG4@@;"a +Vǁ SbЗxvRt u LI",abB&T5 P4; ֒ %SaD>S B&ґ P" 3#6-##D ((u)='ΐvqq Bv(b(<0G BBqp"3A"s"G7eX^rGEd%hQSwgZ$u2t2X-!B}{= s1Ts.[xr!5fՂ!1878Š)"-)#;-25v4uC*0."06G1nt(&ޒVR2-2% % T 0ϲS" +ӊ4@C;u37SS5nj6T*6l.65 -ia⢊jIF w9^;1l>9V%i1HSiy;=sG%$ S2Q F򄇜aS&%2VB@@ ( I" f^հ1t8]Q>v  @gC!%?6W^"@ B'%cmDh}fEvVFhh e2ye["su`+RqSZTIMID[*HnM QR&K!\)|b!Ŕ$tOց ) De급ئ~DIp\}\Ibt/TO jAZD:,:uU `;/`i);;aQ%HPa  8!YMI@;g3/x0E=#zZ)&|9Xr0]\SөZgZZUuI`J$p+eD.Ǖ\\exxgj2,VZ':@-Z]qZzk_*}̂B_E>zeqZAe;`Fc4 w5h8 !򄗦b) ;@B Pd9И[FD-F/Kԛa əy FPęÙ>25Vc'!"Y*h!E1ED E2Fg i62h7)Fҹupm=-׆w>ģ\F:Z9#Y-yrǏ)L4BrpF]'hh>džWc$:%:KW-VցkIp ioQlFTF6fŶok8r*ؠ]r'˳SYc1ƃ38ZKsgrsnXT\buR5QǠ[R1zK21+`IE{|8@AnPAo'8cb)36h%f!t~źX$\6|8zQ -1aez9CCKA`'W|[ d}L! ]V )eaǮJ'PE@Z Us@Ȑ|Ah QqQHP) u juM48:7p1L),8j (8QutW Ehbс!PЄ" t[Xx֠hᵾP$ᵳB P Bχw\Ġ+Jh! ϓ뱔!$3BRrc4aj@ ;pgT E,bw1#M ڌqnr4"V)V'L*8"r VVi5BrBSIB!38G Q4I0PH4vqD;Au_O9&;e_S.iu}nI2(557'#1D/c"۱YcM #w#rpOjoZ:֊:588`:-S;Nܘ8&ӯn!^Q)C-S ==FtCv>="@@ tYɵ ,*m4w0wF$۵ Fq1M#M 0*)iW9eݚ!#p:AЊr@ ~mENE )Aƙ9L"{:gꩋOJG %I9|xA`AR`<H_Hz $ck̗!E.uzYo!-btqՇz鉌'88 V1b"(2bóM=R&pW EPD* E<qKkF[F0hT5T[E N/01Zec96 -4F3F(do))Tˠ$.&JM~s(XG{9qllykT&o{vom{6SnF.#O4h* ,(ă>8PCF1"?LjB(3A  hDp{J hF &8"BL~VXe͆gݚl;=؎;(`iEX1Gs&d}ZhҥMF=k.lk%y6Ǩ&nU%5ABV!R*8 Ǟ]v`19SDwcB& QH߳BV{nڋ@DP4":D0>:¤?[*$DOD1EWdōb p3q : Hyq,pi  D-6򡉑 | /q#%  *!aThO>3A >%:T C!SQMNO8*R*OHSҁ2|T' Xhζ":$ׯKH)ϗrPD$B" *@.H0LH+xiH1ۣ#9 `9A(bE(rd:Ey1־ty  p5"gnXvy7r<YάD9#cUW^|OYh8Sb<ȁ:R6rD[5ざe7gFy3v)x" ,Xp>&&HW ĵ8|)H<% F13 GHW 4p*@c PG $^0QK(mE#D$l`?aNB*/A 2p"z<" 8c2 @)jf$%` ?\tc#ɯz̙7TRnU&b⥆z1V s n;F >m Nξr]t$ߞR$3MC3V$ІYhB " PI<Є, LAzI0QfJD5@UPQm",pJIcٌ0Rb54HJE:t%=@*R. I=Es43WHL8=MZCW jhCۀP mv4Ti*<ù4`+1y`%C$:x ) bxEĘFx,[XohoȔBGAn%E1]-VY X`εRT棁r.2 A҆3 9 N Ѓ UUR7@nbA·"H^*G-,aӁ$e0" ĥ2/IY~[':P sE3Ѧf4I+X$lsR/-;.އLH4IroTḺu|&)!`rƕ=cHE#)?V=mxfTJl!MQN X(baR;#GR $澰 +&< VhLThZʩGxg'$H8"< $g<eJ^,X @8[d"2f#l F&ڒQeq҉k %[Ѡa"`&Ѐ B@e9f+2`&/CRPW#$NEl:JJ8L@I@9ۙA urZG^<4!3+PQ9_Wz%n݆GΟl͟鱭a +e&:mb@*n?D:tiE:|x=ovdD`:2@L93A^By#qˀDAWd{2s:(L|ob}zַ}Uj a?vO6!uZ:  _`f/Qi >AȔ#bMR?ħQ SU)ϗ 3'x0rgX爆XHL]T 3˾ S,Q` jA)P?+ 0 C P2Yy!n3$h?<Nl xC-73HN%q!!rC8D[Œ = 8MH)=F.ň`T1Ck<܋;x=jg١>y)x<0= X#o GI!ة]9(1ss0 qHm!ኡx9 yŨ h(!TI8gGHz4JĻPB8"#R HGD% t`HyAq; 0W+!8H ȼp %<|koq:ȼPz  <.X!e, *:980>.26<58H<(`Wx gH=s35JKC$ѤMJbh~} 38o_#S 8<:م"WAd bU;)(=;ʄË6a&$D( çT ]8Y 8јAB`Ѣxьч$zj ( BCy0 cI>1 b`*(XDb)1ī[!H1Z % /  (,(S8YB_|Tf].Ppq9ĐH+ Xګs"b H !Z H K m9 y ` iVVe ̨ ߐhrb,9툨=#:=˳ 0o-^뺌ؕ( Ȍb`bw* &.# /1_<4:頤3N4n8RXz7th?;؈:`2FDV X#c;Υ̓X@8Lv A>^EVUfĔ b2d%Z!f-cVa&b6c!!6ȃq!ڃH'jQ0A'Tǃ0ghjAHRY6 @;J{Έ`IP@L`)3<9iza`Fs. g1+Y龁 /T9>nH]E0!LB`i&4G|JyݎЁ"Ћ :} y,X(x]38ж~ʮ)J}9IkP&#=C0ŮҩM>rPDG4e&A06ܹ^b$ȃxOqQkyF0;!J]3A*t8H 52ktǏj@n[ɛ1+!%B<XHG -6ly 1^pGaHI08(@Vq1@7 D< y̠r&I1؈HPPOYөqz|P`a{r\WP-'~oA\.Iр- %$<#s C Xfs$,7 ӸHЈrs :}%j^^;-L<tOO[ mx8#8ͺ L"/ 8&&;K$N B[NrõN>8_4"܈TP7O) FEC4:. uFwL6ȼ̌( "ȂS;H+%c> 颐ioj< \x/ xlgt/0p(1ؘ JRbQ+#I Єl 80 ѩ7RbSiӨް%@R)Q;~4*-(xx(Qxqxu ?] 'H)zTR/D5:cxFS=s[(W2UfvXxޢ-\6_UʒZ^a#0-I8U!9|=;:OU{Xvet^kW wrp9w&?C|"X  ӯăyX%1X"&+ha" vbD[ 4H04`AD 0 |/$//ȌѤA9I )ԨR <%/U ֡9ʯbǒE Y7 *۲R(R`"\`dJ}>hNpJӗrzK݂4JE&at7 AANj#;W .?x|8fWmq$P$  ?/ɣ*<G?+MWeANG%VAKwbe#.!,u[XTS)OUAPPzN(t!K BcyUAWO$%UAC!0iBApA } cu&uL8ՔS!l5`!lD rGS!4J:(O1c9*g^P*˔yRwA(N ,pEI}d[8YPm@>l^8}T]Az@,@%QP?g0O"(/q- /]23GX2%L/?(\bA,h2#" C&cF} c|xEC !y<՛`1/!a%V(.x@R3Բ @EH43lFt:H )PS")$B@p_*̖4FI@+I:& 'D BD2M)&! )2z:f+ܕD`Mij *i&P H ~ _HAfGQf*JG[bAs?< IH МE,ڊPF)j rQe%-!?40BR !ATS,D따U(Jr@ee- ž+xr* PbɗD%ZK@"P &+BsĄ A81PE ЇEw8Fp5h(7= {(!bA^,'YWF4/h13 =I1bAZ $[M5 APM0#3X #_ATP6^@VDNTUEe3{P`}I 2 Hˁ[ 3\I!0 G@ o1Jn"l0Mg C P/ [ q(QB1X<` !I*bOIdC HŰ,qخmG"XbJjb`2 }1#+.3O&&$ {Id 'xK^ xAj W-h'1QrA@2ضbj\ %!)Al8Z%Q/YYlBe33qH (’8 %NwJW(PBjLa$NcQKX NAf.HW&jy`/?Զ>H4l@&v iճѢjMC1m:0[4\ѐ.׸HnA.ҥ{qAj :qAlcҐ(?l_;а-+>V,>1/(62vʑOdh/8c=/@TFK,9 Xqd4 DHSyGe p I pIUA@ӡ\ |вLAT ^%`oDV_L \KKLjd !BUY~ Q]!U8˵K"#4F4N( DzpMAEy 0OpCl`%?8T4;3CCУ0ým0CڡRlA A8}@ %ܝHA= <ȸ5F$@8XAdレܥLK׌A:Zya.2T`ecp8eelAV$~8|XGyF}<9|t=58vDWACmUH՟ǟ'J@LҮxIWVE| Y5A\TG@!NC, JI!i]J4$Fufz@ qIHg'>AEC8\Z,R\6SJmrdTL0R姃RA,ۈ>EthAT%0~PȀd[u褅JL NWc)XK8fMKQD %UA7߃)~DVNH|m$GAe (4?(9eATLDF^GA<]Hx.4(ӝ(IFcV@h#!+FЈdJF֖..%*Yp/)ALV,V@ ( }JdЩL Jp ű8D)JEvZݠ$C6|@Fû ,Z J$9ĤNbμܧy<۫SZLHޟ@gjĀF 6ۆLH6CPHA\FDNE쪟YTh]J4a)d- ^[;ۼTEމ\5f U f #4݄fThKz\^ ܇H.c"-|aYU`\+DFEڃ<ޙ*ADC#kA5D.䍭 T0HigKH)O,!zzfi'L̤В LT!Pv*JiAdx0Dx`ߤ2,<^ĞF[iFCeA(ZU꧄mP?**SQ(.ﲫ8Hʄ6 N -OK,0//3373?34T(YHL xc @HUL(0hN ?ezuFD613KAAS8PĔ̤KDMN}MŁA("DxDrdn!dZTAFgjXwȑS`JIN̩q=D]> v=,5$MEʄtgPs_4KEA*JUno:I2SC#TV4PUҦD U?-?}~N\STaKx $qna-=M \qjZm?&\TkNM`+hS$g8(*PBJo4'WHhF0BwSu'Z}q'R&(c@miThсBHJ/pv\=|N?@zDA0Mc* (mEFԂT8cUej &s|ȢD(HvT8@E8A@@>đGŸEe\?jr\u@U;b΍@ڝ7@ܘz< dI6JT̬KFήlA`Xz,JxAEȱXt#۹}Yb m(lŽYX e1KBxdǣ' VeiS<6Jh tʺY.QmrDz Af$SdN4<9JYJBDkDL0L.. bYP*wT@/.caD?0G<9=`G_QTс;Suo1,T `(`l‘T2AlWe<?0X(Id ȡzUC S{Yw 4qI=QP PI (-ÐT%JOy_c|5K\ Ŏ{g,zbm˃Wуˁ;>|2C{>X؃K[C>'AKH8 5FɅPJK~~tXNcWX(ɩ74TI}t#ZĄ-dAy½‰UTb^K)x)\hBBA&|@<  p@B8'Bb^tqLAI|,!0d1$cc<5i9㗖:~LiSpҎu8H4\TXD+,1N Pi(2g*s* -?N*&培%8?hj<ŜT_t.u'8tEhvxv RG",Yx &/:>wHD8 G.8"~៨W@) ;(S,p+A<(/4 8  p:HM'z@tj"(R z@r!j L*@D?ϡ 6p3IBQE>E A"Ath-Ft$(R,+HosDTM95p?75 @4U 3HCS]W%Kփ0* ^"=1m 0Ĵ@{ b)COS(@a'&V-,'RX!sr:=&Bd$a'ȒaR8`5߯!tis ʼnooҲ!t{ ;rb?HM:z}@ͿuLJޭ)E#w=I=YQ UГ+!8@U6DЁ1H@XO"Q[v5!u8 ^0Ջ j$EB!>eM;Z7D%p+B a'd$2JoXo[bRIe `VLיHK+AzBp !ns Z" s|`fB/b<"H05(X4R)IhEQnʌY؈ dF8`H{- ( ɏ9ȽF M 0P" ObI! ,"($4 D`娛AD|P>?<[$ [X0qф:Sr( &qO@JTI0[-sqHf"Xp%A 8k,C$2k#8{P2AF1U#CuSq- "478~ *DJ%x,hIRE CD肀a"pQ{7ď) 0zM>(s |AMT4aA @C4вPB֜8tJv^AG@JР1hCюD C*Rb<<@8O PcsFD[)$2/sȜL A[Q:ez7Td< Xbpx`>IlCx$hpx+>+):΅^?ZYR${~g>4)Ct -h̞#yKa!n!|-cOu $B$9jϼ@yLAV@>v<1\^hFfE+)+d8Z Z E f%6<RoJV 1kTWo))n/df!b*J8.1kؚ{<͇[[-)FH4^n4p!&Hn:pքAvG g'J"L p0eu  $B$B(@j> A74A*"0FiDƎ)3lY4z(D%{<8&'"<$ΧڇZe8.'~Nb'D$' ocX^H"1S-7dH"A b LyܦfM':`2ሆxG})P0:eX! 7L#XnV}BǒBLjr xP-,$a.UV)BAA\ΖF4""t^j_aM|f i'ā !+2-#1T,_FoV, b\7dOd&" c΄# `!ZMmo *F+w$UIL4 7>J,L &GiB2@FuB&k$ppaT:l1Ѱ#cNd+' Yj!VWzen$, ΰ0B?;?)TSA=)1*tPDd8d0{Hp8 7BCeTE"$0qFutGyGK !!?ZRB(VbFbHbB+õn bAQ2܁5HH t&0 TH BD (@YZ"0e~} !Bl " t@" U/AҺ@4lP7p`!X/J/HaKAnc=)܄'n*E@,/s%bPC؃ #""l "hc$llȶ,6It, 2`31q^ 3b arxBn[t&LQcgi! !` NC"/x\ԕAms)zmClfOE(+ % ^:T,8Hx$G!ƧO..r/ `N:Q#m ;HKMCVQ^QgE#ėU<&t|""BGuM:y,VT)\BT2TJւ)X\%$"wfF4c" b"H # E  `=)`V1."r!pGFOb_Ae*!J]l6Ʀc`:~J'6 u  V[n(v3Ȗ A6֕V[aB\3쁩r$aGBQjB`w iWGk;6e7b!&alxvt$rmơ!С `" c^1in Abo"JlLf IЈh!@nju!!| (~HO!Tp1C`(bo"f 0." j RA#aK_ Xh\J9?b aFxubtj9|LZO$"ONG (&{8{7DŽ@ZOЂ*OX Y",c@!#"ģ aJ3EbL 3/oJ7JZF"@.e8&ȉZ2S' 6C!:ܭx-N,@D@,"N$ $h#2"Β8@4/)}Ty3V~{NpE)V|jU]98ݧP1M}ySm%2d%ǓBAcoԁ=b>Umʢ'!nB?O=SQ#B01" [ϽS OڼLTp=J:}-x0" פG!~Y˂Ioʯ;˅ BJG"  '0Lk2 !~K= !hjK!PaV(eV³†0 :QJ#u|S3ll,J¨<'ltjlKAn,pZք8Y N0RKpT̘ZBށ*^a]V7Q*o\ vk#]1bL_ ^U25O#<]Ҷ U"| 0v#J *>=*dikY B a =!~!F~8 Q\@#!%o"4\cߝQ<'^ X:{ϘjȰ#G>>U“9d92&F"br&b{x#p1EͥL:} ??1& JmT+1@ B@, $8~CɑzKX7r?kJB*bf xsADM~U m XDP wrG@BHdYepP%P^ _!py !P?`*ؔ ǢSH#h4>5xN]L&xMMG&@6R=&h#q#8RoJpK$&`fKWX1Bgx,"?<C&G/(pT?2TQA*Ma9Q8?v5"%#pIgt]LFSAK ʲU?20 G ܱ B3J?F:F.zB8Y|$g(!qS.ͩ cOS3IqHyE9M[6AR|&?s$TMǩw#%xǦQ G QO4!$6ǔީ`s 8s)@UZSs<,1T< X;5upoqk 5p3ЛD0%qJJ\Ac xxҕJW3Gc48/:?99`1u!6=Mۮ3epRN9N.arEblf}fIyT9davT& a\9=6ǔ{DՌ}mQϘAgd"Er9{.L$TL!Rf:q-B  3/f2Қ8V(CX-l2ʤ4)OX }s>eF@5g?ˢiF6zc,hkDZ}/Ddkm$6 ,Ru?{HAņ m[*wHRdks pG]LMI's"b™[PG1#vcXB w3m(Ib<:e௃)"qa+,PDi,$x#duaGJ@"Px4oc"NUƾ9)T"a}F`j=Y*F|s)ds'2F/A.\FlPIMf,%-)^JԄܠ ωH$p#-@GJlmBIVO1ZS-"@IBz)80頍A5Y1f;62RB8rLYXM~PI4xZ`xKyůB+S|=UoS9wr4xZڛZk)v`fH.XStD $YlL n#w&ڤچ͊<씇aYvA=`3 vG9ɡJNÆɗwq fIiϾ)$5A*sCS1[>*h|7ZKm2@8bг@@x0GB1bHXz{<" S';)X r,0.f)x}g,An Ԅ| v|X:jc%("3u@ 3fmZ*V9v9IḺc6!6F9Rt*nm)Y@9R MJ!o^RJ(sr "QnNMAy6,3o-o036#pwIv6A.pAdg# #lW 40 gp4#GlBN$:Oچ du qæ\a3"Y3D3p@"cJ3bw)2M 915yXacaS8??n @qla2=a!%D%C3wmt/(C:CX0CO$T#7Y5QAՊ0!^PF FЋOF"3 qHnz6d_&OXp#V~LK~4PPL1Q -AdՐ)1JsR-0)P#]MO Y#Z<GG™_ԈE2R p_.*ە(@ I)$))Nh())O&)5Om\QqP׷%ǟ0~:VRf jB`i:!@<UW1#TVKD_TE& \R* W_'k B @(5WV-P*`w U?Ya4@eZ%);χiJZh=ɥ6CwIb_<\*PjJw` /- ZW#`^rhicUc @ @3N]2@?+y&_Y::=]TSw Ū_g ?UX<)*Jj ;FfLAiѺW 2CfiCf6jOj6h2 2p `?F6I cNW)By6lO«!j~J#+Qf1 JAj6cC<$(yA:JY67 8qʛ1QeSvl %il'&Up: *4}P FHd{{!g;#m'3ؗWpKQ 9_1Xb)$&6f1Fl)=U٨^&m1 m&3q,%g·{TQbG1YvuMA Wut3{s5!%K7}7pI,.pCV100 $pvw bu1cRV)-4!s+oš#16r |[Ŗꖙ6~],cL]_p ZOQ26g3a#$:xA,iXCpM Qsrv@a2*9B4c, 3)YA[c!v;|r>b =;e:]j6YF4%wbo> LKҡnvUE\ա]:&ZIk$u;#|"R 12#!{9Tr:tedP5#,XPv#Z"c]:mho`:^jr"Zk8=beb+`$62a ZJ.NZ;Kѭ*՜G>i:bK C:Y )rQ4k1A>@qEl`J Q$[>0f0Ts116"p>#GYѱ>Mט22 {R>( ŀl6 UOq Oc\x Nh@9Olk' - eE_fVT3G"rз5 y ې_JAL#!WYR,)r>ҁXպr,+)(J{6Tj۹ƪ .^h3 x")NT!yЛgpLWJyr;!|yȲ^YLBx ;Ћ!@$?Q[tImyb)mplNGד!= uQ6GB}A2~Rzq#Yh"tixH#XA 4`C%*HD:I Q(ʖK tdpL'+]fDi"8'@5σ8kVܥӯ:-H$4:`'~7[}}%A9s ,8,0*:j# b&r悜iv jX@윐ج@ODB!p@n޾C:ظqc&8r)!H%`!rmMLg$j|*yBP/ C)d ­?X%8