diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 77ac5b37..2d5a4d55 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,14 +13,22 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Run Tests + - name: Install build dependencies + run: pip3 install -U pip wheel twine build setuptools + + - name: Install PlotDevice and run tests run: | - pip3 install -U pip wheel twine - python3 setup.py test + pip3 install . + python3 -m tests + + - name: Build Wheels + uses: pypa/cibuildwheel@v4 + with: + output-dir: dist - - name: Build sdist + - name: Build Source Distribution run: | - python3 setup.py sdist + python3 -m build --sdist - name: Upload to PyPI env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc27eb77..f1a082d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,13 +38,17 @@ jobs: # collect notarization credentials xcrun notarytool store-credentials "AC_NOTARY" --apple-id "$AC_ID" --team-id "$AC_TEAM" --password "${{ secrets.AC_PASSWORD }}" - - name: Run Tests + - name: Install build dependencies + run: pip3 install -U pip wheel setuptools + + - name: Install PlotDevice and run tests run: | - python3 setup.py test + pip3 install . + python3 -m tests - name: Build & Notarize App run: | - python3 setup.py dist + python3 make.py dist - name: Create Draft Release uses: softprops/action-gh-release@v1 @@ -55,7 +59,7 @@ jobs: files: | dist/*.zip - sdist: + packages: name: Post to test.pypi.org runs-on: macos-latest @@ -63,13 +67,22 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Run Tests + - name: Install build dependencies + run: pip3 install -U pip wheel twine build setuptools + + - name: Install PlotDevice and run tests run: | - python3 setup.py test + pip3 install . + python3 -m tests + + - name: Build Wheels + uses: pypa/cibuildwheel@v4 + with: + output-dir: dist - name: Build Source Distribution run: | - python3 setup.py sdist + python3 -m build --sdist - name: Upload to test.pypi.org env: @@ -78,6 +91,5 @@ jobs: TWINE_REPOSITORY: https://test.pypi.org/legacy/ TWINE_NON_INTERACTIVE: 1 run: | - pip3 install twine twine check dist/* twine upload -r testpypi dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e051121b..74dd839d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,4 +19,4 @@ jobs: run: brew install python-setuptools - name: Run tests - run: python3 setup.py test + run: python3 -m tests diff --git a/.gitignore b/.gitignore index 6da63f06..28ab5c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,8 @@ build/ dist/ plotdevice.egg-info app/Resources/viewer.nib -Manifest.in +plotdevice/rsrc +MANIFEST.in # local development deps/local diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 00000000..14bec026 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,158 @@ +# Building PlotDevice from Source + +If you’re interested in unreleased code from the repository (or doing development work on the +library/app itself), there are a few different ways to build it. + +## Requirements + +- macOS 11+ and Xcode or the Xcode command line tools (`xcode-select --install`) +- A Python 3.9+ interpreter with wheel availability for `pyobjc-core==11.1` + (i.e. not a brand-new Python release that predates PyObjC’s own support for it) + + +## Setting up Python + +By default, macOS 12.3 and later only include a `python3` interpreter if you install Xcode (or its command line tools). +There are a few other ways to get a usable Python setup worth considering if you're interested in using +PlotDevice from the command line. A key feature distinguishing the different options is whether they +install Python as a ‘framework’ or ‘stand-alone’: + +> A **framework** Python installation allows PlotDevice to give you access to a GUI interface for +> running scripts via the `python3 -m plotdevice` command. **Stand-alone** Python is slightly more limited: +> it supports ‘headless’ use (via the command line tool’s [`--export`](#imageanimation-export) option) and +> can open a window displaying your canvas, but it will not show an icon in the Dock or give you access to +> commands in the menu bar. + +### Xcode + +The [Xcode command line tools](https://developer.apple.com/documentation/xcode/installing-the-command-line-tools/) provide a +**framework** build of the (now somewhat long in the tooth) 3.9 release of Python. If you're okay running such an old interpreter, +you can install the tools with the command: +```console +xcode-select --install +``` + +### Homebrew + +The [Homebrew](https://docs.brew.sh/Homebrew-and-Python) package manager provides a **framework** build via its more up-to-date `python3` package. You can install it and run `plotdevice` with: +```console +brew install python3 +``` + +### `pyenv` + +The [pyenv](https://github.com/pyenv/pyenv) version manager lets you easily switch between multiple python versions on the same system. +You can install a new **stand-alone** interpreter by specifying the version number: +```console +pyenv install 3.14.6 +``` + +If you want a **framework** install, you need to pass a custom option via environment variables: +```console +env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.14.6 +``` + +### `uv` + +A newer alternative is [`uv`](https://docs.astral.sh/uv/), which uses its own prebuilt interpreters (but currently does not provide +**framework** builds). If you're okay with the limitations of a **stand-alone** interpreter (see above), `uv` gives you a number of +ways to run PlotDevice. + +Run a script without permanently installing `plotdevice`: +```console +uvx --from plotdevice plotdevice +``` + +Install `plotdevice` as a standalone CLI tool: +```console +uv tool install plotdevice +plotdevice +``` + +Add `plotdevice` as a dependency in a `uv`-managed project: +```console +uv python install 3.14 +uv init myproject && cd myproject +uv add plotdevice +uv run plotdevice +``` + +## Local dev environment + +`python3 make.py dev` sets up a self-contained virtualenv at `deps/local//` +and ensures PlotDevice’s dependencies (including its native-code extensions) are installed. +Once this environment has been set up you can use `python3 -m plotdevice ` or +`app/plotdevice ` to run scripts directly from the repo. + +The dev environment is also useful for doing local builds of the app (since it has all the +necessary dependencies pre-installed), so be sure to ‘activate’ it via the correct invocation +for your shell before running one of the build commands: + +```sh +source ./deps/local//bin/activate # bash, zsh, etc. +source ./deps/local//bin/activate.fish # fish +source ./deps/local//bin/activate.csh # tcsh +``` + +To stop using the dev environment and return to your global python interpreter run: +```sh +deactivate +``` + +## Application builds + +The application can be built and debugged in Xcode with the `PlotDevice.xcodeproj` project. + +The `make.py` script contains additional utilities for building **PlotDevice.app** (i.e., the full GUI application) +from the command line. If you have the full Xcode installed on your system, you can build the app +(including its own embedded copy of Python) with: +```sh +python3 make.py app +``` + +Alternatively (if you only have the Xcode command line tools installed) you can use `py2app`, which can +be especially useful for quick, local tests: +```sh +python3 make.py py2app +``` + +The resulting binary will appear in the `dist` subdirectory and can be moved to your +Applications folder or any other fixed directory. To install a symlink to the command +line tool, launch the app from its installed location and click the Install button in +the Preferences window. + +## Module builds + +PlotDevice can also be built as a Python module, allowing you to rely on an external editor +and launch scripts from the command line (or from a ‘shebang’ line at the top of your +script invoking the `plotdevice` tool). + +The `plotdevice` Python module requires the compilation of native code in the `deps/extensions` directory +but can be installed like any other `pyproject.toml`-based package. From the repo root, type one of: +```sh +pip3 install . # for a regular install +pip3 install -e . # editable install, for dev work on plotdevice/*.py +``` +You can run the test suite with: +```sh +python3 -m tests +``` + +Redistributable `sdists` and wheels can be built via: +```sh +python3 -m build --sdist +python3 -m build --wheel +``` + +## Utilities + +The `make.py` script contains other subcommands that are primarily useful for packaging: +```sh +python3 make.py clean # remove build artifacts +python3 make.py distclean # also remove the embedded Python.framework and deps/local +python3 make.py icon # regenerate app/Resources/Assets.car from app/art/PlotDevice-app.icon +python3 make.py dist # build app (codesigned, notarized, and zipped) and update release.json +``` + +Note that `dist` requires a "Developer ID Application" signing identity and a `notarytool` keychain profile +named `AC_NOTARY`, and is intended for release builds only. diff --git a/CHANGES.md b/CHANGES.md index 4cd9fdb0..2108ec8d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,23 @@ +PlotDevice 1.1.0 +---------------- + +##### Misc. Improvements +* Upgraded bundled Python to 3.14 in app +* Added precompiled wheels for module installs (supporting Python 3.9–3.14) +* No longer requires Xcode command line tools in order to install +* `plotdevice` command's exit code now reflects script errors +* Added support for running under `uv` +* Editor now uses a `WKWebView` (replacing the deprecated `WebView`) +* Added new default editor theme +* New app icon should avoid "squircle jail" on macOS 26+ + +##### Bugfixes +* `ordered()` now works correctly and no longer causes the `Advanced/Sorting.pv` example to crash +* `Text.flow()` will now invoke user-provided callbacks to determine layout of multi-column runs +* Command line tool now parses `{4}`-style padded number blocks in exported filenames correctly +* Silenced a console error by fixing `super()` use in command line tool +* Restorable state in the app now uses the newer 'secure' api + PlotDevice 1.0.0 ----------------- @@ -24,7 +44,7 @@ PlotDevice 1.0.0 * arguments defined using the command line tool's `--args` options are now passed to the script's `sys.argv` ##### Misc. Improvements -* the command line tool can be exited via ctrl-c in addtion to being Quit from the menu bar +* the command line tool can be exited via ctrl-c in addition to being Quit from the menu bar * simplified unicode handling (and improved support for normalization of user-provided strings) * building the module now only requires Xcode command line tools—not a full Xcode.app installation * the `text()` command will always treat its first argument as content (even if it's not a string) unless a `str`, `xml`, or `src` keyword argument is provided @@ -224,7 +244,7 @@ NodeBox 1.8.3 NodeBox 1.8.2 ------------- -* Direct view is turned of because it broke vector output. +* Direct view is turned off because it broke vector output. * AppleScripting is back. NodeBox 1.8.1 @@ -267,8 +287,8 @@ NodeBox 1.0rc4 * Uses a scene graph internally for speeding things up. All objects now have an inheritFromContext that copies all state out of the context and into the objects. * Added animation. Adding a speed method indicates that the script is an animation. Animation scripts have a setup() method that does initial setup, and a draw() method that is called for each frame. You can use the throttle to change parameters while the animation is running. * Removed document properties and variables. NodeBox doesn't touch the code anymore. -* Added measurements for centimeters, milimeters and inches. Just use 5*cm for 5 centimeters. -* All drawing commands are now methods of the Context object. This makes sure that canvases don't interfere with eachother. NOTE: This will have some problems when drawing from libraries. Most notably, "from DrawingPrimitives import *" doesn't work anymore. However, libraries that do drawing can be imported with ximport("mylibrary") to automatically gain access to the _ctx global that has all drawing methods. +* Added measurements for centimeters, millimeters and inches. Just use 5*cm for 5 centimeters. +* All drawing commands are now methods of the Context object. This makes sure that canvases don't interfere with each other. NOTE: This will have some problems when drawing from libraries. Most notably, "from DrawingPrimitives import *" doesn't work anymore. However, libraries that do drawing can be imported with ximport("mylibrary") to automatically gain access to the _ctx global that has all drawing methods. * BezierPaths are no longer lists internally. They can still be accessed as a sequence, but the returned PathElements are not bound to the path. To manipulate the PathElements, iterate over the sequence, do the changes and use the BezierPath constructor with a list of PathElements as argument. NodeBox 1.0rc3 @@ -304,15 +324,15 @@ Version 1.0rc1 * The BezierPath object got a major overhaul: it now integrates nicely with the state model (using the transformedpath method) and is actually a list containing PathElement objects that contain a command, x and y coordinates and (optionally) control points. * All 'w' and 'h' parameters were changed to 'width' and 'height' (image, strokewidth) * All of the major state change commands return their state: font, fontsize, lineheight, align, colormode, fill, stroke, strokewidth, transform. -* Documentation is user-friendler and inside of the DrawBot application, so that it can be accessed through Apple's Help system. +* Documentation is user-friendlier and inside of the DrawBot application, so that it can be accessed through Apple's Help system. Version 0.9b9 ------------- * ValueLadder now respects type of original value: if the original is an integer, the new value will be an integer as well. * Fixed booleans in variables interface. -* Shift-dragging the value ladder now adds hundreths of a number instead of tenths. +* Shift-dragging the value ladder now adds hundredths of a number instead of tenths. * Fixed an obscure bug when using CMYK color objects to set the fill (they used the infamous "DrawBot-black" because the converted from the RGB system instead of CMYK) -* Turned of line highlighting on error because of bugs in PyDETextView. +* Turned off line highlighting on error because of bugs in PyDETextView. Version 0.9b8 ------------- @@ -345,7 +365,7 @@ Version 0.9b6 * text() command returns the text bounds. (In the same way as textmetrics() would do) * size, width and height property for the Image object. You can use imagewidth() and imageheight() also. * Fixed a bug where outlined text could not be stroked when the fill color was not set. -* The drawing region (DrawBotGraphicsView, a subclass of NSView) is acccessible using the graphicsView variable. This is only intented for hackers and will remain undocumented. +* The drawing region (DrawBotGraphicsView, a subclass of NSView) is accessible using the graphicsView variable. This is only intended for hackers and will remain undocumented. * Added the 'New with code' command which invokes OttoBot. Version 0.9b5 @@ -384,4 +404,4 @@ Version 0.9b1 Version 0.9a1 ------------- -* Just's first public release of DrawBot. \ No newline at end of file +* Just's first public release of DrawBot. diff --git a/PlotDevice.xcodeproj/project.pbxproj b/PlotDevice.xcodeproj/project.pbxproj index 1464ae4a..bc322521 100644 --- a/PlotDevice.xcodeproj/project.pbxproj +++ b/PlotDevice.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2A15C45018A329BD006BDFF0 /* editor.html in Copy Editor Assets */ = {isa = PBXBuildFile; fileRef = 2A15C44418A329AE006BDFF0 /* editor.html */; }; 2A15C45218A329BD006BDFF0 /* themes.json in Copy Editor Assets */ = {isa = PBXBuildFile; fileRef = 2A15C44618A329AE006BDFF0 /* themes.json */; }; 2A15C45618A48770006BDFF0 /* autocomplete.css in Copy Editor Assets */ = {isa = PBXBuildFile; fileRef = 2A15C45418A48765006BDFF0 /* autocomplete.css */; }; + 2A2EFF223005C16B000B4593 /* Assets.car in Resources */ = {isa = PBXBuildFile; fileRef = 2A2EFF213005C16B000B4593 /* Assets.car */; }; 2A36AE2D2868B4E50088976E /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A36AE2C2868B4E50088976E /* Python.framework */; }; 2A36AE2E2868B4F30088976E /* Python.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2A36AE2C2868B4E50088976E /* Python.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 2A36AE58286CA75B0088976E /* placeholder-light.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 2A36AE54286CA75B0088976E /* placeholder-light.pdf */; }; @@ -90,6 +91,7 @@ 2A294464185E575F00E9F650 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = ""; }; 2A294467185E575F00E9F650 /* document.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = document.py; sourceTree = ""; }; 2A294468185E575F00E9F650 /* preferences.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = preferences.py; sourceTree = ""; }; + 2A2EFF213005C16B000B4593 /* Assets.car */ = {isa = PBXFileReference; lastKnownFileType = file; name = Assets.car; path = Resources/Assets.car; sourceTree = ""; }; 2A32957218C59D0A00EEDD1C /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = ""; }; 2A32957718C59D0A00EEDD1C /* io.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = io.py; sourceTree = ""; }; 2A32957918C59D0A00EEDD1C /* pathmatics.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = pathmatics.py; sourceTree = ""; }; @@ -270,6 +272,7 @@ 2AD0A9B5186E552700D1F6DE /* PlotDeviceScript.xib */, 611CC48110BA8B9E00B55455 /* Credits.rtf */, 611CC48910BA8B9E00B55455 /* InfoPlist.strings */, + 2A2EFF213005C16B000B4593 /* Assets.car */, 611CC49310BA8B9E00B55455 /* PlotDevice.icns */, 611CC49410BA8B9E00B55455 /* PlotDeviceFile.icns */, 2A36AE56286CA75B0088976E /* placeholder-dark.pdf */, @@ -428,6 +431,7 @@ 2A36AE59286CA75B0088976E /* placeholder-dark.pdf in Resources */, 611CC4A210BA8B9E00B55455 /* PlotDeviceFile.icns in Resources */, 611CC5A010BA908C00B55455 /* PlotDeviceDocument.xib in Resources */, + 2A2EFF223005C16B000B4593 /* Assets.car in Resources */, 611CC5A810BA919A00B55455 /* PlotDevicePreferences.xib in Resources */, 611CC68010BAA08300B55455 /* MainMenu.xib in Resources */, 611CC68710BAA0A600B55455 /* AskString.xib in Resources */, diff --git a/README.md b/README.md index d12a29af..4bd2c683 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ -PlotDevice -========== +
+ +![PlotDevice: A Python-based graphics language for designers, developers, and tinkerers](app/art/hero@2x.png) + +
+ +
+ Website   ·   + Download   ·   + Docs   ·   + Discussion Forum +
+ +--- + PlotDevice is a Macintosh application used for computational graphic design. It provides an interactive Python environment where you can create two-dimensional graphics and output them in a variety of vector, bitmap, and animation formats. @@ -13,103 +26,58 @@ of 2D imagery and powerful compositing operations. #### Requirements -The PlotDevice application requires macOS 11 or greater (either on Intel or Apple Silicon) -and comes bundled with a Python 3.10 distribution. The module can be installed via `pip3` -on Python versions ≥3.6 (including the interpreter from the Xcode -[command line tools](https://developer.apple.com/download/all/?q=command%20line%20tools%20for%20xcode) -and those [installed through Homebrew](https://docs.brew.sh/Homebrew-and-Python)). - -#### Latest changes (July 2022) - -Over the years since the last release, progress in both macOS and Python itself led to quite -a bit of breakage. Some of the highlights of this maintenance release include: - -###### New Features -- Runs natively on Intel and Apple Silicon and supports retina displays -- Python 3 support (including a bundled 3.10 installation in the app) -- images can now be exported in HEIC format and videos support H.265 (HEVC) -- SVG files can now be drawn to the canvas using the `image()` command (thanks to the magical [SwiftDraw](https://github.com/swhitty/SwiftDraw) library) -- image exports have a configurable `zoom` to create 2x/3x/etc ‘retina’ images -- revamped `var()` command for creating GUIs to modify values via sliders, buttons, toggles, etc. -- updated text editor with multiple tabs, new themes, and additional key-binding modes emulating Sublime Text and VS Code -- the module's command line interface is now accessible through `python3 -m plotdevice` -- the command line tool has a new `--install` option to download [PyPI](https://pypi.org) packages for use within the app -- document autosaving is now user-configurable - -###### Bugfixes -- exported images generated on retina machines now have the proper dimensions -- hex colors can now use lowercase letters -- automatic variables like `WIDTH` & `HEIGHT` correctly support the `/` operator -- the Color object's `.blend()` method is working again -- the `read()` command can now handle csv files with spaces in their header row names -- the `translate()` command now incorporates non-pixel grid units set via the `size()` command -- cmyk exports are working reliably for command line `--export` and via the `export(cmyk=True)` method -- arguments defined using the command line tool's `--args` options are now passed to the script's `sys.argv` - -###### Misc. Improvements -- the command line tool can be exited via ctrl-c in addtion to being Quit from the menu bar -- simplified unicode handling (and improved support for normalization of user-provided strings) -- building the module now only requires Xcode command line tools—not a full Xcode.app installation -- the `text()` command will always treat its first argument as content (even if it's not a string) unless a `str`, `xml`, or `src` keyword argument is provided -- the mouse pointer is now visible in full-screen mode (and will auto-hide when inactive) - -###### Unfortunate Casualties -- The NodeBox Libraries (`coreimage`, `colors`, and friends) would require quite a bit of attention to get working properly again. - A first pass can be found in the [`plotdevice-libs` repository](https://github.com/plotdevice/plotdevice-libs) but they're not - ready for prime-time. If you're interested in contributing, this would be a terrific place to start! +The PlotDevice application requires macOS 11 or greater (either on Intel or Apple Silicon). +Installing the `plotdevice` python module and command line tool via `pip3` require you to +have Python version ≥3.9 on your system. -Installation ------------- +See the [build instructions](BUILDING.md) for details on setting up Python locally using +one of the recommended interpreters: +- [Homebrew](BUILDING.md#homebrew) (recommended) +- [Xcode command line tools](BUILDING.md#xcode) (stuck at version 3.9) +- [`pyenv`](BUILDING.md#pyenv) +- [`uv`](BUILDING.md#uv) -PlotDevice supports being built as either a full-fledged Cocoa application, or as -a standard Python module to be installed into a virtualenv alongside your source files. -In both cases it now includes a command line tool called [`plotdevice`](#running-scripts) allowing you to run -scripts and perform batch exports from the console. +#### Latest changes -#### Application builds +Version 1.1 adds compatibility for more recent Python 3 versions and no longer requires +the C & Swift extensions to be compiled when installing via `pip3` (since they're now +included in binary wheels). The app’s text editor has been updated to version 1.44 of +[Ace](https://ace.c9.io) and has an updated default theme. The module now installs cleanly +using [`uv`](https://docs.astral.sh/uv/) and the command line tool can be [run via `uvx`](BUILDING.md#uv). -The application can be built in Xcode with the `PlotDevice.xcodeproj` project. It can also -be built from the command line by using `python3 setup.py app` (which uses Xcode) or -`python3 setup.py py2app` (which uses setuptools). +See the [changelog](CHANGES.md) for additional details. -The resulting binary will appear in the `dist` subdirectory and can be moved to your -Applications folder or any other fixed directory. To install a symlink to the command -line tool, launch the app from its installed location and click the Install button in -the Preferences window. - -Prebuilt application binaries can be downloaded from the [PlotDevice site](https://plotdevice.io). - -#### Module builds +Installation +------------ -PlotDevice can also be built as a Python module, allowing you to rely on an external editor -and launch scripts from the command line (or from a ‘shebang’ line at the top of your -script invoking the `plotdevice` tool). To install the module and command line tool use -`python3 setup.py install` +PlotDevice can be used as either a full-fledged Cocoa application or +a standard Python module installed into a virtualenv alongside your source files. +In both cases, it includes a command line tool called [`plotdevice`](#running-scripts) +allowing you to run scripts and perform batch exports from the console. -Easier still, you can install the module directly from PyPI with a simple `pip3 install plotdevice`. -It's a good idea to install the `wheel` module first since it greatly speeds up installation of the -PyObjC libraries PlotDevice depends on. +#### Application -#### Alternative Python Interpreters +You can download a copy of the GUI app from the [PlotDevice website](https://plotdevice.io) or the most recent +[release](https://github.com/plotdevice/plotdevice/releases) on GitHub. The app has a built-in code editor and +is bundled with a copy of Python 3.14, so it's everything you need to write and run scripts. Open the app's +Preferences window to [set up the command line tool](https://plotdevice.io/tut/Console) if you'd like to +[run scripts](#running-scripts) from the terminal as well. -When using [pyenv](https://github.com/pyenv/pyenv) (or compiling Python from source) you have the -option of building the interpreter as a **Framework**. This gives you access to a GUI interface for -running PlotDevice scripts via the `python3 -m plotdevice` command. Non-framework builds support the -command line's `--export` functionality and will open a viewer window, but will not show an icon in -the Dock or give you access to the menu bar. +#### Python module & terminal command -To set up and run a script using a Framework build, do something along the lines of: +To install the module for use in your own Python scripts, you can use the command: ```console -env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.10.4 -pyenv shell 3.10.4 pip3 install plotdevice -python3 -m plotdevice -``` +``` + +This will also install the `plotdevice` command line tool, which you can use to [run `.pv` scripts](#running-scripts). +See the note below about [using a virtual environment](#installing-python-modules) if you don't want to install `plotdevice` +globally for your whole system. -#### Building from source +#### Build from source -You can also clone the git repository and build PlotDevice as a module or application from scratch. -Consult the [build instructions](https://github.com/plotdevice/plotdevice/discussions/59) for details. +If you're interested in doing development work on the library itself, take a look at the full +[build instructions](BUILDING.md). Documentation ------------- @@ -310,7 +278,7 @@ use of a nested `with` statement in the final example): ```python # export a 100-frame movie movie = export('anim.mov', fps=50, bitrate=1.8) -for i in xrange(100): +for i in range(100): clear(all) # erase the previous frame from the canvas ... # (do some drawing) movie.add() # add the canvas to the movie @@ -319,7 +287,7 @@ movie.finish() # wait for i/o to complete ```python # export a movie (with the context manager finishing the file when done) with export('anim.mov', fps=50, bitrate=1.8) as movie: - for i in xrange(100): + for i in range(100): clear(all) # erase the previous frame from the canvas ... # (do some drawing) movie.add() # add the canvas to the movie @@ -328,7 +296,7 @@ with export('anim.mov', fps=50, bitrate=1.8) as movie: # export a movie (with the context manager finishing the file when done) # let the movie.frame context manager call clear() and add() for us with export('anim.mov', fps=50, bitrate=1.8) as movie: - for i in xrange(100): + for i in range(100): with movie.frame: ... # draw the next frame ``` @@ -341,7 +309,7 @@ use the `page` attribute rather than `frame`: ```python # export a five-page pdf document pdf = export('multipage.pdf') -for i in xrange(5): +for i in range(5): clear(all) # erase the previous page's graphics from the canvas ... # (do some drawing) pdf.add() # add the canvas to the pdf as a new page @@ -350,7 +318,7 @@ pdf.finish() # write the pdf document to disk ```python # export a pdf document more succinctly with export('multipage.pdf') as pdf: - for i in xrange(5): + for i in range(5): with pdf.page: ... # draw the next page ``` @@ -367,7 +335,7 @@ If the filename contains a number between curly braces (e.g., `"name-{4}.ext"`), # export a sequence of images to output-0001.png, output-0002.png, ... # output-0099.png, output-0100.png with export('output.png') as img: - for i in xrange(100): + for i in range(100): with img.frame: ... # draw the next image in the sequence ``` @@ -375,7 +343,7 @@ with export('output.png') as img: # export a sequence of images to 01-img.png, 02-img.png, ... # 99-img.png, 100-img.png with export('{2}-img.png') as img: - for i in xrange(100): + for i in range(100): with img.frame: ... # draw the next image in the sequence ``` diff --git a/app/Resources/Assets.car b/app/Resources/Assets.car new file mode 100644 index 00000000..dc3c8a27 Binary files /dev/null and b/app/Resources/Assets.car differ diff --git a/app/Resources/ui/autocomplete.css b/app/Resources/ui/autocomplete.css index dfaba7ae..f7303dc1 100644 --- a/app/Resources/ui/autocomplete.css +++ b/app/Resources/ui/autocomplete.css @@ -402,4 +402,13 @@ #editor.ace-cloud9-day ~ div.ace_autocomplete {background-color:rgba(251, 251, 251, 1.0); color:rgba(0, 0, 0, 1.0); border-color:rgba(0, 0, 0, 1.0);} #editor.ace-cloud9-day ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip{ background-color:rgba(251, 251, 251, 1.0); color:rgba(0, 0, 0, 1.0); border-color:rgba(0, 0, 0, 1.0); font-family: -apple-system; font-size: 80%; color: rgba(76, 136, 107, 1.0); border-style: dotted; } #editor.ace-cloud9-day ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip b{ color:rgba(0, 0, 0, 1.0); } -#editor.ace-cloud9-day ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip hr{ opacity:.75; } \ No newline at end of file +#editor.ace-cloud9-day ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip hr{ opacity:.75; } +#editor.ace-samizdat ~ div.ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {border:1px solid rgba(55, 55, 61, 1.0); background-color:rgba(55, 55, 61, 0.7);} +#editor.ace-samizdat ~ div.ace_autocomplete.ace-tm .ace_line{color:rgba(235, 219, 178, 1.0);} +#editor.ace-samizdat ~ div.ace_autocomplete.ace-tm .ace_line-hover {border-color:rgba(78, 78, 78, 1.0); background-color:rgba(78, 78, 78, 0.7);} +#editor.ace-samizdat ~ div.ace_rightAlignedText {color:rgba(133, 133, 133, 1.0);} +#editor.ace-samizdat ~ div.ace_autocomplete .ace_completion-highlight{color:rgba(251, 82, 69, 1.0);} +#editor.ace-samizdat ~ div.ace_autocomplete {background-color:rgba(24, 25, 32, 1.0); color:rgba(235, 219, 178, 1.0); border-color:rgba(235, 219, 178, 1.0);} +#editor.ace-samizdat ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip{ background-color:rgba(24, 25, 32, 1.0); color:rgba(235, 219, 178, 1.0); border-color:rgba(235, 219, 178, 1.0); font-family: -apple-system; font-size: 80%; color: rgba(133, 133, 133, 1.0); border-style: dotted; } +#editor.ace-samizdat ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip b{ color:rgba(235, 219, 178, 1.0); } +#editor.ace-samizdat ~ div.ace_autocomplete + .ace_tooltip.ace_doc-tooltip hr{ opacity:.75; } \ No newline at end of file diff --git a/app/Resources/ui/js/ace/theme-samizdat.js b/app/Resources/ui/js/ace/theme-samizdat.js new file mode 100644 index 00000000..12530d0e --- /dev/null +++ b/app/Resources/ui/js/ace/theme-samizdat.js @@ -0,0 +1,162 @@ +define("ace/theme/samizdat-css",["require","exports","module"], function(require, exports, module){module.exports = ` +.ace-samizdat .ace_gutter { + background: #090B10; + color: #858585 +} + +.ace-samizdat .ace_print-margin { + width: 1px; + background: #181920 +} + +.ace-samizdat { + background-color: #090B10; + color: #EBDBB2 +} + +.ace-samizdat .ace_cursor { + color: #D4D4D4 +} + +.ace-samizdat .ace_marker-layer .ace_selection { + background: #569CD655 +} + +.ace-samizdat.ace_multiselect .ace_selection.ace_start { + box-shadow: 0 0 3px 0px #090B10; +} + +.ace-samizdat .ace_marker-layer .ace_step { + background: rgb(89, 66, 0) +} + +.ace-samizdat .ace_marker-layer .ace_bracket { + margin: -1px 0 0 -1px; + border: 1px solid #FFFFFF44 +} + +.ace-samizdat .ace_marker-layer .ace_active-line { + background: #2A2A2A +} + +.ace-samizdat .ace_gutter-active-line { + background-color: #2A2A2A +} + +.ace-samizdat .ace_marker-layer .ace_selected-word { + border: 1px solid #303030 +} + +.ace-samizdat .ace_invisible { + color: #6C6C6C +} + +.ace-samizdat .ace_entity.ace_name.ace_tag, +.ace-samizdat .ace_keyword, +.ace-samizdat .ace_meta.ace_tag, +.ace-samizdat .ace_storage { + color: #FB5245; + font-style: italic +} + +.ace-samizdat .ace_keyword.ace_operator { + color: #D4D4D4; + font-style: normal +} + +.ace-samizdat .ace_punctuation, +.ace-samizdat .ace_punctuation.ace_tag { + color: #EBDBB2 +} + +.ace-samizdat .ace_constant.ace_character, +.ace-samizdat .ace_constant.ace_language, +.ace-samizdat .ace_constant.ace_numeric, +.ace-samizdat .ace_constant.ace_other { + color: #569CD6 +} + +.ace-samizdat .ace_constant.ace_character.ace_escape, +.ace-samizdat .ace_constant.ace_language.ace_escape { + color: #D7BA7D +} + +.ace-samizdat .ace_invalid { + color: #EBDBB2; + background-color: #F44747 +} + +.ace-samizdat .ace_invalid.ace_deprecated { + color: #EBDBB2; + background-color: #FFB300 +} + +.ace-samizdat .ace_entity.ace_name.ace_function, +.ace-samizdat .ace_support.ace_function { + color: #FAC149 +} + +.ace-samizdat .ace_fold { + background-color: #B2CCD6; + border-color: #EBDBB2 +} + +.ace-samizdat .ace_storage.ace_type, +.ace-samizdat .ace_support.ace_class, +.ace-samizdat .ace_support.ace_type { + font-style: italic; + color: #B2CCD6 +} + +.ace-samizdat .ace_entity.ace_other.ace_attribute-name { + font-style: italic; + color: #B2CCD6 +} + +.ace-samizdat .ace_entity.ace_other, +.ace-samizdat .ace_variable { + color: #EBDBB2 +} + +.ace-samizdat .ace_variable.ace_parameter { + font-style: italic; + color: #B2CCD6 +} + +.ace-samizdat .ace_string { + color: #B8BB26 +} + +.ace-samizdat .ace_string.ace_regexp { + color: #F44747 +} + +.ace-samizdat .ace_comment { + color: #A89984; + font-style: italic +} + +.ace-samizdat .ace_indent-guide { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEElEQVR42mP4//+/AgOIAAAhyQY7mIrdGgAAAABJRU5ErkJggg==) right repeat-y +} + +.ace-samizdat .ace_indent-guide-active { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEElEQVR42mPYdOZaJAOIAAAbzQVbMToY0QAAAABJRU5ErkJggg==) right repeat-y; +} +`; + +}); + +define("ace/theme/samizdat",["require","exports","module","ace/theme/samizdat-css","ace/lib/dom"], function(require, exports, module){exports.isDark = true; +exports.cssClass = "ace-samizdat"; +exports.cssText = require("./samizdat-css"); +var dom = require("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass, false); + +}); (function() { + window.require(["ace/theme/samizdat"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); diff --git a/app/Resources/ui/js/editor.js b/app/Resources/ui/js/editor.js index bc0670f4..cd9d8402 100644 --- a/app/Resources/ui/js/editor.js +++ b/app/Resources/ui/js/editor.js @@ -2,14 +2,16 @@ ace.require("ace/ext/language_tools"); // Wrap the ace UndoManager's mutation methods with callbacks to the pyobjc EditorView -// keeping it up-to-date on whether the file has been modified & whether undo/redo are available +// keeping it up-to-date on whether the file has been modified & whether undo/redo are available. +// Also send a copy of the buffer contents with each change so the EditorView's shadow copy is +// fresh enough to be read synchronously at save-time. const {UndoManager:__UndoManager} = ace.require('ace/undomanager') function UndoManager(){ __UndoManager.call(this) } UndoManager.prototype = Object.create(__UndoManager.prototype); for (const method of ['add', 'undo', 'redo', 'reset']){ UndoManager.prototype[method] = function(){ __UndoManager.prototype[method].call(this, ...arguments); - app.edits_(this.$undoStack.length) + app.sync_edits(this.$undoStack.length, window?.editor?.source?.() ?? null) } } @@ -60,7 +62,6 @@ var Editor = function(elt){ // responsible for their hide/show behavior, sadly.... sess.on("changeScrollLeft", that._scroll_h) sess.on("changeScrollTop", that._scroll_v) - that.ready = true // flag that the objc side can start sending messages return that }, _commandStream:function(e){ @@ -68,7 +69,7 @@ var Editor = function(elt){ // objc side of things when one of them is entered var cmd = e.command.name for (const [cmds, menu] of Object.entries(_menu_cmds)){ - if (cmds.includes(cmd)) app.flash_(menu) + if (cmds.includes(cmd)) app.flash_menu(menu) } }, _scroll_h:function(x){ diff --git a/app/Resources/ui/themes.json b/app/Resources/ui/themes.json index 352c99d5..db3d9fea 100644 --- a/app/Resources/ui/themes.json +++ b/app/Resources/ui/themes.json @@ -538,5 +538,17 @@ "dark": false, "theme": "Cloud9 Day", "module": "ace/theme/cloud9_day" + }, + "Samizdat": { + "colors": { + "background": "#090b10ff", + "color": "#ebdbb2ff", + "selection": "#569cd655", + "comment": "#a89984ff", + "error": "#f44747ff" + }, + "dark": true, + "theme": "Samizdat", + "module": "ace/theme/samizdat" } } \ No newline at end of file diff --git a/app/art/PlotDevice-app.icon/Assets/Image.svg b/app/art/PlotDevice-app.icon/Assets/Image.svg new file mode 100644 index 00000000..5e1ea198 --- /dev/null +++ b/app/art/PlotDevice-app.icon/Assets/Image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/art/PlotDevice-app.icon/icon.json b/app/art/PlotDevice-app.icon/icon.json new file mode 100644 index 00000000..a324604c --- /dev/null +++ b/app/art/PlotDevice-app.icon/icon.json @@ -0,0 +1,40 @@ +{ + "fill" : { + "solid" : "display-p3:0.19596,0.19617,0.11763,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "blend-mode" : "normal", + "fill" : "none", + "glass" : false, + "hidden" : false, + "image-name" : "Image.svg", + "name" : "Image", + "position" : { + "scale" : 1.6, + "translation-in-points" : [ + 0, + 0 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/app/art/hero.ai b/app/art/hero.ai new file mode 100644 index 00000000..cec056e3 --- /dev/null +++ b/app/art/hero.ai @@ -0,0 +1,1972 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[26 0 R 27 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + hero + + + Adobe Illustrator 30.2 (Macintosh) + 2026-07-14T11:43:46-04:00 + 2026-07-14T11:43:46-04:00 + 2026-07-14T11:43:46-04:00 + + + + 248 + 256 + JPEG + /9j/4AAQSkZJRgABAgEAAAAAAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAAAAAAAAEA AQAAAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAD4AwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A5FpK6hpGoPLYXwh1aIKb MwGRZPUWRHHpsVVTVV+Gh+LoK1GYPJlT2PTv+cn/AMwW13StKms9MliuWso55zDOJW+sJGZG+GcI Gq5pRKe2XDKUU9T8v/8AORX5a3+i2l5qmojSL6dKzWMySuUYEq3F40ZWXkp4nw6gZYMoIWnoWi65 pOuabDqWk3Ud5YzjlFPGag1FaEHcH2O+TBtCOwq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq+GfMvkPz55WuTpFzpT3MsEsgjuUtBdRyQ0Qo0UjRv8BJY07G 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 8PtuwqKdaVwYz6lI2f/Z + + + + proof:pdf + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:ffb3df92-75ea-419d-875d-2b67da963ab8 + uuid:c9b21bba-7a03-3d44-ba65-e298b0d31278 + + uuid:f71d7eac-55c1-6547-b1f7-49bb1ac7776f + xmp.did:2aced3f7-5c85-46f9-b931-27505630ddd7 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:2aced3f7-5c85-46f9-b931-27505630ddd7 + 2022-08-13T10:18:57-04:00 + Adobe Illustrator 26.2 (Macintosh) + / + + + saved + xmp.iid:ffb3df92-75ea-419d-875d-2b67da963ab8 + 2026-07-14T11:36:52-04:00 + Adobe Illustrator 30.2 (Macintosh) + / + + + + Web + Document + AIRobin + False + 1 + False + False + + 1000.000000 + 320.000000 + Pixels + + + + + Avenir-Light + Avenir + Light + TrueType + 13.0d3e1 + False + Avenir.ttc + + + Avenir-Heavy + Avenir + Heavy + TrueType + 13.0d3e1 + False + Avenir.ttc + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 18.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/Thumb 32 0 R/TrimBox[0.0 0.0 1000.0 320.0]/Type/Page/PieceInfo<>>> endobj 29 0 obj <>stream +HKWnG +hxHpf($Eu zpmqةq:[/wvK򑏏'N^>u_;wS$of'?,BN8 e5oz/ YFq矛3&_qcΌ']b̸ Nٳ oN8&O*eZ+ƒ м$<5;N/T|IkA$>[pڸ!a/F-~r r^Uzf;Ib9 5nI"rf-7f͕+ zvSK}iK$ Dp%Dj%\wk +'5"{PR) 2 2R}!<#>2+sjJ@Ezj'j- |d1긐yOySJ&>μIS8W!t-ZFLۺa|p\C )ʎX̔)F(CͽZ]a t}ʮVٯ$,M$FkV| QD̀/HZ HZ#X%s1)f$Zq6*^j2i]@hpFr! a٬07Ҹ6THhIoV=D4u:4iXOI^ +6a^~UJ}2OyybegpS wS'{p kz՛˸ex|iEi3PxIzM&R-^ [&Qʨ*&4^--Zg!xVd\sKn6wp*_"Cx@K{f,XN8DL9*t.Zc rMifӐّx*X'Xj`a;Ki%AvQN0uQ073#]K,%C2EI vƊc*:~MI>Z_S^Ʋ+4hH[B@_VE.bߵ:`Z4a5l$ < +8& z ]lWped [-? "Hah6P-+XZvC= 9?`)5.+-/^ I}hm21[Z_*@(WlVWrOM%H {.N7U|vx+'Ѧ7dB +ՈT,jhtX*&/mvxT:jL R;W>`Ɛ|+M}'qٮb0iP"߇U;P!m٧eYCH0۽ }vAQRƺ@ۥěnj1;ywNޞ{dQl,LŋA!Cp/>ώ_~}vu<^a=.=l#$އ}~vpwg_>]߻볛˿.o +P[\͋w?p0n\ph!Y%` endstream endobj 32 0 obj <>stream +85$Trc'a5-%/"-WT&S.]c"o$IZH#*g`\]CQ_+[r($D#?>HI1ACKRB4e;A7T7LJab7KF)r#fu&Q[_VlKeQ3g4 +Ra_oQN=KQl&KsAl,"1j=XeoP7RtN&S8$k6TP=8rcd+_@#+CV$7o41f3R2;j?IO`/n +J8nD%*?7gnhQ)dko-GT+)Q(_+-Y&pc8jm-cpJ*Uu8#[/jiS-Shq^*cX+a*,k^Z;PQ +#eL98F!`?$U/FIdce4f7I +f\6?M2,ginFe\!jIZOKMar>N]%A7aIjYr.a0@IIV\(]K(bVsQ1hI@K&!<<-"rr<$! +s8N0$SjBIp~> endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 30.2.1 %%For: (Christian Swinehart) () %%Title: (hero.ai) %%CreationDate: 7/14/26 11:43 AM %%Canvassize: 16383 %%BoundingBox: 11 -6526 2816 -3610 %%HiResBoundingBox: 11.2456570437262 -6525.87898786497 2815.27061070215 -3610.38878837786 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 1 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 486 -4580 1486 -4260 %AI3_TemplateBox: 480.5 -280.5 480.5 -280.5 %AI3_TileBox: 608 -4708 1342 -4132 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 2 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: 416.442989008114 -4208.98972118373 1.99308052565574 0 8623.65976864606 12425.0451327306 2486 1330 18 0 0 6 45 0 0 0 1 0 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: 416.442989008114 -4208.98972118373 1.99308052565574 2486 1330 18 0 0 6 45 0 0 0 1 0 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 73 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:80 -580 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/X C=`it0 ð(p~#$RHlYExfql)7RJ)D6"e{gg㐒N.9 FpAi4 9pC#1±4E84$D4 4 "`4,84FJ$T44 a`0K"$!Y8E IñxHh(,hVp8$`8$ Kn5a2/aWLز1oՔEKXٹYIdO,5W.N~ Awni,LxR8(.D*``4Hñ04MbK Ex$4DBS01,qp$BP4)N EC*5 ɂ͢A:Hܘ$kQ0&@40$%vM x( &8 p,M0d! c䡱D̓C?@ +'Yh0p.Yx4@D8XD$蒈`Ƃaȡqh" @A\P9ǢTH`"Р`8 QD̓"ICq,@c@Hhp(ޝZ۱5*{I+j +`qc sL$w顱phD8IOdQTP, + +'@]˜KtDX4X(ǣ Yh + +A䡐4@HExHX$ pNA2Yh8G)I<#`0$Q4EA$r(9F gӻtSmm\x̼+=ZҤHu)sh$(J.kYG%~0K%봞cI]n>-ꎞ)2ɝ'eʤL<=MMUWԺͣhU̞3J09u9mʗO~L6%=%)tF.uIKFNV^fnv~FxaŋFziի6?GOW_go?ȓ/|9/~=!JhF!ЂZA%L4m'RZJ+1ɖ[pEE-kaKjV+ZRK1-\t/9aQ̲S^N/r`r!?(crtewv(-j1cYb&1A bqfYFdaZGe뗯^vuQK-*WZZJOb,KZK/K[laJ*- O:mҔ%-ЂJG-R!/ߟ_?3\yrľ~nδS{wskc_[3xĈ;7/+#f.,ͽetE"um]UM=5]2eONNdNBȜs[\ӒYjA DxΒ}34<;K!tM븶%BK&403;ԝhǫnixkytRO%dkwثgXjwmo{د;*עzE\lYI>uZ)6~]yo_O7_w_RʏĹnY0yB;-R<̯~ϭuR3i%<עתϢ= Bh(@--}ms}^-+&sz?Oٟ+{;礞>HQvxt,hYGjGjӦ0kITB,IaB;*=zFq*-<;>#dv9.](:^gw0j9NBߜx%MڇWɅ~ҫ]_{=6͏O[=3ŒdeיѷKY7SVavU,VS~U,I}SovTK>-3YUK._6U;05ͱͲ?_3A:Rv'u(T}b:vPZԺs:r%R5_.鱱wu +׷llRڤwͳ _WD~֪U~xWudJϋ+1h{8 +zˆ?٘դH\reiRzB(-ČVWG}2%f* 'gvmjŽ 4_*阼-+7~# +!SvuGT1Wz_d|mfg7IT|FRMvӔT4 YooN߼Yqmus1ϖA  YH4@YH`@/j:_bk2=|ngwL>53QS3ABgN[&)mE1i?5FUq2ӵМ=m{ON6VGSc}\vJoA +o^c[i&<9>Փ̺޼७-bBkQ˜yը0oXOsXYƍ;{iߧc/Kp#+‹رsaV?*a<TSG8rw6WM6t6\DtL[{[wufU>U4od{gfl}SU,ݿ^^Tqݵ7q?;- ϵlj/(8ӹ^zy/_~!D=.ed |O+wfwywɯj~2< Kw4U$ԡ[YTc+zΆwk˭əͨjf,r}h&wwqɲr&y 'ƧXw眗݊%ƽ|}|̖˜~ؙaN:QG>JRZӢPbRɬ:9ik0߇ j1kӷqm[lB>zA|%0e +0C#2 Q0E`j~uvFZ@ $A( +1 C b&!*ȴ`,K hu͋cՀ3bi?=c.yd)TN]nQm-G#%m| %qŗZ2~/+HoF汮 KYer5#!f?gg]^}qtJ-YpBQ$<_5G\+4AoJg=uvK= 8!y\ r 8@nb%oc}@B]aČT4$0TuHyZUX_4ۙTrRGk/ \B9y8Shq.dMQzA0O>b)؊9UU>QAҚQSZH #ì)}>;:N^Ǐi$u2w$oO:M^vVtyns<3e/=#5 sqԿ1oD.|(DZ!nj@(-({Ϧ @+z.(#´kZDu ҝ&6kʻIK9UtTl AKD D,-[FKEk/ }@ƣD-*Y>ledsYtcX*:š-ZM@tƷmT4&fbbK*֒\TG]K/]AžMPw#y)NVم:AN*= )pjtTmw%G,Q;Hw3VDGSk5# <m$" Z6[`A<!RihRm5j/uDC[e9˄E>!<)G^|(v|j*JfN&m,!i=rMTP~h:Xpq8.״(9O}^I֧!4r8T5ýnYu`[ +9w\SSG4C*@?W `a@@%.""fƎݵUGZCfg+,',YuÉM501] [)髿sؤEjdZ%P3fcuvS(x\deFP2xIբ#$9_s?r$]jvJonjv[Uw#WPJ ~x(0⊀\D)ሂF}:7l_P;F hlzp͛Q[VG)HAߐBI%L7v88, 0WMT>%h4QsJ7HU)AǎeFYz nO]UU +n!t lvz PW۠B +R5`DP9/Gu|5SخqwS*3| '=-Jx{\L j8g:4S=5{#nPP*,b ?d[F3'=G':pى($>r0. ;8Tjn&3( `Ӕ +8fkaYWȐ +۲@;1,g@FȲr(@L \. (_J Yw +p `LInVq )Ԕr@cw\x[7>R!񔱈B@N;pNO{$-ZŹla3ai%L{k:lyVꧮY^՛p-j:l:$ 1&VAH"'p_p&ҫT_%)6(Sq ;XPD#M׫#1_^׿h6"LJ!kBJ".p*Ʃő|{GEA(Pn') }I$0U+)O|tm[KJ z{g^Oaclgӌh0N TRvau% 4F9t 0SQ(h4A(_IQ0W*CJ^gª4ZZӰحssL9)lpĪI8k<_ɒX@ ~\HΊ&1lGGssqO3|P*L&qӓKYrr vBa^=E"<-@ $.6_5RhO.PJߩP &":'鉈P K kʥGHK?&fz rҰ`EDF!*}v6 ;@YԐPyIuu'WIJaJ\S!5 +$[F\V/]Tw+Bŋ^6tR!7(mr fуP:wbhvszGmOL:ML"Vo}U +f)V\u]RA{2O1v1 b5uؽc-'˵ }\-)NIx>d~&C~ȰΏ +~}bJjR?U e=aY`Z#:A PkZ6l-SAWPqA4ͨ]o=5~LIqBaKHAGXVzc^Ja{JQ6zJ,:Vݷ)0vj9>e&~,E>쑧D/EDY pFI9jsБ Z B\nj9#"̋o,GʕL >pn{~О ktĿE +M^CF'+[;8;,Eu! d qdm"aN^/ȣM"-P-u#zjʇYS԰F/^*KX/W(rtψt9@blyvJ9uIhݾ,EPާ(e IHDq~ +Lg`KtաI&"ᅥ-AͰEyn(=E Z73x Q (T2ࠌrt^@GY/94 +fN.)b^K̿5)j$KW8C,g*t-8(Z6&zQ=JΤDr)q 8m.C76E9-ǨKW_ ц4P[Β:fW2oW'=FM pQL"y*X<^6:Ya^9 bW +%Of 86b"0%flj/lW>oKA .   ߫`P^%ttE#Bђ8#TpR̘n?~&fϢ2G#reocgվv v &$,kZ;p_k]fgk +Bj?xjSaexEWJM#,{"<4_Uitʙps4VfVO>"By)&bi\W@'A0[yd5Q^%kn%C&kAhb3VB,BHP@R2 $uN!Өh(&TQHehAv!:ڏ .Ү:?{o(yrM/QSXͣT W T^gWmqO)?_"{WSAW9z#FZO~#l< @kW5ۭT_ƯĔ9/]2ks (.L/唗V|~ Ja P6xe_"܁_xHv ߮V1,Kofg!a>л!}7nXz| {(GO7!r8U ' @ +O<ص6KN ?fF ?MxH`x0l"Nk!\EYX?b/wjOΒV4Iž^L9.=ڹwC90@ cb]#`fุqNE'ntת +Q\Ek!μ +&ZMƅB6^I8p<\p8xu>94hT Т&A {]PF l|%/pN)/hLq+3tGcpz\p.>FVO<.sqKyx:OÀue0"6AcSiFg , 9;V?v~4^3PѤH4\I:4d@K;{DAV&2ou!W@=ab|#3EDcF׀ +qs.)~ftprl5vq;["pAϗ +{XíLg…y.}.iLŇ7@ތ7Fj>f=+GSi3${U8iѡ\T[ŵRno#>Z?s(1\T4WzзM!u^T.콀Nzm!)&2BAv ̀zqY7h) d"նxC +vQ +aЙŒ.4''AƠ?`x#M:>$W|^kx k>N\|݃_͍XdU\.y%c ~ 82[pU%K WFs@" 3`n:]J-?:`+e3)2 VbzM? V&+Ƞ̰@ݔQYj_ӳ]uV9v& Ċzi[#ԄsرȔy0 37WŔ_@{x%FN+?Q S!UI!wIi䅤U'vSw:*|Q6f wcL<)G.5ZMvO(D3&`~c?Ptv=ä>x0d5Gf̐ [q*詡eA=H,ޡCX&G%qB+Ϩ;ejV rD/ʸv$'_<+j+οQ7~[Րrf )dy#QڿU6T^_ۯZۗjF5O;[fttL[ݪɍ}Ȥ4v]ّ=p?|JxHS0D8}RGj+v3|Z x#iA @[c'Q hQ,3e5y!)\: LeÂRŴalTvX*-AɳFb8FwPd4'o*Rorn'%(M'u8wVEL_v)EѤFK8(9s"f.b xGEu P@ckmp +(j8"F0VJ 3剋HR w"To2_lF@5O,j.!H 6'j +K~0O_s: Ly\4\ʱqgzog AnatSWT' +a2Ey( 5N84O=,t@X*.(.<]#n@qF:Kjt״rh[sjv$}J UisyKd( f-l|#$&8Jɝ]Ẁ5](T-EQ~"tx#)= o PQ^0jY1O$i`0^\*#^ovF"R:g N|vKQjtW(2)?%(jf$AS +t +БLbLB#ꠒsQ35OGyE^,?%eљtD .>PCWU,w;Yy'-ؖ1X:<6)jJ4uȏjZF̖B3U&(;6ZϮ3Ziտ_OW+dbJ:kenF&_{5]%ц!/cH꘾59Ջwi6/L)T҃2[FP1j֣ӣWK˻OMZ,rm׊c1bR$IJDo.ܖۋmI#BpM"yaU.G7;%ט4b#bi_Rjۍ z+"I pe@]#MH[MM_..>2WYX~Ȩw k(Nȓ"_ၫmJI\;ftbYR8tY_!mޙ@Y7ךDc1 +!2Ѕfc;%gADL1 + 8>0BU vgx@]w|e)\EKk 4?<E^.fYx$tfH8-d~G`Dnam~/> )+8X_S"HFtV?[&\)=wiʣ. odB{ x3F|^NY "w7圎mެq!I)yU +  /:4]&$\33'KG3L62hJ07¾WGAdnNA nQ>u\IXi(tx1+? + dCxH(@Ǖb,RŚ0)TU҃8pR,Yy,l>a8V,>gLgb0WfihEϗ +LtBw)B^VQra r0TUO>42d\"S4S㉉=x'JΉcunL_ِDwNUX啮ER\Z#)[H5tk6 C)Fq6ռBKuTLC|MѺr)n.Aj3QFa]Zɔ*'!͵\!뭾2#Z'(gW54b Cz3c=hm+uP|_H; Lg` \|jH"{\1W\(vd;{0yP>YR!S1SZ.|&ƫvGk "A#i(#Iy;Xh$s-Tw0wQh',]$3T-)gBep>w}񉤪$&LG/3AS#r]U 31$3D|%2/WՉͅ4I oPmW '~!G>C8ޔ%lBYYLR:CQ +}# A椪j_ +#2m/S"uxS@1%pqDVC&YOH<󑁁/,@̹F1*bHb ɯ[2CBwbtǽK/Qt,}'Ԑ`0|DV !!I,v =stڧ.x*As+Q]_1o+dӈph ?5 dcy_nV'mal̴e .m/ {*<8rC`0bq& 4l\?c`08j5^"~ $\Eqf9 +%BGaʫ2ByI\hpRDs +EP͋A$<:iH{ +L} B(j*{RDn!B.qIdy̦t^{r*H;iExl¡~ Iҳ 5Vi%6K#rAeFz)!ڦj]"{%~n|[(_KFOFvV#.0pPf%f"aL0QW|Ev2gx-k_亐h>< F#WVN1<Ȑ-_aeE'f"UQ>0?9}x8烳@ԎlW(Ҕ#pNUDK[PMT4%l HŁ?L0Ya$y +kb7k_d"jap|m P̰v>B0=`c*'9;,7jErU8a Uŏwbd68;X)qAZbzɤw^hne^֗Ӂd!%&N;,@b , iE>0cPăE\8Vkd&*sJ"|Trsb +||&&RbbFx07b{2P9x./ 1<8./!vЍ9G,R,sHx 6\G5B \ppW2 *gAԼR^كa7My(R9.5lB`J$lJƎC99% 4 VD<Fq'y 0s wm9UprkNPyFϐ~PgC"ZL 2$8 KqP59.E6Mk$DW(L} }+~]u$V䚙kX$ $hS-amфIЪ p0LhcR"&BHSc'M" yJ{Y't¡%u7we%C454|"%!DCpȡ8sn$p%7d .rRr}$eBg-P|EEDgA"% + CqNZ=r$ +P/ĊY)*ңXftG9_Lb(7"-쑉^lM  +o&B`d79hx@;XgdVi8l5UB̓y02a#=!J񊈧0Cl02'p!+82Tx:#TBDPBMs $' O!E@r Ee|q!E9 )BA%2TFۜHqT%iRE%UD(@Ce +[s@EӰ9?(/o#|7nP<Ҫ +4GJTdVz_z签BѠqUaDà4ApyWcCk92%|R43 +LOlB=5H33qJbaf|## +,3,ZÆyzc0 cTjЁTKvP &Ğ8d>QPZЧe !ψäy'"L-PQNJeL"~9*KQ雂`.QTy+@^=JF\y2P^dYi,j;1FBeámXDOVڙ! EZ KSL'ttL1"qTK-ippt&(Y:Qta g>,$Z7C"8 2֍t CM9<}H$: EQeRZ$dIBE,bO^! +=dJүS3#ilz +9Li;1i#$,j1C\V@o榺h xk10 ]jb{ OX)H^G)DrFVOXR jЬqIq^-E"i=f0vQRO/6tz&0晞60R }#hf&a]cT4?j;_>YW@ K$ӣ@2Az8#@c:(G5@p:Kb0\42$C`SCJ2+8 +DE3T#뤁@TMïz2 + 4pB "@! pA4`"0BK(Y 2`"@6؀ hP"  H. @(A4< D > lAP4Hs/ !j Eq0eTgh/ $@}!aF,W>r CIntZ`P@2tQ m \-VM4fT#BR4aL{$Tqb(: *`$ QVXboJ8 C#Ӑ'f7t&f(thN?'ŒVuરjqS!U)/)Ф'.GKPө2b˫4K bd\vkB&䞊(-i`tC1(#*"1QvZq .4RG NԬ(%Cd.Gn 2 aP !O #4-Lzςb@J?xoS‚B)rhMC 9UhS-H}u0 NC)YO2x*䋔A|~FoBu!*(Hl 2} +l=qLWYѫrNHb=a Q\[њc沈c&nݧ,"#T7n^lI5LX +$K](_%0}=U37H{ +KLca j6v*6ɉ$ t' KfX&ȰZ-;cA:N'%y pP + K[HYqI%encW&QHBD!b!.)b'%3D8-h$ڣ9V"=c\{#WE$CqR"mB8y=*JΊh͉&,g`W5Nf0-DRt)҅r!721ĒP()ābXR*,A"Qf !sr(ÅB08lbD1!Zb",C8Bo `6P "%X9T*Ņ}S"p6P63BC6a:Nu vmVXB۲u0D74f5-[-Mi#r +-%}h#R nV0-@061w<ސt0)V>+Ҭ h=U@@S`*/ *Xp".bU*!34̠j3%HyiJF g"˞SOTCCh‰^ND8u$4 B&9tA muHh҉  Cx[D&Dka8i @X;`V0iLL.mgvfGϗNzԼ "j(Nt W u1-VI]b2rpa6Fc)1fwQ1dH#HR +\E%iNhPC["KXwn"UjWIP8kuYSB%3Qo2qNd"qz+z$=eFTz+:ǻ;quN*NYY4R,}:W*EWjKUaL Jg:-)"  *?> +R?Uoտ^>.<U;"Ghnu ^}$/es&Ee%U'”TQ+YF2* +t_$](2=H.BHؤ")X=Vs2ыsƘi)-pmoKJDM(9uV&'X "+(I˔貨{ۛ8+ʄ}!2Wg!RrP#%D3 !VEC5TOGEسIUl>0uR oEH\6]ʣ--0n `NK5HfbDTw,HUPUoAj248j8U((RgByPiSCF$ U CglWQO*C;!p**}Nj7]14A㙧qxDĝL%,:6'FL22v= r UXT䠖ɥUeTUh[)T0Ev`Ru3zTj> T`Sc\&^z#42+i+e}D=v=f)7̛lt拢3T1"Yfgѥ$!ȗїQ~CE"ts?HaׁkHx!K=WxfA*aGt@GX?m)Qy8eL%S"t;QMx,2u@E4=9TgPj8vBJ*83$mmvL+>1%”ĥDN bԡ%P0N2/`렌-,YX(+WiKdCӈs +bybeVpߜ¨ZTH+bFR |e486bDSB`*RRXG'ьRMԠ}jEAV͂q4 d8 +B3%BS\uYXx0HўN_E=:Z' +{Fg?t8PE^jF &uHdl1 }d؊&b`ClCS MyC$Y]*P"%"` l ``$؀8!++E ax4E3(WgvO9UYIZ<Wy48W)ц-Bت_ӢdN$SmNg4Qv(J)wȧjh]!Un6)!Lj{>VmGeKOa15jXIJmTV<(TLU!QKAbjCE1rP}eJr9Cj\ iȃPSҩIQ(tbCDf>$.Qj#>[]rKֻUq0'V! +r tou Bѷ<. =`4jrViӃs!BaH + =X=́ϭ k0S0;pJ G&&T9/sx3EHܕy7*wBYd ќ'4`$(3B`L(>>-bM Ĕ,bziE`MHlBMR-LMe| +Ͷ0n;n%T\]&[w=.)zI!|@aGEıϯP!ph*(/Ҿ^o`L*ܡ팵#FTHr)HHG2%"yewȣ<*gGIA+[-U'֑EOMfb*^ ^f"K`<~,!&4 O-f$@{$}oĢV3>:33:2eǙs4 +2Œ3 +;_}k5^سQ^Ǩ *zUt> +ikũd;.1i-Q#q-G2i Y&N|Uht铪2b*UFzXHYqx<:5[f4d$(bA}qcjz''Ş ΅gĩ!3.:HKXf1bJ8ъ%qKDiF3Sce~d/ɕMEiƊTRb[0\eԐ"]Ng4qesnD +n$[4X)b5p!a7OilI: >)>{F-u@]N{޵qzߑw0m O +1Ra<>ǎhDmae}iO48i͌ [$Q )¡?F#+|<Ąr8C'Z*9Cboƒt͐C5p4 +)A*RwR 2}>ւ,l}$6ƾ_' _#1?u VּAꤒRHzCgZb[#PCĩўyXlhOPIH!Pr2%Pr46rUaY + ˥C^5#\'jU e,pJ]NC~7fVg$B?ܒy9VՊ^0S 9&!K +dH g'a4B@Oŕ0"L DMݤ*$CVfjd"ҖU}gnFYMV#eh!投zbdFrϹBb*,3~Uf!:#w~Ya*Z20N2'_W>#L&.&#Qy2fzgv䯸PS)Dnc1 O9GM M,tR +҄˨c^]ICܨpD.bć\&mD(X|>bGXDHL 7\9U8(D0<(8X4L1m2[>Ӱ`qBrCDmz.Rq%♷d5%k:d+rBQj榒P|bhyue^Yt4.) +HYVV?3GK$w  {b*;5tl;hBмJ9:Rչ"[C$5uդՒzBĒE"Ek?e ׊PmeW! ,{J   !DF #, ĪQK-PIfP*B .4 ]hЅ2L~" f̓2aD.p $hjDBe:lԬ / Ri YOb>̟JyxBӡ0m!ЁD'4.+%1!pXF(|{̔p " ""|DD$)Xjp  =0MUI.YejU A UP_N11u(&}Jz^QI/h Vň&FbA AY gaK3/e|Ux:U1<*wNWEijpkPgbčL\]P6Օ҈RGU$ay!]C鬕pk4͘$c[|Aq_> MD5Aj*WFXE}'C~B6hmn2#3F<^ۻ-?=crFU>k"nZCC3ωk +!))S[6e#~qgXP3tC0UJCð)T +R % 1Z4o慑B43#ppCQؘ6`(DSQ"4}Iq [v>7H*kۂ}X/a%` #HKd9$C{5zbU32cDZZ#YʒcK޳TafA>5RY?7Ҋ o6eͬjEd)?שHxS}wON)Vs.FOmRU%)cebbvAK?ZrLKDxiҕ~˗o(jW4Dgr#w'YV|bĶ%/Vv6YL:YTzYSoR7^15;P߹.xgЃJU1T70Sw  "CU> @eQLj0w"/aUa?!_fsY?*p]Z3dӯ&TgX8ljuv@bCGSVr:6$,(?]RU V̕O a[E-h1}QA#>hBTƽP'3!h‘r~OSm ޾z==>yaԭQqQzx2+g+[gl,?miWtpzsO~FR {Jk@ pO0F, INl$[s ՕQ֜kFHǫݨԞ>2`r\}4A)15-+;ǔ 쓪4%{]0[ ZyL@/u褐 +o/ ث +\n.8 ^"Q/gmBZϩ @e֛SK- `X/P}1]=|o[EWg{UM*VODFG%~+|g)HK=8.s_QߏciD{12.O3[yEszhjh}yazo >usQ9`} {;ʾq3OpTJ_&jkgib礡&}.xN)ɠ`~٤`'$ך~RzJMVd٧BRK5$ \Lk+>y"W^qšFHۍy3  7^O,!=оvB!}"fjz%@9b Tf#PP_&&DjzDѼ),CqA zR6ٺx}ҵdE%Z=E@5O'\'*+G? +6hx9<^yͳ4.~擤m9gv71EkA IB>5(*-_hye34|C\9i#[I-,,ɭ|']tr~jޅ{H˱Q@yF}@pH&Us`s0P6ㄵ.ǟ#8e{D~h?Bq "v ݺ\&`+y.Wh]227-UdL3f㲃2^]`7>x4{JqRCSgS:}"o,N<2v~=RuJAE:bH p M@%S[+Gh8P:Ȯ2Cë0p?T۪gc +?ȸ7w^tZ, #|LTł\#%J0mHaMڅ+x|.$P{mX+[1;daQTwǍ0dqeP5$F)\> +`>edSș6oS({KJ&J٫lԞ8֙AyO)PSqS/f8oP :݁C9!wFQN(c|zHcݯ Ωcq|W@鮂rs{C;bk^MLG$rOEXr{Dr8WQuF̸;q?r$/Bn{h*H);i۩vR.nǬ6:f(En'b~n9ݭ20;M'GdT64~pGIg.AᬂQ2K$B`6 GY/vG37*XH +%}MqWI?կZ1U뙑k'5+\:±ɋqw}R<%.E_5ŸʃHs=Ge1|'kح%CR#%@CY-k ,ӵmbۦ3, |RM0,eH"z4!)OK\NsOZ.> V3xr?ᱰ: hVeͨ8uOܶ3Th^8>x9-0Lc7uXiefQr"P/ bo*nF}zΠxH8w`xf<6Sptb-oׁxk*8cf32/f%x4AדBƦKl!ʩX:]8V>~Ui;r YүTSm9_B w6$Bzd+:!^VؽqAAڣXZ6]¤љ^?pEH(olDWCḱ˭\%tANFR%@ >O56W/zĿ҄ED +ʥ!(Mr* .$"5yl%申x c74@vέËaxL%(oĹ!N~Wg@odZ&{H`LtIRsj͖s8w3K/Q8GXM3Vc@޼I?/[:CxH4H8'` >9C[PU@2TXlSyjA㙆1rIwū|\LW|`]N%@ c\I_tjch5lSiyb4u8,kt͠ +s,yW9'j/Sp>0qq'UȅHZ*'Ut sDA"?8|yM>m+mf? ߕT8?(W'9*|}@ɟ['RiՌ:%M -p; yG +y']bHuW*4</q _qrKJYuo4(P5}~+ .ms+*MGF MYg>8bF+$qch' +tQ1n# ~b6(ygyLaaSs#@5t +Q,&sR=o!vùwYAUIxziRiXDu2pU0/\|:r.0`2f~ₛnX8>W%|Un}vmr!q.1R)$ů{/ +`q[ 29Ŝr_+i{\\xODqq$sN.=%$?#S#lthqۦ`ISZ;˜{\c5/ ~Ǵ[lo)~䥦POҹN|Z{NTjLMX2x޴T,>?&|~.&;E\("hȋ[ Rh.Fɍw&VTJKypakp#?bx'V$OTPBIa">' B*`;q BD +f,iph?32CYrFxb` mZLi=8j\ #Җ1~'E:<5%]V|@@FN9eNaa>[xKfmVoIe!O;xnj )xC%uZ牙O$0~:]/.%`Fwkz?kgr~S-@-~/dBd":9%}#.L>;k6gctP S)gW,x+:WoaZ|''FlsRK=^SC΅Wܯ1m8^jqufӯbՍ愭ި;5O]Xac4åߚw!sڛNڵtL{3a>+6~CL{6PhY-(a}y-V޺1h_7P XP){_6M̓ȽEݨ_w-*(1uQެxz;7g$pwgjĆ"ٳ]q{3n +T {K} lo{߭b_gH +9!}z欄::{˷wz C/{ۿ!jT{.lZލKZck.4Kߩ56>2voSöv0w\v[uÃҡtE{D| Z\-bzotr{P џrýxK{?.35\v FL{ބ XBSq_5Jt0 [zk&>\ü7Ooۤ_eOd'ЧpoԖgs@8{^q ,X{D`#޻pD]^Iwo4d໿].|C;ޛh߭?lqp{s2n@H} ["._ '޴ْy1SwaKN]IJ]"&}F|oQޱ4:TzJx\.z)Q@u~&]1yGmyw;z‰VXSh8䧻?j7Kp7kɔLݭNb֙=4Bl(vi͢jh8Ýu +h&GHushw//['mla.ѽDq| i\ + C%y7LqޓܣIR^iV܉s'} -5g [++~3Dnҳ\<6֋Pu7[zw`4HS;J!n5N%tíobrYEzmΌ0vPBA=~rdېTxU"kCN+mbmEILnN?܄ےj̄N42Vmr=mWi_Ւ +lu)-K-r]*_wwǶg? g-Q,><=w;B:.@s툓赿֠Ӫ.QT#ǰ'cQS}G{jnЧ(i6*vbV=;9TT;p+md0΀Jp%EKUϕ.Bm=[n6gCf#c{{ Dd飬d:TokkEq=:Y1wS>C,[\5嵭7[Xy'FH)j:-j/] K$p6]guY*bz} QaA3Q8WBp#WZykiV!詄3ʣwQƔ~DBpb _i9>՟֣#ObM;knog%K3k0(d{UYc:z dm@;k +CB(D|υOZYTTjKwcO2zotF?z͆#-LCYJ)Vg"ڳsWMזU>f{W4#zrMmԂy: ώ 򩱱,sq37jc&oRVGKNsR?4W+NLPD#9yaIQܭӭ2Vَ=@T;@U J`KD ]}:. @nA6ş6Wm}H0> bek5x[TYDAyL NwqoUG4fQ78|iYYO-Y<lw#k:ńobh+`s4(k6אzYiiĢ0_:8DsBf^gZZ!}DM{7FT6{ d "eʷͧBIyV66,?8elnۣijymn䜩ǣM)dϛl3o`D WÉw1C|c [ONf9d9W'cj4h1`:6TSCTuv? w OӀw lG3-޶1gUP`u1=䙯gυ'x?3&?.˒wg>S^)b,2uSI8@@@ gEv 1Р\)<λ_#3vG ]]a^kww2bఇ*,,4aE,Q{}Z4PqXdQV,B&.\aJhčrX +ȼ[DDѧ]|QG -heL KDC4RjIIn\W?f^5cIWk,3!/^JqXtpWJmI[*Δ.nKO6ܠL8\TB7X4 6by[hz{msZ1Ӈ)Rj l\t=kk՛^h4߱ P U "ć.KQïÎԲ(FVj@&E]l5(tjM-qS3NryB̠ +.ЩQ̔*.RܧOjoY7bնuա"zXgfV"^VXV 5]`!w5ݯoWi8D^m_-xLr`/ذrblA'kV?|C(tM9,2'`ϴrU [>UnqpmHH\烥¸}A+r5s͢vj\ ]rE4xcoM~tuZ'OV|U*J2k_cYj|5 )??`|WH_#l-S3>KwZauV3ӫX u,<گA̹Uld+Vne0!lhYD&wy0f;zf1$3,-0lL(A>Kvf_L ~GϲȚ(~"o9.O%[8J1K{QŹ|}s?\ \-{﨎!VHQ,Eٙ6'R\LC%pF& L5Z7+,&δ?%qnS>"jpsj%#nG`s @Ii +Ma WHEe2q˛T^ Ɖْi:V +}BזZ`28k Y<r(A +8rE jHns0'C/IP|/[2( pPZ,ai%dՓ)5L0@~2C19 +ra$v^3[3y䮠U.$`t `\ðWS.gh`[s`#ZB%1^Ҫ[ 4MѴԊlJO=51 GGAHk58gdmK2<;NJy7sN_ +P 5ٳ 9,37o>Xu?>pNc *4/A}[}޶uP8t 7I̐NP +π vqxa`VD!q ^q%)&njg`{;(39A0@lY+x?V,%Ҟ T%PI8%/&x[3!SBd YLG(Qo6'D N~nde`8' bds FKAe~'Xto@^֓'y/n'x {[fΙ +.4l!p,7e`$釻Ă[-,r9|+[ZpMT ~@:+ ..U0*\Xe fGXp}G* 貆L= .æqƾVOO.TF I_ct<K?qUza mGaj/H3 XHm%?ń,C)&_08Rwc(bRy1x18OcC P1. B\ g~ +X~ir-A]TIҷqvU plmi`X,%A+*: injBۿ=>X嫸L7sq0`x*E,wR^̚"ort*3W;SĒ6Y*RrNn(;&ךV즔(_)bJY+Sy^b(JTVz},,WV5Ȕe-qAXM-+~e+B˺|W +*A2W̫LE OW3Ix24ވ9Xm6su '96l;#!6MSxtp岙o=,3 +?d4IxffkcM#e9kL*bǤ}g:kHĪ\YΞ͏fR3gc5^1[5~d'[m{VAFailV +$YHجn)cϛy\Dtff8hh/PFzpeS!͓ڔ]4m}y#{3PwoX \QNu:s>gP¸<|]3tfPlcl0LjltgFY,HdfU!T靹4f, J溧yZOsQ /RWo \3ӷeOfō%^4ZSI1iG7/O3e}5%9$O|#F+ +gtq&6ë֡<չ IA4pN \ZdPl(Dq:sw;qh MeuA NJCU:4az"Y]M8Mz 뻲%G>iXt0gI#dCi"Ͼ#s.\$[*Ɇi#;% 8.jz ֧4iq!| +R0PGW/(w eľNb.eR\hͻAP]임TGUwvJ:j [=h&@]v aDjGiẫwʐY#Z;JJ +[0=neAmGqr B˙TvϜ.O 턭%S+No]ag/lڎoeH6]jLL=&tI Hk(`۴R Xflyy](oCАmhp7dmsmǥEmۨ'zL]idP2UrҶOqۓ.Xf哈T 0ZE1r.s8r߷KN,U.qǏIr˧0֭}n TMt ħ)(h +oI;}y<WjfWx²{[WQ>P=E!ST7S:ݔzDq{1 +ն38\eg N~ a`4mp6m&ǘy쎹' 35z* gPr +?]_x{ $ֈCLBG@hNaxLuM\ +"@f,pJxA9̴4SqI,x| od79+ 2ݱEc{1SFCɍ(\ZI撆U'#t#ty7;Ϳ57'T#7f{͉!/l"V')>ceЃ7dDžD^aή-;(GW0 ?wRFUb0 TUqA'OW꤂~C J4돞++V5pRzWwYV{/`dTցNIZFh[r}I֮|AӷeKi5.b?>R4vk'^ t%rE]&BԨkJ.9휽lb]}FZ{{2/ݩ]5DksOq ;w52ϻ`!<+$;4H,>q _/,ʓgm <*oiJ=H\S +v)-OZ&^a*./>pĭ,V5Y(O\j=1`֓ !dX^%ڍ w5ze5/<_ w-^v}E_x{Dc=pơ~.{ESOتD<Y%PZR-BUNM#Oil635ANY:N&@`nŷ˹EYKo+4u z/uzCayn]s#8zdxCЈԂcxN8a X)ihYy,5@?"Ԯz%$f}m]5a>-ZCQG r541=4| 5g\D|!wGC⬘ dO%pXT|k2[gpHrNLr↾<_v~DsQYOZm^?֤%$}Cw>3@"M_)K| .fNUG4Їc q~iqf$)ѩ?/oP&_X!zQ rrr"kO8KWqL'R#F#O{ aX4[MJj.DٯQo~Ŀ:TZ)sTvf`/&&]{Yvͨ:$c_]mXŠ5I3!f=;JI04n|JzBx!fn `+F/  Ί.#f`]ĸ/~vX;c]]F9tس36t&vվ\oB1Y`nW$OT|]"ns\DQOL^gJѢOa}}G"bw}9ƦK%af3)J-B"tCm!4>2`m $*0%iObT·D(3^Ah#d +?g{C)b̩Vӡ?,\:᫁3Jun$03q>F+ZUy(bx4h Rِ˅Cyٞe,[5,A lPİ$3~noH"_ZQڶx]]Ó%oH3Nv-[{q ˻3tWEYf"P3lCF'o\2h#wF,A7+ }BpAgk2jg$otȄ LEMwQݝʓ7L Bc_BמY7,1yF/hi%m @Q68.lJ`MRTM;rT^6 +q׼$:7oGeU'#tkq> +Lɓ~j.w)f: +ضn@G0g&հ>}P?ÄAW$r#cEG(@ɳY 'KLtSUXz(2 +pgvZjKꙠBF,5vjn!e*Psd:$;'RA2$sWEjh5rtZIXy;Є^ʖ {-z$ )l~:b]]L]٠ \R^ݜzgn_a *+)/MTK٨.4T!+Pju JJ<ֆv&߫$;Omՙ*t|8;7GʋEkԴC1Jİ1P,|T u'4ݐSa"gN #q+݀_iN0'O q 8:@jbq/(%9N!Ro*S]{2A +S-M1,/ :a8]Eo~4c'$K(MpUKPYLJ@e.ocfRygV2-j +i(p#zfB%ہ N;]L{5i#/g~܃j#m;GfwlaKt[:UL#%2[WJؠFiH %3vK};84N.׿X`S<͑yh٭>Gl4aK{~L +qW4m0c*6*MT [4 l5_@0maoIjH +@D~[zI?T;GqiWL^ 0Q${`ޥ zYiV!mS |149||\}zOhk>&ka4 n5,⸱ck~9c5ǩʍlW +vba6|yR\#Y㵤 nԼٱ% /}BYQx=ij f֧K#/_-Sx`K {atQNs!J}-%ܯN?7[$KMp3ɹJwD5&qb`&ͼ0zԮk؈M5o*h!b] @MHc$ -cY BThn -w$ LP-jΤ`Cd?h"t%ɩU`Sj*1iE?RIaːL_qtU֖&ckZc:=W +_y^l$8} =뜥Wܮc4(~շS!{`c 7M¥ +i +"v\*y'5kWe2Ǯn[`kbh;UCA`0 /kN7t0e/nq1!&jh2k +|3 Ϥ1A`A[V$H2 Tdk-#@`@Pe.tX5*EsRL49 s5;ݘOg +_@=^_pZ(\I)DR |*ZKkA-Iltf7LSA Έ9Z13jj26ÑÖ7Ƈcn\˅ae N,4 nr?0spT'P{f@ 0ĞZ.OmanKC;ѓʹp:ex@_r!ۨ8 $@` aOdC}=pӲY@*+O;0D H$Ô#րּϚgGmnw/*&Ĝbo7ǢRY1lT75j"CAy_@;#-y\M|#{y&sVDX"a4֖L(}RG?@,hkoԜxVa.?>7#|Y8wg̴ ~[z_lwybI/2 dՓϹc&IdY[G(!U3 c3;Vиo!]@2eCtSZH_ܐ?5hiHp@䡖JIZt(mI`T6F%LB^[X xߐ9KǏPqwt|ƙ[Іh/yx[[[FP1N^_'[H 3MN@"i+^{j+#ܰ] éފJ7뚱]%-([n4s/.iGpbrAoH."R?,ŗ\s`/pd^_AY:;j_lYEHxu +l0R)!/qAK8SY$vv:reNY=({a* +P[Am !$F4lɷԵӽu!!V횦 hh cnBH6jŶ$JĈ=:ptVh8~óјSg.GmH0طѧ:&}0WLseïJ!3@gA8SC60M1cpcŗP2zmx-j. +iBb4AB +1&~.O\n.].Q|G?ْ@q|@!RIg csRs@)q A˷.$m%G^TdY K3-#*c⏈WDΝg}_Ps@uHTG_z1( D祸–ʼnBy,| =ۺxtQLE@N +}sa_ӌ[~}*eҹKWt-A$PdraH鷕9on1Z/?f襵n'x4t L*EpYޠ‹V~!5Pfqd4uVQÎSdޞjIiYJ!G:ȥJK x]*|39b7 \jd^'}&B l]5p(uETQvFԄA?yuE u3q^2~)gxl| +Fma rwr)&)7PÎB-\ެ؊> ۝ClrVaf`š+;S*EGU⟏D3?_C;["e|bJBNJ;[ ЮqvoFz0Β[%kDg!4Ņ1pmᤔ`ϐeӀWJfݼ 2dĂXs9TI`H"yb%ʝ^i!.TPȼ%1xBH[o]z%~_ŗ#|>{UD) +dߡW/Q<MDw\J`Llm'\ƞ٭ &~%4-vFՄd,4c )ybdaDu)2`a- ۵\s+L#OaFX+w>یV~npl9Ve|adLT%n$k#EF_pL&@%iIQFtLdF΁10^hV-o+ߞ>{Kx&K4~mF/ +s{b/4*X\bAedEx.Kkђi^jaQ.ybXAVb]3&$fh^D!vt zAL J55 +5.US/sx p&hZZNl["vA0x`84f:cnOܩ~X;y"Q%Ļ}w9}l$:&?.VԴ7Sšț_ 85r=fN=Ԫ^p ~27F 4Kaރ!t}.]ۑ-Mz9YE7ɟAڵGj(FGqR$h(]2X魞'”C w-=yz֞ڎ7T#G`BUݝ0FVKSŖ'.aaj,_0r(cF:bώk[uڇ_ɭ`19\z΂' ̎ 35.H#nGeFAt:-;>"EL8y1(/eg9r}P@G1=-$ -ꚔK2zExjJ$9Eܓ5[5̋ylBi͗ S'6VCK'(nc-f\tHEV)FΆu7>Cf/8L55#G!8X0T +ښ8aQ4\ҼFp_W6 GSR q)CvS*eD:R`ҠA;T@425mt\Yԯ^DI7AՅli]- +.Z u1q?*Dj+tQv6Ed,)G|}'^s>JeD_{Tȑ/ςb},Dogs $82t ~fofiq1!@f#)_S {Y)0#pR-@|v txg^w2,QpqfK8znhE${&"5 T!r!=ʠF) Mϐ7ew*TCNȝ!}"!R#p蓼o7]vd,yk-OU^y7^pV ޅ&K(8 {lH "ঘ=;ݮD^*!Sk _?s#>|-R5" &`fxwr yq@9; 6ft9Y~ xZ51g|q\eEfCB;5K@#dN=Iw,n!WH0,u.uF }~e(|3{uĺ Mm3d!D:!ERAW1UpYOB}nJpQI$A3 WVBQ[є;: +Vb[qr`<M +RQKzG>W]C".5 +}ׇ̅=ݚR |$.u(@n-[b/x_Οs_]eְ}+L<9qO!eJpߡ2J+tNMZ +|!>GZ=suay+?r+:&5%c_ uCdY8!DN,8fC^Sή!od,E6 zeF1ekp{,-KˆJa}S{ϾZ?8(bqn3XuUdyy"2|J[WXk*vl].riltk2Û&d}8~$~Ze+"Օ]&kIO3s8 5{Nʠʹ9жpW7pvzi1*+bZ~.9̚0?Xާc@fZ 3 {y#_rFu,A$,?@w=amһ#:9 bìd5{7QsN:rLۀ6 δ)ǽ͊x:{]c$cWZ^f +/`Kxb2T芧ra=:vtSS01sq_㰎ET63Ɋ}V~02vdhV٢㌙e%"Jsd%=EXڎ UPyBu|<2b<^Z\ odY$y *u\b2I qb%?mOu)f=TD;g5#Sb#}4 V3 iq+Q'?mi*"!v1K?>(Snn4ABCMemNMbҐ(∍wetZ򼾃9/6 ʯ$hxappZF :^J +Sgic۳b7AԖs|`%&u&8 ,i;3a8ae +t/>}9۪Sn$8y[<}ymج@)WߴJ:Y/$D3j-.]v^\4pSx$:Epq2|/zLX&i:8]j k4lLi̫^[aR|6y_\O +8kJ_NF;}E-f-r_آRs2 6y$YAZ+Z W-#SE,I|XH_ ] ͼb |p3ޠ.zJ_X89zx3ǖJIn [M\ Q0~I􎡡((Ӕ@ + +;m;f ?ŗ LRIl ҕC $Lƶg_' &ZR'$%,A,EPL@xgH'XUI.@sZIT? -yw_\uNj?XՊ۲>-~J.^EK4Xq,ܒ\7?e.qgiQklN'0Z]Er]ưRdy؆k/w)Glm`Ri@nnJar0A+0@p ʕ90.s0ud(pyr6 1?Vm.Ifhr~+mcyeū>y0Hf f5` ||&KKZ]0g hVQg:%/$7_GBs]b9 %`wŹDtfGX4Hj80I J5[0.ǚۚlϴkDM:blo4ޟ>Ԑa 2#pEJ@̝T?d(u _,m}h'4m9PF`6MPᲂJʹhyJICڡ=&dͅ[mR/dT zDY:G{/% @4:͑3Ȩa[dP[7b M 60p&KmC)1}w+l#9w"ycUfI3olqܭɃ|.2y5m{H4ˋsEfpptP)Nk#>(7 +y,Uݼ$rog܆|E105W1Mgp +BvE:x!VGʓPO9_|SedƗ*1ԋm^g)d;euh#v[+ɾIeb&(!'X,]~:b;`Ÿde"·+{pͩ꯱ +O@~3&zR2>DZ|mXNf7PIX`AV4}PH_6}%K@+{O*.U4LH1#_)% euC (ZTlRi QTPsZ4jP?I[GYo&zgA>݂ 0׆Ɵa8pOP4>ecce\3 K4Oi:KD[#OoFU\C)lcO4 -v~&_lB'ᚳD ?OegPerɨQ}qD;%q88'AuoUS=~"rAoI!g;&{ZrALC5_D +T7p<4}`HQ*~9 +=_Yc\utܹ(Tk +$ ̘;NjjCOUǤPLÿM"}Jz#q/BqKC(*zB0.}Q Vժt@Kct$u!w@d(/]޻tqcN2vmc / +BWR jCc>C€yO t( &MWNSFlckOKi< BB@`?Lyqpt9e(H#D$8ğL[]B9Wd{fvFX_Q5OW"BkC4_AED{V"cH(_' lJ (U{$ +z(Fĕ'# ѿ@$(!qSȗC`by\ y V[ȞBV(5-I\e4'T-iC:N-(BB5t_X#X] U#(.?"@M SHMK T:Q +лݘ/sjö0~3אSy06FKdn|g4TZEǃtѣb3s(TC!Gxv9H'MYaN4;"ᕿ|D8s}TO|[jbr*CNe^+Z+?)Nzޓk( Q={;)9]SzO=.=:4S{\#^R՟5=FAIL!=cZ3}ƕ|]¸/BC@amp͙94 |< #y%gDВq^ƕKUT/񕤣(T8H޲pl9 e3(̬Cr8>dN?.WϕzLd +mׁ +<sP8.*_  +lYGRC|`UMU`|G?l܆sn첢Q +,#G mBܕoDA&)][z'ֱ"S3+_bȄJAJ<&e*+I F ZEx\RNS(l&&A/\ YW@ }nZb}M^elɩBτ:U nS nQ ^KLΕ^P2v^Õ",.ghc^/8[#$a|7Tpj5 D1`I74q,@L@}Kn)ht5U(ŏOG^nC+$u^\T3)|Y`3/6;TrumLQr|kv8 $Riabe)0R,z1qKvqt'JCKpOZ>܎Wh㐢Bc,Πf w{J3b)F3ԃvu|J`T.iBG,c4P~1'O87{E$q44lq c [̮ aQXS—Z?swg +Mߋyc+`֓_s9WWlH>6,eW.li{Dx[N>R4MzaĦMR?Ң3YoKKvBheuB+̏<G,cg 7!𕼸RaiTJ#LRV( ڥ @r#%VASY[2dz QM04Or?.:@YWWCBee>miJuCZs![*QdJEKL3{ gUeޕjA@!@.L(MQX]QBg/<c'POG=M +E@@P(8LU0jwʁ! lhULôsד5 xyh \{68|x]oݒ `|Ca9ԌrK8CaMZvD6O1@IYxk<DV\X:fC2TPXq!+&{S?p?!h:m؆Sb$ =oXcsw /n.2z]n=jE̿NHW&ݹ[Q݊n[ +z5qd[Ul]I .v1@ݛu mwKϚLkl ܢזk78'Fc6z5 ~Bã1˞H48`Z81g|OPOz4j!؛6^X&G!כ>}ѯ {כTi^%YW2O"V+^rc!(`9]=- ~qNKtvIJtACr%v%AaVqg e9:JV+WĚDKJR]S{ViQEP0Q_ф[kQ d]v`)uu6fɥ^4j1tTn=j`-.,Ҧ/?([ F̪.!;ӯq"r*9 h81I*[SD4^NJTE_K! $ֿe7,蠿wT$߷x 0Kp+2&WH_a>VGz-UXeL;hԾ#OF?|imw#nо2,[ PMB3 ÇSUeJ)Ifn<WO!J}guzuqkw2ܘZ,d< gLFD8#%IfOQ6uC司bCTNjViA8eDHHܣ:C%E%$d"y Ux]첍eW!%a`ɉ)~X"$#xLT#Rcʵ4J&3=-6$4 UOGήIJ/CɭDysCTek֡ +IGH?NVbylj~]qbݺPWR} _Y4RU+ \FŌgAmDu*!%iМ iP yUЈY9Xqukv2I-p}->]lLTe2xU&Hfjjsҙ!(ىj6R+"%,UDEޙKΒPL>u0#Q .hb}@;1-pf*pEF_[[9O$&k7xR!+$Vh$1\DJ"2\1Sks!$ @Dأ6Ŭ`q2©)E#8x>_f3Tڿ4q>K{&^0= &k:uBѼN ,PX7@L}`!~ЉHCWN\~+sQr G(8Y%yIchb-tH(耈oC)ؒ2Xe+`(2G(Z7gbP4HEP;T=7(PxWB`1CΘU$>t G qDIRXʵzfXcX g@}c.NP:Tz#dJ00;n#yAFA0$ (` ` `D@ DNo&Ѝ#"!2ȘB٨04(^ %HUP"| +Eܔ㵬*s&bDG=x +O)Mjp$6A;)" +hF?̫Kʈ T*p zSNPz *ə@gP(ʔJG^# 5ѝg`!!8zffS([GKSWL5 v#-D)[ !8AIfϬ%¤6=$*4HdT$KhOImڥZxQ)KDxCvgPAjL2#0 &.\5Ooj7U=y~ T#LX[X&C .\cLտ5!!3 ,QĂb‚CΰE¸&E8# }rh1aɡ1k;4K 'G#Z"ʡb^qwHADQ",&_E8'LD>0g $/ +$i>4'^ σ7g"ThNCο" +='yQ)P{4JskCLL^Snz`W9Cq w6 ڷ,˽_zxcN">g1\&дH'rtjMOGj"< +quy܏L%$NBJB1E;\(\X"\oQp5ZņZkUuR'$Eկ(UQ-G?cX/Z({ +{^B_'ylt3 ݔ3@BrRkG=Y#j;Ķa( -rZN_m"E)T-(tǹ\tLF=$v(ls\X,vɔ W&5*ZAm#2 Bޞj.@іmK@ۨ +lk2SO&LgAzb!*!6 EJHRІٳ7c"M M`+CF JZ1(Zh38!@Zth'Op4%m_WIlL)tLgD sWc&9nV6}߸n}5.BDz>yK04s!]ŇW@s.Q_>&||,nȼ1_Mgjfw0Tc>c? G,ăc9s;8_>n\^%[FANnpSIhqM8OB .J wQ9X}c^ 6Od>F/VtVf3wGbCfe7Cd^JNysH Pa!o@BG/ޕР3lmL ;"( eTGxŚ0_ v#!iyB>1I Y'wYT~UnWˉn GI@P 0hIlϽ2IWt&blCh +~7PwDrbOf=~ɴv8GL&2bJ2R =hvH5qm79hr6re3F@u>`iLg bXS`FJV셬{ Y= "IiՖx%#%)_='NRI`"뼵lhК^r,31D8rW :<\H6D6TxEHƞa/@3;vV&"o򀁨BK پ)(,%My2f(QJtc-@`XY\stI԰ᡎ=`^"lsa76o1C2 Ǵ( 3!ץ!NaxI?o>I{PRn-n1!#U9C:ĽXYGVy5l0q ߣW0MΎtW4]o^5KrXas6|%q…)G |g79[t !"8}^Mڵ35EXм^KǼ9&pH>Ƙ( +z@1g \"h aSчde+}**X*j Y4tKuUD>Z T3ב-T9 adF[Xs< S1]~}߀72ݤn#y]BtļT Sdy gg[\CwG~@Cy!~>a1j@m6v69| +9Xtg)QkeWY\f Ut@` +$2Ӎ>ƌK,O%o>VɤD7Rs5;4XZr;ppN|{E_Iu1 b:T;rQV C*'S\bU5TZf0zUz!=| +)^<5Xבt1v3E[".*ݪAx +=C~K|o~ޅ[h ѝi%J(mY'_i xKL4 A \= +KraR&]gz4"0v v2SNl pĈX F -q+CFL 3Ϟ)tIgQ|iodRԀ&4:42HՑTsA=%s!I@pC6^ĕ?|9]kʘ,q̩m 8ԻԶ B;4f7"ְ6 鮻)5KSP)8[ XӫU>#<9v{pw=@fC<,J +[/:#;6`j5|c2x dB +\>ar=l2کY(os\h;Ԙ #d*ۀ3!^M3ÑN\QMcA6S6࣍7ou{H0d77iБydx`QQKm$@_ +Qa\Գ +;Nj7aS}>=tytQPðВx3Ɔq:˳gg>V6U@+,´iQ?G5]M1ۮ]X+V\Wx[CjNrQ5Yx/9_C 쯳~>15fqY& Tg~y6#m.8<*,0cצ<Ae⻦ ad`acB3!H?Ӄg3\*[k]|2CSt*c]8I7 $dc#UKdNU=yU +{X_ޚe v&0p,a^REF (uN`Y74HT7!b&:c^7sRQyíCod%W,M!X1'zF*9 (q/)BK|\-W6v?I&3Pb>& $]zHw>tIl$|@eFP ҈.E6JLK6 .#>?pإ撴TE! wK++Ji,n(1^jJ`G.&.O&E1KN;Cp['&C (Sǟ)< JPRECK +%'tl7u&"RkN=#ઐ/ sS(aеuȺ6A "k{),NmS"t?…1{}V$|>05a{=>+{=P}И|emBP㡆0%3+ ήZ+}oU:*405PwߌGtm۷V'.xzEx;L)wu,S(5@mRQ~XN_OdaBE0EZ +P]9"rNIf&>@(/TdYfD 6 +W7kϷԟˍHx9'JY@šzV?yNetRT^oIanՏ=m_و.#lKl;@oզE\)X X}Cc_+;"҉BcpQX8ƖAcK,@-T#г5 b/l/S=u$,pJOPРQq~ -XM +4JSRXnRlQS1)*Գt$ y޵-Ȍ_SzG,Ne g"C4-Dr6r2K#Ƕ^/FrNxƕ2_ +2GAʖ!׷֔bgA&Ǖj \Bg%vf[ -|X^Hu,3FgZKF[V +xRrt}3~7KP\d9>"ԝd.$=fR\w)fvUԼ#2];0 Ihlw?hѸj`fؠI{E?359IxkuwC˿uG1q(9r L{D`J%J e\AhcW/BEL7ep/ī>ߝiXXR.`&H'<,6aw`=L(jx'OX PK9._Ӥ# 6Exwr׫ #i\|J#ОZFr pq2B'5/g~c,SrەTztDA3~ k.mEk E2\6 -A|I%xl8RȜ!#MXt>M9jVb#UXdC6#@`dvj'uٕCkmc$|} !R#9 S +DMŽEvtP"/9dcjH^n1HS=I K;I_r +;IwR*vGdol0ӞEF &<1&CK8tpO>2?#a6:HA=\l3O0 !g&P|` rS^+N%y2EId( endstream endobj 12 0 obj <>stream +h +5_>qiyYȃi06CaPLwZP[kTS~#a[1#qZYc$jfo/m =:e*Jݰ,y0$dPL0*ҩtu;ywB-W tO+OGHhg7{[;:a}5#K㣫 +u#<82VS,LQ +i}}"Q^1ˆ=wm#'R1Ccr +&mg-Sh!{9qB?T=3%? YWA)O鮩̰vB1^Om@# &<%=ʅd6Cue҄K(ו%3b΀y-T2؅4>EkWw)"Y5TÛ1BF!+c".u=~- i{U~U~u S{,NUM.1 ftiqeN,RlePtpGO'bUK$nK@,ȿxF hcԽs(,͔Dr/웺!+؁OꂑbPSXb7 Z8$.g?ky\V1S2bP8vTz +B7CNNYGsIWGEٕ!gnggn=61oɢQ˗Er}Ֆ + +ͲFdVahEu`5{BMއaQL:iKa +U' '/;jHb ؈/?$P@HqJz" }x8ǐL {qs&KO޾I^і)U%q dA0e6[OPp8G\;C,~rh#>yaR:"g 9r+Qq$16l'gCaP@G mZQ ֍VpX,tQ7na{v$*Fb3p^uP#agv;DaXF'ޱeȃ,46cα= {#Qѭ "L.´2~ز-BZdaOv(`H ") D_"NXq"YGb!aXܫ!HP$"]ь!!]đ ҩ!@bHCPT6 &RC,C|"˔xcUbYcxYBb8W L B8 +1|@<&rBTrLL,3!F?6DM"!j Zg0'0BHubwp8! A\N~& (7H +4HV(nez(Jf=b"y(|tR4 EnLF Ar)R;y 'SHƦU"rZ +$@#*̨HTTX{Tf@خRU! }rXq :8sb?+X+"ێ +QկXd8W!sq6h?F@B~,>Y`4? <-8ZM.ݧ#m0'>ȋaG/*ȍ/Pɾ=${|/^ 2{ցqQ{D/cmBS4׃ #O<,N=`"lb\͊'/FH1 =qp&,_"Xyɨ0 @2<.p ŧx423d^ՌLoFi慨3N>3ÀFLЀ9xAwr4?)iv\ȶ:l혁qh*2;,R0m5X#LᎮqz(6rZccVfȣ #emlS6QǫoqLG ЍhD:xM xaE 7@{Ŀ|vS ns24Gq9LYq$AQ`v cWmxrM91#o̢mJ̴8(9pA{XtD8Ħt\i;Qhh^Ծ8>lu<h d8C1Mtx'áQB;N(!8T7i dK;Ñ$,!"ٱ5E9@)w~;2̭c,ǰTX::mjƋձα|c7Fe17xc\uo7u#dAD7:JXQ ܘu\܆Xi)v^mLƈȷ0*ǽܯQ?bl$`m>'.Wc#5یk,()5hc󫁡AjL=;vG5 ?ЎH CԘOÃi 6!r4SJ#A҈?hxc8qv \<Úmg0Xb5Zh39p8f|n' ގi #d+3_0ÖX2YyenA 1ܡ2R'CUaHw8J5y#Üw !cã o1"o#b0Qvxpqpj@HvAPwsDغIN +ÃxD*1 =n5Ɓ* IY&0bMӈ[cz\4bIIk#z9 `5 *Y +!hah{|F~=> ƍ+2E0^'<X`<?{3꣌_`}@?m{agGH.."=H #wq@ Q\.1uagYPtao 9N˅M.nR:X9.1շ0 PI-d}[Bk $q@gB" +>f!QtU1i!³4CE7Ķi97 +cáh.\{>-DzGaY`H0<_ȚXlEx88E + ťmx, KWگ"9E)L[dKPw^_\%#3fHxF ۈVrhEbYcB#WR*b#;*~:!G*1Ň\sOUR*\TX.7E,T,# +\>"Sh=Rjaj +}A)NKsR§Rv `GK +jb)V##(8|Q(#Dq# D˵CI2B!]G.#Y`ŊqA(_EᆖbO~̑#{{d'2GPy|s'\Ý8׉%N +XEA,9RrB uNބ q`A#kHAuL t&^I3DIdbHńQ$Y:IPD{%y /!c,pH%d%鮬2%XRRq?%wm/J_TYRl׿%#*]B$%MªR X&1Z}DX=%]t$LHb&E[gR<$ H4`ߜztwDX5!GlGoR0x爏'8GԮ,E-gIh60 D4bzw @Fta+O_dE8'0> dӊpZ? : @PFD&BKp|#\ ⛡uCTYoϻvbltË!hQj;C(ys1De  + aX2O!<) )<%X:.E&!t {;Mi)KaVD-%6=hR.LK!⁝yXA.2<rŠ)cxPmҖr;;uRbC:(v֟Btu+@@F) {>È9lc0"+ rrPRȊ()m f`R`ӑi7)VRJ]{[ +>t SHKMܔS)z+7P[Td30AZXh*]z*!?ڨb Opx2`8k)%bUP\U2Q8U+'XD+nZEV26 $Оp@ܡUY3Ж\pX*Z09QCL*r CO\+i4eh@ 1d8Bӓb#XFi_MS.4z ($䯌n4*Ti\"hjRGQ3ۿx!B> هB'^}# !j0 +_!lv5!l_5oI5j 5Y!2R}A 6qpMX/+>6!vM?xr6 }6SèF>~mn܆o=`6H fy`8n^w`nM7<$8VA8G8bqI.@C=rzL[|V9_н^4x7lF89Z sWNL){HV:~I FH?VG:ôN:ba`"aY;`Xv0 P;;vn`6r (aWOwQ{yWe;]^w`<=raӿxqd҂n<gv~<'Y4\ҊĂ#-+cbWm6<YA \zވ䑣Ѓe?VP!]G_M"(؝-6 +ӃU +R\ +kW 39H*O* D}RY )@ؠ+im]3%iKO9ӤN/~2@EaT+xfI IϤ`5{KIkAk6_k& +bIA$`Vh6ْlDI:Ҧ [RP@FʕDPrLx1DQ6T[ 2>$%F2ЉQV)؉ӻPOa[& +2mex+D7֢PqhjdZf|$Da@jtg.#vǤ +ԒIPLIn{ x;͓Ǻ=CEh;~7J]q0JTJ<*NRa&ޒ Xw*`׮e R HAb ƅʥ\KS ~9iӑT.IXM@t5j'NS }1~tF,ف1OSY۔C@%-xÁ]Vh`I@\ J݀Jjm$FgɰfTllwL_Y6%+g} S .kBM>4"X[Go@DCi 0$%Q Ӓ|fh2TYyO.Z+݀s<&:q@bw9P<1"x,x,5([ 涔:ɤ `P'0[( G=XAI0W +לb/ ~R.8YZ!& Pߐei+`Y* T*CTy?ɛ|F<MqyM[=I*K +Лalt + `r:D\ P5!P0\X`KM+涢2K8dI= xaޛI4tM6A{-p?xziܬzE+FvcHh*~n#%㌫g=ᡊ(ry)MQ&osk`7盿P7LqY2U@DOdVm^)?2W +ue#^d8@q~A A@JB/&00>Z 7<H@ ly1f@Ц|mkEȂx!ݙJm4*WUbcX0),bW,zZXs0ksn^gܬ'KvzhꟉ-}l^-e!_'O/ (R=sS;GX'rKd7Q$Ch+Zj3m¿cX[5/̨P-F% iߔe?IhOr6Y)?PrV#ѦN>9:K852H]o\IIJ丏VPwkGޚ)ꭩ[^6obx[K'7$p?g5SPtBQs.HIq|6Az,Hek_bW_l")~Wgl_ke]Ne/'ZWK1s.'"nG/î+Bț@?tG?ڨ5;Q$A[=Ul2 [tT5_뇊IL_+J$\5-﷪ P_OٮHlwbd?g?PGd- +r1dAK"`\Cp$xo%O/,{E~Hjg +'#xF>{chXYX;GpbEdRId9#Ŭ @g;W*q銢>}7>#@j=}+˟kg_+NghJ^ҏ;> +|O07H`'80FPa; +6*`_r8סl}LĝHu~J)I-FH?*$f9"\"!4xQSt-+&I"@Z6So.1nMި(Qk(LMŒ(Ao&(A<3 [5=h3jJd=`i(fXcܵu:R30c0h4~d cHIP|?GmwqEvY5ʻ]r N|+ oe)ah?=c4X;Bd=_K?~d}'.Q*zUգaԽGC/ӧEaxx`~Jkg"R4 R4ȿKKŧ!ORߌ+r̊WZb$^MNTi>|lYR4- +`m)sa~?ApgAQg|)m*nQ%O8> )2T\Q)ɴ|(W2kXf~*rAVA7hhm_CIɫvһnZV5[ vڷx<5[Vvír(s1bj*DigDCٝx<^˚«t;SM +Rafx_ŗۭwrLPPz ev;܉n3z<ӝv glMrݎNx:rz=ov<τrDt(ۡ\0x;ق 7.;Np +Juw3n9ζr<nPݼ-x\ӡx.wxATf$I:I49DX )2Rf"gq7 +RIR\WU䝁3IL| Ke7T.ToHjKc4o>~>QĽ>-4im+1X0do3Kae д쿙f:Ȟ=d,wzY:`|,bˊ[>l,s/Дt_?}j:cgH#/.򏢮8rZQMw 31k7Lhc@Rc1esQPaZMMTdiOMZy p$4*a./9_fةvif$; <B["EH|{Ȉwvig;P v*l8=q/i'!I^NwU I[/x!$$ ¦Kpak&l~ tbR%t9|~]S2 1P,cbk#$m}ao!_B;?fS.3 +3p+䭭&?9,OE<4ڴ;Ѐ[@2$0z}s#H(vψ]EE*ؑ뇉=clӷKrRp@@iVy3TU`g]ޥ]z蟁zOMz6遮d_]遥J)bj [d_mMXǻeFr , QogW"򖒱n)YN!uF]e*}Amb%G[d!HN(QH>B!w*n<ȑoXOt79L 4,{(__'ȉr9Tybaō~gLLiBWCO{7Ka8OdG? z/ӊ6F[!lY=Azܿc(mHS[~c%*r<E~j&/ 쮯D7oDӎ}#Dډ 6Ė%gC^ŸpMifLpUsc;Uh=;8de6+URU,vu/\]|-6>I]SF"h`j_ihTWz&tiO>.]&<9a4ykL{LGYZݪiڬ,~!0-r;yjWؤ1R"'/@{AŸOp=:oEL9%H###RtDu; |/zz7Hg@خC!;o؍YL NE(vϵc8}ӈ~&t1hzGNw>c+F9v 9?b+LM^.%+r׋ +ƞ䄇Iz +=!oy[cgEA\C-nπco/X;K1BPBA PizЁL2F$IRRc]hXtaz^\wCWQ7lme~ؿ@Rc;޾ -Lv  ݏm~#MڷFLӷx[CC3~JDi!_$E2hzB&1 +@(u5Iԉ$Ze,Ǭ FsF>M]S,6i!)i̜ ڥǧ=jAQ0dzu(Wkv+f|_=G^t0 I^eB/W89AIv5,Qycʅ3 EW߹.T ]CV>1c8m]eH1A[ՠE!|<g f n]wIIszrj7d7-u+ߥN1c@oĆL DӘ:d3hC!h@KbWq#V1}5V9ߪE6r6y^oK;sXcGɹ-qX٠AηjpלEl3S +t$ U4WVd4=[Hw׶Ϟm'N]cGsÕ «%v-v$:E 7;LT8EʷSImQME*7谣D D.NC5-LO~ +T$4UF5KQE~ΫTAbrܵw\%roK;.[5y%8\[+z'4%S`,{AkS0[vUW>=4-=OqHv~#fݨA YN α!6hN!lc u`Yw [GuPUwNP"" u&^{Nξ2WyBv +pEpi$ =-cˊ@>YdF}b}#7> ah!!{LշɌA?k.{lL$4WמEg9~#4)_#OAJrߨn3")2&,# +9 9$מ@V='gAȔmF+EGx +)=(M9Oa ag&H`Q(yK 7[ؚ~QNSk!? _eI(! ?z|rvS})ۣi^-LOu]wD)]d59qaeI]Ci& [ti}OI]Y{JWu|t)DC +OC] )ɺ~|EY5J7*ϒAhTE}%ɸ?’wqzL~7j\ cnAVGɗXIy{LPߓ$W@F!"T 8ZI&qFDDE!ݞŽ%zJRf7;nҟ(,c h]f(#OS*IMSkOuGKyfO&Wͮn*e!:aH=.r6oN";K[jw K#a~Õ,KϮJXkz` ^Cozl;TY=XQ9I 31 V> %Cj\M{mv 0 EHyb&!sȷk-%8`GD LLU !la}Κ*\YPkn#J Z :]COC|ivqVc灗+~Do^=.HI{cfD(x`A,(ҢW.kI!4QiV'PX.OS~2c~2cO˘Jð02ydmUw3VFf*U͎R%!=FƉ" *Y2h7ی7oFEYA|1 14Am2v^QEz]#] |l?dUU+a8x[fji1g +~u[F򚳈ܺ#%mm۔G/CeIw }s BG7YϊA,sFƟ=1KՠlЌze2PԺE<;$l}#~/TSv̸sԤ).{䵦Оd^I+ Exj <%&]9]ۿ"]be5(Qb]~v !4=NnT{O7W?"[ 9K-ZO͟UnUcRAt];uAχ}&5h0`&>lNBق~\p%K+FQ'NP/h1 mcٯ5S$H6VZ- =JWyaf'8񖥨G}Ί켤}eiy`fJ.i?2oEqȸpۨA,a(~itHOkq°%3GD} +HЃ,kV8Z\@%.(n +^̠'8ԆIGTS'3d*?|lO /jȒw|}o$; 3k[plB~<%9v WAǸpYkZ3Ǟ-斧? +qңYiZ|^ΐ8hFXPr3d*yz3c;~2:3VՏ3"ssz05oCN YsMc.㩟&h '4*TQ0 rcI~ ޾iĐ$qچesYOA2zg_,灗j˒Lcɴ-;Dhu [HC߀."y`g/$T-z/-2  43EUɕ-YQvc|OLthH_fu8A='HE?Kզ;!xJfaIӒDsN(pӒ9[ZÕ/nY~/ooQB +~aq{޽'¤k|tm7&|1%: _B2\SȎb%nܵc0S~,\gcС܂- _j `CzÆ3< A c8'TÒlAJ7[њQ[(i.-@ߠ: V^ +NKf`eTu1fg@Et!9T۟ ѷG`^Q*9SD\]m,R  *# \\M?U?mFX!)p_B!ȳcECuI:E))L+J@ALw8la|%R',L~ҴEJJXቆ,"*C[(9u8ZI,o#(iCЂ\"\Oes´Y>=~ H qa '@lxglROu9ڨC .*t7n 'd}{2<=qytN̫RoTQϼkn9)ՈPD|myb,N^>s_y٣6fcgp\'W߰8dkY>/0[٧ oꎉC9F'w'6NҾ,#C7|q ڟQOvEdzHM}7iJ} |ZAWy|4rʹM w1?ju)Qsl;> fһ/7haKq%A2O/W'ףq*J؍rFd:c.;[;oZ {Hb F&IR&0-k\[8}{ #Ԫrg"ަ~ ;kIVff" +XG"~QaaJ> +-܄]᫄WB +;r@CBבu?ĘpMA##5LxlAԛHP0 `)`"`cC XhQ=EL&\I/iRnJuGZg## hjѱ.1R贻 Ɣ3!#X13nSFZ'h'_6a5){ +? +w1{Zw1`w)Ξ A$Zc쎩us{Τ> n. wص[L+pWxR*[/(Vˌl][KE:-,Q. TDzJA>B!'˔4>Aw+y ]aiwIڔn듐pՄ&xRA SjAlp%34} +#CmP|]NJE#P@CM~_*m*Pѧe'ptS/ʷ(.hLyF?>T5 TmH!y  5)ȝdVJ䗠be[:N$d5aѧ]i|ZE *__ҦXDA}Nρ._<|iU"VCM]} ZD$#TA84|E׆"ܪeNlwIoaz7)| *Ȧ5_ܨ^5FP4攟(찾X5x [$sO"+D.MJKm{=vYE|~K3M3e +l=IYM;kW=8I=fWa? 9;X XtKjGU}* #0,Jw]Ęv +Y&yNzLr| [$8,DVBl"0**7{N =!D))`I!|! 6ij kHkS *][v#ڮ<y +\EBI'4F*2[cQIv$\'%|tf$;trUy9h5?*WaC +WG:&$U [MƚHSH _Jc!5⽭p0S^ 7I_j(MCM,rlNP~Ґ@χ`EWj2@ìY/b !juS̘vVy :/bӑ ZE_C֚c%0 * XBC +MH:7¹Dwi9fAff&\8&n~_W%"p [+(n{Y 5F=J !ؤ,h ( `Wbo䱼7GQ)&`hGo Rd`WlkSIU`ۄ]7UH ne1c 0_ׯ~fP:э#u< +)@& Ac8~z h7d2(RW#I!N__PN1nOI}S'`rSl(r6%y^]HOVU7eἭXSav+d$"M"cAMYlbtzIm\I}\3eB,#7~~Y|SEQN|(V ZA3fK6eڞ[ͺOEBRETVԤ +` tjël94 c;(nq= >./(>4D +NqݝO; q6 -g\MvTs|2duDZ f@=Fyh~T˴Sg(̳cؽUTH52je H12_e˕Uwv %'̹k<P7\stW6mx L3)a{'m[=|'R/ϱdW5n>e1iv04lT!JIV˩)GwȳjLA Mm)n>GYu4z6fS9*Ԉ]ܻsϼgsWz\ԝJ.s#8[)tO]?['x#CiOzgK-ؓHC",ăTAހt_+!Hy,I6qOU0'A.|CRNϒm" +L䗗!(X#r[,f7u +UvG*a>nk`z):t?c95qS%ԇv u"{;iwXI1*C1*.)/rN}{ʷ)鼏yO:J.s*zВVˠ$/}To\5,_Up 2`va -Xe}Lf߸4c w +=- +Wm}9oU:? $-80y[ؖ))h1 DoM<&q6ed3a^'%Du`3l`WL/=I.3mpjE\1 ǎT */"v i${G@0)*^g<\EGIe)7pO#vvwMBbAT nwPt_K+|Xvk:,͊9ػ~uYx +-K>'6܁G:GR]Μ\Ět +\E_$?alߨ?EG~BˏNZT@70lgP$T /[Ș A9 /Jw )wAcAA'\} C 3gQ1Ű#uӚ1=ȤhUr+_{Ljr&1wR^l&qY|×_Шps0 +,N_z͢{ ,EO1sZV +Di$0lWbP8Q/eX%M< aض)vzPpSZT͂K1qs3|ȬF,R ?ag\F\N4j1!#b Iq[&E61K%ĽrnGed0]7rWIĪʸDOr .׼Nļ#v65O`WHpLEF=^WKJ+"^) 6'Z Ф8y"Hy1]6/|2lF$[^S}Ji@ޚ^,U:=ȕ68!C,pFQxp;XYrПEiEZ{2{b%aG#غuR'~*(H +hBX81A] caC,sع{*p2y=.1PjX]:2ym$NSg+0tr<]2sPաU{UUq"[@c=ۅp%TV~U"*S.!˫ +%I"ee!0J.NdI.@}G34xuc#MGˠwN00%PXG;CX29BHzecyϻF\c%'BӃydg<\8rTF)u,,MD}Ԟ)5K@A/i葠Og*pK)Ļ{'# N[z9)Иo˫:f2q63fK{,Df38juJ*fllq +ϲIUO +tܧ5mGƍ_oD,z[?*s3ߚI5Jo9"Gcç R`Uy""D脨(QDD>Rhp! +K. DljH͸·+k3.,`f4M-k=3N^V2VɤV򣗋Դy7rz9]SY!x4mF }m1:Q]]N`Z%B=#ĿR`VI$%їRD0DB%*!@TBTCBCBQ % l][g,Eq\E=Qd_iZMV9TB)vq׺Ne(*DWXr+W9$I_4r(Յ>u]+,[wL˅d#Fh:CZ&$FEF̉h-TDm<#&JCF Q&Ts0Vi)?&~tm`1jɯZ򰨲h\=aOg7L% +ޫ'l`2h! 1+oP{hrRs<]Oe%AmӠ۶I0XM22΅,<9qO@J;єğ<;ܣ5+iNn[a֩{8Ÿ%?FЇ5X b|B$=D' isl#Yɴ̡*2$~DJ 'I"|KeRhFG1ȫ103. U~dE%A$m\3'oMe$DX}KvuJb\GOUcՉP߱@摦"̕@OOGϧHڲfǩӅ;d5fɝЛh͚wiplx.}Ach$\IJ)noEiޯ}o:ZmKL2 7LI4(ZTX_mV^gu)TK$h5DҚB[lڧKil[4c!K1UcF)x]6nő)-f)MJ ҧ%wZĂȤ`YW׫*1V9'MIWaļWXާB/UvY)Zu R+y䨨 "CK5:Nʖk4Yp'r'{ߥB-RbV 16Q.Mjt2AWO_ײKL)ovahBa+t;[Xtm!gosG;Y{4"Fr"w\Q PVAJ"s/ԉȁ;O%.-88jiR$d\[hsW$@]'ʞiP !F :b1FuúMe&1aqk5Ed*آ1 6qNue2xy7̝341*I0G`gus2,O<^}C$IeܬrvN,|'Mc!;KL8a}5>yZ3pzֱza=oj<ރUQ]ן +z ꭁNG\ʕ=a>Cio˃sVvVׁlQ$;)e >gAEQUe=r[|·y'裲@@狰qyrZ_=L.ng)$|-sٓRnix7)g7w*H䒴[b51-kGS"z&3d_e`qz92o4e!m0W%__:4}gyVl^=+8/21}S҂Iiwg܍׉nsֽۜu9=g̓/5$7 $)x=p6HxP̄jR$yoq>6Ki[j|D!ngS>6? ט,&G ٕ*֕E9!^;x חz .WgO7<0r}<||L˻XVG%C7)sHu߁  1\rK)ߟ!}|x_ y gal=?KƈTv zf wg7_O0g}2t-uXW ċgKA‟bxanIYnmi.= Wo1@) +=ݷtnm.ْ=k㎷ xIE*'~/Rh!uD6)zBW.Np}e_>fWVh>m^a-//;3C͂ +Y |# Q(jwB7Rޥܽ4m7}GyC^&mN=JA:L!o<<k[/2|)ߑT丮2#H1x&y|ͻ<ΊӰ|y]n>^GXSz4@dܑG8i|P| cY.5C!(lu^jh۾R%H++ YYH/> "V# UCQ3vJ2ZKo(Gie6`PsΣ^S,lW>Eq- aI}QAz/N+H:P>#}R1#uN,>>8PA`xjkbaczQVz aM_=s>ÒbFUa~;Ut"25uAyGnGnGO](Gjۈ(i xCrAt.Y/=>XlpNSTeQW>`{l9u.d AҨ㶌jt~ZlkYIJBX MrO{]LMbs3 ~_2i31zAnP^1QCB[qE,8"^*4\GUWO՛ttU3OW ^nf( ~CWGk@f&&}OCeA7=BPaHFrܥs`A@d8#r}l6[YKgr-t3<!Lź3 a#W؋t9B8tm񷟆=EȖ@/xOV N8fw[T$=V~'||~z|ު:!:0 +f>ǚy!o'sku"=(]CO%uCו;GBd`_% o$]Os˧˸1Bjӣ8S<[s}#.O{:ޣ/;/!\u|yU*gtD6YiR8$Đ$mL8;2~kFH^#4 =Ս|Z2#7ޚޏޣ_g}D`ۚtx~FftH&F2=O}g?8EXo!a>; u\$8As9_ElМ E;6lF8;2d{A0qvd +M'&_ |V|;sb+ < G/᠌\x>*N2_y%Yufi>u'jѾ1r.$!u8O 4Ku5eb^ +f]skgXn +ϋu^DaJٞ~{]A>?O;gy#XmZ9BYsykulRV,!Ӥd!"nHb(DOB;7&z2N\1Ȼ/OFNSK9|d +ނSѫM)H+XYcՎpx^3Ҍ#O |ӼoYy뙔QZB֫lahTU>لS.3Rh'yiB/H!D $rIްdKjo;E=_.z%*PiMHN8>=֝ayy&~EwUlBw5w&c :\ld eLt< gE5ys(+'ʔ XLH_mT۫d9f.c KSNxB}GXXmI8RCלv, 8J0(gƾ0Yʈ^LU_X<x$KBYK56A2.yk;eWZ," Z_L~1Ʈ%p?S%=TO]oؕko0Úyo樐;)a`Q9WTJq~`,FyVP#aM%#1c| + ?O^orufB|e K25 \" R kKQ41TgXjøpdCwnު2(e +VZoСu'e\ƱkNLÒZID21eD!$:qQ2AJ1RqpVꈶ0FS#]Ud002`9G 0T'z&]#Fxfom@e}soqPrUF93?v:ݘD b=.Dۋ=_`k +T˖^jr^惮61]bsIѶ(iřs znS#'&r3MmɈv<市HvI(F0AQ5 OUYҰ\w"f3I5KhS$+d ^tVJح:f7w#eoBLʖaYע>-uJ6:o3} Ѓ\Piảlà(2a6b\q/ +Cs8hU$Wa]>-쫠pk^~Y$QDETQk>M~@UM%duJ񄬢xzs17`KP}몏z2 J7B_[PĪ#^ 9Q}@%N/nJT5w +dVtJ=QQˡjg,\󛁽+p c(AT4JMLgqό5ʢ7By- m,H[?5;}r)e%ۧp xFAi`L@8\w*ܥ>c9h؃+[-=OL^t%8/֩"ï_^+R C@ z*㴎S5!qrH4(q[ {5͢:2SdL CwҲ9*PrRIXBAnJr[G5;EȾnb*CXPGs~˔O!wcpӃ& +TF϶C)^rXsSsJoRI5"=Pw۠@M9l{(OÒ4JRe ++'K*=R uzSaWOFaj}hNWp=(7TR[B]Vu9= 2ӏ 3x ^IVshJN2Zk8KIzex :I uKey A4Ck +d%mh}+~v콰ƵbH?XVG1qWاU֣moiOwk = +47z(Y[7BHiF2?J!^cp#T1eF%Z +J$lU3qVi 4";D]Dk}FӼ@^gA14Ï%i(-`̴#y(1MK6Vř?4: j.0mQYL{Kga2oY}͸V&ua?}NR]L \qa112lq|q1UTuQV>+ v0a:(7aT뛃qzE >ұWI1'.AbVXј cT|g}TC.FHY"wr< gm_]ZE>uҢ 4@xdDM2 +Ú%zù%.B_zKV6Fv۲`6[~yVօx˺eCj+@M®$7N F׊ʢs;#xjW`X0U]$wUiI0".!:hCRhͥ8_Sf>"ipEr #-GmtaoNfB֎[W75wyyjI U}LGY ߷?0FKa3LX"zf2Rh2A\7ƪ1r7Ч= : #SnJU6ѵ2fo%g}j~DHӾ3X,4{*lw;"WqtndGZ%(qN2Y,)'|vTRb/{،bӿ>\fW i♽ f kb!D1͞1Ä3vN!D);2>@dxKhv:MwFL _2R  q"i5?HNN͵H SQbqw&*4F.qB}A4C ,GI$bsB1 +5{m}6To\NavXM  L zf#0&z4jW\bZ֊ݬBz0#%jɐo\-EGt~f~-E=eWt(&KIzm} ltT#H}6hXQԲ:+~7_b]. h ~ӫr`(DEoH Io6󵱞TJv?VCm]")AA$)L)%)m""AXK]5uY<P^KHn( F3J"q:w +pp' x Y~B_|0f }(T/EєYHIJ"$%3|Sx +'1 Km 0TvTdRVd6zk1\,aZƲh {X)0#.; ڂd#R?6!X|D$f +ta`'&($&`q-hɼ⼐vUTp*= `+P 5{ ~q)}G!#bw:p} q7);t [@('X8oqoFPۺlh-ׇc6_ a]]C4k3,OP'!Xز̒uu]İv6A3࡝ 0X5$zj[J^W@1%:n 69n_I˲h}=zPlzbcnҀ39pB%^p^)?ND-(!3̶RP=4EH,!RN栀ujs!h~ ;[.Skn61Z2ڦ/͎{VɒT W`'`&lGs^.W+h +[`qd.kWWl*ꖕ| 1~"TAg|%6 '^őJY(RI!heYжh{qcK-^ ZaKl\sO;j=^E~Bi _ԫHOcfQ| UļZھ>֩c66{ց 6 +=УniaZGxWE3R?.1yE%S>W0ugPRԲ;︊f_؉Bzީ9IILة}\_)X$'qT!q~L=D<( <"D.> '>N"̰m&6d 6隼6CW]% %0<$~^ 4r`Cyxׯ +-3zi\)d8mfH'Y9$Ghr(ezI8)$tR)$7Cp@RF210*HԹWUvāE|6Nh>Aq>QđG0+g\X6]YSuݾE9d?v˾oa +Rz騩^9DDJ b80n^1Rx34O瑐8PS<r @&,n u3!/d)|V!X*Bfpc/MɌ~ND53~i +&(]D&')N'' 4g +2)qюB1xgZ cW*]UƋ932]ɺA /26j9G@Aގ3#MX9n~GQqŹ?u +c?i)9 t r +ytڷ)J+4qպ˶g7}m4+h},lDZV +JPZZYt/'@r9~!/ ,eS(g|sAV%@T+!l™ iy%qOքG}G/6]cԳ^bR=0F1>d+NvK* _X-\nF. vS~  ̖p~ _ldY,C-j^2ˎa_*)EL-S%LbC2R*,_ZO>f̄aH43Vfc';uR:\(е젟63}>DQDZ[G^2Jw!flHHG7 +5SXqK(8?Bova]f\2aOHDRK*)}JOJ)k/<9~o5KٖLעGEX$6ҏFaF A6 gv 4?&뾆!) s8g}!͔/0rct eZg9-:ZYE-:ۚ4`QQ0ܳrK0Z1i:ϋ\GגKj'e<#ɔy}DGctڂ5\gG:2PlHK eh}C`R9_=ѬKHz>{R)$h ~:=jiC;" +^O>Bd)tszO8[ra5}jKa!pA@z r̳m 4~㦓ȸG yhϿ_u[ +5.fWp|YhV +gSE|WVŭ [xM!U(T]Y¢qk91,yE+ ZJu:=RE((8v{TL-"m/6ؒMc!z$76"ɥjBWR!92]grTȁ@"%tU*m}0sfUcdYlLO0%4}i%mô +VT/r_%u,f]'0yŹU:TcMy-\0pftYּ"%ܩq'3~Ղ>gh? +*evf0L'I_|M_V|1F1?ǹIJ挔q&t/Waʪe &0?u4<-6lN[k1m,g[MV6z!{[6xל6aa7 +@/$Opi9ѱ 056{a%B/mc\U;pu`%sш5cWH\+JŲejʗ\%YX)DH2m9}76taa6|Z,­C# Î+,cbsZ]V11,Y̴|_+av}NV*[26F>reΞ $eďF#Vtj.̝vI cWȰ8,uHů*Hv6 +ea4 ( 8m<(3}e U%?NGo(>3_3R'_%t$k }/}dP2}2l'i}{y}.a& IoA0IBz 2c_侸ө$¦#nk K; ;›l:ƭ@ߤAlwr\ݲ$1XV&tozBY.F;g:MhƯK`H٨'3QtQ|nYV6Lctd[%̢x A6{oVC.>\-=Y̴vƅi7+e_nYYڸbްIyJMϙXS:es( Ht\fNgi: ZV}? +zpnӓJo"XWܝ@N!~~ v< +ySذRw\_9n%s~Ho@@O 9O='NĐ\td?!SG,"XR55%MSW+ꖏgh96G,(SH&^"Yذf5z԰0|btk xLY͒[\''&4ʼۅ ̷uq ^725t[@w@"H"_drr2ŬD2oJ^uO-IiȊI&8 ۍ㷅q3uװ6l~w ,^ȵJ#XPv \ p7_ZMF]G1t"nqN=W̲2K }q +w]JlV(=0/U4w-Xȶvl@ =# +SH?qCo>^(8w?1**y"OBytN"waU~5ƫ2}hMZq|r,&4l/" qFU@p٭pTQ, 4jqg`c0#zȺ#IJѴP6[Y[Zp";I@٥ UBv|Mb?"%)X+?]0F *8zSJC%4r zOpxM['HMJ,}"p¦#*% (9wSnKܗS-t< +] @wt Wik&q ޚ;)'0)#w3tWȲ^pK[M5Id́ M~?GނՃ$7!?I5>zƫ[Ҽ8̻ .h?Z%˰U +[ֶ8-MA.[!.S;m~hpdK+gYXB޴A ,RW/z~pښ)dy fK3:W [>4۟3Sf޲,HbѶڔ Ohi =A1{p u9[DFid}ďZ?3">鏐(>; Vk'Zt<բ-i:1rI{Ƭ.EO:@U@A\}w +庂VI kμƖR C3mi\m۶6˓.*8n2?lpB3 +Hj8HRNJ%p=2 >+qry_ą2Y_-ćoPo3EQʢG" +8ОAcr) +!3Ș3m'q ' +!~tvR%͗uC8H3{vja uYD+jJoV(6`__&mY4&&GsޤI=tri$N|M44T$+'@9xlX=̙Ѥ=*%=dRr`H;`btEKV## 'yFo2A"՗]6K&YOszMZyǯź,.Dh 4Xv5lКf \d8F~՗\4+p\Z(aP}Yau͚Tִ[>Q?Yl>ʒ`)7+GPR\jRY)g.&K'93p]QXBe6т_̖tswun_V"ZQ8!-JŁ`dy6 :b4o4pJTca~8U´6 y~ +s?)aSe)UUʓN U,:TL#J%hߡ#710aa& @f<ȓ=!:`uΛ/.,(>LL#)&3jfbyn3~v4aHyiDDrV9FmQVI(Lȉ} _a 8p1)=5 ϬeTkۀB6c'6}A@Ueݯ3*Bq('9igО6OqV *ttNL203ey0=K'J09lE?ZY,a*N]z#Z΁p ^;;W563Rխ?3!@[rqw֏qVs?4E%؍q>,GaP ww(8yIZzx oQ6ZWĴh']^lWשLmzdbiEnS"wxq(ׁX7`û׺0֯^vjnx`E^S4Tt5}:_v!@NuY 7 8=~ьmrfhVscL7Be/a8YÏ0M×3_0aEK?7ߕ#.׭\fn񔰪mtIV#*NhrKd[p0"bݠ&0D/ Zuڳ>.Y#*P %faJ$M*OiGA\0 {>pxyYGQԫ7mF'@qњ5ETSU%5Ymؙ7؍zdR 66nZ}ٞz7ϗP@^QNㄣʕ +j`JE>hXFظ Z#G^  #u@IGY}5g7~9nֿswu@ll{V|W"j(_UiFM0YD*KfJOu<9Eư "~[k/ԇkbj<(I%J\yGdlyMXI~9%׼A=vI[-IT( +"&EPLCO9e]KsɕR.J!qu6KXУmyFYVX>{Y6( m'=RIVH,XQ:q9&7_2& Vb' #'%2x ֗IVlʉFҪ%hѭ#{z'[ Y9zh0)7Rt +7$p3_rZ۴A6}WPӊ+N'+ތm]Mxi.Cڞ:> + +ũDRzg0X+0̟PUUeڂS RGU_g m$#Aُ9qS'zh) G=Ҏn v]Q _$F0|MjY%Zmn&D2jGzrEOh'fjkFM}n3Z=#=weB H-7xUmq' L'rգMC%SӰS2IRUA1i`G1gKor&R6Ѱyehpi85F1 o#wҚK |xΓW8j5gƏ߸Q;@;Y[T'2v 5 j=_Y\}%aмzw_<{^9t@..Z@xQeE3y V䰦Q wŐ@A"gLuI4zd KX{~"|)R9[D܋fWz p'(6E{5QZ?kj +6UwAiAxaBDžΎ1%2/3 $Ç:3v 40Ls <ˁ]Pp|Ҏ%ED0*:(Pm.7?Ic> U;G<2 L;ԙq7{8'i#;~.fZŚ!}>hD3MXhf$3hO!m9̼2i0'M cFI!dǷ6 I֋b 9අ_(ǗƠKrl0W0݂p@ضX3z&,=z@aȺZ[7(_40z"hGp8qc1hKuYZig 3̶I&ocΌ' +TI7hPWYGQ[%,PfL$XI +z(72X:ٚobHP[EUǙvjтyl.m##5uYHL'z<-שE/RJԪQv|O^Wib@ˑ$|Bk3oS3'F71 7bwD +yĊ/ŠlBjowƑcl7v]#=^];mW'6䒮:y҄}/ǜlw֚6֍X𼁂B;#w:2q5d>TCl`7;+4a܌@ 9I6Nˏ⪏.fl{ t sF6߱zm'gԘǹhmۋfloޢ1j_~0Tʍfl{RgbZe'aʼM(aS;[gǏ  :>ȏ%]ȸ\- {MM_Y4xtGK +2*{HQOP3uIB{HzG?7|6 /vr{gW ?q~_o?됍o_H@XI?E|ih8t뵐=lsY?wŅҭxMsNVk۞ޞ.+-Kp}G2iK}yNzΒ_#zPY FϪw +:3W=f+0_>*H.ot`~7s_=oTcFd7WOTq'?k +ΪhOt\ʢ+ܛZ~ {pB~83O3-R9?+QOh9 hOv7I> $r6mݒU[Ã7=eNj`gŊ4ST֣vž{ >|oЛlh_~V.$gUdNJ~^9'o?fv!^F39xX L1lZ;2j]Xե/\1b7(g i =H ;hqlۨ*LF陧1% p@`` ;G濘 ש"k?!ï9oF*^ 1aiumfzE:ld W}ĭ w+ositin(SourceGraphicinoperatoinobject0%xid)AI__1idh10yww/Def ;4alNois4GaussianBlu1bstdDevifeOffsedddoSpecularLightfePointLz(-2000z-5xy1ysConstant(Expone1urfacetyll-color:whspecOuk3k3kk1litPaik4arithmet2kkMergNod2BevelShadow4y4Morphologyradiu1.(dilabnby-bnd5bn0DisplacementMapxChannelSelecRy(Ays3b3atri1mb34animaddit(refromcalcM(linearestaralwaybeg0sNd5totofillfreezaccumunonb58cccc8cccc1ccccccn-5CoolB4-1D_663erErod6477(spli1 1;20 15;200 200; 15 20;1 1 RpeatD(indefin1removc50 5PixelPlay;20 20;Diffuseyellow;green;blue;indigo;violet;red;oran5Diazimu8elev6d1nl5re010122xred688-1213y40.0.nSta45d4ofeFloofloodblack; opacity:dnsCd350110Grayx-OCompBlurTO1nentTransfFunctabV2FuncG.7 0B1XferFire]cL!"IRH 8VNF:A8$1( @A`0P ³ku +Ĥpj-l4qPaIݮP9%m58 vr\EzQ|,rǰ+!Cj^x +K~8%U%y%qoj2"B^ʾGY +0OZ"K7nR*lU߯)Kpa і.:큵GhQ [IrDŵ!E9 +B0<kUٵ@UaJK+8x28N}Ur/-D;BM-b iJ*CZ 0Y7RQ,d!I239)b*1F`Pa˯ +*.ʟ,eZ0 + =7TRoIL'rWP/~ ` NmYUI|ckNQ}V )9~ʲ V  9B(d\UzP4I%U8B9H01BٺňVGLYBZL5C)Q]AdTĠوN-zQ8e08ҠshR!-6+V̲f.@q@b ]J~.G/qA@1hM$nm 6s Y *=3C@>2Xhb2Nampa:QrebDV5Mr&(#L fg2Hsw ,I'l\5e" ߘ8fu` +UhkZDA|ӁöC#aEf8/E羔H%,l[!t yTpT>wH[1cVmq1og'xp$JJ#kڀM(48P0OL&Ĕߝ$f1kH ;e$F>eH0T6p5ˇZˑw*VB_dos- ()"Up<!Lԋg2d2uqnQz&FeDya2s?HK)PI_{[)Fug&\񅡟_x:_3}T +V/:4\@8%c"̞',J:'bLYK%%tܔ-':5Plō3ڞ<qP \j,{j80_z;'2nouL)bf֢#SCNBfmt/:g)BJƏՉī +z +4"NGpUg, - kLpZ)Q* mׯ5Vrҙ ۘLÎE^ɜYm~& jĭa(.lɛy1$ 14Qu>p5 ?f +ފD((CCB(n^0v,ٵ +C;@\q {n8꾁fzݨ1~\COٯ1 ͳ e&*- 9 FBU]_3WAà3-T$NK'_xщ "MKf@X?'\@4-b|i@Cv\`Y?"8RNdlgSDL*D?<@$N<"u5>(>F'A +鷨RE] +fZ K3VUb=dkVy +Fx 2@/#à2DmRq epgr٭Aڟih%?'9pi䪪:n8B[H#ɂ>T @(IʔRJlu?? aqUHF ,噌Rn}̤,=W#P)UR5Jj)ݽ{5@\"PBQr e'2'rBu7(Z4@0 F@d8uJ!uJ*MF[aI^hiuYZ/=*@TPaoR+-ʬG=d,VzPKGf&!5BJ0Â!qacA !1Ą$FY>K$I2McT?8"aɖ~@KrÒ,8p(悰p`I24",ɒ,Kb.pXG "Llw>GS2zsVVَ4<wTg>V'5Ü震us=>x2~AݎroEF(QEe^ut,"mZ4LSdBbB?H[hHrf^ԇuPs]1wyu]4OB*0~x7GT`T& yoCf 1a * =`EayH"L  +ǣ"QLdN3O3z> T@\HX[d=hOZmZ9h(*Mác7g?<̼&ն ""bj {w3w3fn7:p_k92+,BVVl&UZq;6s{2Rb<*;ҪMrj@)/OuIVvС/wk>}q']8ϵqy#9)v>veg>W3ɒgf&Htt41/2 +-H +J Q_sHFgQX3]Hfj,EH!OHnjT#JQ"CǪR<_3 +2Лd:ϡ509Ck&AS/@p>S0mtФWd"Gr5)!dɈ*d2+//T'BFBbQ(VoǬ@ꭐSx`PEsg2-2E; 0 ћT쬊V_SyeǃGs0>Blo{|fDn=^r7J2QL2JRy2A(z&affdfotGIG!dQ췊nMnḞ~0(nR(~//4Q_Q76C-"{cQ߁hyiFzg"JGΊKG Âh(,, PPx\Tx0`P,`0 &$jQ4,( ,,.TcV* Y\@PGEC"(CB9rsA ;aCa>z0\8*"#`#5Eh ?0b_Ej&[+C" Csw,RFeu_E: YJ|tP_bEi {k.@<]2#K4T2k;^Igiv2-}-w\yX3PB״Jzf#7UQ6+޽OZ\{y'=3޶U"s~.sTSw%O&}w+<(vH9ADSܪ-Ē,ĢIvDDzGP:{NO(gZ>#sv&?4 H˿T/}U۪{M C=7i ݛ?Gyଢ଼=t._e3G|iQ('_45 +! P#P("Ǣ)ubG6Ftj9ƀƑe]hCJqa}1IlCNRhАq@*) VD ,ƖkH˝9d'xQ3ܞhW"Mx]xgW^BYTs1G;T)49 , aeW{d#wd~̰xǽ7/t!["~co}"Ƅ]ٷm\IowH~los0C 8 +-hfqQpMQgPDh&O3gը;8PF@2ZT߼y۽XZl]vҨ%V1q@lЦۗs׬ +nt Ju]if+SsŐ3]'{,\J^jK8|RSӄeðgWGjo԰FFv =hpĴX2B$oUG(hPOS:% bS\^6:Fk(M% ։q0$.8ieoD]'R񚢳[ sw 9@W!}Y)CGFY`!SJ]GWwTrESHd{%8aCB!4"|̥Vhd%ڹh fQwoW`E BG ?]v1P̟gRKnzO6~9ֵZNjh$dY_yYz_pa8E',dv,'+ t[5Dk-_NV{|ӱπmKy۬r# @ɑƯ%Md \ + eܗINu+pTN\}GwBTh8' +KvORfġHA CMj՗ +Z4iKy;ۺYWj^řLZdȡa*JG1g 9[@tw4m` +ݛu`TaZS}Iн:v}Vhq^a @?3w33,` zhg=QcDx\C6 Pb@ZWg ϱ{u,e@d&#yƅ+|$` y~ρ9tԇ3l f921NP9ǡ7(e @tk%eZu%C沮37›UdTX.&|4/[o5A$[b0#Cs&c|Kzx)BEӮAHxA؂ztŨst5 #pӓ \}A`䛾Գ),Bf [h{F WnGS[f*f".3SAXXGh;2|R;+Q-*~> +<面_-i@Yoc8ET}d,ŦMV.6tO,FN&yJ9/@M7d ͬG,KGHtN2EU \ " *e=݇nUŒqU+fQmwzUюN@S|H "ZosI>im667sﭭThF^5Z +@i$::QM{T› 0]e!=ߘ0*!Kp-+.Dm~ n伷\m.z]A.IZ6Q +Ȋ*6\h>K?&Xa3.xށ GN0I l{RFLg&C[ayѾzs=1H ~edyoQlz0 9x!0rBa$0\=Pʍu1sQ}lTmƄ0' $Ї^.҈6PY-iQ}&|l /B}bV1dTf[y'K1#lz׏U+1׳&.BKoUmH/>ÍZ"VISk0PK-)./Y'X<~ƛIUݘ/*lnD-% `#F( "G׃G255 of@0F˕B v"TY:EA?i{~;׫VH P Cg p9X5%8$~ +Bк: ?]fHCpkqQ=U]4=)r@JHVcPBe(ä)Nj070`Q!@ \?ek9~=CNRgJ2,bLw#L$/T,jYn7 y S"A*9k{Vl~J?5*uyz2̛ $[tL L P쓳+w@Lh'"BDr髠L)Rԧ@OJ'RVenVFEY`QɱM_忪ܘx{0ՈhVB, bolEmG|Y_d9A~V>P + 7GqHCR>k-%TLRU|\xEŋ[ *4#%e Uૡt4j,_qF|û_ u[ls2׼/rmNV$c{37 8$T_>_깤Q.ϒV9#z&(qK +۾##)CB)T'Ғn"9~q=`m| +UI'!EaUOU> >}8qҵcɦIWԦlÁ fO) 8%IPW,, 453~@K*@4"$6PUW`# P,uv?`NwѠ]%`tvֵ)Pp؄KxRʖЊ8v7N}QGp.f wy/̏*BL3ŏSβ RuLbEn)yz85Ұfj]|!θȑ|I6ᙰ?~4dC<^pB\~[a_b(D> d:&OM^8\)>}-?-[ +g[!Y2֒r(C4UksQTnxа8G C&I)QqĠklDwo:&0]DD_-j%q|]++0M QV"+}T.lcV//gԅ*Fȸ%#'"nz?-'fg(p/B!L`~~Wˁ%e `V폡ث#֢8!(NqQؓj'wPbQfCP s\TQ8\(i {dzêbY0^$YC1R3 "_W$SߦgL)5^քhDQ RR?~\;C^=%@!'!#IO0S}zqλVz{pG+a͓*[ 2%P|"qe8;rP1S„% D%ޝ\|Es11W NNNXP^g!HgvZp`.\wk:@HfD5Y1]ҳvV[/BuoH7g +K ܧ]aot/jb~uJ:/W>h7`.?iʊBuK;;\a'ܚlqlGY8q34JX."w'Y'OsC+M $KހEl̾)x۵:+;UF2be]HaĠ 2m~(FMY ʟ?mXjLߠO2:iǘ2Uy%vY!QgR:j̼/pa8N5d=֭$ާAB1-n1)کl} [fum}DQ-Ali +Btduk/@;̨+V69\V7!ȶ:~`tuyqv4zQH KΩp_JZ4C69 Eh̓Pj|BGT옐[(A98EDcLV\=-@6Os}S]4cwݚ|% /J+ +-4\ tc7Vf3jYެMRH{j|q,ln\a4 ~c7 C4?i_|153#]e6(OH>tꙛbbg+%-/z}{ FsӒS\Inqd.]nSpHQ9P4, +0Vx)4&s2QP }Z<`BqZM\qWM_z9+sKBh}_o^;6:Ks`ddxG<ءAw&RhkVZ:lCnEiPc}8â/ b1Kg3s2h!1~1AJ:`H)`-ⓤbU X xjUh=ho<Sh)8W|"m4]Co2)ܰOjJ!9@ր>As؅#+8BSHDdH1%:r:prcxcYk+ҽM{l8\@o "VȎ#Ԥݖ19b]1YAkrsMYiKxj& ye ++b5֧1bM@aNBP6A 1?, 's d 2&S%/kpfF~`7 +/`]_¸y-$PC v owW &6VzQB[hVZZ rl_8>9=19\ LPo+lqzR=6LPԵn7ީUǐEfx[jd|ur~=PƱe&sdМA;K?3-aR/d# ^ˊ (Y1Ԍe`TBtFza TN *6P]Kɰ\lRQ+"守씾3ax!%UqpD *p#HA ,E"ITބi8E Bhle3ۗ>D($B" +XC5=60 28E2Uz&ˆ6 +pP"eU pP1di( Y3H#QLLtD4H#ѨHBAl@/(hhP8 C^ĕ0#$HӃrz" Ndi$fyNϣz^Eڼlom9ٳf2Wdu9fm%7k,71Wm%;/{k]cgUscf-ٲsVykM{y}WlԓW6V,ߕDoN6m(L4 ӡh`)A:zQ$>Rjzty@Pe,"(ZsdlkεKՕNQ[ +T`\<*Sax@&PB,RQy0_|LYop Z3![s dm@`*)BEq\|Q=Vzv6wZ?y2tJX 'z@Q"lXL8/RW=,ٮᄍwNl0߆ tVսemݧ6N&o0A&9mDrzJ7sw绿\ݷ>:ifeܹ͒ߟ?>K͟,7M:v[2g\G%QyIy0T.B ֍WU/z.,Rp(p<[aQ%U gͭ`t253#i6@4U@pa91B!B!c_[6YN +rf8aH$+\H|}C 2WN6gwiB]EP$6ioswf!,F8BWCϺwtl>k? 'm**12`L}y憃4H&ɨ8 endstream endobj 13 0 obj <>stream +m] I 9%X]cHGzvc߸ 4?FJ-]Jػ~CͰuPKN#Pk} +i435;W`\3Eʫv'T(\\e[jO:-]Mk8]Wy7"ۣ@9IƼhAB 3|4 tmr {-g՝@;Hţք_<Ʋxbpa{-@iΉ>$Ҿ܇l)F,laSr#".I.]z%Y&QeAQҸ ٹh 0,K:O@@ +Aex/ +F@0_"2A&[e/ %GCj&W_tXht_hIg[I I%/ LmEgL !wo(QP"cU41ʳaxƍ|"=v*!3D? f=8]1QdKQ)p.3h^/qYC IwL3NZO7 Qi.vĞ T 'Vk $rh/Le.yܽJTMv,!rj6Abe]'FK5J946P~yT93FQ$38xeP$y%Sp^?cDˡ>Խex9Ѕ>J +quD݋8=NY a,F@.){pbYӚDw2u4ѺGklm6p5Dnm5 SPIVB`\MV\.g#Wqpk ߝFIIȜc6HpÈaC)s,&F+`t0'?+ΉxpI p?4$򤙛t{ $55dP J"=zÌ]Ee@FRB^FR%]\6)A$p>#x{5nno_\Nqm7g@%]i{0>)d6éF0;9GaFɺy]Z!t L#NoDjxxU7i1ahGOR.ؿhVdxT&(mRkOs]tKxefY a˼諭y:5|#qђ&(;+JRi-GEw㘭ks$PW[]\TE;0MHiCqo '(sQ~  ؞~.:}F7ۥU@Q:uk.t7(u "EՕÓ=+rp4J[wsY^zgEL_ZSRUYVFX ˣbB + Eer!#D66 `. :a`4*(0 +H&@B` lxd$ʅx$d$K@`!q$JAF##l#ypS<#Ddhd$ X8 Ƀ +Jp0d2B2AbQipP4ΟaAvL2 h@y2d\$#dH2P(ҊJp6\b\H.a8JŹH*L&Bp$fRLX,> e"D(  +bLF P.D +d*& + Pd0<"O"];SB;lQR5t_i[]ߒ^OZ-/7ToTxҨTL!$(a 1@j "b I +Je* ? 8l`/8PD($A^-sEƮxA"bCH%x; a!:EV n0&)((3 Xdӄo֥P#"f\`,+ &(t d" EJn8xn '8H\E* Ώal Dp崙-@,Q8n&쀆44Q`MLڗl1- j"[=_& +IѨD, D Pf !ha)%<t2mYF'3ApptKiT؃r"5ĩݞ@?iٝeZ "m"PTTe)Ex1Ê@)vYl`弸ai"`@ͼgX,tp $i\Be~ՔހXJ=l5{4w5 +\c"Wٖ1 6[$"!! Ӛ +00kRzX |7Hv  ?ʵ+\<-ujSU*g?NH00u>\ `x +UqA\"h .Lb- vGl]D :b31sI샒Tb#U;R$ +Fqu,^_d`% .uDxbH0Yy)Chj̙h(U,E+~}=KԶS 1 )zڨ߬z5vшxP03Z6$ipPcZ4g ަGg!~c-]`1?$fGhu 9%$MԶD,nLK5Kџ|Cl0:*$@! a$d#thd4otuf]t6"=l4ˎaw8 |n`#.CݶQm _%D`d! +@@U- BhY)h"=^, 'W6bI > !M:$w2\uuGK:)/uu  @U5,M.;+ '%ݪv<6󶙧J_Jzc6i/ҽ-GogF?=Fr[7OR\pv. +cc7eUM-S:mb*ZRAަE]ϊt{^{` :@2E{Bz?4Nz:{7+{.]ARvbP]BeT10B^cU`Ս}`՘R~bz +rm YUFHe4 +8j AP<00pw0%@"7 1e_'O<0^{y42q=]MQ޽ y~`h4V92YX-=f5*gpZpl•eNU\SoWoWFj,gd%a[‰@aaP}¢4Knqy2nF6#Ltⷵ8T\vݺ6QӔ&-\-\.Bp{'.Loxa߸Ƹfϯ]Ϟv9W8\`Ǹo{usQM0ns%\C~+}Z˦Dms傡H|;&{"ץDAwXnQj;CQ3fͮд eX-74]hY$DYhenR4+sf$ٕ;\=+ taaůr0QS@_yےVykw^{ 2;} 1`@Q S;. oPf'_,!PpWUmxG!lrC4 MR4+W3h\ze]y4 ^ע,h\RIt KuPKւ&٬+*\OduVzUJ*F mV8Tu Yv H)I'v`rS SB%֊Z ^ 1G XTS SOޖ`)L4HKa_MTQQJ{(|̚a1 JjR чY&L 30ca 4#T`"G9Ƚf4qe'5;JÁA7ZX^ZEYh5j¸&J1yoSJɛCD l(qKSPEUάVWyOW`'sNF h4KVĥ:7HT2WPz“r瓲5~ L> +JF^) bX1ZCYV@ 9m lNZI x4g5BwQ8zwq8D(@@w5$4Bo6 HАM82HSaܙ qyB`x1i!iav XAC.vDBّrA3J(8 h +2 +h,)J"?̠x|"$$+6tXGvN ֹ,Bg饨/C=kv5Af.X `ZI[ TegB+[ TzVS6ROYz[9ԤiHA(ߐV!!gA$!j@mT)3wVV1Q8BM73%N+ x*8-x"T#q=+\-F] + +]l brAŒêCۨCkÜHO<;(4mR*8~NŐ_i$YB`e4B0tJ^@I +]UckR!]AOAqr,Rp ЀI oLtf"DfABPAYA`!!<dkxZO/!bD/H_= x> |VPɧ*C-=Oτ/ \2eG'w(o".XxXc<FR@aoq3:҄d@MDBt}O:rY34^9>v g=u< wΰ +Nla>o9Kn}^쭽-޷[ÿ2-[@o/n[@qq_- [[Y..>vٰ+γmjko8km_ѵe-򌋮-VtmY J{o9ke5;\k|Z5TLx4mpV,1?*o_0xۺɸ_k \pnfhijesjos6\=W\7Wx/qe[o9O0nX-hq1<8s>9[>&$/\kb-\9kbj6 [Z֔]۞Le¤eK&iY-&ǐo=Cj;^C~=ͿO2EҮmOB䵇5pmWh6Wgn h?uYCeQѼۚ. tkr0{e R0πc7jN4EbмmA~- I> !Ƿ^KMhbf323nf,ePW _chblX 2o#t>rDðJtlQθs},(93vN`3&p^Z愍SRWWSnܒ+a 4 /A2_q܎5%5ySZmdBkƌo$W@^U oUe\^2Vf[_)@]'eM9$۫lb$k!OFx[920K'DxDQL]u +JgGh2W#TEz[g)vR%dMz~l!KޯCd4@3oeY$cu(fEX,QeI=+Gr"AyqRHl& +3bPE眄;|A4XֺB)U^|0i}&Fp!!D$HOPILmCO={ u! dFE}ƅDIJr53RCE4guQ8o$rpYTǬ]3VVdqCm/R21 $צ ӱ؅l{[p(C%AuM3!ʐm!V?8.*C ۾W!#ϠAbKP$QAc$0 |Isd}oQ.?fe xWMZ})( +FRr~Si?j\F + WBh9(`Ξn9>uJ#DOxw$^FN18/@&ڲR5XQ`r+hpZdXu(uQ$`j9 ʜ='PD6 ~렓d1#``U͐$SNJwH +\>hKԆ3^LDW.DY4M`G V9(u3wXCy4l-iyD=')E}9GND_/K!1ɰA)CV/t ^c Vo#a;F+Ez!þ:j`a/CC#H43q,0.hK,I(X-=B6)Ab܎ 3{GS ;^VDư{[9J+G1aScr(4h-3j[b;i2-Y=Y|m%VD/Sн )؅gLI@s4eMiM|F̀1̓f8iQYq&yQ`+;.( Jyhy֍)g ^A,GjR&yC,$;Ȕ @Dg-[:hbAjC &&OJ\θa&Ch4)V@EFrQ}$QzgnڇЈ>^1 ) \jipgH &DŋiB$ s:YBn>O\dmrl[f-1>!+}K6"ȓ+^S9'=ΞaW %MQv1*Z|Y:qQIM]>Dv2w$J>h^&E v.6kCI\ Z UW/E`b1n_AWڎrZ2k_!*~Aa!Z^?dD^ iye}ڔ7mflGW[Vx5 # +/TА 'DVBprZcŀdaɥ39έ"f>f8஢Ma(<" >Lɐբ{"Z[6rCyԯ#[$n|>ĢjLP +)RP +4ݕu:! R-ȏ▚Oڨl/2)8 PH99*KFȎ;ҹAd件7&jQ*kJ^TYڀz5&1z/`KϾhZ2o*(NwlDnbP mbN$`*JNۇپMkY'n@kUP}Nћ;a9S$fS۵k9 FWF3!wIG L )m# w9L482 + jKf\ 5\?aiizԨF*im`_BtD?0/˯Z|t9W^`P ѫTamlH- yz=-R܆B%HmL 1,q}s7 k Y$`_xV_>,|6 BLsYeS-˱`aXO5;H<7? Al\.;[^oE|\+PwʈHIQ2Qx?]h\7o\\(kM1am8敛VT`3q~xF%"4E<2&^F|bIhVl)UPOS(T@mϓŕu.y +J>zB9̏Մ:T2`(j(f" rOmYɑX)? +و[H;J6V +*ы*u,j!)Yi +(< ;a炐̨s0T0DHM/h< Z@ڈ"bý*>AR `bf\[~qJ#.OM݃N',:6| eHLFS` 5(xńmTeٮI>}_ Mv Igp2ʨƊLᢖ{qJXU>oA@h?Etl"Ipc`!'-l`:-FW,{x mݞAc#gAK +,aSLޝXv’o=p'] qCO x(΄kvR Qfl-&mک*9[[Lnӄy||0{8%,]l\sAƘzpYw1)gy 'r8?y9v^kxۊ$-F<?7$?heYG(d2,2*\>4븗$p{Dҩ9#"z"ӽWExϘ@|+ >s#oj#Is PqL|+gf2^sIS 9޲1EN/5sF4#-i\!c$tsϹھqΌhaa:݊θ)֜lYVebXzTaթNL16c &h4 |K- xjSzf[Y46,R%G_=M 'qg7ao6im+}݌?v~aijP]}\Q^ +0:?K;E1Zn{"7h{?bhp 㒂5 I,ء}P#\do@yg9 Kb0s[ylGIx  +Ўq2Jf;on57u巙+,ݽd(I>TjD, SN:v?W2Z 5@^Ya95 IO;R0hr +Lve 't +"G٠4/Q$re}z^|o=,rwР Lu2/,YK0eI`TzΟtL;y<R +}sAz&$DZnұeA-UtZ0 + ZQN3 ,u%m¶y>M OђWr7Cw Bjw# E.ϾR xGԘ3 ˅E)]9uA +(}_)"W*W jddQp#LW'ϳw拹H +jJg5NFZ,9 эXq [`/ :7T)Olآ@6`nĩjy?xqc%f v1IRch{2&}\@(ɉ^(fO2--}]{C uZ~~'bަ zdY/7` +=2Э#kkj|&,Mݢ9kr35cȽCM&rʸ2|g^.!ϻ"Twy[KJJH݅IzYzPJ}SJM)uk͝p1:6_AW4ë4FtP2Jg+t0?|y>{}A@f1S_&*}ɤZ}ʌC8ǡˁ=&fRK9p뿭ݗ+}nĸɆR짘fva͛@SP _ׄe]!HtoD(7 BqY-L]SynTߢb05!oez8.g +_ɕSR_+ʴ.g%KTݭ.;-jYrZjT$@EP~ejWA* +AIà3Pw瓨jEvwOn:?:?*\~Io}Ǘ{W= 3#ly7=8nx3"}5(t̵E2֑ XDQ`,Sˇt@sݳCMfPSw ;Կ#7őb&1u:ʸ2d8{>_f^SENYBi](*uRJ}S3\¢%1ޢ &A +_AYUn _ҫ`x\J0ܩƺ;1sSاn?lRW?R]Y9eA_&*I26 +U +~$Rq].f!@Ļmwu"E][_m 7R_g:Y%[ q@ %bӍh Pb@cʠDa" E.o̍(t [nD9t_6|ՒBWxer1ERy& UdLCmP3ì7{%3`vt@F>1[v7 cU +jcT9"-j?6K-z֣~~ksxDz#B㓣kw5ݢë`\쓹E]-E;O]J}.@)wRu O-z8/΋8ћՁq^̕8ػg/yȗ\@Z֥u[օgZwc n]ZH(KJԕ*"lUD0hL]*ua4<8đ| +³rPɤn׻Ֆbn1<Tq2I~(@J}'O !3i녒E׆)3oG>"7WFNAX|9ƐSWy UAn Lhc{С>,] ϕnc0)5ٟ3>]ߥW9_H"\` %Tl%^P!?s7!o E|; UH+{; ̵E\/;@sXCz59߻z'y; 0%墫wk:$43 +{+pwl8kHכ Dl.mDD6nD̊ AyNl܈em\ޒ +8| +' $rq5("ֈשi~"$)=iZIQͮ, r0)7sP7 +FIx + &*`Ͼ*#Z%+#mHyXJXxM=uE~PMH-G~<3ElIϳƄ(3=8gNȵv xBa;@ֵBg  +]9&CchK>$2-^Jg<nqjq@m]B?hse:e-<'uS3C/:zpWȖV?w +i3 +!Mk[zP Dc9[ReudDbyV_'c ٖ'rH +Λ_NJB*FKW(Y4F>vOHހKSd}pV4F>^Bk?mU~ݭxFrM$etT x ȕr5h9gȵAi10N OKb +)T)=FV4x?mJIW$OxgoFNyƳ +*Џ) -"K@ <*>@#a jayv-@JAo?O[j8 0Dϯ)?Z)^<X9+ 若+Q*;Xҙg,+o0jy.հR$feUmg{n1DA%r%y:h̤p`o8weMH(<"0U1T(vK+dT_Ɉ]+d>J<EL* E _y;;#wF4m}xyB? +K:1VkQXY]DL +"_UB +%r`1!HN{:WVXB_;` -E $]jS%]' T}bd?m:b)ȳQ6lb=>j$Z}*2X}1q|?<#?1N$ 6RaQB +R#̊L f/赝9}@I:=%g󭰍 + U`x`\0zJ:(.FO66LO1fqI!NO!|ŴNJ-^% }Mfv%ft©4،, +Lgw1WI:|\kFk!ƮllfA)x_$Ag +VXnS[3gLAAYrs04̴<@e?? + ,p"p%B+jآScTR)Aw,!?ZT4$";XgH+KM?%ˁ}(}Ⱦ7R a\`=ߦƿM .FuQ-$CXP#$ +@soxUQ:Btwݝ'؈=&."dgχ'yЍ`ܿE&|$ِ`,8z`WHr뺨e#Ux6%q71z ; 'ayf6DCLAřLJaà*.zLmX? \w??xK("-x}5 ixګJA‘f>ڇH&dCaX%hșҠJ%f5#b6qєS27T[,6&Nr J` !8(ıWP%YI +q4]{44Թ{ke`ŹXb̖\ܪۡ-CG#G +NʎAlsJ3: $"qGt%`@) 16k"%.>o-#3.1{naoy'#*H"_qޟ4FߴFBϝNW;bXV` @S/oȷ/Ov2 +nNr`^F_=RWS K4V"cKBz%Jkȗ? }or|!>NzPSG |a@hq,JdI<3F)xim >NdTN %4V蠄 m)' +//+\yv25idRP2>OHVWԧ{y3l=yz( BPg#{ּ\ D")1l6'x)RDR!.ޟ.`t/Pl*Z JrtM}U)ɕTu*hBFNA Ör2Boب~tw)(8һD^)|o<ST!O[nRJ{Z1â!TY\}~X|ar^͟_@͛H(+ ȓC絅"Q +$Wp +qU7MHfK sTqΤKBMtlH)XB?OY2)(PS?R/ .ˮ E"H +z:)}oC@$ y7B3VGlHI"(I }Yl.9b")H'1d[CWӄ"}JNA;NAr +.jթ3uDv)u[J])ῲ]EY蠄<$X +Ώ[j=l)g4ts:0?~_8G +zh 팥`y2-仑:KA~_x +^u_(U +z%{ 7R/LhALtS5veߑY3)8 ->_?RW_0CCL]zta"&{+ y5n#ZBB?t5BM3BE9S>REaP- `-eԄ" fUAT0L,`B{o,üBr QU,oȤȄ WBx[`^!/a#6T7ڊ7in޺R;ي +3 {-(6P)c ʶ>< VFD+ Td0Y3H>ˊ@/GNs7#Z ~Aj,/<eK3N=!]9e3ayf3 qbC: FaQe\x`ε}JE"d>H?KNR56A8YlC KwIi+@$T_y;h2ցJ+q踟Ɔ]R9ۙ *tPQ +88 ̷股tG]UPQ Je`t?Rա)ּFj%v v0)jF kP&mPq^7R7<]œy"I)$@d(~'G 9p ?!D ud- 3%Ǚ D&6ɖVϤ}Pޗ[/W|!i\QQio!@q(z-cgfx-+6 ͲW>J; #ss,?wg'G7~JkqvK w_Z禭dźvZI a[͵Dx6 ć]Фp&b"  {ul ܞ:hafǔ rb˳L9^cפ4n"L-/ ,<ɴW=|kD$ O{ D:.t2۟L>D}1TbzyrJ @Ux՝iJ͛T:|[3c:&~[l? gϚ"x&dcyT&պ͛ARɎ[$Y bPvL٫ZyBxHJJow]ДgcnIg *NR3OC0F-O"P $ё'&z$yTAC]'1r +:,"T&(Ɲ aW,;״$;/ۆiaN\ Hנj'o|9Sp\5sy&-'d~м#LQ1"@Ġm2S`%%W$+g:xCsJG ޯ*cьLGK\h@._mofL}(АX DH;v):^!Eq3Iv{*ίpva;ig0 (bkP6V?́Lx_Ap!Dn\\<# W!*`A)QI_4B_N"".$a$c+ae }ycm]n}W}D>ta˄"M)1Yw%!Sd$&##Wp#L<5t!R9 ֖,z%|Cޟ]IwOB_@z*h7R?47|4xtP]u{.x)8SgXބ"I=#Y)R޸;SUWS#\S@z]Xd%Hr } +V!ifBCE,xt*)u278.PCtP ƿ͉AXc1<džk +qCIb6.+5/JOz[:!3XlġӥJLBe@=##E1 +T_"h ƻq%l,%0 ,lgOKȑuVJx+Wud<;`>6%4vX}xvKzr|ݼm9.j> +$`DJx3g<x3YCZdEsE-u*u Ba)&˷"hgQM"`:Cioi illLҏ3P&>з eY;XxrVG,f$RJs6MdsSX'ƃ|W3!-/;{DBgFh8YӀZJ'cqHQI huTM @s9ȳOh57qBhƗ8dEm<XG>T'dwȶ\=SA1anN1.qf<7R7FzB_`QK +\5$*!2 "HeH0&FFB00$2*E0 bDSNAk5|YȡȢQOBEdkE&D{_Z"\wj[6AvҤs΍69iYI00NZP N$Rȷּv&6= =XC8/EZ>dѹxc9R?f`)O]EQqV9m1%l͖kNse2fڸ2K;%1@OFnri~CjAm<p 蔣 ^ddPʉ<7E_ث.ߢ , q1oNYe /oAsͮ|p9a +;3lxJ]ep,'!&uL]8N>:+Kl'JbYo\fy;ъMKS1 Ȁ%5=DLdBT`l[\b1w&ԋ,f89A;R"4W >X ͖E8[M)=źHo%9l1'IDzۿ yzfVF?S/@|,O_͞Xiƛi @{W#pS+2/V +$yF԰u*K22{,"0Vέ> T ꓽ+ 3 +txL\je7I=[3c +k̊o7$Ϭ/g؀SZu:ǗX/EdPآËdM9 + PQ xHgA?e xErZt.GS2 lt|E qit3F.D=$F*glGxSzze j)ySG?S A$ l"UAm;H.Z ""޻p޼ IdWJ 7# ѐJ,%i?s]7\$& PW'b Puvo@LCv.KGF +_f(W.m"XMzB[Na%?(fVcT+- 2Z@ +ǩ[P?%Nyf%7_.?%rhТUXSUסR"x׃|Fo}^VbJ8 jЈ(؎d_bs!SlzT'Nה7䴛fV 6ȆCgg*w0IqEBNAR*U⨢Xdxm7ũI;r >MͶtq JQ?LznwZ>+͘kL*g`KC:ދէɄ`Lon7+@kAf#E\  cBc6FN8NCh08[ ˨r)R-Dccπ+v)A{sV< +ҶysK%׃xn)΀Sqr7xuy,M/n`;MS%%E&H&fsS6 0mZ<kJ?Q^xŠh3u;8\ )Ye[SIX%mߺ+o> A$vP`6^Nfp&9c}}7/_fjbJL4owp23B+ywzO3Uol/s'5ȡxѭ$p*%`ʉl24qUjلs\IJYpS ix0XnfY1;!h&>x"V^788Nǘ ~;/ Ϸwҍ$VĠ,L(Tya&G488ȓcXUP7ЃR#5Mলd„oO#<7?mkGnE}{R>QhlF }Pjݐ*! z?*.}٪?xD݃_vz9 %N_Gqp fxyѷcw\P3,1`E!(ڋkוO@~ M꽍aGGQzdz= \Nr*EY@AB3VNVw%jO)-C]F]H4aK*A +oċ*SLZ)=c}g t$֑e.F5鞳R+5nF%<Aq捠XsAKMhrwfKm!cgl1qc%lu6Rtۑ?dW!NVmR5Zg^GEٔд76r1Иgh#Sk 34?ipЬ{ i(&hB{N54'iׇC3=o'h MH4hh*Р @M 'hL~&hNw `04 MMF4td# \CRu JC Cs. pBghqihVC)L =4[@MCԆ߉C#h8;Aczhk*'hVu>4M3 4ƲK4ECokB(ah^7q4F6yjdiZmˆ'mKD1NQlO*%SD.w{iU?5SG}{%U"`Y>2|Lv\з=j TSHHRlunA_W+́lOyCt "'+hePI#ql5-kybЊ@x]:Du gEyDc֋?_Muc[u*ձ#' f6>&BTz2: ٹ\ZN0ɋ| = V/xz66R%`OV`:Q}ֹR/< l"p8Œ3J"%؊5#bW”č7by=6,IV $-&sʛ%'OIz[p0@ؒ٣n #׮]%w _T.FTgr%o6lZ4o.טRߨSU4MLe1F˯f*JѮB$fe6@+a/O܃ ʘ6"E)"hvȖ=2iq_d8N* !l +`2]Ŵ|gsD-NF`{yLNDR|IQcȃ岃>[@}EE-ޤBr*SbCyWQ8ho#(gkX3`7I@FafrV97행 L  np#ZH m!DE|*SZB_q)Le+aӿBT%K i4dTԏT&1 *Ll-!}H {?XȥoBB +3f| G9k\nNrvϣ`jo3ߌϗ}O6( U`FEm$F,n ƱrRi,#QTKS)DmR,(ISȎdY}ot?D(eFRz* +a3-NqA \'7ÿ͗*ZntO&D*$_K(MhU5}|C% Nbg4ˬу (cGcfTK8ǴC,+ QA9R8cg-KUXS FALվU~@|UL"A?.7`g_AhbBh"&XWs40]:IL #~ǜ~Yi(Lr!2u{mCU\YX٧0bݹc?+79º/t‚,}P-Lʁ)݊H'aC^߆a6^!.xeU +u1,ζIxqU2`Dye/Fjju(mXܷ &#vI}l:&\k4ZJe5йP@mWxc&TJQ_tύ%o!L%'js#)[|Rr˖讁C&j`6;.yA{R_|,'rZZ_p2u KENaX *{VU|'Dp)hKI]9hxH?!v2{"8 .e׊ ~{DnC .ǧ=D"/vl9(RQ.$dH=6v&Y9]/]Md3R a#kiqUO㲻›BԲMCQUo|sK='\- 5no?^ڤM+A@Cƾuj( INnP$VlvG _:f94eL>bE1 +5y1"p1S{J#\C_ w`:tP/ Ⅽ ȶ.r _BѩZh2^GxZ7?`X.-}C0ϞIAKh~Є wShd)Uq#L + (Ŷ5{ 3Wt]I_u7V5|q\nvKRo.?*P[ ~i״mh_y.W~k0͊Kwv3cRmvYAJ<4mE(v2+Jl]!Q:q0#\L--qi1m6H8E΁rk{R /ږàM'ljyC=̲EyBѧ- >쥮IEA)YI~5wZ7*F# LJsbbE83 +dE`ꥊ&&|&?pϳ~,ILɳ.Eg7a+G-x19<Ж,Vf:)[X{:'o[L4G:O{ȱl@k?&I˩]eG?B%+?+l9vNCcj M"HF&BpVRK:sZJ$6O= c7OF x4,smg1?~;Ю'*=PtaZΰ0X.hJQIoݻVJZnd:ˆG"֏oh&Bqv9~#cߠXt&acl@llqjsWmncN)dCkVd<@c{:rm|:m{v[cLO\Q>mF +̷)V~%BXY%VS7[ifcti^(HLT +(ܫfcc؞FVdF.Ft歅XRk3Z6>#E m9զ::`q2g[X]db+1\0K-)9Tls-QB)˺w*˛rP\FAl&uRmN"SA +8M)-n`f i'" +L q@vAN{qm394t9Oہ֮kܶio5B e.'b)nIm$KɯyeZ:.Fî|\W Hnvypԭ   ZJzhu1Q.˳V),\~2p}KHpӖfo JbHr#(˾3zO@qi!/C~Rf>^mSp<)_j7jٓ'hdd`܁wz0TSd]MHl]/?=^V%?>RmWUz)^6술8ߒF<U?@P ᄀq N 95R4*\ + +T8Ǥ0&7tXQz$H`88"f\$#YP2 ;9[]bi7ݱPBF f}H|y#k{!vZ᚝\Aީ3 &M^[`I9E>Ǟ>ha=H wY|C +-3rGG +;H5}1K(D>K ύˌ888ʗ.ܸNz3t IUKY"8_`W3^ЭeCˑ=hم8? +ZǓ&[^,"܇Pt5Sؽ "s ,g-CC,w}AXv5b`k;06_ oI"BR,J?~6~̍8ȗĝKh.("Sz:Uq@TMb1{VYY|'5U |G_VO%ٜ,fK:Ɋ]FK^P)9V X`1OV1H>}@&}_ϥ3y-iNI%ntnV;wc .,.Jӏ=z0um'9Uf#߼lۻ/KWQ$S>E'u) +,l^4'174v\@a)g? ֪lW)KF/3f#_pCǝ\#]I0:COOJ +$L)][u]'NpMs,ne+ԹgRˆ6RQK 'ff0mY5\NN l0C#-<[DY!73AU-BLr[ty! + ,`f*[;x<, 8n|AEuc бBT$n :`Η07U,$b cI4n9`e sfbЉIaމ'Ёx|]9p)u/̨_,._ȉ;'0. B,Cuӗ{nxJ7̰}qD/qj% ]{p$E܆k᩼#;%Sc xPP;>Yy1YXIQIMyzHS =fb*¬4F>bRtY>ѵ¯RW5&GbR^ FcQ^M:,BDnJXewB9@%,btW(av9ϋY18anSĻ>9.HTŬ`b2V2}>ޭ)juQߗ0rmɹSAS0 E/<\ALRi ƈu0 Qa`Z/b/|}|#EZ@‰^aI`"mC|]!eXOrOؗϱw߼^vד0-AсR6m&s%ݒ8gUcBMv]Xw>/vF%x&8z[TFEWf 퇔E +>y+7#_8hN|x1'priV@~vY 1<4,آ6^n牥A#Yl7Ι 1i\2uNswzCׁaRȏd'7v=:%GϽ49l`_C.,۹ W66?i /7sT8).'.5i7-#a|4~jT8`+hzG#C8&MB] мuUhCk^yvAxQCrxU (YFW f$Jf@xP;#'z#j^y LbzO>L+1AgsQ,T6F+Rm=h<>΃k@gH7m uYY r-Wu'%ݵ ܂ v`Z*֐*KաZz'-J@/0;.-ڮr ug= +iDO'敉/1T9&|XxwF>kwز>`:LԻx/^-L4ߚzޟDJ*&^%-}!.< Q#>)#F&jxbK+;Vz%MzKuΎ`cfJd1lJ =+b'RM~_C >RA4꒷t[4f\mv~!ɅrW)UQ eʌbijœ_/u[`3x\喢aFؚ]rj/]RSoI!;P  2GRI$gI;&>4bygdg.bnsWyD&~i~{\E6~ܺdW'3 ^D=rbkA, d̘ cH}9} mޞGBsllon2G~2!h{d_3Qˤ(o7+; >޳Ց Hbl fA)U*c/ +fR&ofǝ~Y Ԥu-K]+4B,esV?P6 TBv5&$m0\X+;e1\:YC*&%6S3ثhGPݠ1XJ) _vjL]VH'хڵR)J`1l(ByŎvf/[' [szڧΉk4Dȕ;:(F n)\ ڒǀOq2-,=55`P(>A3>ɪ?T$U]n'!AzYV$;=g.0-'B(`XqۗdeגėY9rGǫW` c@2. CjӲ(>nB%D#-F^Λu!XgCI晛a/Iw&B('6Nɭ|lT MHǀE  /{Iʺ4`7!dE ҤmKB |#_cg}=U2jzCa7d|dJN[xXeؿh&:dj@XNN;<9/S9H]8/_F@Gr'EJ~u5SlyszB5CC_*.aK; B<@崌M:6 ]Y̍(BQBCPPQvbUq>2o@s0Rd˦ QJ{ o(}K^SF.f)?ZEE`@}0Ek'4d)S^0l7/"v۽'sƛ6c>CU'HO`j'vi0SEnk58hm\GF %7œ)xǂb]DwJHÚFHL2ctDhA~g!krV|Y"[lL>I$}Dܽ"R%kj"yh%{&V?7(g$T~Qt井ʼn^ɵ <Ddy%{6q4 vV' +5xl沆f tGyʏm]BI8m +foA3 1Vhn,s +9f4]?3cgxۥ>"n>Ms̵|+ 6)`*W#Y!od~f81Ph{e|fzRhh&Sgq$Iz3C04̦DhvkDŽfN~-4EobEaw>,gxM%{)&ٹWkh h>sXBjs!`̤ 4¼,r ygFSfO1h$ lcO 1*Ws!.f, V]HxYjx>IdWDk)Q*i?͘;wh?w a~f(07R㊩+XǗ +cڶ癸Rv@2UoN|$hHq6yh+؂UpmM$BOFD`Uƪ@{u(6^FCA\':/E4˜FI3azsK*(2đ;,<݋#$_|jv dvT6_0QiHzuLZ9(#xVjmeV/ fC)6Q-2ލ3bq[1l:2y!3x\N`iy.ؘt: o1)>pwAKDlZFD遬J2Lz([ a5QF"f~b֐f:4[~bzz@ǎ,|p= 0Yiyn.gJKHK$40ߎ\MgĐ/! Na/(u^Rs,6 RiVeWg^Κz'79Q}#en OW/DsP3XȮ|*/#;!00MȀ,KA$e;p~ ˧ҫ_J,!;XtlݦoByPԓRD5) enEK#@܀E*B +Ce&vl9S(7& }CQgYkQWve!Bs6n7,qu|^rluqa| XlwE`_~J(tvH6Є_@,@&kG*tzV"#Iy[maT2/'gAhrMr`PE jGh-<ޥ_13~c2r-`,@ d(ÖStxا +II*·Ӓ9N5;@9\&p+ܾڵ%AAX,ʴ.bmX}Nʤ}ě eڎ(Cbx08 +)[N9?b*EW¥r!ݏaѡY@4-!#F0<-h=kar}lϙ)Lݞ 9XapN3!Fbz\|J`sA}s[M*Xd?E eG{tK-"#nx>g<>|cGBᘇhq/PSYa5wBr82d3LIw$;'_l|3{q+V0f|et0˖ +s*MIGcS#dlm4y.39`\4쯄H{zyQ+Ag>խt:!wbG9a8E eVҒU}O +YFn&dHU yޞKq8M >%5u&n1 +&yћYjhUhpN6P/q +)N!e#^kW[2At}/P +{?CYv9,VQQ1~as 3Y!lMi)O@;oNHNugCl~=e92ʝ,%?,"Y +兯 @+6eZE:rvP›~S>fj]°oA/1b.W:2̜Y'4F#i3P?fAzf;9 ){ ^#]dN'?"8u8 +`ö2q8E-ë%@-!<̪2wX}O؅g^KS"/Z:Diz P0( ߚgm<7"%Buf jbj$HnhrCu~З Lcy(+; + +v)`yQ;j ]92k "ʃJHn(qzyOqcS25H3靫s 37},2@J GF 0.xOfG/{@7%5fwGȞH.3ɊlIujO3 l䬽S{3햧5 )_[N-A1wݠz';k]9 [FMD؈3TMlAW/?8GB(!(? wsb#r I*5͏TΉ4-0H;j'`m +O,;6ޘYB|%md`#NXx@v9UQN^|5!X 49h~bD +xV\$/$<Ɩ ﹴ9k@2c꺮Ztˁ/3WumP}$12R𿾨šH_{ 5jj<Ƨ}D5 +IMn- +[\kOiBص[۟w;Y8{rKB_IPdrT})@p1n yy?,ϑ8ЌK+O<"[g\Ҟ)hv1 S2c]_'f囀 +*1F}=pkIEy\5Z +u?F*.hAN( rPEr)q[$ .u]dyS>urZj 'AUW% r^]~3;%1aJd o˖ 9ޠXfCS<+]γ*AD!zZ4FC: S7bdހJAGȲyYe(CT]$ZHfDL5/έT\SD !$30J҉@VumD~UdϷp{NaэV3[ -_Be%]aC--M7*,>!5 sE~[)љ>oq1;}#{kQ[`9H}$AIV4_Eu-Uv&7=ܪt԰Uƶ_?$dkRnafda&GF# r,*7GmaVǷQF&\q"wUI1{yLKzTO9ꁾz+z_xxgk(V[5%쿌=\N)wtH#)ayt7*ȏs=GAG5o_gqc92L۰Ktpg⠴J68j auei;%٩6ۖl`MO#R.S{h)K;}T?NB:(m$*m1&^ = 1TW\.ou +K}!e棹>ۦ!jG -^@*'DkSzqqQ7i$+مhIUnJq7lEa)[[jI߰b6y˯cbnTK`w9(#MIc; V$5z ssh0.([' Zumݞ\o VS%EAS3mkO2&Tc!8٩yG~q"_9{&@t\Y7e+G۳W ^K2++_j :3[y\%rк0 ܠCea*_1΍;"Za'<,B4~Z+kč'Kq 㡧zdW|fM4+R.eށ{;bU^?Їtt6bAfoУ$E~͘ j'O`,aI+ n,tcZ%yJ3X'ȷΩHЉR^Go4Wb޾CY]j6'Ms78©r3 +g!c͞Azr}īLYTq3z1[ f8vUpE\IAQ!OcY^Q(?XcN<*|D4ٚ4t+&s +"reHw iشqfݢu{M0y\?a o֪|q~(?~7~gwv'f/iGs 9w)sw\&RCso5+?`%RhRMЛ,_YD|V$}$ɑf=-p&152"Ж-ls_C\K4EgYdweg7eH{Jב`- 0-H +GӊwÑ67^#)O|BwBoYh 8HʜDu#h3i%VAC?+DӢ]+.xsk)׷[$%D|$W0%'Nv@];>22i$]NX"7W|$e  `hkk"y`I#)` PuI e0S6I9gؑ#=822b}H#)"E0/2 +V-Rk؛sV0l(J⍴I(V$8q:<@\$GR)6G_d| ;+vLp"2c|瞄ԃ +̏x{S= ;uk&_H +岒#)flfn-ϕGRY~R$xvER_ r[{$PֆX̑!HTVdGR簕bnRכ|¥S:|1MlAx+Q,e7 \GR}5dʏUzt$ErvRً/w+'W$eDvkI勤ĵzfPّv}i.ĭP BO0^H +].˴<R6.B/v '#) -yeH$z=3 ^g:r "|||cz|xYJ/t @}ӟ{(]{ͻ4c)n[&~Qgx)*@ ^hZҖ2i2s*Bu[B̿m/4PY;Mn.ږؿKj^;CfF^h4/]yS\&bp VINgp-~=0q<^Y+Ӛ.Q7% , BG9/p^vRDWѹg +KLΝ:HZJsz^ +cAFt;RC8/+~xn,̜"!g OI!ة#^D1 j?VOe\8K5.fHz#,r[V +,+%d)0VbV)Z}Mt/\YTA#B]/N[<#->bXj2U@^bޭbVVw/{o[\wRnGsRn8@U#BWfr.ܪST )B=-h[4ܺ8ax-oj,Vcs2U +*l࠲6L(2{W8$JexgEy]8bW#SƯݦfnX+C)>:~E5ܓn:Dί-A>uSm6p 2}a؁C4ifK!4yES<'+Q݁n ӑK.-43ہZ]W&r&~G7@[P3|-3b +*5`nYD{B6+ 06eƒw_1] /W[kOU%_Y~5>6:0$H֒^fBbSyc#b}D cj`ctD6jiLlw ǭ@Ik~>Vhs z +E}_(e+1*-U+{1(9335QVQ8k%S+eGUu4ñol*Jа̐_`GuJy Z.T%ulDʞVWY# +j"Dۅx[FgI s@.[KeL֡1|}-VSu&#Z'jr]ֹf:(^O9ڄ7d KfRYd0HeN Uؕghxu<5fe|_$>řeh}r敾g6\AS]ŰY"![{+N@ӟwyQgL|v|;O0SuV^4zEL +=]/Ոrv>@Υ;&F1f:Pjz|mEiPF`M'*ߋ)70oN0#} 1 bSbYFVQ4.(u9H4~gNBqcph8YI0USL;D{<]H6=mpG`hn +Y_VbQ3ߺ_w 뻳$" `B,~Jh H-:0gO:EE-}jtz(bbߐ4٫ū mWF R95L\FV%4^)|ɏtb100IlYt4T mK )K ?Lh#g;%lWB2Lhg5Њ4Gl+P|01"\2i⿣n0s^:P DCGZPA ~-#iLh^΃mi^xQB*ZvS!JGp}#A7*j6^Zp"_d [h:!PWNT!(ڸ0ΣgC0 FvqdJQp(-X)+6LɛoH!Mиx&4XgH%4"UFTBHLhmJbu"荫[A SmxnĄj '8: +A +k=7u4v8m D >LhH%?[F'}UPBkmONJ 1zvxڹBEy%4`,EFtѰXe:Z *QoDrmyTKi q~J>ŕhQslZh@Mٮ&D^+?B)IMy$8 )U咋 - q @wqX0"H^~NS 걳Y6`猰?KV( ~4X=pe7ݼ7.cC&3PN_8,!AD_^B>4 pW#1k ^*R%Cc$a4Mfӓ<^/p拐͙&goevo$OjռWws0a^s;׵T<^9.1cczB:r8Qկn.v[ +f[.xVzq4k [%mEc+L&7//ޢl+VRXǸ(w0DZpd*+ޒz^{- ʍCa^ٚam|pwaY]pruiѹa eG"0ۜBJMٟ宫Kx$Wor.90ٲ+bb\$tl9# /{?X~kkjqv4dyQOZJL(e=PڄQkOڛOJU/@iq"+Xl45muM9;n!(Q 𝢓. -/5P=9ԤHލfR{#E&ۊBF}h-d,j$v+޹zZ.@mS5;(Չulp5QL_.H}N:f5*F}!rR8ILO>b32ᶓcr?>[L|7S;{,|z8_27$WXYNBTBwJdƞe +hɣ(rع(/gtT=pSMb,YpVEQt`l CLitu/|Ivtyh;:ڡ~-#2!t2QjG4^*V+VlrdQwweA1?!%$Wj|T$qj( *FT!`Hu4\V2aT'< >PQ,\]p:CA54 B4Rb)!+A$6ZelX4 (E`B s&uJ᫓b"6D/ŀ TS2R6@a4:Vf28&#$M(j %"ɰL3UUNH-PZG5}8 +r Uav(Zar(DcTlW\Ԃ2 FPʏ~h0 :а!NdG"QPa :Bʀm"\8#p WTxʉ6 M7So.夙zsDbg2IВ<2{6>bqa6Nc%J i8m p<lZ:rٰ, › #O+- cb(fa'8hG)&0n,VA 0Izx ap> a`T%3d> ⃵ +RRRA2 3pa4O+YNBbDa,Q%IH %"U@P!"e5q$EuPA5Bv[$f>ݥju;Kگ1uӟ;߳{٣\A1uϷGnoۥoϪ߳.#+ç]4o +_2g >?wЍ|qGfq7m~~Q*|ُkzv;R::NF()W9.{/~>_"WF}׷H?~K=~);_^u#yzwRePS]u;n.#-l^~EvwuٿGpӭo?!|wU +7Ul焫-ލݩ=7Ǟ2UIpB B(HS`A@ + C1B!<ߺP0`<c筇x`1tD]f`P\ 2t!V_aޑZ"zd%ɳ˗0$0XSBRq.A<%H$ÍKݧT7\)!dYdLD3myOx&5/)AA#Bjol$Z$ T$ss ZӣDMܿ# x+袉 Q(&-Yk.T+& -'*ȩ{ ^ڮ^&)dCbnBu* OEڹcÙ#̠J6R! 'J?eB>J#+#J?.-f¥`ص h}`P9>A\ҺG +zKZ,aSA[rQi[*ƾY =VPg;uK]^^A&[kJĎo1U {QsDل]J1``pn- G(f `'xE]+Kd-S  ˬe`nCE ^(kj H6e)ԥJvR +^ IѲ}VF㘙YidZ,7пޭ 1fߣE +mdS3և-C(&h,e*S<2X?!V$wEԕt%r手>IU#ƬV/J{D(5䨈״9Fq zZU2K,! +T }**323-DŽLõL_ qԸ5TmK.DQ$WMvv\ $FuiX8ƽsSDN|ͼovQmn2ăom&e@`EQɜGdZ˲85 ]C .qrxQ&*+Ȁ 4gNXm݀U.(,X_'l7&ZgqwoZ@mdL2  ݝJ?Voяehܝt}#BT~*V;C~ioN~?JvOI/$ùeEJW}FCvY\c{=ۯtq78 o^fG +RnjdO)O#5w36*5nF?;W6֊㮼q[wՓXv/ٷND(Y?p\k܆ɳNۍrt0.!12 v)PĸWjQn@T˖"iKxgu4L+dǏ,f\%I3#D榤>+k{$}_D#aaxB]GD~-u1S(:k\+mXDGgڇPOЊYXS;j0&&Dew@qFuZ@\h%ze'þBw;˂`V}T qTs CZ%LGf݁7LJRuPQ +4@EbԦHجޑ4*UX.fEE۔B3JO[!e2t ^HaI8\Q<>CA1A;q)<mE@d*H3E-'% 堂D#&Ȍ;1A +Gߪ,bPG8p*Ԋijƕ)#4f1e==Ԏȅ/c +h|^8!.!2QjvwwqUf=@@7݃ +)O >9 no ,QB8+D!vLFGr0%,͛$xRIj /&p!w.Xe);Ob S@E7NE\4DwLhbazd2̆s8DlCxh"" IL kB<ۄ8cCv.5XaPnͰj'!*XvBST'LsҴ-%YXޣ%$M(.:E,q8gQcAjXC\ƩKx N +*՜`p;B'D /:S_ARn#@J( Oa`s:ZEk24I@r-#7&,m Xt~C J_%?t! HABjĊn #kMP¡bƉK^#5Ty&GʡgNiBQ<:31H 1B4T]1@@I p]y1c$|8(CV@w)2a3H,TSaYhl"L04b#MD6Q89L9g;S* %5@BtrՖoE%c%LT`DTN~G\"TGET +H1:^ PEQL䥕#<**LLW'8J.(U%Kn'SD8>1Jj a!((bCvDP6ŇES - iդ*@y7\7ā +SAUSNU +THYCgT#zF"1"HƬR+4_ e* + +΄!ElTR0$ańTr TG5H6 ,*2 rl"r #T*5BSAXpG,I8If3B\} SAƌ%(,%Cxxt`= CW9r) 1LITDe H01x ,8SY0 (7.!/\0 WGϻb)+Df$L'Yms+ܞ(r61 mD46q`nG$k$2*K/Y}tI;B"?P qHx8D ?Zta>,1E rAMKKv 4D +Dг$wڄ;bʚ)9)a&#Tc0+!/D5'"5i$oBT)UF8LXFQ5onf$G 0)KΆaFR$t$FaJqMlpAFj]4j% &P:+jíR c|i08 t)Ԙ +RL:"tD"ф&P"MIC6:)^d&Ck ǐ)@#4:=F 'kwU)( Glaj' @jCxLў'0:CMI2<UD'Կ^FDWijC=W:%h(.#Oa娠-XH+}BUErdm, +`- 3 2sSap +Ft"F({PB 2EV2JL`'Ta9`s_,ݰʗdyc $6%s _BQ` 3_ejV[ $5 lt/mjRJ<ư/2܊κpнZ1k1M<SR1N8\ +ѢE +U&B71XIʥva j(04]Ge]r$t +C&"!7!)ИC1/hۈKKFJvXbDU1ɚ$Y<"9c/l]:bc`(i*4rǸ_VBBEjṕUSCŌvv2k1J Nb09VrvX(Dut1 VkPIEvd"3.wօ1ex;`+2S*LyI'%6s;7(L7.Ehװ= Sq1%P8/Gc:SR9WTQ!!*(ec蔘r*xDWSI}eᦆ{?*RDCvU$CoH=G?d$P(RwHy|ʌ%|YxD,⫉jE]f U1D=Ley Q[BD7C +h\AC8Q #.HYȪ :*|NȔeh."}bt,qnԑA D\Ќ Qdyh@>$.2O67+(777("5sH<8un f?5,$BԈiy=r8L@VsQ$"{I(*"("İc0HrxYH f9ߖ{ 2 L|ZT^&S ' ,殈YSP30V8U/TT D*b5H]egBiF9M(5!2VQV")"i\6R{ L(ٸ4ԐDCtd4lD0C$L5452T:*lnӹSgvBDV%4 sdxrX A_ *#T$wSCbb8D*X4@R }CByP?Uiy>DP#UoNjD3Zt3ȩ%BVVaq#$(2!H +W\D̜:#QDG8IhU:|BLZ[BLLjŏ4ʱhdA-db$"I=ω"BY +XZ_0!w'.U#BPDoXP%R&}PJ@Ck 7!Iy*0"!ʠcH;Z P%6H rW T%لT)Y#>A!a?(ܲIjefej)bDBՋCIdAX24 + +U$LJr mB8'P5$xƧ +vy13-x%@4=Eˁ6EM~D׼w&B{y8LoV!fL )B:rKN5V 66 ? +a)AKib"K*JPj2 &iHO3bfE SU, e.EĘjPf-Phn)(( u**3#QA%A# GT> S\#*LRmd)zb$DnGԄ Q0MHXJ) ؊b1!S= d^"sJHaj4H0 +mb MYհ&Ń:¥!,FGÐ @PbIԐX%bXav1 }ɄP4Ef !a_ j@n!J,J":,> 2Sl,+Dٵ +4tJb! dϹG' CiHuy +'6iaʞ/,dgBrTLULD_BUUgF2qP@xAG.$%uۤK)V +U&uH^/)1#sZABG&CĆDBj*՟ +au<ÌUa+8r$|b+n"9n0VM*Kuxp$jf!8N|V +ybWCdfhWՋ&rc=YބP*!mO(,UC5` K7Ȫ#YqNᐯBّeJj̽ GB!ςyRh!e#h#YbI 51gD8Cjp8jQK[`PĔ<$G""zӶS>QakbLrD0L֎bEvaRThcrZ2AL9}pb~&FBa!܆I 3p.N$1!7N(Tr,pe1!䤆4” +X)?ńQ(T3TB8>8qy&3W65"%US ("xp7K0tJ DU)643ԃxJNjH`8!6ła+0IJ%HFx\GeҐwE-A@/ jpS یDHPLū.S/ʁ(jL2O8YF(gL9AaWaU*F,&SNHmn0Ɛ(~瑢&d,CE|qFa)CV< S7y c))mLLMkж_ .oM7CSgF@n5ʌIF8T"ZIT iK޻bf$u Ux"\%Su:S'$'pH 16nn;@OTogIkՁ +鐬A)T ¼Q 1 U3Q3T"CGÈ5"Q4=SbEʛ|0>QH +"zF9jDk;#{Hqa<*" +/afF^ރ8paHe@XɂO RG^"DD r4I6"0$VP+ -IF(~3*]E䦙Q **"xB(`*(>ʧ? #yC$UHXi mKHUW 2*<r%9Fa^ɛ&c +)3xT؈*"d +ocJH\) -S?[YgHfZ94}Ph[~*(zUi5jF` TpxU2ۊ 2-> +Gi/X=?*|ppI#JJC$-~y)μ{0Eh\BK""n\h{`ֿŋ*c`)?ғ8TB㖅QI(}*1x_ q`FjCph54Jh{g2yN?a1[*nע%tjȉr"JPQWmP (p*؄c.JޡPOАTUhkk1f+&:T8<$ }SvhCJE0Db$Q4T,IESZ[l_fV \qܠq;%4cè NBl*PJ$@g$qjnO=rI1Q?z"{L y 03y8].JH) +ʺh٧ٔ4GB:1#' ̲9.&S8[Ak5L] UؠGbÄ"A8Mbh [QQ34aFOGucMQH>, ## 9DAE @˰s'+7f&!6҉U)# +I "E+$S$bNL;)j"J:%%_43rVp;P/ 'fR74шvq-m[B2d-*qKɨ&DA' `Ҁ Á|p$0 +gjb8@ (ŨXH@7U/JbBo4\t0w6ir!cR]Wht.~6"P(k3U4.@Y~}=^Y^#(Fm+ Op$[ +H|L+bb{`SUx0\TΛ~FL<>*N_ꙃī4ȏzCFvXDiZolC!' hEDAj}W`wP/iG.L>AA䨾Ǽј1HƇwDOb޸OqKk?;GMn.ظ }TX 1vg>vB#Pa:5fI@v& ppaV5xЩT[<1nX<TR8)++48Ȃ _b8 n[q4.\Uh/oR!0Hfn!2ؗڵrc^^eBa'Md}| ;?6@ۘ*Tʜk ^-í/ȕ,*LYXI',V ոln='pYTv Õ]EhS%k1=]HxHFei,CqVh0/#PSaEuV0᫻buke|%xh~8`CPhfht @s|ED>9F5Jڸd|FhڵTP]יvJf5m.6 +ꮖ`-5]́nzco3@FD"f`ZsF%x }Bl?XGM~V*-;1"Ъan#Af[wrsd0! H,0(u~.搸'8l}F6$DKaوת}[CXZlt;E~CK!)JZ T|ʛ_9D͟㲕_y>`b9ˑ]x<YW1 +E [ /e,Q@BңSmTy'Ĥ|3+E*܃EaL)Tkzbsn~ұZ_nl7<Dt:]һ,S}SXd[>%n/(~Rm> +ƎnnǴ+uJc +d=0,Y׻rh^wdQz84' ;\x7n$1:slA!ފr^T(HEܖ&yFP(|86CU,6םZ"2j?f m zK5<ĝQ& +z|N? +{}Lj3ew/MPaiV$@.OeC@Bc;w +.,M]Q:;|$6YFIpb~P.Ksͯ?'Ѐ+P|tG r*kdhMLgRޠ ~]** +{&#c Oc5^bBwjԶv3]#C fk`mwϕŠ\j{-]*@ +TX0ñ>QE\zsj8EWԏ5<e!+ 𕒞[{|Mϖm]s ;?/pꢋ2 +XЎWK r9|Π6<@;/vcØ4SC`?Gj<6:y1s|* H1$F̢6RWۋ4j`=xSOe;ޡK.Nr+[FA,-kdy!5V;EO$z%,%eۃayS4at,I JdQfuVxQ'.v#N˕%Y>cB-] ωfqP^<$f1pw'N8܏5"ˊ )qݮ] ;G ʈV&jj圙!|ZjKiS|]^YFزTQ//SՉ %eB{8 :oo2ъ fyM rcP&ȀdIK7eTR #@W1H+3Rzx#ÔF}┾"lŝꢎnpLo;bA:qc4gFha%*]DE(TPfew*9o- Y[Xi]vA99H/eA_G%9NC5W:[bzIn̂EAs!RەpxcL,-׃U.TFG΃f +ZX;-c)/4O +K}a 6InI2|g_0 .~FyVu" m`Hm8c+~˶vQSӸ&JwQnw]y\{_@LAᒂ1Nא2Z@IW)m苺R(Ʉ2Dj"3)ZkQ1b+Tj&3ބZXh/w9Xs7Pіt0"ev"nAzĎg7ۓ\ Iȃ/5}OR&kǻ~Ax^X6fH;L\\>NICRˈ󂜗 pͭ Om{Y[Oڠ&'z]e٭/ylGDu<]s紲W=i +%xhrO!)/VsO _y<1{dFh56Ft +qWMbI,ú+ /;ф?$t/wX(,{@j QD#㛖:+w$lDԗB!dˊgc7N4|JJL^֪sc_O_XR YÔ^x:Q497P#ݓt00J'/Qq H:\X'I*4Sˢ2ioTRf=<3Ρ®:S͵ +A,)[4*7(,{1.4d(W{@w!}d$yCt?2 n-A-gF<{т.I,wsU̯ &^m>$ 95a5 u]?ٸ^qT:V)tQ) KBea*6/Ld39.eCci-sb63}dT!EP4y endstream endobj 14 0 obj <>stream +>+m}ߖ80fPUmW!bV::NkC.1!VhW1ن0BЃ#"q,D9h ._Q,OMbQd8u-VsW\NQiB ,a!Yh53G8-*" x\T=W0E~_Sa%A@=B͸B,ޱ\w_h9p\7b rҗϺF t@F#pe 5Vd9[ҝ+fY'ucR3Q}g&{YV̀N1HF?^~9KG=ǤdOd0N/{DF?dv*Sͫ|S9#@m;Bᮩz6xXC C@d$x}#%4 ޥrciLUkOIPDçt_{ʒ[ Fpe}"UN(OcSGQ)(m+LƦ0`[(O`;(\G \@nbX2+E +YDNВ[uLP,.j,C[QLo#cG۴VqY?R'r>@ƖSN̐o;x+4&N ;&yQ*!@^/Z"N%Qr ?m<d<L1aoEI:AbD/XuRRNMQUrAS2D_vb4wZ]Dz*hN1,Whq2- +28*;t(.md +[9Sȋ>D)=rs ^#+\g!t= |DZbT͓8Y\3ceL#ZKV5Y` ir?yhR|NH4TCZw /0(d(Nz95bE1Mf+J^\T;Y%g.o wmiBk[ede8{7Q9q-{uwq "2[ v +w^FDT1v{3- d"QRrsH|3Ҵ v,C}R|&`WsXXLWhjakqٳxh$]"PԊE v'Y"Z"Zc#xӌ*!d| 2Aݤ'o +}u|,@aB%t6GoIPGr +'B AVY5e]t 1'aUy Cڄ~Jx("=2CQ ߽=R0zR#e3/"Bƍbpa>TGN徣x /PⲔ %Bc1K[dB[t#]<`Zi*7NEd =j|$7S3rXpKi"zɴ(4r]H sgޝJk Cu66^#P%+Q(B7cSҺ%-GZ >蝹9(0!qUqN9r<Ă>*!)eX!H~_Оi`[ou|.[C +zf27+H< [׊w֯Iy.e!Z-`W9P"@SK]zYPƕ|ìVuZ;G(ugD@CuaC%40XBٱX{$YEf2CwPz" +v!ົŕ32IW϶GrYiHe!T )HLP5JA(faOg=m>VD 3"ar˴8^9  ;WJQ$l:IJjK`X BC@.7¿`E6Ȧ]~TL>-zaxbjXLC}u!! + 24T;Y3yFjTuy .'<=qp IJ+z!ڍ1݉7ɗY=td/8 iM~%w,ZVè ;CAx7 /BH?|GݸQo;z)9 +pɤcp>o(oȑҢڥ]!RVf+sMav!+$ˡY_;SM5*FUc.ꚶ2\ˮbaĐȄ"-gf"kEPXJkȼ(X8NayOs*N=]:F`3ꑒ9zCq<,Hd<4` +#T}} nd('fS~4,SZ Wmox++D驎]XDȀ +r+"Dv D%I˕BD 5EVT qD$3dGk3'-iB,< [p㋥7M;' +`+`DF`jz&E{[>Xbdǡ{u` +[ʅt#Dtߚ7by݌2Z%G GG֭,!k=? Z!<+EOqXXJY>47 #)ѥah]p:E,sP>GPo` Gػ!Qxu/@[ /[ͨR<)Vvmoaˏml;;q̚8/~Dэ*)~A+ cx/xHrc 1c *)8G(ybZP ݆\}uy.,q2u$\ՒDbei]NM+S2X#"&UKMNF?h 6(T`*":>mv[UqYBIP8Ș;|=K3 B9@'~ }!MD?1EՌmp@@HgE.nH8[=q6ǏDpdR-2qm `}-V>\J "EǢfYr6}'e\-܂NQB:K *&Kr xpe!tɨ,o5LؙXd Oo˦ +0q4r':Yhmv>gL~`gO;c8">{s?bq3WT8J_\g8%G*SWp(k!?ev:5)?'SifJA`b\lIwW>E!qq98v Hڶq 葌\0=^~lP|TD*DL9^ULCxry6Aزa3TGפu}G#*GZEdTn +=<49Ӓ>̌Vm_((GDs2J.lU +ʜw- +J1:ƍ=[HP"s;ݞc`NzcpPu3A1\N<% Sh鏋_h?6{m(d*vZ}BC12~hWW0^;hMI"3kAÉ+~f#].nb<$<6֣d%?'Q%JM?g.҈W) @=.VxwhTw-mӿ(,38ZGw" *|ʌ# '}h MK! `􁥭+Ec<[QѪ>x, +z~G58g,0³UEIJeE;.s(y"Q5 8S$}_qS% l5akt%̓{bc02ug(4/jSj#,2|bM7-GJGDCeks#? 2$vv#we0~h{h[ǯ(u |D<@FDB'?򢘱.w)O#lI:|]3C<>)%x$%1ݦ#P[rʎDMf.VK(נ1^k~՛JYR=2B"ڲ#l =:$05;]NXpZjpQ}GǛPO18* +2)3 +>uS Ϸ܋$Tz̄SQDl 8DM;@~&)pHYT]/0I, $8Ar5k3CZJdi yS<ehl]gьN&xD}LbMQZB4dX@ɢ>F# @=&ۓ8]o^rXɰbEx4 af'6!eՆLy\N(ڧHk:>tZ@;Y_#FsMUR3TzgSqOyRrr ;Y;JC)lPMk`/.*M",xZVAVLoj P0 jhhk[x+ ٿvy#E++Pw4p ߝ E3y]LS悢U% B{@.9RvA ͍.͠#+KpF#g(*9 MW,X*nRQ'[ʹ#ﳇJ`C\x|Ft_YA@ȡ`gc(Q}F7sȧ^{g6{Sl/ΔIh,zZ&|6ĝJex+.,VQo ]@`'SJ*^἖b?ݱL6pNH%H_QLQăoNLwJ.sf#ٝ1#me/ΈݧWh9nXef&\\Sv *j0e+,=漅Ϣ"dvRqa-юj +-0Ƚ Nt* Ho% `7iXĤFsi8X/]GHEVTmM.8tt6lSd6FIg}˲xP$8 9Jd$F)%pu^RZ8}So̒f 2R_ %m@ey9igתI.܄6@uv Pe1T^]4N>Ag +/K<>GCl]ްpCgD 1!4 ՚w>2:^6Z-]U߱_/P9 C\$Zry_\yx pWAIe,>G*8À w3^Fd $- y5%U d@v.w]I;3=APdc^;/eFd^ >a-xk@d!FhˤdsB?n ^9X|9jc8^f!+Mr,2pXlC\%GjA]%\3yy&a6]m\d #1J:JDs|VWWN7k2dFUXXr:AEA;ˇJOQ^fS嘍.)~܎Gj GkB46(B#PN~ Mc^',?X달,Dz )Ccx7ࡥ 7XE qfrݐ /"sܪȰȵY8}D}@ LoZcxqu7O1 +:It)tzA/aA=v xIX9FuiEn@5 IxW{`-S@pTz9Rk1h!<`A^YHt]@S +lf8s} 1=z yx\z|_E W"@HR)X\:A7KR 󒮴h D L>f%.YOwaLhXt[$@w, 9'$v o@G +h䭃P[,J0\L#Z¬0@RλF妡Ѩ`,^B1$[(@HS9 +H¹y/yf IrrI|5Ɇڒ$e.¬_trhn\R۩ʮVҍ"?w;L0qVyGʇDvlRN-0* ,BHFIraX7$DIyO~,Ham I:jc !_ or0 +y11GvUz6.܂*btbOXH܇gbta#jb}p噌fOxBY5f{?=O5Z{K$LFc&K9"}A<)mh$X 5c~@yn>) i 01 R][|"X9*">>~ϵNiL䣓#úy1fL~xhA?]%{#w[*^aBD>-L~H޺2|7,4R;DhTts1g`>wpkx- z@rF@FȯLf r]Z#(P%dh2o*C>gXCĈn HGjeI:(ٴGlJ-josQ#4[@Rw^pybn* +YB{*]DWr_tY9"bbE}&ڰdfh OyT #֎D\/MKg+myB=ǶëBv9)-㋭ede'`22`w}d*֭x[D@=2<X+X"u8z )dG4]b4r<}%',T;:Gdv +6M b|um) bU!h-\bM㢋9St|Hˑ\N ,@2"N \1L)8y-j} c?R,SI FۍF߮>tԁ;y]gd'HX +ioށ{Fᣂ_@WF.ibD3Юa(ey ΔQ@P8 0DQGEY*,""QZ )0]-ԌGN<|"9W[Glj\ &u? -~c0a:[Z"a;x [iW$ǀ,Z^GL.GXuP. r l0hl3'!2K]WߋHӄ,ċ3y- 0KQT \˨^1ִݛ {ɤ :wcF(7[-eRQP^X}d-n/p.8h,;eIO~s W*ǎޥC!Pk? -Sqwz!A; +)'Ҳ*1t2q=hد W.b"/>?>Y/Uk`U<(S&"*Z< )]M)_}tԤ\~p&bBn ^ c)Zy7(fRn9d(Tߡd1&OOFANPp/"s\;}e+"BE} n &J/y8˺{'*$ah?4.V"*hb~+۝Ge >!D Dx8kƝQHe yzϫlK xwJD5MzzafޕɎwU2rE&}wGBE Z_ +| MyWY,? e/gvuȏF VuI_̟NWO\^t-T:F6zM`PQ +O (|S#1(` Yc9>`HwVmE=&1Hc +A!_$DZ'lPF~]4HdZnڨahRݡH?6J`s~AX["V7dkX,l-beFl7Xx] +ѐzxD#޴  PH'; LQ QrO%T;ʠt 355:}lJs} v|Fa$)bQo/p#A !;+sٶ +FCUC/,P>DUor3nC*eDr DrJe8&I`+؍+76͗hs~b'Np{E O#Hdi7+w=^|^Y*9d!0-P^vYQdW,qPAjY'ˌd<+P =panGVT2qSIq;p f/t0B:w^ӂK?q4 TuC$Y:36eBEȜ#Z>p~Gk2 +%V $g1M1623m +†R]!&5IjH0iΓGL;"EPx w+Ϭaw#oW7Ӹ "ݖCuuٶqR'cp'^,Z`FLPgRYu`jdGF o6`9D_|J1Wxqz2leK'VE},RLwVccQ h̭9xJe 1ȣYcܼ#  +pوqrxmU(!)f2h[r$}|X#`e濬q0p< ,Xw) ֊=BϾA#Vz+h  +q#^*c̜)ÓKue^Jh ^Oᆤè|.@;i($o=q +e򔓩FDFQȑDzlt7f,e3XU&~ &#׶retj4IxS_#qOϪ&} :lT?`n?1έ{ !S[ mԱKKBR 6/a?Yvj:y4QFF!\ UP*) +rTuF9O3I}k#8|5,y(D""jgba/CizTXI?@5ѓToJFTUD"T20nհ&JɐZC ^a~R5 +!^]M3(DZMXHD+b$4<씷h5c"Z j=X"(X=Jg^O#S"Ibb*Uzݐ?aDzm eD-+rQ&,x.eAU}Q1{% Ѕ% F4)?Tc~6)΢`wUcLUzbb_VAT;» 2DЬ×)0ƫlb@q0!DFfX6~(M肓-!nf,TL(%,S-s=&bviP%L2[)Ȉ*RǦS$k|6Ĉ⮘.hG;]O5<~DP hT:>\ϋ X##B&BJBE"K@K_&QTx7PD a>Jnġ(a) ؤ 18UVV.miIdbjakT*lUFPPI +.1+2sݏM(ޥ5 +[2I}H2*K5JNi#4Q 24!C4k_5<>'E?qsQsi +ކPCja Q2UE.PUFQBUHՈmGkj&d"BNWOׄɂ4&jHI95%N}UQ +EpSP&*%l̈́tyQro%1%ĵXp¦Ǘ"U9XXqH(L >7DV(͘"I?NL:wre"FGB,0!sVpmv*%m| +qL5f$|Np&,21&CI h2W"LE<(xdԐaCIXRBb\*OKy8B$n'R!.b$4 c3r;ZbrSjCC ;b_!1Z%4i+jUsr@ԬWSao<9MGSG' +IE0zdK13 q ŊJ(CӔq-(E*$X2PqzZA]Fp Ü"Lq0XP+T]4d|c!IyPRׇp:d~<.薆:?,Aq8Z>CُKJjy})׹L&DFHrݎ_a_EG`ЙH= +D(%$zfʤUUԅ%E" +?G3ᵍ7deRgP\:"&<%2Y'a?DR0rcS0}>W+bġ֠k RL'Źac®OAKjRoAZDD"8v11!R#ѩ,a#Єl+)[V+XbA'yLc@QI(iOYS3C[ "9Dh54q3ɤFsQ`h&m8t֑h;Zg*2;8S-"OqguQ(",qZ8a +iQU pHNav(\ Kkg pPq@hOcf&V8L+ &T&‡bVjnGFĈ! @,r8W& !쐘KN0Ƈ`4lPH( Cò9rljF(UM>d8ҋT áVB)6B(I\ac!DKۊڄQCփԌZ1ĉBEĚ9E Sbas +-4QHCu^V-E1 @D4^0tUʂ8 2j83T*TeW¢ Te|#HPB(!2&XZRh5' T/Z8,RVU?Pp x#P 9XZre,[9!٘i +k%TpܚS«4Lbh}@ +2 k6J{aꑗ$a2W~Ga8z.*[zi+q 9 +!G3MTMZ&8Mn">(LgۅsIC_Be~~ +3.UV$4`I0&(AB,_KQV3 +]Nt~L-2 9= 3uC,XJ* ķJK4u3uEcVCӜ|u^N2/E +"HMHz++fjRiH}P%ctodT"3Uf yjBUXs 4[A\DAOU2 iH?3Q [hR]ZkBIR u<"&yfj8DD8 IgȵV!jvN!_@r1jzPxbZa.s L5d!]N.-4i1=փyҝ`Z@@@98z'3`9C6aUCcO|B8Ay.Z.A_6AЦR17 ƼJ7" *[53B6L4QBzdBQ$B_g$PUrL`GH]}{C*N(J?RRf'""p8L>[#2R2CF?PЧrqA$8VW>5B,KݖEVUP"P0f$A +۰L/堊h+UQ1Z +LmJ "- dd2T\' T?O!DQVn uXJ&!N䧶FzǍ @`"&'?Ȳ੶*RA:4pu&@{%wƸ(耰eD0gIC)5TpMyD2L mhSb| +׃0.C2dV UD&P,"PE`I%028AiBlas(j85GM*MM?o|1#Qc]p b]}~wsL.]x;nRbmXMpE|xk63UO:@މCydպZ^О7L۩D:"K5?47ql<|ir84? ,*CaA۪obvVoa +<suO K24MP/oa 94HV.Yw\϶_^> Fpn qPNB7Ogt8R8S+DQ 6gh(gr{+.͘Gy2G 4#Qv 3=2xxR!ի`rUh 0I M +5[Z>*^++j/ϟ[i1J1OJjL*&^@@ Ue F = BAr]f] % Rbo^5ID/%i8X|YQFhJ$V;dIhB9;-^w7[Hd9QVQfL~{QD>^`@^h_\!V$!*qQ\Xv]PT\:`8 6}G"y 0أF +QRJTZ:Y[<)$K{fKn'Uw$=B&M$tĵbN~, ЕUyFjqmwc9+"uXA`(7xGmPJ~L3^BlgWHN%vR ,$߯'"cZw|oAyd@_z-E 鸸ǽxw) |b"!"YkA7vpi:"!bVz[NEkO2饲$qTb@5"^c3MP3 +u,mtq퀺\s eTkH.CS2dǟܭڝᕆf ɾobU0{D ʛzXuoL?I|wT"VY/a{8?+u r tV4("3rL1po !r(y5+8$mJE{Ʒ˷{i`{!h+qԅDt١ڔɠhlaǦKm!W :xӧ7ٰv|Ar2@v |4y$ 12Ȩ~1½Bv='9_OFj>D(6ETmQ/ChnDĈJH#}9s| ]19aS~7 @|}3ZK +ҟ׼#g¶8ѝ&f+ǟyz!_"tN!`J1!Be] >d$Mc{x&v*2 ,6rI=#S$ / -Bv7~{nBa'_ 0voeDx548)A.q812.l[i? +8.A'Vy>k-89tX=~I%p$4GޱBni9gfo'W{*pFj%|HF Ιedzh*sSydy!EBHD`.HsA9XCblDN)k(nE5V࿇vg>2c鱂۳RZNs܄$KWP[ keZBo`ck#-0&_1l\ɷ +f/} pX#KdjK-`hZ"EBc@(Vvdb3kG lN^ ctA>ŻKwNVB&>hTw1}Sn'"JR{ VLJq*!owWegE{VџC( $!,52ρ<)[ZB +u .5DR"F轙@@n'JwA< $,w@z 5J*[2 >JIZ,PM?b;cDw.8 Me>8!-V[0|*3t4)@!޺M,MEA:?h"0J/#27 ,Db\?b1ҭ4[k.X4PfPT +3Vd= +y4i9pRfH3b@iȠX U=e=tjxNF?ô&Cތ!2Y:^Vx=Dש-.[o=P]QF`{f-9+s\[V$ +2*==6ݼ= `QIHOeII8IuݸR 3'<gn@[K#@ )iV nU.q p-[$GTaL5s ХՒ>^`y>s-2Hv#(8j~IxYR ꦊ@$ EAt)Q[OtS" Od>@ɇue\<TJUy +B#ZA:Z~tl!!=H$fR +ȽQE[C=u}dxcLxrŬs5FJj^.gQ)iEi:vI_ ђEX_;jZ%݆^y]a@-m4EBY'Jl.Gw}DNSʃD@J_؏<<>Z%.b4Aֵ: z]ٽDrG֨wZP@^-5 + ]J",mE| `H;E +M^iagӉ o[C;}X.?4)1<bR6̣SD +]@v Br.ӴAPƪE$R%nn +B`)o}ѭXD1]Xh /~ E*b< T=Ю,i9"0uBs`cHʃ@I62v_|qWN'bG- _{-+q~b`-rfPI!͎=gmCU#-*gn\B<+I#1vsI*WwꨄGA9}/X#]X8S`8xf K'TH'X +=.VE;g/⃒\ȯ'={QYG<o]JUs +kۑلYZt $s$yP5/!"sp!u#\P"Pj"0ZNWe9Юtngy),FaRrݘ3)k+OƳ*X%)n񉅫cو`Qdz6A1RDAǧWE@AXŖ3d[ N}>;JKJ@^ VD2j \%k\D+fvJC.MWߧ"ܩ-nĈJ}PV^㴃KrhY=c'u9AKPh^HWتP(yXz~/~#AJO#2Ѵ{DDIoftLi8¡Ssą(e)5NVX-hyx$lk q`Yb95AX-]]X&Ш<<es@d|% cVѼ~o?589zЫ,{3~ daX +k470wg>l١`m&l[9 "k Pv Gp)hDۃ` +%?sf2{-ч@rZfI@ + 9J +p$ 1iXndډMZLL$.zIH>O>toE*`+{x | tu-f@D +.ѺEZ[7h`<[0:NNpz_4^Fai!504B/"ԵcPI: +2OD֖ x%qJw{ + EG!{/G~E BRK`@AVvt q1[É (v9w%WbF؛#!<+~McuI ۋ,x&5PD졊* f.]{5UeDoJP@-`"f3)"C!dA;aJ%tNW]h!Rk%@Pv+E`YtP-a=i54̊KH9y2IrErWFf8+6=Mx*CG@.RwYY +{ 1=# /heRHXW#ST?R;㒦h,7Dc!wC^7|$ 8>y.h6zL u]t(eiugH&MU uhI&ѵh7HU}4e!VC(/Q^ה-m/P݃xJ7ف^Eݷ9t"S2;x-o>.^%tVzQC4b[ŠBX(jArжͅnl K\J5 b4 +"0M +YT85A<؋ݖ.k*#";ymsfmfdZG<Ĉ'C$/y|!YrtwCHӆ{VY@<@n?Y;GZƠ-桽Ug g˨y)6ƶDoUSq7D.9VB7::]ܫ\ &p.8ţLPnZMUl?-Uy,j0' +|w+䢣L3L~w3h4el5?dYmEB&lē I?6 Fn?GKI7[6ѭW(",ꚣ 8$4"wf;UHD&T36 +ZjFh!eVRQŽbLcnѡh)>7;MGv3Cr/dJoRi2~D6kmPaVVhE_x:oRd(,h-Z䟊HَEs'yn.a ETgV +3Ȃ7ƱZe +݉UkΩX_^"Z)%:Gk(j %"1eOcBJ{?s{Ä-+]i>pM,t}rc5եZ!(p_ ?jl!g-U@KԬ4A2ްHTOEF`$7Wrcwa)ș$@/6 H(G#W[xn?Td.F9d`)ORfqɦݼ(V|!4ԋB DMWNT H"Kf\Ɯn CKn׎.E>K )BY8zW$GՕ3^ي+bF.-fNs2 8[*LKۍ`{>4 +}ځ%XQX,@,[7`X0k`,2*bʓx[h>ˢsĎRn>+2@ސvp_kʢMYcGQGCxk&{mkjdJX^˗p$1c=Cʭ ?m eE{荶+bS^\3GYs94BK*.2 +%펻0P16*3صD"`1`f9ߏN!(t^Wc>$`(`À"C&޾*Y (2:^YxEitI+R[G+,=[T{G?RK!-L7ϴ٨&'DR9 DxF)s ,F)]z>}/'՘5U|Y^p1y@^d7=Z]*&"V+$QӋ.& r< g, 83!jaQv$&Pm yd +_Ay^NC,W(^CCl!8> hOc"ކH Pha2!VS9'ɣ֮\2r\b"D1vV+#FP0r ,Ni\Y >hHr)H#w5-ҬM#.:g1{@?ň'-|<)8.kpcp}a~xUV GVa3uQKȠJqEЪ~:Mj#6CGiYYJ{fD\rbABrĢ=To5G%x>Eͥ_ +c܋ HGN!0+#%ur`Ii)bW5@B:cx.fC[䌡[8_JÀͭ"P^m)(\Y箂0mgS7*3f( l؍KeN dB1݅nG#\>g I?pX!f}Gy`#HI~ǿh+zekTV5z.m@^%ҴX(FN[B)@(v$LhpB&zH3WnLmʐK b-%)t,&9~Ubq Wquw\D 'Yc06Gr+  E =[f"& _s|&1O3 +1>I +Rgr$@G1Ԃ@7T9᜔`-#B)#pLv(_<}z ?9>! _* +N B6U"'& yi|k8Z~QYY8p. |`!VHnUXtSkFRl!R_gZwň[ cf98ge)h^l';}okA6}i#Ogͳp%uww=2S8X'n 08= آţ"a>6xWZGwN}8=n|h~( +가ˢl'+j{{ 4~ xGX+1VZݠ-ȌY ~"="T `G6ece9ОոFE.l;MaiQ۬8J r#N0iU9f~qvĻc$[/&}[|E :GhPs*;s VV.D9U|@uV 8 %*H$g֛3ެ:~1lKeӉa˟nb9Ӥ\A`:"sl!o?E>% +0P#fkG<1(4(;w:_?'4 +/a`W9v<uOM2QTpE~ @L^? MS~UtYQo(Ǖ(@5Ny `S^X,U/B(ᶛ*AIR=C<'a w e [v.tz"v) +6h#?PSlo5REmǃ/q2\So{@7CC ڮz%&;a2笶F:8!LU6x.]E:7(P+FBe5 WR U]a?Nkm3 mؕ=d(EhS`0)* $/m,\(E8*Wu#f @0Wv^\Q~ǮrATs ;z.hlT +C- ح݂9v|N")5wܴuz*&H(,#[]+(:Ee@3ܿ,%@JVE.`Xe9ٶƛ}%P%FոEx5mks8D6RI,(_pFƏ +ۑ<@=0BF/[(J:bjК_bԥ8B<-절tJ)pb~TS +z +٪_ƄXʅ& Lmg!V hwҩX+ A>ic($%>"5$y.ff@ڛ@]`qj؟@$wqVc`;i KhR^2^TIXCz\]m5c}U>Кr'ԑ/& +ΪQ0k l!cJ;eE +EkKLa=$IAL(DY@ߜ.G X. zyj(Ro^* +&RbUdjd bb5` tADxxߢJ Z,çiz,?wV`,dkU;La 危^S+R: ޜ 9<=yc҂JV6a{8 +ůHJee*-Ԥ㺣s$pFjчۺs~&Or8s#R haC}H-N4&!i'=q +S|EBZt ʅ_YFwncts^c>'K-be&mcxݲ*+|3/: CFQ ŢwAb8byֱClPQz,V>9xD_q:B9,/f- D_?3#^@dX rmQ`9v" E֍=wwT^vwGE(zFtO4 iC?;LX!$1zsGKâ/1{LA ULgT)܅xdH.=HxN=MRPPIiPIQMIdfX05Z4c!q*3JoRM k䟳'Nu^"`(e E8)ixn$b& +߂yǽ<~x}@XqH#c҂8P9νJJ=C1\{ui]`F"<23-Q{^ +zƨ CcZy- RrL:Ao ‘w̶/38FE1D`3a>q]a 2X^is!e_,%I0Y9 5V>Cy\$g(6(n"v\D-o.NAwX#w[3B{g/o"WxU6 ?RmT=3l=He1T&kdlhd1sQ8MY˫$^"l]p?nih Y5ǻ;Ut, +dLM 7<#fp؅TοQGRyyP~`BnȳX D262 "CjhyD]%Ť*%q-w]{]0]^E;:CCR};7{#iW V`V=X^\(t-&NfJr *0)T>m[ctVYpULCT ѼEC!'DR(_s]k li@ AK o'HNS|x+:тqǗ 4k{jQ.N C-V&$Dȟ끄J=Wbôˡ SёF)yabtAB|W` +}i CZ硈r&Đ y> =fgЋ~VǸKaeT +Zq5"OUw:`/]>5^;je:&@yvŪ*1q>g^\qP-25ۺ?mw& C^]5 968:}D U|_+,}h}8#RND +f~Bڿ>+6矈%i`%VQgxj_o}v'Hi\Wj*Q;E_HkqƶN_H|5R.nMHoi ^b,w r E7 (o/FΠH6?rFcKw2)ėu2 mgCxdcւ**́@bcqfn՘-N{ ظ""cz&4A +>bi(n&q`\,0\ҠF*@ )YxXhkFq& 4u:楝 |?{4=.o.*Ta8?n@]b}ƌq;+VƣFds;gCzYp4+)ɏgOh |Z9Wd_јZUfʶ]#.ԁ(#i %#"[Gybf0d٦ +Eu\boRCH2n/lb:v`[uUW$?@[q4Ph } ųI_;..ufS4]H˛d*^Ywdx {d?/O/,'asZ02p ߩ9sl_[Z_WvEGȦ]EJǚ=8b&\"鬄(>r)3K;v#͛p%4c?1%fmNKl(f|)ZU֪^)HD/HHB&Zn '+sy,Kݛ_{gJWb֓57銐a`^:I ^dK(f؝сm[$ tQ×-ROz4zE̡T"VKcZEYcGixxW|rr"8CJEګGF1{#."x< +J]?"?8Xxq<ZI"*5Gh2ߏ*f%CR*)0?O1?{]%]hyy0$ ԡ8>G2'E_*O%P +å\X +J`~ㄭ;Kdh#Y#2%9cMe!ٳ8^|8Odwx_!;4#7ka:{, yI ~5(dy%j':s?<*/D=`4CY$-܂gCѸz J +5.!`|q&_[PVu  &QV6T ITGD,w}JJqBR@cv2jm YQ%*^L DN(${> ~2 sMUzHq\s=CyKHIGjKI*o#Hj?(9@APetxD35<k5оRDz3a?łvm}AՉDh]aЍѩݼ+\6y7/4=@e"ҿڍBs@RC-4&1Qzv@^%0L?t)لso=G\fZxF;Xl{OCy֎$:E? ?3W9|~}ETt3ѯ@Q\|1 j^оw- +spD/?_}R6vG^}0ntoZ(fVؖ<x.nTg_YPߪ\ P? 'r9c_: ۄCB@‡rԇi<-j/qIx| !,I2!"[k a sƞڐ^=ggT^أ(hxg &@H&NHMNd+U/ +-Ѓ3&DT9 +o*2Wr8Y#S5V\t +p-]MajCp1::A?#vl0Pz@`$@]P*%o>d\`WĀ]-yL0VM? &Yó +_CK.^lE_хzVte셺 D8Pv$u }ڸ Q53w!ȳ10Q2ڌ2@0AY(7*UsJCyC5]!2[&tQe1A{q.D@UPN0/ϑ}GvV4< FJD6};yE:$.)z=>H/; 8*iݒ>VMt}7 +Rv3UX%$-bi[af`b m~?;FymG![+IO|${^xҬ38TU -kg +d1ElG^YI^UU@ZB_,,ߘc8mm [vLBHV7!rĵbHΐ/+Fbg_>Lp8yF "eW#Ag?AL!Z X`$Ҋi]&m;4!j>!P*vIp&{!TS@T{eԩU24̬Ў`5# +5g ic#U{6]ltFR-CׄqMպwH;bTxxDKq /ݺ1~ZRQܚW{ZգuMK5.̋*C#=J=anY)|яizR*/?MNzA tM/RX΃'PtEa.}Ax;#Kې\B` ]%VY1R԰YYA;ҢY) +ԩ *v\c6xps𝜺aBK_,2 %6rvzd'|ӠD"dm_>~h$ .JϘR9Z# +bsx:C#g#a+콵1<;QYt& &(.f o񪩀V(8ckVH1cA% a6J5VE:\ oBK[ծ/jy`(#lp+?pyc-%H;b'/>xga']WVE/2镀#PdC;m-kUd@tm)=8;GgY}5bV Y@Y=>4SyVkvw<) $~VFfxDa2vdgsIH5;! +&3ش5Nu!^73Zzs~QкEQ{mև<9ܡu\ ]߃Kޥxޠ| +tE7t K'`B `9EO-]Oo V5`n{;zeJ^+[q([ux @ +픜|w[~D9l4'k +S" + _TlF6oת%%֭i@wHzi^FC5v_Xo7 Ć}:c54A3 ~]֓D֌c.uHО9B=Ygd@͏/\9H^Gky!䴌L ]W-E^!jRC딃\ҹZI `1*1sJ|Іm$tB}8/e3q'-a6zEXP42_.]3lZMzհ؅vKDE|绉i񀍭JaNKsbEj\/%vH&g >FG9);cxY{F8Lڽ5-[<+wOT|́Goyow5& ua(3B)3B>ݝY; +OPzpa^S(N|,wny^n!}ΚF˵v*KEb?Hxl4vu-luPzLw88пr #U8DHN94s 0U .$7eaZU^sZeI":eb*(;*DEX&藅PsR)U!!m9#*sa H@B b KVE LjтHO\x,S- +P:vMK"Ct|.tYDbEZ ɁƢQ̷pұ r!&Y8$}CbVfT$&_a"N+*J)G/4NԘJeH +欈2B.diLBĨ$pb35"#"T|> +Ȩv:iT@_?C x XaRcFMa)) W~B1/jT]a9j慯ț _vKuBIJ!7J `VdZaװTbXkacf2Eq((*D:N+BZPC'$W5+QM>Ra[Di yU``'(*]1A*,# `kT@oű FhJ#:NI k1qe,\y6>@/ (%C+^eFv-żqBhmVx!!df"4IH"4ZFߌi],yLڦh^EIit ҄4QQ|CGNT/2U#q""YeRުmE#،jl>Qu-05(rXDLHn9uLeupRH0uzFSRx^ }x 9Ҕ^G|"7siy|R>NCMT +YPHь(6#%SD":wF@Hh!j tU3Oe` `lPnMT5Mıck聪*!x{bJ9dgHuj0z&T "cZ=m&fe៻TtypyjK jږCK#C%S>Dμ [fԦrED@B8*%%R ^p˯ +b +P5jZbt+a?-uDS,bOL>WQØ@=k"B!J(2Ub>JI\!''|˪ mc+"!&!Rѩw@ +*`B)5Fy)|I  Kf+ޮ?SX1% LUmL EU4tC33L8da)C;>*X;z1E{&((d^i5BOqDD PP/9iAUixviq*::4fWfa((Z)O4~RC!%B(<S0CG&FiqzE$˻WKТ0WfXqH 9Z'᫷v Mu0 TĘXGMRHZہ(!_UA'hZ-\ >է5)W$M̰8J.7CN@S,]-]-Pt wIQbb㔘pF*zt99%:)CY #"1R=+gSqٔ@ . 98he:UJW̴w+Sta NT 1+EDP +kD@I,t (kD(w壙MtFWm(/O&CÒD=@)Dil4 /#لn:PE#P:Z }NM=!VLqrh }TI]E &4zD i%5T P'Zc! CT]Q*bUB&rJ"5r*4gfˆ5Њ +z È~1TE[ .3\PquVA^ Y$PrُTd3EBa:dBkRLb1 l@R \iA"`)jwHSP3ATxb*36傂`($=*4frt8QgP&ŔBWGFU7!6qipU&5q3#JN{,22)c?$\82:7 +aza` mľ\6 Lxٟ1km "\@ +r0cL"ڦgfA`1:\9`jx)88<UpF0D4l ҪL 4R$|o'b U8`ba}%1NA+1(҆*f* +,qor@Z^I]EHQA4!$%z$FlVAU3]*U +jCS.2(LR@lraKѢuT!Eh8XVU% V22"+4b,s"4>۱:Sn4d3='UKcU"-/=|#pd T?@1\:Z9"3*;%@ȌȔ2 +h!uYC X)q‹OrFGQq1U" " օod5%PlYܡq56PtR P0e."{ I0xl3GIMM`dڐ(P1}%"A\ZDpL*$>!6SbGuX \6!`,~y" Uę${V4ƛd男i4em2@2Ra^f)#sć@qR(P;^BYMT=Q +*Rzt#LX$$L 5aquG YDXebEN؇L5vd46@ +9)TkԢJ@H:MA̲f9.ӊ?"!l$3"PjtrYJ:-M8g&k1$PQ@hW  EŚ"L|ȞEH.ȅS7ƭK/K 3򅊓jU6u[m !0^0 +0p=F10:48-0i_BĒ eՑ6Jqk01-?U_'q"d2 e FΈ*J`$v Q45d(+J4Pʳ@rdn\yh p0G $3=84 +Ke+qUg!tp /=%. 퇦 .lZ^WT7Bi㵇\ s _2Go`LLhW%mU`ZU4NrQx,RSXOcm$`2yy O ?ltЎKTܮ6,W tH`;~ڻ/tbXžӅe .h4sIfQ2&J +gez\oUB*uUw[Y: bbה/`NC&ehlP %zɅSARZ8Ϗ;ksyyqOK<1Q-ݚkdp(:^~6٧&;b-nH!V}{;0AZ~) +M&0)x43k_S+̑E#Gr~f TTQ´#"ZK&Ѵx-4(lШ AHcC>TbW#xAYG*!$yĹ@AqYV2}{SFꞮ8 =H̃uS!E|};| r)+y۞np% VU`Ϡ\Ph"3s'0g4uN%O,#j v|haij<+ތ1̠dPhMs 5@(1Yd7*W V'zL /'F, ,ܰ4 +xتstʃTdD1?&n ڒlL2Ԓ!ݩHTxJOL͢%CԜ%@TXT: 1$(0網f`a^Dז;?o5wߦh-W.kO}'y,-aAL;LQ2X]o53(ggp(ܫe1C&3I0-'Ѕr$ :U# +q%9+䠰#4]UynhF;xeP[hE +\mF`h&@0׭<ӌ!c +OnZ{+:r(v> J5yZC#qa4Ggm#/?ȓXj`.S֙( Ca~'4+;yOSjmt6Pw,XIˆ 1x1%(/ 11<:= r)0VWmc]~kyithkoh^6^p< ,Gvi}2]"E1\4Kdx\ ?dN(ʑF)$ JI¡#.u@&S,PZN-r@bW7!pػ 0X{Vc$KPB:bBP8t30ƀ[+ Xȵ+' +=$sSlU0!TMSVh.r +'xн^ c6/Ҵ4:w& +5k+ UXvo-A2_LH7n7Y2Jr$ +oˢ(e6R_sZFKrVY 7,*Ak.BBB@*8F۫yrYAGn`"=:\W(aY{mN)xz5y uBw8~LdP eOG=vџ? @͛vQf;B!Jݮ2[h3.ôV +"$;~e^^]Y͕"%2oUr4e27L*hyb1-~FDjL#${C@(Y͓LB=%Wtͺ @YwB<>*J#2Ƕ#*]yujW9)jp^ݰ|XPߝDu+2V)X: xXr0Gؚy҄_Y‘3NTAa$_`$%u040tWIHy87` +ICA=ىT5Wb49]t>am9M;9O]ԍQkV]l?+H!wɸ_XG #m$ (@WsȜ[BqJ*aX`^T Fx +qDU0e@Ngh&Q}>F[z> +Fs3Xg + 64f< pPҋ^w~oZ嫲@rNnw`g@0):jGz`.ؒiy{Ù"j131JEJF* %ƽIaAx!.V`xSpXxǛ +6)+v(#`%;!彎ҥ7og !/TN87hJQ BPlH^s7?P 9^_B&z_[>L3vB*3$D9v TuD0?nw?C 8 +UfBA1F1k7OU2Y+9>9&7'UFtrʓ" Ypq ,3Nm+2a( J R_8 ~MHR0/T-X Zufv )*H}5+xI"$I^kl6 +d(B;4z d]1hGo*.uq+Bqte1'7+$xzʡ(]3;Ӯe,E/: = w + +X hT c3u۷CF\&T~xvEUwP$\굑UGD\jLLWgOLg?ZO4YM騺< +r1 D%28>] uKzJLqf oQ08] GM@\Y@I(VTKuPLSaƄ +QsI\FvŇMV;n=^\c$+ ')pi@yn+ ؈;o^3KxȺ&m7u/Εcޫˠfl*F\ڄ tS E;;DI,1wuRAN+1,sҷ_^ɡ@In.}Kq.qF*C܈nt"h(Rb.ʡVi=㽟'V j2y4֞Fx@՞w\֨^ǍHnTH3VJ3 _0 +&IХػ* "=%v2zAu|h +##Dѻy~A+o7'5@γe#jOQC9NO_Aߙ`^sBH_ֲ| +Ggb-~bd qIdviB|Gߟ?5A+lGΣ_ET#wP mj[=G/Kd٣BSPPzVeXo9".k}(3n1X ?0RFtVdcBo9ȣPe +դy|Ǭ8ms f3e⿥Sƨ)Q UN%xD,U% ʫD.&:eFhӧu:7#':;Vx6s( rF)Wi +8CnLZbHS 1wh߷)r Ihez:!J]O#&>^\WK*SFDJ$fR6J!]Q+rY qӰ˘ +@On%'nBa@ob _Q)*M"LqnZC@"ѕ~=hh4z$0҂BPgƑR=y$8>Xbąb"VĠOw;|`A7fy2 !pI~9߻;-Ekq])G;!3  #S!|&F֖e]`@8eus\$:fHBt +1tDq<ZU ̅"qkQ֥ bX㐮˗[d#Aev.c䈈8/(kc:sHݽ4? ,B}A"- +B*-P!TЬ2 Qnxl6kq9H99F tLia,CpУB7'ԑ;QD~:F7y#1ĊT9'~Q7Չbgz@ ^"jÎJʅbRpFؠxaeo} +蠮`|#ڙDl`'S)PjD=^Dk^7|4YyqĞ}%.trskt*f'{" \DȈ~e-d$ +\}j>͐sl]G'{8_S m\,!)%Bt+XqqM+MSIOV;4τŠ5*T洢I>햚g;A9Zu QD"Xwg 23޶҇$ Do34ħE(`N}\7ĄLFEaLc#Es z:VJ-W IBnKNPxTw]aE/"4z(:ٯ`-7b97 ͈< + ~= h@iGo,drIS)f.Y4BJ3o "Oša,cwkH01YZ/aD(@h!k̃[ei v +꡼<L lk#J9폨+Nah +/v0t^.9of*`=^ըRnD˴!(^$>ga1TJmHu\ !I8X: 3&26 %/yi/bʄ$37\ D3+`f]juZCdd6$Hpu`FUDh/me Hr(cb +^hfy.)f̆DȞ<(Vkn +{$KYٯ@7BR#iܶ:]_= +tk%hh_#dST`0* 1n/ e='w%[2 k >Q#t>ThrmH :4XU O4\7adb`iS[͔#]fQ뽗xhr[/P* EX'nFuCsR?YYRvDx{?f# ԋiӹ]W(w@$D6a4B1Ef +E)~|LuO2ƍzG+܋K;ŚP S&y /7A=`¯|H6kdgH8vh[H&$B3HGY-Y*_G;0rxp08Ǜ@Ԕ[bdqq}65u@W^Gp- +NUR褦؆ @[}+4 +jw݅zg GijMhx:k`p"5ѕ7 ˺ ]ڀ}U ?2FPQe5|7F2!|7܏W`BѰ#cWjRcrB  +@13rMPnj$dPEAn+sQ+ )emZ+"Pۿp"73$UPQHQS;wdNɅ٨#,p1(re!jj5(DHy}"sd[L邼hҎ |rUuVq}ށDFXJhrrI҅ +YZjrX +֨+d fSFw^ʗ1㕁%ozjؗs BH5Fe 9CGD~JMSH,:5p@w"<؍g +vloS%ABaZ,WHHeZ۟[ԂV͞'1CQ0a@U.6 {[GX-P +Bݑdӿ$@0pj> b4뀩D%Ca0*))x)#*u!RҸ9_Z;w"",H]c gvZ`]s_6Y a/pO)*d,L9`笰]+P8Os1͚J EV\Gq̗iTøRj>!j r72Į tjo{g+QqZroPdl 'KuO+Sh-=,`*ˇ.-s+'[/1)d2T^.fh@J7`A-ܒJNq1]d@ +#c"l1ɂvytJw!Ϲ>{6d҉7~L8( l6a$gDDSfF5y5L/g̳@~~K!ATS&\QS#eķ8t"/3&@adFfEY+@p ]ג(E? fD߰ +Qt$Kr?X:o#y[CH}p掇9AD9ja?&W3gTA7G7Θި [$ sx~M"Q KBLgAFnϡ>ڼ'a2grUI2o/Y {AGslr0kfEm{P_Aٕ)ezn.jWzq, nH,XqyG^ĞkZjұ![7@][4_yU2+$-2Q{*8qB-DžW zcxkYc u5+U;ٚ^F\$dz:̥FL&]lj1/aÚyEʼnֶ=>Xů#n)jq4ՙ#S8'*[' +grLC0! ?,/· ؋W]2 H#\ŋ,wP>yэ~KGA  qŁ/%=uF\ kĢyԌ`@ʀ +L2Ũlc[Bf_)kL ++C+=FpL[V)r"@>/l6ؙiXב/ʂ_"4\A [ո8K֮r*NP|Z농/':s<?uXuHDe50آ?y#1bv{z^Rc6@^4<9`b6vwu Ra92~ 2vYCWzn.Gö@pCvK].-p[;xUbvU% +;D8XĒ9$s,{4X +,FUǢutűiNe4~,*4afO38פ-/cu5n +i*P} ;YƢCm [S +x'0,Ƈ +Rvb&aN ΂ϳШ V"(VO!rZ3ҳ3e7mai\'E6F݌aٓA +ͱ +lʇE Zpoşdw Mp%*gs"pOL( #:Ip=2BAY"F"[<m wICpAE]RQn7ew>v+A=oS%>cґ@pEPp$ {x*d0""uZ@EIku+5 +s0U=V?* 4-r9 +݅j Bēb1MԺO(V;$BÃayB:ە[L@+'vK SQߪ1ؐ w9M6Eϔl 6f3P^3CW7gq*";L +׀3 |Z 7g_sEEF9"I5\MM`J0 +,efȵU;!(, erBXesz4enw'/VUhve?q7vHwX]Yޅs[!Pai frc( +#/sOsP a^ +a +b#ka9)~!1@Y\#آоFmȑ.Oa1 <0rv83<3eL,S9;FB]Ĕ!$Ppfi)MH8ZhPdQHMEg,| rjMɬ~J>)nRd$0p_R8 o !C[#g;od6S <ʹ1Px\DxѠYҖBx}T`uQEgd.IvZ,)/3Y@@i5gҢl";yn CZR@֓{PDL`Ra(@[B+`@28u ]ܒEdF83J܂ N%?K5hD]+ӓ 1|Dp#I$[pR5 6 U& HM# +>,@2CX7yv V[w(jq!U ,! +4{A4BP_]bW"mypsv0B)ȟ]@0) P l `șa!p$Un!x& Nz݊a)wrg_ Cn{\\>1s$EdfC +ͨ#Jw1D7w9kΏu]c&(*qU0drwE4"J ˪>*?6`K2+ƴ]]vK -s"bYҮ$)H]'kwK>dٸRv1icDP^@*7(2[HDg3S +oe jPL*|gẁ*xKkF%Eyt}HP;F'm8g1 +Cvz Hί p(n$'I^pam=o {c$ Q 56эE PI6p|S-wKdB@~FX8 VVm[Ӣ=!{Ilį9PW˺5r7Ie|pѦ@Bՙ.@VLDm Խ, wGF +=IEls*hk++/;$ +.y@2qۘ4Rj6$]8/uT4"K4*շD$adzUů?}E LcWr + EFйÏDUp{Q8O-W7R +C.fn?jL\A9yX̙4;TDAh7NJ[[ ^ʢqxS,ʀ)5 )gCgBD" endstream endobj 15 0 obj <>stream + 33,' >GcPAJ;ɠ\ՅEJb\R ʵ&= PiPwX nf>/ NRsS2|LV'-16ɾͼm7v.:f앨VcN[z7s+uauU[wm˵]aK&ި(7eims%]LQ[묛sDEn'mȫ@0YӪtgΦ=CL_4HTpEC +O +Hb@ Iy5M |5~zS`<x$8Źl%II +Ri D@" @S0QŒRHB g c$ΆؒOh3j/cE=vֱdTNi2ⱑ"ϓ)Qn&\AHݡ5p`KS΃2ddLY+dx1;hw>%u78|k?htlj)" QPuOq8Pûo>stream +85$EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> endstream endobj 26 0 obj <> endobj 27 0 obj <> endobj 37 0 obj [/View/Design] endobj 38 0 obj <>>> endobj 35 0 obj [/View/Design] endobj 36 0 obj <>>> endobj 24 0 obj <> endobj 25 0 obj <> endobj 40 0 obj <> endobj 41 0 obj <>stream +HKVkTTߝa@ey{ +E +!@|(Dg*( QP4ՈIiҕ,ӬMTʤ$؆hZMk"4]Y5iwEW~q}9۾vÂz"!uCw[OgU~- Uʆ]}j9*=[孍[pSG:-_?LNe]1cy-~˶ m{M&vC-Fy/{gێp4h1ֿ1E(U+^V/e-$&YE(x ±c%K1O˕cRϩhs)SXP I䏖bMɲk/цh`&bb}&#$ ޝH iHG2A nd#Lt@.򐏙(B";(A)P +T +s5<ԡ  ш%X&,r4wbV Va55Xuhe!%N+ ~NU?<\[ .^؎`e1قGGzߖv(N zorM܅ U: <Sx/` ؊mle|MZц[a=ڱ s~(~G@x /EEv⛲RZVɝd\F;<)Lk-A%])W^U*w-[ZNZ$5MR]G-TuϴDͥyⲹ&\NW+˕juuwGOpTgR e,i#p2tsD5EPUsٷRoΑ$~/<~7|9NH ^W3~dt0rfȳ#OFD=|loZKCCB+C+B-e%ڐ'49{H֎l?WV]; +L MlTbi%*;e^ک̍$uW}U}SgjAl8r< nj9a| ( MD J 2s[֭j"WeW^O~AjkXs7:>] ٻKiv|sj~>z]1w7Xrd6t­5P5{f63T/-^95Rugtᛉ8?Adm&1N5mCu:bsƒ%SIN*yCIN*I%9$'䤒TJrRIN*I%9ax8KM\%>&Ɣ䤒`33) gEY3)NjssW6>Y[{(,7-="rʑafh@g]SGeCwxʱ 8~9=tYb4n;!?Ў]FAۡ`d;f'%Qk͙$3+k^w +E3X=gf=;\~w:$ _I:K aZT#Y>30]]moߺO.Sgʺx-z鮳Lvrbym}BTjS>+=\&uOjrgny=4FLs03lTUH6 EK|@!>">&/''DUYYrկ1?ո\݃q5O襫tK-I$ݒtK-I$ݒtK-I$ݒtK-I$Ҧ[tK-M BUVX\ +p*UW\ *UW2^YX5ӉtbHM +S]V:QuenVuVTv7Zj.D~UX;icPߣl75_oqfuE//uov=3Q48Q"pE(G8Q"p(:0xtǮ鉚rI\h<"x!"xh:_  :R5 7gV 4dАAC 4dАAC 4dАAC !4dwG5Geݻ FMݟF|a}Mi)P"DB %J$H(P"D| %J$H(i(鵵)QPDA% +J((QPDA% +J(K۵$1ao ҵ>>jnwtD~SSNE2"t-{qFf4[r!4w. +UVW] +t*U@W] +t*U +kiZڸ$BKH! $@H! $@H! $V! $@Hb Iq+؛vgnO^l&7ō,lK O0re~buVo]ɲo/k zӚ\X]9.aN*A-wwQ [R;1=1'vy]:rcu+O!E|Fʹˉa,ۧVNCar.^Z/uzs\KKqЗ[ +ި iR-R-R-R-R-R-RXo)8Kao-1lAZ)c(z[^p=lniZGsG&zD3=Q'-u(CyP^:סu(CyP^:סn)[a{j9 W)Gs!?s!?sys!?ܒFe-]墛Eki]_;KZAϋfsc݊镍i{7t>sWdsyY _Vyvesee|ћz{fsi:XGy&u5 -٤=iE^ӁVtJ(tJ(tJ<$@P:J͡kñttMwu6GXpFtu7}sTDcuy U~8q|n)|jЧ}jЧ}jЧ}jЧ}jЧ}jЧ}jз5AE9A +h_tNG}9rۦՎ>Gi2DAp?;8GUcƎ]9:aw[ɞzkjyZ'mN[rq'>q'>q'>q'>q'> 888\ Ti6Pn7X ʦ}--mntWKuc}wg4jWr^.йzm[.꽕+)c^:1UIlm4h#txjm5cb%5cy֕@mUq#f 8 ~~~ ΁7#**`ܚ̤=&9v1]SٽqYxJ7nવ<Ѱ"XOӹֳ]ِ,.s3:IݧΦ];[=6:pI|;xeL9->&CjExFf5 Yxk^,f5 Yxk^lk65 z՛+v9aeY|J)T+3?W|Hؕz^ˤEkWxeqg˷ƪf}u3i-T~ʯMk>rx[]R:g܈d]Hv!مd]Hv!مd]Hv!مd]Hv!ٍHv#KoF/I^e⴩9Y.togՊʲ +Ϋ}ݶئXye_u~+Ië@Y~'j/B35 N d:aGTUN4ٯJec` HY|tubh~-!(ԧ-} i?jפIa.$u݈ۣ?.(q/A e,|~GŲ +lǜf|QV.=!nv[H-sn +oSΰ$=dk8iu/ZڪXOm4MOٝrIb{uAK(~:mR0e eMVlc-=62` :L\.:ḍGoK^y=`0h7( y5~<޷'Ls[7}ҫ}SY A _c||8/g+}+VxOA\KƌUCgm||nBh\uRh<I)/+,1kqK-7sUM'ZbFV?Be&&&lK%_NJ|JթS=i>zVNWH,<9ՠq^O]Zpؓ0# eiŒ}K6i?i]rKGa:&Mmat.vnj5q[˒~yL2%qi97NYdz.'3MeyTI3O-.=vJbyǎcg05wqdVҚ?I)i3{DD#ZOVq~]}uj#=&ޔAm9Iis{ؔO5jӆqSG{LS?x.L1G)`I +^;q[SZ\{ʽůk=+fp.Sǒ/UM5 G!)ma^)lkK(9riɉwq\\[WgHDM%zeo ˯N1xO\]\XfGMͻSalI5\L%R endstream endobj 39 0 obj <> endobj 42 0 obj <>stream +HKWktT97@fL ΐɋȃ4HBxa&<@@ V/S*ZҖ6QV\j"Df +XSjkY {dE +;sswٳwVښajM˺{vX 4^oK]C[O{w@oG^rtގ)Ͻ 0yM||wt@U˚ϛ={^lE$v%aq!4XpN_8T yV=̬ 5..qUxmOM'$q :|Jb!!և!Q=1E3 Q +a&(B1@)Gb Ta&fa6TyC=|,4`!a1 ؇x?/q ^o1:pom${>>O86< ؈-X8'alV +eօX{v&Nn\|n߲IN-UN'r|P%JQJd*ܪ[hkbMVY'/j׸ƨjZiwY>2YzĖ>´bMv}8n)80Ä Bdb(aJ/HL#I&)QʓJCZ"m'L,쳜\\CDYa*/L SSS$aB@a"L M׆OG;NNg5ͻ] ++ߴ$[-&_]] +=]m'1;Vˢ97A}zFZp1bg?=Eݯxzx=%u|tRw{{=]NqgXքlm + + KWoi|;ڟ<|;{vc9VjЍ-;.[3b(b.ӻU!* Q%v3bhM8+OorR_VhNe.h}4r=@O>gkQtZ}go]0~L6򤣔E㒌:Z\YbMt$JHg9d+컓Ȓ]wR_oW\epGq:>M +J-DJ}_nv;̊"Wkrp,Ht3.s0ŬHBK ӯנiv>p@Y`{_3w +QN>^A)~=áS> ᰻a8=ÑsaefF3 G}rS= +?Gy=,Q*4@w RqƒAwQz2NKI4) ?7:2جRm':6RdsCpLl7.CdkTo*D)T` F6FnjZԳz6vf?jyļΊʅm=Vӱ!댦.uvjSWT^Tmۻa)یM[WEFlر ˕-{Ԥ9Mp iH<bR(kk֠A[>!Id.j@,$D8Ddt3_*dUW{_ 4,lYNgg ly+&8<а~q_7\>?2u!^ê1 7@hm^)eQʨ4^=jD?Zȭ_'ԈRkS*BcFRS\A;}v\eg*> +vx_yەA&Y+̩{VU㇝yg=aUlSRҏy'Um$vht܍r'ek_'z;MeJ=.cj!t :'C"혨XE Ƽ}5<5ɺG:0@\zKآـ߷uwܹ;Kn[́ˬWf{e:Pycm|g[)U~qahBY)vχowo94YO:[k|ҞnCeBvŤ+/ҏJ?;K-O+37彏|…ǻؙX~6VA&"+e,W(Y~pY(%%996.shS5M)ƯQbOb)I~l~ ]U)k٦ۈ/s_ü#1NN@C}a^ۋ`O&FW(5~3/Y}G$$1w&r3ԑǑ+1<+,W8ALHruaSyn؃rr\G֫qkqۉSW=)/9PD\qU Q|purV oy8G .?^W }NL]"y?Ӭ/yo,yv^|9|)r;?N6wx{=+¼X(IxϹ:j*5UxA#fC|k߁csA*IeqUbDOei+9͝k8U7cn-gC?[U^|P%t wpw/j}cרS'U~ ۯC_ܣ8pHjURh m\VvLET^ηf]jw옲g1{|UU]OY9u%ZQטJ*x-j{Hb1r9-c弽]8/b?9nv! cOZ;}IwRojW)uw5t;#7.3L{o tźռA[9=5fN3|lP v@6;f=az@tK, |2?M EoOH'x3y\<<33<3y'*6eW*%7*_v)'r fZwoc|v(~h+Gi1]4 /0ß cMob֤]4>K;cX0=߹sME/3pA_c%W7'j476׼F+xq=[QZձNVP{D_/@MMD̚u=wMT51j0{éƀsʦ6IlXt ôTXeyY/!YGc̈|lhj'v1U\0 4g/bLHby:.YM{Nb5-!uf{}k^e3fVVS5l|w k{Qt.^VW^GΛJfW함QL^ܢK5om) VLu(&fzSLE]L3gfdXYH]c0f4ͱzd-ut6% +zeʩ8v endstream endobj 31 0 obj <> endobj 30 0 obj [/ICCBased 43 0 R] endobj 43 0 obj <>stream +HKyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 28 0 obj [27 0 R 26 0 R] endobj 44 0 obj <> endobj xref +0 45 +0000000004 65535 f +0000000016 00000 n +0000000161 00000 n +0000062988 00000 n +0000000000 00000 f +0000063039 00000 n +0000000000 00000 f +0000000000 00000 f +0000066259 00000 n +0000066331 00000 n +0000066570 00000 n +0000068105 00000 n +0000133693 00000 n +0000199281 00000 n +0000264869 00000 n +0000330457 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000332114 00000 n +0000332392 00000 n +0000331740 00000 n +0000331811 00000 n +0000348788 00000 n +0000063484 00000 n +0000346096 00000 n +0000345983 00000 n +0000065511 00000 n +0000331166 00000 n +0000331214 00000 n +0000331998 00000 n +0000332029 00000 n +0000331882 00000 n +0000331913 00000 n +0000340278 00000 n +0000332780 00000 n +0000333037 00000 n +0000340538 00000 n +0000346131 00000 n +0000348820 00000 n +trailer <<6F09A1C756064C16BEE961615B2563D4>]>> startxref 349027 %%EOF \ No newline at end of file diff --git a/app/art/hero@2x.png b/app/art/hero@2x.png new file mode 100644 index 00000000..1ac37a29 Binary files /dev/null and b/app/art/hero@2x.png differ diff --git a/app/info.plist b/app/info.plist index fc9cea2b..ba230385 100644 --- a/app/info.plist +++ b/app/info.plist @@ -55,6 +55,8 @@ ${EXECUTABLE_NAME} CFBundleIconFile PlotDevice.icns + CFBundleIconName + PlotDevice-app CFBundleIdentifier io.plotdevice.PlotDevice CFBundleInfoDictionaryVersion @@ -72,7 +74,7 @@ LSMinimumSystemVersion 11 NSHumanReadableCopyright - © 2014–22 Samizdat Drafting Co. + © 2014–26 Samizdat Drafting Co. NSMainNibFile MainMenu NSPrincipalClass diff --git a/app/python.xcconfig b/app/python.xcconfig index 868ff609..20892d04 100644 --- a/app/python.xcconfig +++ b/app/python.xcconfig @@ -1,7 +1,7 @@ // Generated by deps/frameworks/config.py PYTHON_FRAMEWORK = $(PROJECT_DIR)/deps/frameworks/Python.framework -PYTHON = $(PYTHON_FRAMEWORK)/Versions/3.13/bin/python3 -LIBRARY_SEARCH_PATHS = $(inherited) $(PYTHON_FRAMEWORK)/Versions/3.13/lib/python3.13/config-3.13-darwin -HEADER_SEARCH_PATHS = $(inherited) $(PYTHON_FRAMEWORK)/Versions/3.13/include/python3.13 -OTHER_LDFLAGS = $(inherited) -lpython3.13 +PYTHON = $(PYTHON_FRAMEWORK)/Versions/3.14/bin/python3 +LIBRARY_SEARCH_PATHS = $(inherited) $(PYTHON_FRAMEWORK)/Versions/3.14/lib/python3.14/config-3.14-darwin +HEADER_SEARCH_PATHS = $(inherited) $(PYTHON_FRAMEWORK)/Versions/3.14/include/python3.14 +OTHER_LDFLAGS = $(inherited) -lpython3.14 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) PYTHON_BIN="$(PYTHON)" PY3K=1 \ No newline at end of file diff --git a/deps/extensions/pathmatics/pathmatics.m b/deps/extensions/pathmatics/pathmatics.m index b2d116cd..1bc131db 100644 --- a/deps/extensions/pathmatics/pathmatics.m +++ b/deps/extensions/pathmatics/pathmatics.m @@ -3,6 +3,14 @@ #include #include "gpc.h" +// the macOS 14 SDK renamed NSBezierPathElementCurveTo to ...CubicCurveTo and +// added a new ...QuadraticCurveTo element type +#if defined(MAC_OS_VERSION_14_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0 + #define HAS_QUADRATIC_PATH_ELEMENTS 1 +#else + #define HAS_QUADRATIC_PATH_ELEMENTS 0 +#endif + void _linepoint(double t, double x0, double y0, double x1, double y1, double *out_x, double *out_y ) @@ -306,7 +314,12 @@ void _curvelength(double x0, double y0, double x1, double y1, ++k; break; +#if HAS_QUADRATIC_PATH_ELEMENTS + case NSBezierPathElementCubicCurveTo: + case NSBezierPathElementQuadraticCurveTo: +#else case NSBezierPathElementCurveTo: +#endif // should never happen - we have already converted the path to a flat version. Bail. printf("Got a curveto unexpectedly - bailing.\n"); gpc_free_polygon( poly ); @@ -678,8 +691,20 @@ + (CGPathRef)cgPath:(NSBezierPath *)nsPath{ CGPathMoveToPoint(path, NULL, points[0].x, points[0].y); }else if(elt==NSBezierPathElementLineTo){ CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y); +// NSBezierPathElement* values are compile-time constants, not linked symbols, +// so comparing against the macOS 14 names is safe at any deployment target +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" +#if HAS_QUADRATIC_PATH_ELEMENTS + }else if(elt==NSBezierPathElementCubicCurveTo){ + CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + }else if(elt==NSBezierPathElementQuadraticCurveTo){ + CGPathAddQuadCurveToPoint(path, NULL, points[0].x, points[0].y, points[1].x, points[1].y); +#else }else if(elt==NSBezierPathElementCurveTo){ CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); +#endif +#pragma clang diagnostic pop }else if(elt==NSBezierPathElementClosePath){ CGPathCloseSubpath(path); } diff --git a/deps/frameworks/Makefile b/deps/frameworks/Makefile index 788b5389..8b4b53c8 100644 --- a/deps/frameworks/Makefile +++ b/deps/frameworks/Makefile @@ -1,4 +1,4 @@ -PYTHON_VERSION = 3.13.3 +PYTHON_VERSION = 3.14.6 FRAMEWORK_REPO = https://github.com/gregneagle/relocatable-python.git BUILD_OPTS = --os-version=11 --python-version=$(PYTHON_VERSION) --upgrade-pip --pip-requirements=requirements.txt BIN = ./Python.framework/Versions/Current/bin diff --git a/deps/frameworks/requirements.txt b/deps/frameworks/requirements.txt index 150f105c..ebf4abc8 100644 --- a/deps/frameworks/requirements.txt +++ b/deps/frameworks/requirements.txt @@ -2,7 +2,7 @@ xattr cachecontrol[filecache] cffi -pyobjc==11.0 +pyobjc==11.1 py2app requests six \ No newline at end of file diff --git a/make.py b/make.py new file mode 100644 index 00000000..20c594b8 --- /dev/null +++ b/make.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 +"""Maintainer/CI build tool for PlotDevice (not part of the pip-installable package) + +Subcommands: + dev set up virtualenv in deps/local with required dependencies + app build PlotDevice.app (requires full Xcode install + py2app build PlotDevice.app via py2app (requires Xcode command line tools) + dist build app (codesigned, notarized, & zipped) and update release.json + icon regenerate app/Resources/Assets.car from app/art/PlotDevice-app.icon + clean remove build artifacts + distclean also remove the embedded Python.framework and deps/local +""" +import argparse, os, sys, json, plistlib, tempfile +from glob import glob +from shutil import rmtree, copy +from subprocess import call, run, Popen, PIPE +from os.path import join, exists, dirname, basename, abspath, getsize + +ROOT = dirname(abspath(__file__)) +sys.path.insert(0, ROOT) + +APP_NAME = 'PlotDevice' +SPARKLE_VERSION = '2.1.0' +SPARKLE_URL = 'https://github.com/sparkle-project/Sparkle/releases/download/%(v)s/Sparkle-%(v)s.tar.xz' % {'v': SPARKLE_VERSION} + +## Helpers ## + +# helpers for dealing with plists & git (spiritual cousins if ever there were) +def info_plist(pth='app/info.plist'): + info = plistlib.load(open(pth, 'rb')) + # overwrite the xcode placeholder vars + info['CFBundleExecutable'] = info['CFBundleName'] = APP_NAME + return info + +def update_plist(pth, **modifications): + info = plistlib.load(open(pth, 'rb')) + for key, val in modifications.items(): + if val is None: + info.pop(key) + else: + info[key] = val + with open(pth, 'wb') as f: + plistlib.dump(info, f) + +def gosub(cmd, on_err=True): + """Run a shell command and return the output""" + shell = isinstance(cmd, str) + proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=shell) + out, err = proc.communicate() + ret = proc.returncode + if on_err: + msg = '%s:\n' % on_err if isinstance(on_err, str) else '' + if ret != 0: + print(msg + out.decode('utf8') + err.decode('utf8')) + return out, err, ret + +def last_commit(): + commit_count, _, _ = gosub('git log --oneline | wc -l') + return 'r%s' % commit_count.decode('utf-8').strip() + +def timestamp(): + from datetime import datetime + return datetime.now().strftime("%a, %d %b %Y %H:%M:%S") + +def spawn(cmd, **kwargs): + run(cmd, check=True, **kwargs) + + +## Subcommands ## + +def cmd_dev(args): + # install pyobjc, requests and the other pypi dependencies in deps/local + import platform, venv + venv_dir = join('deps/local', platform.python_version()) + if not exists(venv_dir): + venv.create(venv_dir, symlinks=True, with_pip=True) + PIP = join(venv_dir, 'bin/pip3') + call([PIP, 'install', '-q', '--upgrade', 'pip', 'setuptools', 'wheel', 'py2app', 'twine']) + call([PIP, 'install', '-q', '-e', ROOT]) + + print("\nA local development environment has been set up in %s" % venv_dir) + + +def cmd_icon(args): + _, _, ret = gosub(['xcrun', '--find', 'actool'], on_err=False) + if ret != 0: + print("make.py: Couldn't find `actool`. Try installing the full Xcode (not just the command line tools)") + sys.exit(1) + + ICON_SRC = 'app/art/PlotDevice-app.icon' + ICON_OUT = 'app/Resources/Assets.car' + info = info_plist() + + with tempfile.TemporaryDirectory() as tmp: + spawn(['xcrun', 'actool', + '--output-format', 'human-readable-text', '--notices', '--warnings', '--errors', + '--platform', 'macosx', '--minimum-deployment-target', info['LSMinimumSystemVersion'], + '--app-icon', info['CFBundleIconName'], + '--output-partial-info-plist', join(tmp, 'partial-info.plist'), + '--compile', tmp, + ICON_SRC]) + copy(join(tmp, 'Assets.car'), ICON_OUT) + + print("done building %s" % ICON_OUT) + + +def cmd_clean(args): + paths = [ + 'build', + 'dist', + '*.egg', + '*.egg-info', + '.eggs', + 'PKG', + 'tests/_out', + 'tests/_diff', + 'details.html', + '_plotdevice.*.so', + 'plotdevice/rsrc', + 'MANIFEST.in', + '**/*.pyc', + '**/__pycache__', + '**/.DS_Store', + ] + + # Add framework paths if --dist flag is used + if args.dist: + paths.extend([ + 'deps/local', + 'deps/frameworks/Python.framework', + 'deps/frameworks/relocatable-python', + ]) + + for path_pattern in paths: + for path in glob(path_pattern, recursive=True): + if exists(path): + print('removing %s' % path) + if os.path.isdir(path): + rmtree(path) + else: + os.unlink(path) + + # Run make clean in svg extensions dir + if exists('deps/extensions/svg'): + os.system('cd deps/extensions/svg && make clean') + + +def cmd_distclean(args): + cmd_clean(argparse.Namespace(dist=True)) + + +def cmd_app(args): + # make sure the embedded framework exists (and has updated app/python.xcconfig) + print("Set up Python.framework for app build") + env = os.environ.copy() + if args.no_cache: + env['PIP_NO_CACHE_DIR'] = '1' + call('cd deps/frameworks && make', shell=True, env=env) + + spawn(['xcodebuild', '-configuration', 'Release']) + dsym = 'dist/PlotDevice.app.dSYM' + if exists(dsym): + rmtree(dsym) + print("done building PlotDevice.app in ./dist") + + +def cmd_py2app(args): + try: + import py2app + except ImportError: + print("""make.py: py2app build failed + Couldn't find the py2app module. To set up a virtualenv that contains all the necessary + dependencies in the deps/local directory, call the `dev` command first: + > python3 make.py dev + > ./deps/local//bin/python3 make.py py2app""") + sys.exit(1) + + from py2app.build_app import py2app as build_py2app + + # make sure _plotdevice.so + plotdevice/rsrc/ are built first + call([sys.executable, 'setup.py', 'build_ext', '--inplace']) + + class BuildPy2AppCommand(build_py2app): + def finalize_options(self): + # pyproject.toml's [project.dependencies] gets merged into + # self.distribution.install_requires automatically since setup() + # below runs from the same directory as pyproject.toml; py2app + # bundles dependencies directly and rejects install_requires, so + # clear it before py2app's own finalize_options checks for it. + self.distribution.install_requires = None + build_py2app.finalize_options(self) + + def run(self): + build_py2app.run(self) + if self.dry_run: + return + + # undo py2app's weird treatment of the config.version value + update_plist('dist/PlotDevice.app/Contents/Info.plist', CFBundleShortVersionString=None) + + # place the command line tool in SharedSupport + BIN = join(dirname(self.resdir), 'SharedSupport') + self.mkpath(BIN) + self.copy_file("app/plotdevice", BIN) + + print("done building PlotDevice.app in ./dist") + + from setuptools import setup + old_argv = sys.argv + try: + sys.argv = ['make.py', 'py2app'] + setup( + name='plotdevice', + app=[{ + 'script': "app/plotdevice-app.py", + 'plist': info_plist(), + }], + data_files=[ # type: ignore[list-item] + "app/Resources/ui", + "app/Resources/colors.json", + "app/Resources/en.lproj", + "app/Resources/PlotDevice.icns", + "app/Resources/PlotDeviceFile.icns", + "app/Resources/Assets.car", + "examples", + ], + options={ # type: ignore[dict-item] + "py2app": { + "iconfile": "app/Resources/PlotDevice.icns", + "semi_standalone": True, + "site_packages": True, + "strip": False, + } + }, + cmdclass={'py2app': BuildPy2AppCommand}, + ) + finally: + sys.argv = old_argv + +## Packaging Commands (really only useful to the maintainer) ## + +def cmd_dist(args): + import plotdevice + VERSION = plotdevice.__version__ + + APP = 'dist/PlotDevice.app' + ZIP = 'dist/PlotDevice_app-%s.zip' % VERSION + + # run the Xcode build + cmd_app(args) + + # set the bundle version to the current commit number and prime the updater + info_pth = 'dist/PlotDevice.app/Contents/Info.plist' + update_plist(info_pth, + CFBundleVersion=last_commit(), + CFBundleShortVersionString=VERSION, + SUFeedURL='https://plotdevice.io/app.xml', + SUEnableSystemProfiling='YES' + ) + + # download Sparkle (if necessary) and copy it into the bundle + ORIG = 'deps/frameworks/Sparkle.framework' + SPARKLE = join(APP, 'Contents/Frameworks/Sparkle.framework') + if not exists(ORIG): + os.makedirs(dirname(ORIG), exist_ok=True) + print("Downloading Sparkle.framework") + os.system('curl -L -# %s | xz -dc | tar xf - -C %s %s' % (SPARKLE_URL, dirname(ORIG), basename(ORIG))) + os.makedirs(dirname(SPARKLE), exist_ok=True) + spawn(['ditto', ORIG, SPARKLE]) + + # code-sign the app and embedded frameworks, then verify + def codesign(root, name=None, exec_=False, entitlement=False): + test = [] + if name: + test += ['-name', name] + if exec_: + test += ['-perm', '-u=x'] + + codesign_cmd = ['codesign', '--deep', '--strict', '--timestamp', '-o', 'runtime', '-f', '-v', '-s', 'Developer ID Application'] + if entitlement: + codesign_cmd += ['--entitlements', 'app/PlotDevice.entitlements'] + + if test: + spawn(['find', root, '-type', 'f', *test, '-exec', *codesign_cmd, "{}", ";"]) + else: + spawn([*codesign_cmd, root]) + + PYTHON = join(APP, 'Contents/Frameworks/Python.framework') + codesign('%s/Versions/Current/lib' % PYTHON, name="*.dylib") + codesign('%s/Versions/Current/lib' % PYTHON, name="*.o") + codesign('%s/Versions/Current/lib' % PYTHON, name="*.a") + codesign('%s/Versions/Current/lib' % PYTHON, exec_=True) + codesign('%s/Versions/Current/bin' % PYTHON, exec_=True) + codesign('%s/Versions/Current/bin' % PYTHON, name="python3.*", entitlement=True) + codesign('%s/Versions/Current/Resources/Python.app' % PYTHON, entitlement=True) + codesign(PYTHON) + + codesign('%s/Versions/Current/Updater.app' % SPARKLE) + codesign(SPARKLE) + + codesign(APP, entitlement=True) + spawn(['codesign', '--verify', '--deep', '-vv', APP]) + + # create versioned zipfile of the app & notarize it + spawn(['ditto', '-ck', '--keepParent', APP, ZIP]) + spawn(['xcrun', 'notarytool', 'submit', ZIP, '--keychain-profile', 'AC_NOTARY', '--wait']) + + # staple notarization ticket and regenerate zip + spawn(['xcrun', 'stapler', 'staple', APP]) + spawn(['ditto', '-ck', '--keepParent', APP, ZIP]) + + # write out the release metadata for plotdevice-site to consume/merge + with open('dist/release.json', 'w') as f: + release = dict(zipfile=basename(ZIP), bytes=getsize(ZIP), + version=VERSION, revision=last_commit(), + timestamp=timestamp()) + json.dump(release, f) + + print("\nBuilt PlotDevice.app, %s, and release.json in ./dist" % basename(ZIP)) + + +def main(): + # make sure we're at the project root regardless of the cwd + # (this means the various commands don't have to play path games) + os.chdir(ROOT) + + # clear away any finder droppings that may have accumulated + call(['find', '.', '-name', '.DS_Store', '-delete']) + + parser = argparse.ArgumentParser(prog='make.py') + sub = parser.add_subparsers(dest='command', required=True) + + p_dev = sub.add_parser('dev', help='set up virtualenv in deps/local with required dependencies') + p_dev.set_defaults(func=cmd_dev) + + p_icon = sub.add_parser('icon', help='regenerate app/Resources/Assets.car from app/art/PlotDevice-app.icon') + p_icon.set_defaults(func=cmd_icon) + + p_clean = sub.add_parser('clean', help='remove build artifacts') + p_clean.add_argument('--dist', action='store_true', help='also remove Python.framework and deps/local') + p_clean.set_defaults(func=cmd_clean) + + p_distclean = sub.add_parser('distclean', help='also remove the embedded Python.framework and deps/local') + p_distclean.set_defaults(func=cmd_distclean) + + p_app = sub.add_parser('app', help='build PlotDevice.app (requires full Xcode install') + p_app.add_argument('--no-cache', action='store_true', help='do not use pip cache when installing dependencies') + p_app.set_defaults(func=cmd_app) + + p_py2app = sub.add_parser('py2app', help='build PlotDevice.app via py2app (requires Xcode command line tools)') + p_py2app.set_defaults(func=cmd_py2app) + + p_dist = sub.add_parser('dist', help='build, code-sign, notarize, and zip the app for release') + p_dist.add_argument('--no-cache', action='store_true', help='do not use pip cache when installing dependencies') + p_dist.set_defaults(func=cmd_dist) + + args = parser.parse_args() + args.func(args) + +if __name__ == '__main__': + main() diff --git a/plotdevice/__init__.py b/plotdevice/__init__.py index e8397068..34de85cf 100644 --- a/plotdevice/__init__.py +++ b/plotdevice/__init__.py @@ -44,7 +44,7 @@ class Halted(Exception): pass # special exception to cleanly exit animation from .run import objc # print python exceptions to the console rather than silently failing - objc.setVerbose(True) + objc.options.verbose = True # populate the namespace (or don't) depending on the context if in_app or in_setup: diff --git a/plotdevice/__main__.py b/plotdevice/__main__.py index ed9e1eb7..510fe27c 100644 --- a/plotdevice/__main__.py +++ b/plotdevice/__main__.py @@ -143,7 +143,7 @@ def main(): # if it's a multiframe pdf, check for a telltale "{n}" to determine whether # it's a `single' doc or a sequence of numbered pdf files - opts.single = bool(ext=='pdf' and not re.search('{\d+}', opts.export) and opts.last and opts.first < opts.last) + opts.single = bool(ext=='pdf' and not re.search(r'{\d+}', opts.export) and opts.last and opts.first < opts.last) if m:= re.search(r'@(\d+)[xX]$', outname): opts.zoom = float(m.group(1)) diff --git a/plotdevice/gfx/effects.py b/plotdevice/gfx/effects.py index 76bf7214..092a7c1c 100644 --- a/plotdevice/gfx/effects.py +++ b/plotdevice/gfx/effects.py @@ -142,7 +142,7 @@ def set(self, *effs): # i *think* it's better to skip the transparency layer when only blending, # but am bracing for the discovery that it's not... - return bool(fx) and fx.keys() != ('blend',) + return bool(fx) and fx.keys() != {'blend'} # return bool(fx) # return whether any state was just changed @contextmanager @@ -320,7 +320,7 @@ def set(self): singlechannel = ciFilter(self.channel, self.bmp._ciImage) greyscale = ciFilter(self.invert, singlechannel) ci_ctx = CIContext.contextWithOptions_(None) - maskRef = ci_ctx.createCGImage_fromRect_(greyscale, ((0,0), self.bmp.size)) + maskRef = ci_ctx.createCGImage_fromRect_(greyscale, ((0,0), self.bmp._screen_size)) # turn the image into an ‘imagemask’ cg-image cg_mask = CGImageMaskCreate(CGImageGetWidth(maskRef), diff --git a/plotdevice/gfx/geometry.py b/plotdevice/gfx/geometry.py index 0ea76b8e..8e61b940 100644 --- a/plotdevice/gfx/geometry.py +++ b/plotdevice/gfx/geometry.py @@ -431,16 +431,20 @@ class MagicNumber(object): # be a well-behaved pseudo-number (based on the float in self.value) def __int__(self): return int(self.value) def __float__(self): return float(self.value) - def __cmp__(self, n): return cmp(self.value, n) + def __index__(self): return int(self.value) def __eq__(self, n): return n == self.value + def __hash__(self): return hash(self.value) def __lt__(self, n): return self.value < n def __gt__(self, n): return self.value > n + def __le__(self, n): return self.value <= n + def __ge__(self, n): return self.value >= n def __abs__(self): return abs(self.value) def __pos__(self): return +self.value def __neg__(self): return -self.value - def __invert__(self): return ~self.value def __trunc__(self): return math.trunc(self.value) + def __round__(self, ndigits=None): return round(self.value, ndigits) + def __format__(self, spec): return format(self.value, spec) def __add__(self, n): return self.value + n def __sub__(self, n): return self.value - n @@ -449,18 +453,14 @@ def __truediv__(self, n): return self.value/n def __floordiv__(self, n): return self.value // n def __mod__(self, n): return self.value % n def __pow__(self, n): return self.value ** n - def __lshift__(self, n): return self.value << n - def __rshift__(self, n): return self.value >> n def __radd__(self, n): return n + self.value def __rsub__(self, n): return n - self.value def __rmul__(self, n): return n * self.value - def __rdiv__(self, n): return n / self.value + def __rtruediv__(self, n): return n / self.value def __rfloordiv__(self, n): return n // self.value def __rmod__(self, n): return n % self.value def __rpow__(self, n): return n ** self.value - def __rlshift__(self, n): return n << self.value - def __rrshift__(self, n): return n >> self.value # the WIDTH and HEIGHT globals are Dimension objects class Dimension(MagicNumber): diff --git a/plotdevice/gfx/image.py b/plotdevice/gfx/image.py index 8817cf07..195c2245 100644 --- a/plotdevice/gfx/image.py +++ b/plotdevice/gfx/image.py @@ -15,7 +15,7 @@ from .geometry import Region, Size, Point, Transform, CENTER from .atoms import TransformMixin, EffectsMixin, FrameMixin, Grob from .colors import CMYK -from . import _ns_context +from . import _ns_context, _cg_port _ctx = None __all__ = ("Image", 'ImageWriter') @@ -174,8 +174,9 @@ def _nsBitmap(self): if isinstance(bitmap, NSBitmapImageRep): break else: - # ...otherwise convert the vector image to a bitmap - # (note that this should use _screen_transform somehow but currently doesn't) + # ...otherwise convert the vector image to a bitmap at its native size. + # it will only be used for a hasAlpha() check in effects.py, so the actual + # pixel data needn't match its _screen_size tiffdata = self._nsImage.TIFFRepresentation() image = NSImage.alloc().initWithData_(tiffdata) bitmap = image.representations()[0] @@ -183,12 +184,38 @@ def _nsBitmap(self): @property def _ciImage(self): + """A CIImage suitable for use as a clip()/mask() stencil. + + Raster sources are used as-is. Vector sources are scaled to the size + they'll actually occupy on the canvas before being rasterized. + """ + + # if the image is raster-based, use its bitmap as-is + for bitmap in self._nsImage.representations(): + if isinstance(bitmap, NSBitmapImageRep): + break + else: + # if vector-based, rasterize it at its 'actual' on-screen size + w, h = self._screen_size + + # create an offscreen bitmap context and rasterize the scaled image into it + bitmap_opts = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host + bitmap_ctx = CGBitmapContextCreate(None, w, h, 8, w*4, CGColorSpaceCreateDeviceRGB(), bitmap_opts) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.setCurrentContext_( + NSGraphicsContext.graphicsContextWithCGContext_flipped_(bitmap_ctx, True) + ) + self._nsImage.drawInRect_fromRect_operation_fraction_(((0,0),(w,h)), NSZeroRect, NSCompositeSourceOver, 1.0) + NSGraphicsContext.restoreGraphicsState() # NB: class-level restore also resets currentContext + + bitmap = NSBitmapImageRep.alloc().initWithCGImage_(CGBitmapContextCreateImage(bitmap_ctx)) + # core-image needs to be told to compensate for our flipped coords flip = NSAffineTransform.transform() - flip.translateXBy_yBy_(0, self.size.height) + flip.translateXBy_yBy_(0, bitmap.pixelsHigh()) flip.scaleXBy_yBy_(1,-1) - ciImage = CIImage.alloc().initWithBitmapImageRep_(self._nsBitmap) + ciImage = CIImage.alloc().initWithBitmapImageRep_(bitmap) transform = CIFilter.filterWithName_("CIAffineTransform") transform.setValue_forKey_(ciImage, "inputImage") transform.setValue_forKey_(flip, "inputTransform") @@ -249,6 +276,29 @@ def _screen_transform(self): xf.scale(factor) # scale to fit size constraints (if any) return xf + @property + def _screen_size(self): + """Returns the image's real pixel dimensions. + + Raster-backed images return their native size, but vector-based images incorporate + the canvas's current CTM and the image's scaling based on its _screen_transform.""" + + # if the image is raster-based, use its native size + for bitmap in self._nsImage.representations(): + if isinstance(bitmap, NSBitmapImageRep): + return bitmap.pixelsWide(), bitmap.pixelsHigh() + + # if vector-based, use the canvas's CTM and the image's _screen_transform to + # determine its 'actual' size (rather than just using its mediabox 1:1) + ctm = CGContextGetCTM(_cg_port()) + ctm_scale = max(math.hypot(ctm.a, ctm.b), math.hypot(ctm.c, ctm.d)) + xf_a, xf_b, xf_c, xf_d, _, _ = self._screen_transform.matrix + xf_scale = max(math.hypot(xf_a, xf_b), math.hypot(xf_c, xf_d)) + scale = max(ctm_scale * xf_scale, 1.0) + src_w, src_h = self._nsImage.size() + + return max(1, round(src_w * scale)), max(1, round(src_h * scale)) + def _draw(self): """Draw an image on the given coordinates.""" @@ -390,4 +440,3 @@ def finish(self): if self.session.writer.doneWriting(): break time.sleep(0.1) - diff --git a/plotdevice/gfx/text.py b/plotdevice/gfx/text.py index 1039971c..4779300d 100644 --- a/plotdevice/gfx/text.py +++ b/plotdevice/gfx/text.py @@ -332,7 +332,8 @@ def flow(self, columns=all, layout=None): if not layout: return self._reflow(columns) # return the generator for iteration - map(layout, self._reflow(columns)) # apply the layout function to each block in sequence + for block in self._reflow(columns): + layout(block) # apply the layout function to each block in sequence def _reflow(self, count): # wipe out any previously set blocks then keep adding new ones until @@ -926,4 +927,3 @@ def draw(self): # we inherit from Grob for the methods, not drawability codependent = "TextBlocks can't be drawn directly; plot() the parent Text object instead" raise DeviceError(codependent) - diff --git a/plotdevice/gui/app.py b/plotdevice/gui/app.py index 7d312844..61479d30 100644 --- a/plotdevice/gui/app.py +++ b/plotdevice/gui/app.py @@ -31,6 +31,9 @@ def awakeFromNib(self): except OSError: pass except IOError: pass + def applicationSupportsSecureRestorableState_(self, app): + return True + def applicationDidFinishLaunching_(self, note): mm=NSApp().mainMenu() diff --git a/plotdevice/gui/document.py b/plotdevice/gui/document.py index 586dfa9d..c720ed63 100644 --- a/plotdevice/gui/document.py +++ b/plotdevice/gui/document.py @@ -91,7 +91,7 @@ def encodeRestorableStateWithCoder_(self, coder): def restoreStateWithCoder_(self, coder): super(PlotDeviceDocument, self).restoreStateWithCoder_(coder) - self.stationery = coder.decodeObjectForKey_("plotdevice:stationery") + self.stationery = coder.decodeObjectOfClass_forKey_(NSString, "plotdevice:stationery") if self.stationery: self.script.setStationery_(self.stationery) @@ -259,7 +259,7 @@ def encodeRestorableStateWithCoder_(self, coder): def restoreStateWithCoder_(self, coder): # restore the splitview positions (if rects were autosaved) - split_frames = coder.decodeObjectForKey_("plotdevice:split_rects") + split_frames = coder.decodeObjectOfClasses_forKey_({NSArray, NSString}, "plotdevice:split_rects") if split_frames: it = self.editorView while it.superview(): @@ -428,8 +428,17 @@ def runScript(self): self.editorView.clearErrors() self.outputView.clear(timestamp=True) + if self.editorView: + # fetch the current source from the editor (asynchronously), then run it + self.editorView.with_source(self._runSource) + else: + # run in the (editor-less) CLI-spawned window + self._runSource(self.vm.source) + + @objc.python_method + def _runSource(self, source): # Compile the script and run its global scope - self.vm.source = self.source + self.vm.source = source success = self.invoke(None) # Display the dashboard if the var() command was called @@ -587,9 +596,14 @@ def exportInit(self, kind, fname, opts): if self.statusView: self.statusView.beginExport() - # let the Sandbox take over - self.vm.source = self.source - self.vm.export(kind, fname, opts) + # fetch the current source, then let the Sandbox take over + def begin(source): + self.vm.source = source + self.vm.export(kind, fname, opts) + if self.editorView: + self.editorView.with_source(begin) + else: + begin(self.vm.source) @objc.python_method def exportFrame(self, status, canvas=None): diff --git a/plotdevice/gui/editor.py b/plotdevice/gui/editor.py index 7d8044b3..794b2373 100644 --- a/plotdevice/gui/editor.py +++ b/plotdevice/gui/editor.py @@ -3,11 +3,10 @@ import re import json import objc -from io import open from objc import super from time import time from ..lib.cocoa import * -from plotdevice.gui.preferences import get_default, editor_info +from plotdevice.gui.preferences import get_default, editor_info, dev_extras from plotdevice.gui import bundle_path, set_timeout __all__ = ['EditorView', 'OutputTextView'] @@ -15,10 +14,7 @@ def args(*jsargs): return ', '.join([json.dumps(v, ensure_ascii=False) for v in jsargs]) -class DraggyWebView(WebView): - def initWithFrame_(self, rect): - return self.initWithFrame_frameName_groupName_(rect, None, None) - +class DraggyWebView(WKWebView): def draggingEntered_(self, sender): pb = sender.draggingPasteboard() options = { NSPasteboardURLReadingFileURLsOnlyKey:True, @@ -28,7 +24,15 @@ def draggingEntered_(self, sender): rewrite = "\n".join(['"%s"'%u.path() for u in urls] + strs) + "\n" pb.declareTypes_owner_([NSStringPboardType], self) pb.setString_forType_(rewrite, NSStringPboardType) - return super(DraggyWebView, self).draggingEntered_(sender) + # don't consult super: WKWebView's drag negotiation round-trips to the web + # process and can veto the rewritten pasteboard + return NSDragOperationCopy + + def draggingUpdated_(self, sender): + return NSDragOperationCopy + + def prepareForDragOperation_(self, sender): + return True def performDragOperation_(self, sender): pb = sender.draggingPasteboard() @@ -40,8 +44,29 @@ def performDragOperation_(self, sender): return True return False - def shouldCloseWithWindow(self): - return True + def willOpenMenu_withEvent_(self, menu, event): + # omit everything from context menu except Cut/Copy/Paste (+ Inspect Element) + keep = ('WKMenuItemIdentifierCut', 'WKMenuItemIdentifierCopy', + 'WKMenuItemIdentifierPaste', 'WKMenuItemIdentifierInspectElement') + for item in list(menu.itemArray()): + if item.identifier() not in keep: + menu.removeItem_(item) + inspect = menu.indexOfItemWithIdentifier_('WKMenuItemIdentifierInspectElement') + if inspect > 0: + menu.insertItem_atIndex_(NSMenuItem.separatorItem(), inspect) + + # TODO: once a doc viewer exists, add a lookup-ref menu item pointing to it: + # word = self.js('editor.selected') + # _ns = ['curveto', 'TEXT', 'BezierPath', ...] + # def ref_url(proc): + # if proc in _ns: + # return proc + # if ref_url(word): + # doc = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(u"Documentation for ‘%s()’"%word, "copy:", "") + # sep = NSMenuItem.separatorItem() + # items.insert(0, sep) + # items.insert(0, doc) + class EditorView(NSView): document = IBOutlet() @@ -51,93 +76,91 @@ class EditorView(NSView): # WebKit mgmt def awakeFromNib(self): - self.webview = DraggyWebView.alloc().initWithFrame_(self.bounds()) - self.webview.setAllowsUndo_(False) - self.webview.setFrameLoadDelegate_(self) - self.webview.setUIDelegate_(self) + config = WKWebViewConfiguration.alloc().init() + dev = dev_extras() # whether to enable Inspect Element menu + config.preferences().setValue_forKey_(dev, 'developerExtrasEnabled') + ucc = config.userContentController() + ucc.addScriptMessageHandler_name_(self, 'app') + shim = WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_( + # forward any app.() call as a message to the 'app' script-message handler + """window.app = new Proxy({}, { + get: (_, fn) => (...args) => window.webkit.messageHandlers.app.postMessage({fn:String(fn), args:args}) + });""", WKUserScriptInjectionTimeAtDocumentStart, True) + ucc.addUserScript_(shim) + + self.webview = DraggyWebView.alloc().initWithFrame_configuration_(self.bounds(), config) + if dev and self.webview.respondsToSelector_('setInspectable:'): + self.webview.setInspectable_(True) + self.webview.setValue_forKey_(False, 'drawsBackground') + self.webview.setNavigationDelegate_(self) self.addSubview_(self.webview) - self.webview.setHidden_(True) - - html = bundle_path(rsrc='ui/editor.html') - ui = open(html, encoding='utf-8').read() - baseurl = NSURL.fileURLWithPath_(os.path.dirname(html)) - self.webview.mainFrame().loadHTMLString_baseURL_(ui, baseurl) - - # set a theme-derived background for the webview's clipview - docview = self.webview.mainFrame().frameView().documentView() - clipview = docview.superview() - scrollview = clipview.superview() - if clipview is not None: - bgcolor = editor_info('colors')['background'] - clipview.setDrawsBackground_(True) - clipview.setBackgroundColor_(bgcolor) - scrollview.setVerticalScrollElasticity_(1) - scrollview.setScrollerKnobStyle_(2) nc = NSNotificationCenter.defaultCenter() nc.addObserver_selector_name_object_(self, "themeChanged", "ThemeChanged", None) nc.addObserver_selector_name_object_(self, "fontChanged", "FontChanged", None) nc.addObserver_selector_name_object_(self, "bindingsChanged", "BindingsChanged", None) nc.addObserver_selector_name_object_(self, "insertDroppedFiles:", "DropOperation", self.webview) - self._wakeup = set_timeout(self, '_jostle', .05, repeat=True) - self._queue = [] self._edits = 0 - self.themeChanged() - self.fontChanged() - self.bindingsChanged() + self._last_source = '' + self._refresh() mm=NSApp().mainMenu() self._doers = mm.itemWithTitle_('Edit').submenu().itemArray()[1:3] self._undo_mgr = None def drawRect_(self, rect): - if self._wakeup: - # try to minimize the f.o.u.c. while the webview starts up - bgcolor = editor_info('colors')['background'] - bgcolor.setFill() - NSRectFillUsingOperation(rect, NSCompositeCopy) + # the webview is transparent, so draw the theme background here + bgcolor = editor_info('colors')['background'] + bgcolor.setFill() + NSRectFillUsingOperation(rect, NSCompositeCopy) super(EditorView, self).drawRect_(rect) - - def _jostle(self): - awoke = self.webview.stringByEvaluatingJavaScriptFromString_('window.editor && window.editor.ready') - if awoke: - for op in self._queue: - self.webview.stringByEvaluatingJavaScriptFromString_(op) - self._wakeup.invalidate() - self._wakeup = None - self._queue = None - self.webview.setHidden_(False) + @objc.python_method + def _refresh(self): + # (re)load the editor page, queueing up prefs & source to be applied once it + # signals readiness (see webView_didFinishNavigation_) + self._loading = True + self._queue = [] + self.webview.setHidden_(True) + self.themeChanged() + self.fontChanged() + self.bindingsChanged() + self._set_source(self._last_source) + html = bundle_path(rsrc='ui/editor.html') + ui_dir = NSURL.fileURLWithPath_isDirectory_(os.path.dirname(html), True) + self.webview.loadFileURL_allowingReadAccessToURL_(NSURL.fileURLWithPath_(html), ui_dir) + + def _ready(self): + # js editor has been loaded and is ready to receive queued prefs & source + self._loading = False + for op in self._queue: + self.webview.evaluateJavaScript_completionHandler_(op, None) + self._queue = [] + self.webview.setHidden_(False) + self.setNeedsDisplay_(True) def _cleanup(self): + # break the retain cycle through the WKUserContentController (it holds its + # message handlers strongly) + self.webview.configuration().userContentController().removeScriptMessageHandlerForName_('app') + self.webview.setNavigationDelegate_(None) nc = NSNotificationCenter.defaultCenter() nc.removeObserver_(self) self._doers = self._undo_mgr = self.jumpPanel = self.jumpLine = None - # def webView_didFinishLoadForFrame_(self, sender, frame): - def webView_didClearWindowObject_forFrame_(self, sender, win, frame): - self.webview.windowScriptObject().setValue_forKey_(self, 'app') + def webView_didFinishNavigation_(self, sender, nav): + # called after DOMContentLoaded + self._ready() - def webView_contextMenuItemsForElement_defaultMenuItems_(self, sender, elt, menu): - items = [ - NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Cut", "cut:", ""), - NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Copy", "copy:", ""), - NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Paste", "paste:", ""), - NSMenuItem.separatorItem(), - ] + def webViewWebContentProcessDidTerminate_(self, sender): + # try to restore if the webkit subprocess crashes + self._refresh() - # once a doc viewer exists, add a lookup-ref menu item pointing to it: - # word = self.js('editor.selected') - # _ns = ['curveto', 'TEXT', 'BezierPath', ...] - # def ref_url(proc): - # if proc in _ns: - # return proc - # if ref_url(word): - # doc = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(u"Documentation for ‘%s()’"%word, "copy:", "") - # sep = NSMenuItem.separatorItem() - # items.insert(0, sep) - # items.insert(0, doc) - return items + [it for it in menu if it.title()=='Inspect Element'] + def userContentController_didReceiveScriptMessage_(self, ucc, msg): + body = msg.body() + fn, fnargs = body['fn'], list(body.get('args', [])) + if fn in ('sync_edits', 'flash_menu', 'setSearchPasteboard', 'cancelRun', 'loadPrefs'): + getattr(self, fn)(*fnargs) def resizeSubviewsWithOldSize_(self, oldSize): self.resizeWebview() @@ -148,9 +171,6 @@ def resizeWebview(self): def insertDroppedFiles_(self, note): self.js('editor.insert', args(note.userInfo())) - def isSelectorExcludedFromWebScript_(self, sel): - return False - def windowDidResignKey_(self, note): if note.object() is self.jumpPanel: self.jumpPanel.orderOut_(self) @@ -182,26 +202,40 @@ def mouseExited_(self, e): @objc.python_method def _get_source(self): - return self.webview.stringByEvaluatingJavaScriptFromString_('editor.source();') + # return the copy of the source relayed by the last sync_edits call from js + return self._last_source @objc.python_method def _set_source(self, src): + self._last_source = src self.js('editor.source', args(src)) source = property(_get_source, _set_source) + @objc.python_method + def with_source(self, callback): + """Read the editor buffer asynchronously, then invoke callback(src)""" + if self._loading: # webview not ready: use the shadow copy + return callback(self._last_source) + def done(value, error): + if value is not None: + self._last_source = value + callback(self._last_source) + self.webview.evaluateJavaScript_completionHandler_('editor.source();', done) + def fontChanged(self, note=None): info = editor_info() self.js('editor.font', args(info['family'], info['px'])) def themeChanged(self, note=None): info = editor_info() - clipview = self.webview.mainFrame().frameView().documentView().superview() - clipview.setBackgroundColor_(info['colors']['background']) self.js('editor.theme', args(info['module'])) + self.setNeedsDisplay_(True) def bindingsChanged(self, note=None): self.js('editor.bindings', args(get_default('bindings'))) def focus(self): + if self.window(): + self.window().makeFirstResponder_(self.webview) self.js('editor.focus') def blur(self): @@ -222,10 +256,10 @@ def report(self, crashed, script): @objc.python_method def js(self, cmd, args=''): op = '%s(%s);'%(cmd,args) - if self._wakeup: + if self._loading: self._queue.append(op) else: - return self.webview.stringByEvaluatingJavaScriptFromString_(op) + self.webview.evaluateJavaScript_completionHandler_(op, None) # Menubar actions @@ -301,7 +335,8 @@ def cancelRun(self): menu = mm.itemWithTitle_("Python") menu.submenu().performActionForItemAtIndex_(3) - def edits_(self, count): + @objc.python_method + def sync_edits(self, count, source=None): # inform the undo manager of the changes um = self._undo_mgr c = int(count) @@ -316,6 +351,10 @@ def edits_(self, count): for item, can in zip(self._doers, (um.canUndo(), um.canRedo())): item.setEnabled_(can) + # cache the editor's contents for use by the synchronous .source property + if isinstance(source, str): + self._last_source = source + def syncUndoState_(self, count): pass # this would be useful if only it got called for redo as well as undo... @@ -326,9 +365,10 @@ def setSearchPasteboard(self, query): pb = NSPasteboard.pasteboardWithName_(NSFindPboard) pb.declareTypes_owner_([NSStringPboardType],None) pb.setString_forType_(query, NSStringPboardType) - self.flash_("Edit") + self.flash_menu("Edit") - def flash_(self, menuname): + @objc.python_method + def flash_menu(self, menuname): # when a menu item's key command was entered in the editor, flash the menu # bar to give a hint of where the command lives mm=NSApp().mainMenu() diff --git a/plotdevice/gui/preferences.py b/plotdevice/gui/preferences.py index acc9eea4..00bf3d3e 100644 --- a/plotdevice/gui/preferences.py +++ b/plotdevice/gui/preferences.py @@ -22,8 +22,7 @@ def set_default(label, value): def defaultDefaults(): return { - "WebKitDeveloperExtras":True, - "plotdevice:theme":"Solarized Dark", + "plotdevice:theme":"Samizdat", "plotdevice:bindings":"mac", "plotdevice:font-name":"Menlo", "plotdevice:font-size":11, @@ -32,6 +31,23 @@ def defaultDefaults(): NSUserDefaults.standardUserDefaults().registerDefaults_(defaultDefaults()) THEMES = None # to be filled in as needed +def dev_extras(): + """Should the editor's web inspector be enabled? + + Defaults to on for dev builds (running from the source checkout, or an app + built by `make.py app`/`py2app`) and off for official releases (which have a + real version stamped over info.plist's "in flux" placeholder by `make.py dist`). + Either behavior can be forced with e.g.: + defaults write io.plotdevice.PlotDevice WebKitDeveloperExtras -bool YES + """ + override = get_default('WebKitDeveloperExtras') + if override is not None: + return bool(override) + bundle = NSBundle.mainBundle() + if bundle.bundleIdentifier() != 'io.plotdevice.PlotDevice': + return True # running from the source checkout rather than PlotDevice.app + return bundle.objectForInfoDictionaryKey_('CFBundleVersion') == 'in flux' + def _hex_to_nscolor(hexclr): hexclr = hexclr.lstrip('#') r, g, b, a = [int(n, 16)/255.0 for n in (hexclr[0:2], hexclr[2:4], hexclr[4:6], hexclr[6:8])] diff --git a/plotdevice/gui/views.py b/plotdevice/gui/views.py index 92e3e00c..eb932872 100644 --- a/plotdevice/gui/views.py +++ b/plotdevice/gui/views.py @@ -147,7 +147,8 @@ def _get_zoom(self): @objc.python_method def _set_zoom(self, zoom): self._zoom = zoom - self.setCanvas(self.canvas) + if self.canvas is not None: + self.setCanvas(self.canvas) zoom = property(_get_zoom, _set_zoom) @objc.python_method diff --git a/plotdevice/lib/cocoa.py b/plotdevice/lib/cocoa.py index bf74f74d..a6962d61 100644 --- a/plotdevice/lib/cocoa.py +++ b/plotdevice/lib/cocoa.py @@ -4,7 +4,7 @@ CGColorSpaceCreateDeviceCMYK, CGColorSpaceCreateDeviceRGB, CGContextAddPath, CGContextAddRect, \ CGContextBeginPath, CGContextBeginTransparencyLayer, CGContextBeginTransparencyLayerWithRect, \ CGContextClearRect, CGContextClearRect, CGContextClip, CGContextClipToMask, CGContextDrawPath, \ - CGContextEndTransparencyLayer, CGContextEOClip, CGContextRestoreGState, CGContextSaveGState, \ + CGContextEndTransparencyLayer, CGContextEOClip, CGContextGetCTM, CGContextRestoreGState, CGContextSaveGState, \ CGContextSetAlpha, CGContextSetBlendMode, CGContextSetFillColorWithColor, CGContextSetLineCap, \ CGContextSetLineDash, CGContextSetLineJoin, CGContextSetLineWidth, \ CGContextSetStrokeColorWithColor, CGDataConsumerCreateWithCFData, CGImageDestinationAddImage, \ @@ -29,9 +29,9 @@ NSBackingStoreBuffered, NSBeep, NSBezierPath, NSBitmapImageRep, NSBorderlessWindowMask, NSButton, \ NSCenterTextAlignment, NSChangeAutosaved, NSChangeCleared, NSChangeDone, NSChangeReadOtherContents, \ NSChangeRedone, NSChangeUndone, NSClipView, NSClosePathBezierPathElement, NSColor, NSColorSpace, \ - NSCompositeCopy, NSCompositeSourceOver, NSContentsCellMask, NSCriticalAlertStyle, NSCursor, \ - NSCurveToBezierPathElement, NSDeviceCMYKColorSpace, NSDeviceRGBColorSpace, NSDocument, \ - NSDocumentController, NSFindPboard, NSFixedPitchFontMask, NSFocusRingTypeExterior, NSFont, \ + NSCommandKeyMask, NSCompositeCopy, NSCompositeSourceOver, NSContentsCellMask, NSCriticalAlertStyle, NSCursor, \ + NSCurveToBezierPathElement, NSDeviceCMYKColorSpace, NSDeviceIndependentModifierFlagsMask, NSDeviceRGBColorSpace, NSDocument, \ + NSDocumentController, NSDragOperationCopy, NSFindPboard, NSFixedPitchFontMask, NSFocusRingTypeExterior, NSFont, \ NSFontDescriptor, NSFontManager, NSForegroundColorAttributeName, NSGIFFileType, NSGradient, \ NSGraphicsContext, NSGraphiteControlTint, NSImage, NSImageCacheNever, NSImageCompressionFactor, \ NSImageInterpolationHigh, NSItalicFontMask, NSJPEGFileType, NSJustifiedTextAlignment, \ @@ -50,13 +50,14 @@ NSWindowBackingLocationVideoMemory, NSWindowController, NSWindowTabbingModeAutomatic, \ NSWindowTabbingModePreferred, NSWorkspace from Foundation import CIAffineTransform, CIColorMatrix, CIContext, CIFilter, CIImage, CIVector, Foundation, NO, \ - NSAffineTransform, NSAffineTransformStruct, NSAttributedString, NSAutoreleasePool, NSBundle, \ - NSData, NSDate, NSDateFormatter, NSFileCoordinator, NSFileHandle, \ + NSAffineTransform, NSAffineTransformStruct, NSArray, NSAttributedString, NSAutoreleasePool, \ + NSBundle, NSData, NSDate, NSDateFormatter, NSFileCoordinator, NSFileHandle, \ NSFileHandleDataAvailableNotification, NSHeight, NSInsetRect, NSIntersectionRange, \ NSIntersectionRect, NSLocale, NSLog, NSMacOSRomanStringEncoding, NSMakeRange, NSMidX, NSMidY, \ NSMutableAttributedString, NSMutableData, NSNotificationCenter, NSObject, NSOffsetRect, \ NSOperationQueue, NSPoint, NSRect, NSRectFromString, NSSelectorFromString, NSSize, NSString, \ - NSStringFromRect, NSTimer, NSTimeZone, NSURL, NSUserDefaults, NSUTF8StringEncoding, NSWidth -from LaunchServices import kUTTypePNG, kUTTypeJPEG, kUTTypeGIF, kUTTypeTIFF -from WebKit import WebView + NSStringFromRect, NSTimer, NSTimeZone, NSURL, NSUserDefaults, NSUTF8StringEncoding, NSWidth, \ + NSZeroRect +from CoreServices import kUTTypePNG, kUTTypeJPEG, kUTTypeGIF, kUTTypeTIFF +from WebKit import WKWebView, WKWebViewConfiguration, WKUserScript, WKUserScriptInjectionTimeAtDocumentStart from objc import IBOutlet, IBAction diff --git a/plotdevice/run/__init__.py b/plotdevice/run/__init__.py index 5dc8ec27..8c903261 100644 --- a/plotdevice/run/__init__.py +++ b/plotdevice/run/__init__.py @@ -9,13 +9,13 @@ except ImportError: # detect whether we're being run from the repository and set up a local env if so repo = abspath(join(dirname(__file__), '../..')) - setup_py = '%s/setup.py' % repo - if exists(setup_py): + make_py = '%s/make.py' % repo + if exists(make_py): from platform import python_version from sysconfig import get_config_var local_libs = '%s/deps/local/%s/lib/python%s/site-packages' % (repo, python_version(), get_config_var('py_version_short')) if not exists(local_libs): - call([sys.executable, setup_py, 'dev']) + call([sys.executable, make_py, 'dev']) site.addsitedir(local_libs) from Foundation import * import objc diff --git a/plotdevice/run/console.py b/plotdevice/run/console.py index 294d7904..408da915 100755 --- a/plotdevice/run/console.py +++ b/plotdevice/run/console.py @@ -17,6 +17,7 @@ import json import select import signal +from objc import super from math import floor, ceil from os.path import dirname, abspath, exists, join from io import open @@ -100,9 +101,12 @@ def openLink_(self, sender): link += '/doc' NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(link)) - def done(self, quit=False): - if self.opts['mode']=='headless' or quit: - NSApp().terminate_(None) + def done(self, ok=True): + if self.opts['mode']=='headless': + if not ok: + os._exit(1) + else: + NSApp().terminate_(None) class ScriptWatcher(NSObject): def initWithScript_(self, script): @@ -122,7 +126,7 @@ class ConsoleScript(ScriptController): def init(self): self._init_state() self._buf = '' # cache the export progress message between stdout writes - return super(ScriptController, self).init() + return super(ConsoleScript, self).init() def setScript_options_(self, path, opts): self.vm.path = path @@ -171,7 +175,7 @@ def invokeHeadless(self, method): result = self.vm.run(method) self.echo(result.output) if not result.ok: - NSApp().terminate_(None) + NSApp().delegate().done(ok=False) def runHeadless(self): self.vm.source = self.unicode_src @@ -202,7 +206,7 @@ def echo(self, output): def exportFrame(self, status, canvas=None): super(ConsoleScript, self).exportFrame(status, canvas) if not status.ok: - NSApp().delegate().done() + NSApp().delegate().done(ok=False) @objc.python_method def exportStatus(self, event): diff --git a/plotdevice/util/__init__.py b/plotdevice/util/__init__.py index 14351df2..dee5f490 100644 --- a/plotdevice/util/__init__.py +++ b/plotdevice/util/__init__.py @@ -5,8 +5,9 @@ import json import csv from contextlib import contextmanager -from functools import cmp_to_key +from functools import cmp_to_key, reduce from collections import OrderedDict, defaultdict +from collections.abc import Mapping from os.path import abspath, dirname, exists, join, splitext from random import choice, shuffle @@ -106,18 +107,15 @@ def _key(seq, names): # treat objects without a given key and objects with the key set to None equivalently MISSING = None - # look for dict items by default - getter = lambda obj: tuple(obj.get(n, MISSING) for n in names) + def getter(obj): + # use item lookup if obj is dict-like + if isinstance(obj, Mapping): + return tuple(obj.get(n, MISSING) for n in names) - # walk through the objects and see if any of them lacks the dict fields but has attrs - for obj in seq: - try: - if any(n in obj for n in names): - break # dict fields exist so use the itemgetter - except TypeError: - # for non-dict objects like namedtuples and dataclasses use an attrgetter - if any(hasattr(obj, n) for n in names): - getter = lambda obj: tuple(getattr(obj, n, MISSING) for n in names) + # otherwise check attrs + return tuple( + reduce(lambda o, part: getattr(o, part, MISSING), n.split('.'), obj) for n in names + ) def compare(a, b): default = 0 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..6b97df29 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "plotdevice" +dynamic = ["version"] +description = "Create two-dimensional graphics and animations with code" +readme = { file = "README.md", content-type = "text/markdown" } +requires-python = ">=3.9" +license = "MIT" +authors = [{ name = "Christian Swinehart", email = "drafting@samizdat.co" }] +urls = { Homepage = "https://plotdevice.io/" } +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: MacOS X :: Cocoa", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "Operating System :: MacOS :: MacOS X", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Artistic Software", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Editors :: Vector-Based", + "Topic :: Multimedia :: Video", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Software Development :: User Interfaces", + "Topic :: Text Editors :: Integrated Development Environments (IDE)", +] +dependencies = [ + "requests", + "cachecontrol[filecache]", + "pyobjc-core==11.1", + "pyobjc-framework-Quartz==11.1", + "pyobjc-framework-CoreServices==11.1", + "pyobjc-framework-WebKit==11.1", +] + +[tool.setuptools] +script-files = ["app/plotdevice"] +zip-safe = false + +[tool.setuptools.dynamic] +version = { attr = "plotdevice.__version__" } + +[tool.setuptools.packages.find] +include = ["plotdevice*"] +exclude = ["tests*"] + +[tool.setuptools.package-data] +plotdevice = ["rsrc/*"] + +[tool.cibuildwheel] +build = "cp39-* cp310-* cp311-* cp312-* cp313-* cp314-*" + +[tool.cibuildwheel.macos] +archs = ["universal2"] +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } diff --git a/setup.py b/setup.py index 61b181a6..3f7f36f1 100644 --- a/setup.py +++ b/setup.py @@ -1,273 +1,53 @@ -# encoding:utf-8 +# encoding: utf-8 +"""setup.py for building the mixed C/Objective-C/Swift `_plotdevice` extension -# To install the module & command line tool in site-packages, use: -# python3 setup.py install -# -# In addition to the `install' command, there are a few other variants: -# app: builds ./dist/PlotDevice.app using Xcode -# py2app: builds the application using py2app -# clean: discard anything already built and start fresh -# test: run unit tests and generate the file "details.html" with the test output -# dev: puts the module in a usable state within the source distribution. after running, -# you should be able to run the `python3 -m plotdevice` command line interface. -# If you're having trouble building the app, this can be a good way to sanity -# check your setup -# -# We require some dependencies: -# - Mac OS X 11+ and Xcode command line tools (type `xcode-select --install` in the terminal) -# - the python3.8 provided by Xcode or a homebrew- or pyenv-built python3 interpreter -# - Sparkle.framework (auto-downloaded only for `dist` builds) -# -import os, sys, json, re, platform +This file exists only for the things `pyproject.toml` can't handle, in particular +the prebuild and compilation steps necessary for setuptools to package the module. +You needn't run this script directly, just use one of the ordinary installation +approaches like `pip install .` or `python -m build`. +""" from glob import glob -from shutil import rmtree -from setuptools import setup, find_packages, Command +from subprocess import call +from os.path import dirname, abspath, exists, join, getmtime + +from setuptools import setup from setuptools.extension import Extension -from setuptools.command.build_py import build_py from setuptools.command.build_ext import build_ext -from pkg_resources import DistributionNotFound -from os.path import join, exists, dirname, basename, abspath, getmtime -from subprocess import call, getoutput -import plotdevice - - -## Metadata ## - -# PyPI fields -APP_NAME = 'PlotDevice' -MODULE = APP_NAME.lower() -VERSION = plotdevice.__version__ -AUTHOR = plotdevice.__author__ -AUTHOR_EMAIL = plotdevice.__email__ -LICENSE = plotdevice.__license__ -URL = "https://plotdevice.io/" -CLASSIFIERS = [ - "Development Status :: 5 - Production/Stable", - "Environment :: MacOS X :: Cocoa", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: End Users/Desktop", - "Operating System :: MacOS :: MacOS X", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Artistic Software", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Editors :: Vector-Based", - "Topic :: Multimedia :: Video", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development :: User Interfaces", - "Topic :: Text Editors :: Integrated Development Environments (IDE)", -] -DESCRIPTION = "Create two-dimensional graphics and animations with code" -LONG_DESCRIPTION = """PlotDevice is a Macintosh application used for computational graphic design. It -provides an interactive Python environment where you can create two-dimensional -graphics and output them in a variety of vector, bitmap, and animation formats. -It is meant both as a sketch environment for exploring generative design and as -a general purpose graphics library for use in external Python programs. - -PlotDevice scripts can create images from simple geometric primitives, text, and -external vector or bitmap images. Drawing commands provide a thin abstraction -over macOS's Quartz graphics engine, providing high-quality rendering -of 2D imagery and powerful compositing operations. - -PlotDevice is a fork of NodeBox 1.9.7rc1 with support for modern versions of -Python and Mac OS. - -The new version features: - -* Runs natively on Intel and Apple Silicon and supports retina displays -* Python 3 only (including a bundled 3.10 installation in the app) -* images can now be exported in HEIC format and videos support H.265 (HEVC) -* SVG files can now be drawn to the canvas using the `image()` command (thanks to the magical [SwiftDraw](https://github.com/swhitty/SwiftDraw) library) -* image exports have a configurable `zoom` to create 2x/3x/etc ‘retina’ images -* revamped `var()` command for creating GUIs to modify values via sliders, buttons, toggles, etc. -* updated text editor with multiple tabs, new themes, and additional key-binding modes emulating Sublime Text and VS Code -* the module's command line interface is now accessible through `python3 -m plotdevice` -* the command line tool has a new `--install` option to download [PyPI](https://pypi.org) packages for use within the app -* document autosaving is now user-configurable -* Bugfixes and misc. improvements detailed in the [changelog](https://github.com/plotdevice/plotdevice/blob/main/CHANGES.md) - -Version 0.9.5 added: - -* Python 3 compatible -* Can now be built with system Python or Homebrew versions of the interpreter -* Much faster import times on Yosemite thanks to a bundled copy of PyObjC 3.0.4 -* HTTP is now handled by the ``requests`` module and caches responses locally -* Totally revamped typography system with support for OpenType features, - pagination, multi-column text, character geometry, and more -* Added 130+ unit tests (run them with ``python setup.py test``) plus bugfixes for - for ``measure()``, ``textpath()``, ``Bezier.fit()``, ``read()``, and the Preferences dialog - -Version 0.9.4 added: - -* Enhanced command line interface. -* New text editor with tab completion, syntax color themes, and emacs/vi bindings. -* Video export in H.264 or animated gif formats (with GCD-based i/o). -* Added support for external editors by reloading the source when changed. -* Build system now works with Xcode or ``py2app`` for the application and ``pip`` for the module. -* Virtualenv support (for both installation of the module and running scripts with dependencies). -* External scripts can use ``from plotdevice import *`` to create a drawing environment. -* Simplified bezier & affine transform api using the python ‘with’ statement -* New compositing operations for blend mode, global opacity, and dropshadows -* Simplified typography commands with stylesheet-based character styles -* Now uses the system's Python 2.7 interpreter. - -Requirements: - -* Mac OS X 11+ -* Python 3.6+ -""" - -## Basic Utils ## - -# the sparkle updater framework will be fetched as needed -SPARKLE_VERSION = '2.1.0' -SPARKLE_URL = 'https://github.com/sparkle-project/Sparkle/releases/download/%(v)s/Sparkle-%(v)s.tar.xz' % {'v':SPARKLE_VERSION} - -# helpers for dealing with plists & git (spiritual cousins if ever there were) -import plistlib -def info_plist(pth='app/info.plist'): - info = plistlib.load(open(pth, 'rb')) - # overwrite the xcode placeholder vars - info['CFBundleExecutable'] = info['CFBundleName'] = APP_NAME - return info - -def update_plist(pth, **modifications): - info = plistlib.load(open(pth, 'rb')) - for key, val in modifications.items(): - if val is None: - info.pop(key) - else: - info[key] = val - with open(pth, 'wb') as f: - plistlib.dump(info, f) - -def update_shebang(pth, interpreter): - body = open(pth).readlines()[1:] - body.insert(0,'#!%s\n'%interpreter) - with open(pth, 'w') as f: - f.writelines(body) - -def last_commit(): - commit_count, _, _ = gosub('git log --oneline | wc -l') - return 'r%s' % commit_count.decode('utf-8').strip() - -def gosub(cmd, on_err=True): - """Run a shell command and return the output""" - from subprocess import Popen, PIPE - shell = isinstance(cmd, str) - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=shell) - out, err = proc.communicate() - ret = proc.returncode - if on_err: - msg = '%s:\n' % on_err if isinstance(on_err, str) else '' - if ret != 0: - print(msg + out.decode('utf8') + err.decode('utf8')) - return out, err, ret +from setuptools.command.sdist import sdist -def timestamp(): - from datetime import datetime - return datetime.now().strftime("%a, %d %b %Y %H:%M:%S") +ROOT = dirname(abspath(__file__)) def stale(dst, src): - if exists(src): - if not exists(dst) or getmtime(dst) < getmtime(src): - yield dst, src + return exists(src) and (not exists(dst) or getmtime(dst) < getmtime(src)) -## Build Commands ## - -class CleanCommand(Command): - description = "wipe out generated files and build artifacts" - user_options = [ - ('dist', None, 'also remove Python.framework and local dependencies'), - ] - - def initialize_options(self): - self.dist = None - - def finalize_options(self): - pass +class BuildExtCommand(build_ext): + """Build SwiftDraw.o before linking _plotdevice, then populate + plotdevice/rsrc/ with the runtime resources plotdevice.util.rsrc_path() + depends on (icns, colors.json, an ibtool-compiled nib).""" def run(self): - paths = [ - 'build', - 'dist', - '*.egg', - '*.egg-info', - '.eggs', - 'MANIFEST.in', - 'PKG', - 'tests/_out', - 'tests/_diff', - 'details.html', - '_plotdevice.*.so', - '**/*.pyc', - '**/__pycache__', - '**/.DS_Store', - ] - - # Add framework paths if --dist flag is used - if self.dist: - paths.extend([ - 'deps/local', - 'deps/frameworks/Python.framework', - 'deps/frameworks/relocatable-python', - ]) - - for path_pattern in paths: - for path in glob(path_pattern, recursive=True): - if exists(path): - print('removing %s'%path) - if os.path.isdir(path): - rmtree(path) - else: - os.unlink(path) - - # Run make clean in svg extensions dir - if exists('deps/extensions/svg'): - os.system('cd deps/extensions/svg && make clean') - -class DistCleanCommand(CleanCommand): - """Alias for `clean --dist` for backward compatibility""" - description = "delete Python.framework, local pypi dependencies, and all generated files" - - def initialize_options(self): - super().initialize_options() - self.dist = True # always do a deep clean + call('cd deps/extensions/svg && make', shell=True, cwd=ROOT) + build_ext.run(self) -class LocalDevCommand(Command): - description = "set up environment to allow for running `python -m plotdevice` within the repo" - user_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - # install pyobjc, requests and the other pypi dependencies in deps/local - import platform - venv_dir = join('deps/local', platform.python_version()) - if not exists(venv_dir): - import venv - venv.create(venv_dir, symlinks=True, with_pip=True) - PIP = '%s/bin/pip3' % venv_dir - call([PIP, 'install', '-q', '--upgrade', 'pip', 'wheel', 'py2app', 'twine']) - call([PIP, '--isolated', 'install', *config['install_requires']]) + # include some ui resources for running a script from the command line + rsrc_dir = join(ROOT, self.build_lib if not self.inplace else '.', 'plotdevice', 'rsrc') + self.mkpath(rsrc_dir) + self.copy_file(join(ROOT, 'app/Resources/PlotDeviceFile.icns'), rsrc_dir) + self.copy_file(join(ROOT, 'app/Resources/colors.json'), rsrc_dir) - # place the compiled c-extensions in the main repo dir - build_ext = self.distribution.get_command_obj('build_ext') - build_ext.inplace = 1 - self.run_command('build_ext') + # recompile the command-line UI nib if necessary + xib = join(ROOT, 'app/Resources/en.lproj/PlotDeviceScript.xib') + nib = join(ROOT, 'app/Resources/viewer.nib') + if stale(nib, xib): + self.spawn(['/usr/bin/ibtool', '--compile', nib, xib]) + self.copy_file(nib, rsrc_dir) - print("\nA local development environment has been set up in %s" % venv_dir) +class SdistPrepCommand(sdist): + """Make sure an sdist can be installed without needing Xcode/ibtool, and + bundles the SwiftDraw sources for offline/reproducible builds.""" -from setuptools.command.sdist import sdist -class BuildDistCommand(sdist): def finalize_options(self): - with open('MANIFEST.in','w') as f: + with open(join(ROOT, 'MANIFEST.in'), 'w') as f: f.write(""" graft app/Resources prune app/Resources/en.lproj @@ -285,289 +65,28 @@ def finalize_options(self): def run(self): # include a compiled nib in the sdist so ibtool (and thus Xcode.app) isn't required to install - for dst, src in stale('app/Resources/viewer.nib', "app/Resources/en.lproj/PlotDeviceScript.xib"): - self.spawn(['/usr/bin/ibtool','--compile', dst, src]) + xib = join(ROOT, 'app/Resources/en.lproj/PlotDeviceScript.xib') + nib = join(ROOT, 'app/Resources/viewer.nib') + if stale(nib, xib): + self.spawn(['/usr/bin/ibtool', '--compile', nib, xib]) # make sure we have the sources for SwiftDraw - call('cd deps/extensions/svg && make SwiftDraw', shell=True) + call('cd deps/extensions/svg && make SwiftDraw', shell=True, cwd=ROOT) - # build the sdist based on our MANIFEST additions + # build the sdist based on our MANIFEST.in additions sdist.run(self) - # clean up - rmtree('plotdevice.egg-info') - os.unlink('MANIFEST.in') - -class BuildCommand(build_py): - def run(self): - - # let the real build_py routine do its thing - build_py.run(self) - - # include some ui resources for running a script from the command line - rsrc_dir = '%s/plotdevice/rsrc'%self.build_lib - self.mkpath(rsrc_dir) - self.copy_file("app/Resources/PlotDeviceFile.icns", rsrc_dir) - self.copy_file("app/Resources/colors.json", rsrc_dir) - - # recompile the command-line UI nib if necessary - xib = 'app/Resources/en.lproj/PlotDeviceScript.xib' - nib = 'app/Resources/viewer.nib' - for dst, src in stale(nib, xib): - self.spawn(['/usr/bin/ibtool','--compile', nib, xib]) - self.copy_file(nib, rsrc_dir) - -class BuildExtCommand(build_ext): - def run(self): - call('cd deps/extensions/svg && make', shell=True) - build_ext.run(self) - -class TestCommand(Command): - description = "Run unit tests" - user_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - from subprocess import call - test_cmd = [sys.executable, '-m', 'tests'] + sys.argv[2:] - print(" ".join(test_cmd)) - call(test_cmd) - -class BuildAppCommand(Command): - description = "Build PlotDevice.app with xcode" - user_options = [ - ('no-cache', None, 'do not use pip cache when installing dependencies'), - ] - - def initialize_options(self): - self.no_cache = None - - def finalize_options(self): - # make sure the embedded framework exists (and has updated app/python.xcconfig) - print("Set up Python.framework for app build") - env = os.environ.copy() - if self.no_cache: - env['PIP_NO_CACHE_DIR'] = '1' - call('cd deps/frameworks && make', shell=True, env=env) - - def run(self): - self.spawn(['xcodebuild', '-configuration', 'Release']) - rmtree('dist/PlotDevice.app.dSYM') - print("done building PlotDevice.app in ./dist") - -try: - import py2app - from py2app.build_app import py2app as build_py2app - class BuildPy2AppCommand(build_py2app): - description = """Build PlotDevice.app with py2app (then undo some of its questionable layout defaults)""" - def initialize_options(self): - build_py2app.initialize_options(self) - def finalize_options(self): - self.verbose=0 - build_py2app.finalize_options(self) - def run(self): - build_py2app.run(self) - if self.dry_run: - return - - # undo py2app's weird treatment of the config.version value - info_pth = 'dist/PlotDevice.app/Contents/Info.plist' - update_plist(info_pth, CFBundleShortVersionString=None) - - # place the command line tool in SharedSupport - BIN = join(dirname(self.resdir), 'SharedSupport') - self.mkpath(BIN) - self.copy_file("app/plotdevice", BIN) - - # success! - print("done building PlotDevice.app in ./dist") - -except (DistributionNotFound, ImportError): - # virtualenv doesn't include pyobjc, py2app, etc. in the sys.path for some reason. - # not being able to access py2app isn't a big deal for 'build', 'app', 'dist', or 'clean' - # so only abort the build if the 'py2app' command was given - if 'py2app' in sys.argv: - print("""setup.py: py2app build failed - Couldn't find the py2app module. To set up a virtualenv that contains all the necessary - dependencies in the deps/local directory, call the `dev` command first: - > python3 setup.py dev - > ./deps/local//bin/python3 setup.py py2app""") - sys.exit(1) - - -## Packaging Commands (really only useful to the maintainer) ## - -class DistCommand(Command): - description = "Create distributable zip of the app and release metadata" - user_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - APP = 'dist/PlotDevice.app' - ZIP = 'dist/PlotDevice_app-%s.zip' % VERSION - - # run the Xcode build - self.run_command('app') - - # set the bundle version to the current commit number and prime the updater - info_pth = 'dist/PlotDevice.app/Contents/Info.plist' - update_plist(info_pth, - CFBundleVersion = last_commit(), - CFBundleShortVersionString = VERSION, - SUFeedURL = 'https://plotdevice.io/app.xml', - SUEnableSystemProfiling = 'YES' - ) - - # Download Sparkle (if necessary) and copy it into the bundle - ORIG = 'deps/frameworks/Sparkle.framework' - SPARKLE = join(APP,'Contents/Frameworks/Sparkle.framework') - if not exists(ORIG): - self.mkpath(dirname(ORIG)) - print("Downloading Sparkle.framework") - os.system('curl -L -# %s | xz -dc | tar xf - -C %s %s'%(SPARKLE_URL, dirname(ORIG), basename(ORIG))) - self.mkpath(dirname(SPARKLE)) - self.spawn(['ditto', ORIG, SPARKLE]) - - # code-sign the app and embedded frameworks, then verify - def codesign(root, name=None, exec=False, entitlement=False): - test = [] - if name: - test += ['-name', name] - if exec: - test += ['-perm', '-u=x'] - - codesign = ['codesign', '--deep', '--strict', '--timestamp', '-o', 'runtime', '-f', '-v', '-s', 'Developer ID Application'] - if entitlement: - codesign += ['--entitlements', 'app/PlotDevice.entitlements'] - - if test: - self.spawn(['find', root, '-type', 'f', *test, '-exec', *codesign, "{}", ";"]) - else: - self.spawn([*codesign, root]) - - PYTHON = join(APP,'Contents/Frameworks/Python.framework') - codesign('%s/Versions/Current/lib'%PYTHON, name="*.dylib") - codesign('%s/Versions/Current/lib'%PYTHON, name="*.o") - codesign('%s/Versions/Current/lib'%PYTHON, name="*.a") - codesign('%s/Versions/Current/lib'%PYTHON, exec=True) - codesign('%s/Versions/Current/bin'%PYTHON, exec=True) - codesign('%s/Versions/Current/bin'%PYTHON, name="python3.*", entitlement=True) - codesign('%s/Versions/Current/Resources/Python.app'%PYTHON, entitlement=True) - codesign(PYTHON) - - codesign('%s/Versions/Current/Updater.app'%SPARKLE) - codesign(SPARKLE) - - codesign(APP, entitlement=True) - self.spawn(['codesign', '--verify', '--deep', '-vv', APP]) - - # create versioned zipfile of the app & notarize it - self.spawn(['ditto', '-ck', '--keepParent', APP, ZIP]) - self.spawn(['xcrun', 'notarytool', 'submit', ZIP, '--keychain-profile', 'AC_NOTARY', '--wait']) - - # staple notarization ticket and regenerate zip - self.spawn(['xcrun', 'stapler', 'staple', APP]) - self.spawn(['ditto','-ck', '--keepParent', APP, ZIP]) - - # write out the release metadata for plotdevice-site to consume/merge - with open('dist/release.json','w') as f: - release = dict(zipfile=basename(ZIP), bytes=os.path.getsize(ZIP), - version=VERSION, revision=last_commit(), - timestamp=timestamp()) - json.dump(release, f) - - print("\nBuilt PlotDevice.app, %s, and release.json in ./dist" % basename(ZIP)) - -# common config between module and app builds -config = dict( - name = MODULE, - version = VERSION, - description = DESCRIPTION, - long_description = LONG_DESCRIPTION, - long_description_content_type = 'text/markdown', - author = AUTHOR, - author_email = AUTHOR_EMAIL, - url = URL, - license = LICENSE, - classifiers = CLASSIFIERS, - packages = find_packages(exclude=['tests']), - ext_modules = [Extension( +setup( + ext_modules=[Extension( '_plotdevice', - sources = ['deps/extensions/module.m', *glob('deps/extensions/*/*.[cm]')], + sources=['deps/extensions/module.m', *glob('deps/extensions/*/*.[cm]')], extra_objects=['deps/extensions/svg/SwiftDraw.o'], extra_link_args=sum((['-framework', fmwk] for fmwk in ['AppKit', 'Foundation', 'Quartz', 'Security', 'AVFoundation', 'CoreMedia', 'CoreVideo', 'CoreText'] - ), []) + ), []), )], - install_requires = [ - 'requests', - 'cachecontrol[filecache]', - 'pyobjc-core==11.0', - 'pyobjc-framework-Quartz==11.0', - 'pyobjc-framework-LaunchServices==11.0', - 'pyobjc-framework-WebKit==11.0', - ], - scripts = ["app/plotdevice"], - zip_safe=False, cmdclass={ - 'app': BuildAppCommand, - 'build_py': BuildCommand, 'build_ext': BuildExtCommand, - 'clean': CleanCommand, - 'distclean': DistCleanCommand, - 'dist': DistCommand, - 'sdist': BuildDistCommand, - 'test': TestCommand, - 'dev': LocalDevCommand, + 'sdist': SdistPrepCommand, }, ) - -## Run Build ## - -if __name__=='__main__': - # make sure we're at the project root regardless of the cwd - # (this means the various commands don't have to play path games) - os.chdir(dirname(abspath(__file__))) - - # clear away any finder droppings that may have accumulated - call('find . -name .DS_Store -delete', shell=True) - - # py2app-specific config - if 'py2app' in sys.argv: - config.update(dict( - app = [{ - 'script': "app/plotdevice-app.py", - "plist":info_plist(), - }], - data_files = [ - "app/Resources/ui", - "app/Resources/colors.json", - "app/Resources/en.lproj", - "app/Resources/PlotDevice.icns", - "app/Resources/PlotDeviceFile.icns", - "examples", - ], - options = { - "py2app": { - "iconfile": "app/Resources/PlotDevice.icns", - "semi_standalone":True, - "site_packages":True, - "strip":False, - } - }, - cmdclass={ - 'build_py': BuildCommand, - 'py2app': BuildPy2AppCommand, - } - )) - - # include a backport of dataclasses on 3.6 - if sys.version_info < (3,7): - config['install_requires'].append('dataclasses') - - # begin the build process - setup(**config) diff --git a/tests/__init__.py b/tests/__init__.py index a28c5fd6..b310a78b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -70,10 +70,10 @@ def render(self, save_output=True): pass def suites(): - from . import typography, primitives, drawing, compositing, geometry, module + from . import typography, primitives, drawing, compositing, geometry, module, canvas suite = unittest.TestSuite() - for mod in (typography, primitives, drawing, compositing, geometry, module): + for mod in (typography, primitives, drawing, compositing, geometry, module, canvas): suite.addTest(mod.suite()) return suite diff --git a/tests/__main__.py b/tests/__main__.py index f3035676..0f2d0da5 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -14,5 +14,6 @@ tests = suites() verb = 2 if '-v' in sys.argv else 1 -unittest.TextTestRunner(verbosity=verb).run(tests) -report() \ No newline at end of file +result = unittest.TextTestRunner(verbosity=verb).run(tests) +report() +sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tests/_in/tiny-triangle.pdf b/tests/_in/tiny-triangle.pdf new file mode 100644 index 00000000..a90db929 Binary files /dev/null and b/tests/_in/tiny-triangle.pdf differ diff --git a/tests/_ref/compositing/clip-vector-image-rotate.png b/tests/_ref/compositing/clip-vector-image-rotate.png new file mode 100644 index 00000000..a47dab8b Binary files /dev/null and b/tests/_ref/compositing/clip-vector-image-rotate.png differ diff --git a/tests/_ref/compositing/clip-vector-image-scale.png b/tests/_ref/compositing/clip-vector-image-scale.png new file mode 100644 index 00000000..29846974 Binary files /dev/null and b/tests/_ref/compositing/clip-vector-image-scale.png differ diff --git a/tests/_ref/compositing/clip-vector-image-transform.png b/tests/_ref/compositing/clip-vector-image-transform.png new file mode 100644 index 00000000..248cd06f Binary files /dev/null and b/tests/_ref/compositing/clip-vector-image-transform.png differ diff --git a/tests/_ref/compositing/clip-vector-image.png b/tests/_ref/compositing/clip-vector-image.png new file mode 100644 index 00000000..94474931 Binary files /dev/null and b/tests/_ref/compositing/clip-vector-image.png differ diff --git a/tests/_ref/compositing/mask-vector-image.png b/tests/_ref/compositing/mask-vector-image.png new file mode 100644 index 00000000..4cb785e4 Binary files /dev/null and b/tests/_ref/compositing/mask-vector-image.png differ diff --git a/tests/_ref/drawing/pathmatics-contours.png b/tests/_ref/drawing/pathmatics-contours.png index 75381ff8..5b8ab508 100644 Binary files a/tests/_ref/drawing/pathmatics-contours.png and b/tests/_ref/drawing/pathmatics-contours.png differ diff --git a/tests/_ref/drawing/paths-broken.jpg b/tests/_ref/drawing/paths-broken.jpg index 8e284ede..6e123ee2 100644 Binary files a/tests/_ref/drawing/paths-broken.jpg and b/tests/_ref/drawing/paths-broken.jpg differ diff --git a/tests/_ref/drawing/paths-transform-post.png b/tests/_ref/drawing/paths-transform-post.png index a9bee548..fe6ec7db 100644 Binary files a/tests/_ref/drawing/paths-transform-post.png and b/tests/_ref/drawing/paths-transform-post.png differ diff --git a/tests/_ref/drawing/paths-transform-pre.png b/tests/_ref/drawing/paths-transform-pre.png index 776d9f15..27cd56bb 100644 Binary files a/tests/_ref/drawing/paths-transform-pre.png and b/tests/_ref/drawing/paths-transform-pre.png differ diff --git a/tests/_ref/typography/typography-advanced-flow.png b/tests/_ref/typography/typography-advanced-flow.png index 3f606814..a3cc9d9d 100644 Binary files a/tests/_ref/typography/typography-advanced-flow.png and b/tests/_ref/typography/typography-advanced-flow.png differ diff --git a/tests/_ref/typography/typography-advanced-flow2.png b/tests/_ref/typography/typography-advanced-flow2.png index 5edc7d91..ff84fb28 100644 Binary files a/tests/_ref/typography/typography-advanced-flow2.png and b/tests/_ref/typography/typography-advanced-flow2.png differ diff --git a/tests/_ref/typography/typography-advanced-flow3.png b/tests/_ref/typography/typography-advanced-flow3.png index fb7e9e70..f7619f84 100644 Binary files a/tests/_ref/typography/typography-advanced-flow3.png and b/tests/_ref/typography/typography-advanced-flow3.png differ diff --git a/tests/_ref/typography/typography-layout-fragments.png b/tests/_ref/typography/typography-layout-fragments.png index e7dea91c..55adbb6d 100644 Binary files a/tests/_ref/typography/typography-layout-fragments.png and b/tests/_ref/typography/typography-layout-fragments.png differ diff --git a/tests/_ref/typography/typography-layout-select2.png b/tests/_ref/typography/typography-layout-select2.png index 7bfad3d8..4d188113 100644 Binary files a/tests/_ref/typography/typography-layout-select2.png and b/tests/_ref/typography/typography-layout-select2.png differ diff --git a/tests/_ref/typography/typography-layout-words.png b/tests/_ref/typography/typography-layout-words.png index 907515e0..75cdf49b 100644 Binary files a/tests/_ref/typography/typography-layout-words.png and b/tests/_ref/typography/typography-layout-words.png differ diff --git a/tests/_ref/typography/typography-styles-font.png b/tests/_ref/typography/typography-styles-font.png index c0de410c..080c2866 100644 Binary files a/tests/_ref/typography/typography-styles-font.png and b/tests/_ref/typography/typography-styles-font.png differ diff --git a/tests/_ref/typography/typography-styles-named.png b/tests/_ref/typography/typography-styles-named.png index 897be1a5..2fa439bb 100644 Binary files a/tests/_ref/typography/typography-styles-named.png and b/tests/_ref/typography/typography-styles-named.png differ diff --git a/tests/canvas.py b/tests/canvas.py new file mode 100644 index 00000000..0bfbd528 --- /dev/null +++ b/tests/canvas.py @@ -0,0 +1,82 @@ +# encoding: utf-8 +import unittest +from types import SimpleNamespace +from collections import UserDict +from . import PlotDeviceTestCase +from plotdevice import * + +class CanvasTests(PlotDeviceTestCase): + def test_ordered_by_dotted_attr(self): + size(100, 100) + positions = [80, 20, 50] + for y in positions: + rect(10, y, 10, 10) + + sorted_grobs = ordered(canvas, 'bounds.y') + self.assertEqual([g.bounds.y for g in sorted_grobs], sorted(positions)) + + def test_ordered_by_dict_like_non_dict(self): + items = [UserDict({'y': 80}), UserDict({'y': 20}), UserDict({'y': 50})] + sorted_items = ordered(items, 'y') + self.assertEqual([d['y'] for d in sorted_items], [20, 50, 80]) + + def test_ordered_heterogeneous_items(self): + items = [{'y': 80}, SimpleNamespace(y=20), {'y': 50}] + sorted_items = ordered(items, 'y') + ys = [it['y'] if isinstance(it, dict) else it.y for it in sorted_items] + self.assertEqual(ys, [20, 50, 80]) + + def test_dimension_range(self): + # reproduces https://github.com/plotdevice/plotdevice/issues/67 + size(300, 200) + self.assertEqual(list(range(0, WIDTH)), list(range(0, 300))) + self.assertEqual(list(range(0, HEIGHT)), list(range(0, 200))) + + def test_magicnumber_protocol(self): + # exercise the dunder methods on MagicNumber that make it behave like a real float + size(300, 200) + for magic in (WIDTH, HEIGHT, px, inch, pica, cm, mm): + expected = float(magic) + with self.subTest(magic=magic): + self.assertEqual(list(range(0, magic)), list(range(0, int(expected)))) + self.assertEqual(round(magic), round(expected)) + self.assertEqual(hash(magic), hash(expected)) + self.assertTrue(magic <= expected) + self.assertTrue(magic >= expected) + self.assertEqual(format(magic, '.2f'), format(expected, '.2f')) + self.assertEqual(144 / magic, 144 / expected) + + def test_magicnumber_cross_comparisons(self): + # confirm dimension-to-unit comparisons work + size(10 * cm, 20 * cm) + self.assertEqual(WIDTH / cm, 10) + self.assertEqual(HEIGHT / cm, 20) + for a, b in [(inch, pica), (cm, mm), (WIDTH, cm), (HEIGHT, inch), (WIDTH, HEIGHT)]: + with self.subTest(a=a, b=b): + self.assertEqual(a <= b, float(a) <= float(b)) + self.assertEqual(a >= b, float(a) >= float(b)) + + def test_magicnumber_unsupported_operators(self): + # confirm errors blame the actual type (Dimension or Unit) rather than + # the spurious 'float' comparison from earlier releases + size(300, 200) + for magic in (WIDTH, HEIGHT, px, inch, pica, cm, mm): + typename = type(magic).__name__ + ops = [ + lambda m: m << 2, + lambda m: m >> 2, + lambda m: 2 << m, + lambda m: 2 >> m, + lambda m: ~m, + ] + for op in ops: + with self.subTest(magic=magic, op=op): + with self.assertRaises(TypeError) as exc: + op(magic) + self.assertIn(typename, str(exc.exception)) + self.assertNotIn('float', str(exc.exception)) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(CanvasTests)) + return suite diff --git a/tests/compositing.py b/tests/compositing.py index 5db32747..26a609cc 100644 --- a/tests/compositing.py +++ b/tests/compositing.py @@ -42,6 +42,35 @@ def test_clip_text(self): with clip(text('Hi', 5, 100)): image('tests/_in/plaid.png') + @reference('compositing/clip-vector-image.png') + def test_clip_image_vector(self): + size(125, 125) + # ensure tiny PDF is scaled up before rasterizing into a larger mask + with clip(image('tests/_in/tiny-triangle.pdf', width=125, height=125)): + image('tests/_in/plaid.png') + + @reference('compositing/clip-vector-image-scale.png') + def test_clip_image_vector_scale(self): + size(125, 125) + scale(2) + with clip(image('tests/_in/tiny-triangle.pdf', width=62, height=62)): + image('tests/_in/plaid.png') + + @reference('compositing/clip-vector-image-rotate.png') + def test_clip_image_vector_rotate(self): + size(125, 125) + rotate(30) + with clip(image('tests/_in/tiny-triangle.pdf', width=125, height=125)): + image('tests/_in/plaid.png') + + @reference('compositing/clip-vector-image-transform.png') + def test_clip_image_vector_transform(self): + size(125, 125) + scale(1.5) + rotate(20) + with clip(image('tests/_in/tiny-triangle.pdf', width=83, height=83)): + image('tests/_in/plaid.png') + @reference('compositing/mask.png') def test_mask(self): # ref/Compositing/commands/mask() @@ -64,6 +93,13 @@ def test_mask_text(self): with mask(text('Hi', 5, 100)): image('tests/_in/plaid.png') + @reference('compositing/mask-vector-image.png') + def test_mask_image_vector(self): + size(125, 125) + # ensure tiny PDF is scaled up before rasterizing into a larger mask + with mask(image('tests/_in/tiny-triangle.pdf', width=125, height=125)): + image('tests/_in/plaid.png') + @reference('compositing/shadow-multi.png') def test_shadow_multi(self): # ref/Compositing/commands/shadow() diff --git a/tests/drawing.py b/tests/drawing.py index 1d0dbe14..3d01bb30 100644 --- a/tests/drawing.py +++ b/tests/drawing.py @@ -8,7 +8,7 @@ class DrawingTests(PlotDeviceTestCase): def test_paths_transform_pre(self): # tut/Bezier_Paths (1) size(180, 180) - font("Dolly", "bold", 300) + font("Rockwell", "bold", 280) path = textpath("e", 10, 150) bezier(path, stroke='black', fill=None) @@ -16,7 +16,7 @@ def test_paths_transform_pre(self): def test_paths_transform_post(self): # tut/Bezier_Paths (2) size(180, 180) - font("Dolly", "bold", 300) + font("Rockwell", "bold", 280) path = textpath("e", 10, 150) curves = [] for curve in path: @@ -30,7 +30,7 @@ def test_paths_transform_post(self): def test_paths_broken(self): # tut/Bezier_Paths (3) size(334, 87) - font("Dolly", "bold", 100) + font("Rockwell", "bold", 90) path = textpath("broken", 0,80) curves = [] @@ -48,7 +48,7 @@ def test_paths_broken(self): def test_pathmatics_contours(self): # tut/Bezier_Paths (4) size(181, 70) - font("Dolly", "bold", 50) + font("Rockwell", "bold", 50) with pen(2), nofill(): path = textpath("@#$&!", 10, 50) for contour in path.contours: diff --git a/tests/module.py b/tests/module.py index 77a30bba..4f55a646 100644 --- a/tests/module.py +++ b/tests/module.py @@ -1,4 +1,4 @@ -import os +import os, sys import unittest from . import PlotDeviceTestCase, reference from subprocess import check_output, STDOUT @@ -9,7 +9,7 @@ class ModuleTests(PlotDeviceTestCase): def test_pyobjc(self): import objc - self.assertIn(sdist_path, objc.__file__) + self.assertIsNotNone(objc.lookUpClass('Vandercook')) @reference('module/nodebox-compat.png') def test_nodebox_compat(self): @@ -27,10 +27,9 @@ def test_compliance(self): def test_cli(self): self._image = 'module/cli.png' - plod_bin = '%s/app/plotdevice'%sdist_path script = '%s/tests/_in/cli.pv'%sdist_path output = '%s/tests/_out/%s'%(sdist_path, self._image) - check_output([plod_bin, script, '--export', output], stderr=STDOUT, cwd=sdist_path) + check_output([sys.executable, '-m', 'plotdevice', script, '--export', output], stderr=STDOUT, cwd=sdist_path) self.render(save_output=False) @@ -40,4 +39,4 @@ def suite(): suite = TestSuite() suite.addTest(defaultTestLoader.loadTestsFromTestCase(ModuleTests)) - return suite \ No newline at end of file + return suite diff --git a/tests/typography.py b/tests/typography.py index 2f777dc4..1c7f1500 100644 --- a/tests/typography.py +++ b/tests/typography.py @@ -88,7 +88,7 @@ def test_typography_align_block(self): def test_typography_styles_font(self): # tut/Typography (8) size(200, 220) - font('jenson', 'medium', 22) + font('Avenir Next', 'medium', 22) text("September 1972", 20,40) font(osf=True) # old-style figures @@ -124,7 +124,7 @@ def test_typography_styles_named(self): text('handgloves', 10,70, style='emph') text('handgloves', 10,100, style='bright') - font('Joanna MT', 28) + font('Hoefler Text', 28) text('handgloves', 10,150) text('handgloves', 10,180, style='emph') text('handgloves', 10,210, style='bright') @@ -183,8 +183,8 @@ def test_typography_layout_lines(self): @reference('typography/typography-layout-fragments.png') def test_typography_layout_fragments(self): # tut/Typography (15) - size(300, 210) - font('Joanna MT', 80, italic=True) + size(380, 210) + font('Palatino', 80, italic=True) t = text(20,120, str='Axiomatic') first = t[0] @@ -213,10 +213,10 @@ def test_typography_layout_glyphs(self): @reference('typography/typography-layout-words.png') def test_typography_layout_words(self): # tut/Typography (17) - size(300, 140) + size(300, 180) rhyme = "Tinker, tailor, soldier, sailor, rich man, poor man, begger man, thief" - font('Joanna MT', 32, italic=True) + font('Palatino', 32, italic=True) t = text(20,40, 270,160, str=rhyme) nofill() @@ -250,10 +250,10 @@ def test_typography_layout_select(self): @reference('typography/typography-layout-select2.png') def test_typography_layout_select2(self): # tut/Typography (20) - size(300, 240) + size(300, 270) flaubert = "“Five hundred lines for all the class!” shouted in a furious voice stopped, like the Quos ego1, a fresh outburst. “Silence!” continued the master indignantly, wiping his brow with his handkerchief, which he had just taken from his cap. “As to you, ‘new boy,’ you will conjugate ‘ridiculus sum’2 twenty times.”\nThen, in a gentler tone, “Come, you’ll find your cap again; it hasn’t been stolen.”" - font('Adobe Jenson', 14) + font('STIX Two Text', 14) layout(hyphenate=True, indent=True) stylesheet('fn', vpos=1, fill='red') @@ -323,7 +323,7 @@ def test_typography_advanced_flow(self): size(300, 150) kafka = 'Someone must have been telling lies about Josef K., he knew he had done nothing wrong but, one morning, he was arrested. Every day at eight in the morning he was brought his breakfast by Mrs. Grubach’s cook. Mrs. Gru-bach was his landlady but today she didn’t come. That had never happened before. K. waited a little while, looked from his pillow at the old woman who lived opposite and who was watching him with an inquisitiveness quite unusual for her, and finally, both hungry and disconcerted, rang the bell. There was immediately a knock at the door and a man entered.' - font('Adobe Garamond', size=10) + font('Hoefler Text', size=10) layout(align=JUSTIFY, hyphenate=True) t = text(20,20, 120,120, str=kafka) for block in t.flow(2): @@ -335,7 +335,7 @@ def test_typography_advanced_flow2(self): size(300, 330) kafka = 'and a man entered. He had never seen the man in this house before. He was slim but firmly built, his clothes were black and close-fitting, with many folds and pockets, buckles and buttons and a belt, all of which gave the impression of being very practical but without making it very clear what they were actually for. “Who are you?” asked K., sitting half upright in his bed. The man, however, ignored the question as if his arrival simply had to be accepted, and merely replied, “You rang?” “Anna should have brought me my breakfast,” said K. He tried to work out who the man actually was, first in silence, just through observation and by thinking about it, but the man didn’t stay still to be looked at for very long. Instead he went over to the door, opened it slightly, and said to someone who was clearly standing immediately behind it, “He wants Anna to bring him his breakfast.” There was a little laughter in the neighbouring room, it was not clear from the sound of it whether there were several people laughing. The strange man could not have learned anything from it that he hadn’t known already, but now he said to K., as if making his report “It is not possible.” “It would be the first time that’s happened,” said K., as he jumped out of bed and quickly pulled on his trousers. “I want to see who that is in the next room, and why it is that Mrs. Grubach has let me be disturbed in this way.” It immediately occurred to him that he needn’t have said this out loud, and that he must to some extent have acknowledged their authority by doing so, but that ...' - font('Adobe Garamond', size=10) + font('Hoefler Text', size=10) layout(align=JUSTIFY, hyphenate=True) def leftright(block): @@ -354,7 +354,7 @@ def test_typography_advanced_flow3(self): size(300, 240) welles = 'Before the law, there stands a guard. A man comes from the country, begging admittance to the law. But the guard cannot admit him. May he hope to enter at a later time? That is possible, said the guard. The man tries to peer through the entrance. He’d been taught that the law was to be accessible to every man. “Do not attempt to enter without my permission”, says the guard. I am very powerful. Yet I am the least of all the guards. From hall to hall, door after door, each guard is more powerful than the last. By the guard’s permission, the man sits by the side of the door, and there he waits. For years, he waits. Everything he has, he gives away in the hope of bribing the guard, who never fails to say to him “I take what you give me only so that you will not feel that you left something undone.” Keeping his watch during the long years, the man has come to know even the fleas on the guard’s fur collar. Growing childish in old age, he begs the fleas to persuade the guard to change his mind and allow him to enter. His sight has dimmed, but in the darkness he perceives a radiance streaming immortally from the door of the law. And now, before he dies, all he’s experienced condenses into one question, a question he’s never asked. He beckons the guard. Says the guard, “You are insatiable! What is it now?” Says the man, “Every man strives to attain the law. How is it then that in all these years, no one else has ever come here, seeking admittance?” His hearing has failed, so the guard yells into his ear. “Nobody else but you could ever have obtained admittance. No one else could enter this door! This door was intended only for you! And now, I’m going to close it.” This tale is told during the story called “The Trial”. It’s been said that the logic of this story is the logic of a dream... a nightmare.' - font('Adobe Garamond', size=10) + font('Hoefler Text', size=10) t = text(20,26, 80,120, str=welles) for block in t.flow(3): block.x += block.width + 10