From 44e77a22b572a3ffe158a8b4b63c218ae549b8ac Mon Sep 17 00:00:00 2001 From: FangFuxin <38530078+lajiyuan@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:46:49 +0800 Subject: [PATCH 001/157] Fix png image plugin load_end func handle truncated file. --- Tests/images/end_trunc_file.png | Bin 0 -> 30339 bytes Tests/test_file_png.py | 9 +++++++++ src/PIL/PngImagePlugin.py | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Tests/images/end_trunc_file.png diff --git a/Tests/images/end_trunc_file.png b/Tests/images/end_trunc_file.png new file mode 100644 index 0000000000000000000000000000000000000000..5e88c5e4fa2a6ac010219a2469b83efa21102f0e GIT binary patch literal 30339 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 mOz9jWq7NYmRv5v_Ot0CrAEL_t(ca^tlC literal 0 HcmV?d00001 diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index ff3862110..9b6a4c5e6 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -777,6 +777,15 @@ class TestFilePng: mystdout = mystdout.buffer with Image.open(mystdout) as reloaded: assert_image_equal_tofile(reloaded, TEST_PNG_FILE) + + def test_end_truncated_file(self): + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + with Image.open("Tests/images/end_trunc_file.png") as im: + assert_image_equal_tofile(im, "Tests/images/end_trunc_file.png") + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 823f12492..1248fb785 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -981,7 +981,13 @@ class PngImageFile(ImageFile.ImageFile): except EOFError: if cid == b"fdAT": length -= 4 - ImageFile._safe_read(self.fp, length) + try: + ImageFile._safe_read(self.fp, length) + except OSError as e: + if ImageFile.LOAD_TRUNCATED_IMAGES: + break + else: + raise e except AttributeError: logger.debug("%r %s %s (unknown)", cid, pos, length) s = ImageFile._safe_read(self.fp, length) From b2711c3e8b019d0d069d58069912abd4ecb99e61 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 07:36:57 +0000 Subject: [PATCH 002/157] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_png.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9b6a4c5e6..3c285b077 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -777,7 +777,7 @@ class TestFilePng: mystdout = mystdout.buffer with Image.open(mystdout) as reloaded: assert_image_equal_tofile(reloaded, TEST_PNG_FILE) - + def test_end_truncated_file(self): ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -787,7 +787,6 @@ class TestFilePng: ImageFile.LOAD_TRUNCATED_IMAGES = False - @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @skip_unless_feature("zlib") class TestTruncatedPngPLeaks(PillowLeakTestCase): From fe7b6d9e80ae8ee65d2b4e615971a00690b876fe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Jan 2024 18:39:33 +1100 Subject: [PATCH 003/157] Corrected expected image path --- Tests/test_file_png.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 3c285b077..aa2aac906 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -782,7 +782,7 @@ class TestFilePng: ImageFile.LOAD_TRUNCATED_IMAGES = True try: with Image.open("Tests/images/end_trunc_file.png") as im: - assert_image_equal_tofile(im, "Tests/images/end_trunc_file.png") + assert_image_equal_tofile(im, "Tests/images/hopper.png") finally: ImageFile.LOAD_TRUNCATED_IMAGES = False From 62e6d62518f21333fbde364d7b7a57e25d39061b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Jan 2024 18:49:25 +1100 Subject: [PATCH 004/157] Test error is raised without LOAD_TRUNCATED_IMAGES --- .../{end_trunc_file.png => truncated_end_chunk.png} | Bin Tests/test_file_png.py | 8 ++++++-- 2 files changed, 6 insertions(+), 2 deletions(-) rename Tests/images/{end_trunc_file.png => truncated_end_chunk.png} (100%) diff --git a/Tests/images/end_trunc_file.png b/Tests/images/truncated_end_chunk.png similarity index 100% rename from Tests/images/end_trunc_file.png rename to Tests/images/truncated_end_chunk.png diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index aa2aac906..0884ddcc3 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -778,10 +778,14 @@ class TestFilePng: with Image.open(mystdout) as reloaded: assert_image_equal_tofile(reloaded, TEST_PNG_FILE) - def test_end_truncated_file(self): + def test_truncated_end_chunk(self): + with Image.open("Tests/images/truncated_end_chunk.png") as im: + with pytest.raises(OSError): + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = True try: - with Image.open("Tests/images/end_trunc_file.png") as im: + with Image.open("Tests/images/truncated_end_chunk.png") as im: assert_image_equal_tofile(im, "Tests/images/hopper.png") finally: ImageFile.LOAD_TRUNCATED_IMAGES = False From 46741953215a764d18fb18fe4a16cadb82d40f9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jan 2024 15:01:12 +1100 Subject: [PATCH 005/157] Removed support for test-image-results --- Tests/helper.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 99170c765..b2e7d43dd 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -25,13 +25,6 @@ if os.environ.get("SHOW_ERRORS"): uploader = "show" elif "GITHUB_ACTIONS" in os.environ: uploader = "github_actions" -else: - try: - import test_image_results - - uploader = "aws" - except ImportError: - pass def upload(a: Image.Image, b: Image.Image) -> str | None: @@ -46,8 +39,6 @@ def upload(a: Image.Image, b: Image.Image) -> str | None: a.save(os.path.join(tmpdir, "a.png")) b.save(os.path.join(tmpdir, "b.png")) return tmpdir - elif uploader == "aws": - return test_image_results.upload(a, b) return None From 16ea9bd102757faa7cd02ebfc74b5c9e3d1dac1f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:08:51 +0200 Subject: [PATCH 006/157] Include pyproject.toml in pip cache key --- .github/workflows/docs.yml | 4 +++- .github/workflows/test.yml | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9fe345c8a..685346225 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,7 +37,9 @@ jobs: with: python-version: "3.x" cache: pip - cache-dependency-path: ".ci/*.sh" + cache-dependency-path: | + ".ci/*.sh" + "pyproject.toml" - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05f78704b..2044620aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: build: @@ -65,7 +68,9 @@ jobs: python-version: ${{ matrix.python-version }} allow-prereleases: true cache: pip - cache-dependency-path: ".ci/*.sh" + cache-dependency-path: | + ".ci/*.sh" + "pyproject.toml" - name: Build system information run: python3 .github/workflows/system-info.py From 0b6c7ba49e80dbba02705e40274854b8b4b5b09b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:09:41 +0200 Subject: [PATCH 007/157] Disable wget progress bar but not all output --- depends/download-and-extract.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/download-and-extract.sh b/depends/download-and-extract.sh index a318bfafd..04bfbc755 100755 --- a/depends/download-and-extract.sh +++ b/depends/download-and-extract.sh @@ -5,7 +5,7 @@ archive=$1 url=$2 if [ ! -f $archive.tar.gz ]; then - wget -O $archive.tar.gz $url + wget --no-verbose -O $archive.tar.gz $url fi rmdir $archive From 97d24f14a539115c82ecdd0a9801e37dc83636b5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:15:59 +0200 Subject: [PATCH 008/157] Cache libimagequant --- .github/workflows/docs.yml | 8 ++++++++ .github/workflows/test.yml | 9 +++++++++ depends/install_imagequant.sh | 38 +++++++++++++++++++++++++++-------- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 685346225..4319cc8ff 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,11 +44,19 @@ jobs: - name: Build system information run: python3 .github/workflows/system-info.py + - name: Cache libimagequant + uses: actions/cache@v4 + id: cache-libimagequant + with: + path: ~/cache-libimagequant + key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }} + - name: Install Linux dependencies run: | .ci/install.sh env: GHA_PYTHON_VERSION: "3.x" + GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }} - name: Build run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2044620aa..4e23f5c5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,12 +75,21 @@ jobs: - name: Build system information run: python3 .github/workflows/system-info.py + - name: Cache libimagequant + if: startsWith(matrix.os, 'ubuntu') + uses: actions/cache@v4 + id: cache-libimagequant + with: + path: ~/cache-libimagequant + key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }} + - name: Install Linux dependencies if: startsWith(matrix.os, 'ubuntu') run: | .ci/install.sh env: GHA_PYTHON_VERSION: ${{ matrix.python-version }} + GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }} - name: Install macOS dependencies if: startsWith(matrix.os, 'macOS') diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index b7cebbdbf..0fe8cbdba 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,15 +1,37 @@ #!/bin/bash # install libimagequant -archive=libimagequant-4.2.2 +archive_name=libimagequant +archive_version=4.2.2 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz +archive=$archive_name-$archive_version -pushd $archive/imagequant-sys +if [[ "$GHA_LIBIMAGEQUANT_CACHE_HIT" == "true" ]]; then -cargo install cargo-c -cargo cinstall --prefix=/usr --destdir=. -sudo cp usr/lib/libimagequant.so* /usr/lib/ -sudo cp usr/include/libimagequant.h /usr/include/ + # Copy cached files into place + sudo cp ~/cache-$archive_name/libimagequant.so* /usr/lib/ + sudo cp ~/cache-$archive_name/libimagequant.h /usr/include/ -popd +else + + # Build from source + ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz + + pushd $archive/imagequant-sys + + time cargo install cargo-c + time cargo cinstall --prefix=/usr --destdir=. + + # Copy into place + sudo cp usr/lib/libimagequant.so* /usr/lib/ + sudo cp usr/include/libimagequant.h /usr/include/ + + # Copy to cache + rm -rf ~/cache-$archive_name + mkdir ~/cache-$archive_name + cp usr/lib/libimagequant.so* ~/cache-$archive_name/ + cp usr/include/libimagequant.h ~/cache-$archive_name/ + + popd + +fi From a09e056a4ca9ab5283b14bfd9ccec9aeb0757643 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Jan 2024 18:42:43 +1100 Subject: [PATCH 009/157] Added type hints --- Tests/test_font_bdf.py | 4 +-- Tests/test_font_crash.py | 4 +-- Tests/test_font_leaks.py | 6 ++-- Tests/test_font_pcf.py | 21 +++++++----- Tests/test_font_pcf_charsets.py | 59 +++++++++++++++++++-------------- 5 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index e5e856186..136070f9e 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -7,7 +7,7 @@ from PIL import BdfFontFile, FontFile filename = "Tests/images/courB08.bdf" -def test_sanity(): +def test_sanity() -> None: with open(filename, "rb") as test_file: font = BdfFontFile.BdfFontFile(test_file) @@ -15,7 +15,7 @@ def test_sanity(): assert len([_f for _f in font.glyph if _f]) == 190 -def test_invalid_file(): +def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): BdfFontFile.BdfFontFile(fp) diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index e3c72c1ae..b82340ef7 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -8,7 +8,7 @@ from .helper import skip_unless_feature class TestFontCrash: - def _fuzz_font(self, font): + def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None: # from fuzzers.fuzz_font font.getbbox("ABC") font.getmask("test text") @@ -18,7 +18,7 @@ class TestFontCrash: draw.text((10, 10), "Test Text", font=font, fill="#000") @skip_unless_feature("freetype2") - def test_segfault(self): + def test_segfault(self) -> None: with pytest.raises(OSError): font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784") self._fuzz_font(font) diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 4e29a856b..241f455b8 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -10,7 +10,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): iterations = 10 mem_limit = 4096 # k - def _test_font(self, font): + def _test_font(self, font: ImageFont.FreeTypeFont) -> None: im = Image.new("RGB", (255, 255), "white") draw = ImageDraw.ImageDraw(im) self._test_leak( @@ -20,7 +20,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): ) @skip_unless_feature("freetype2") - def test_leak(self): + def test_leak(self) -> None: ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) self._test_font(ttype) @@ -30,6 +30,6 @@ class TestDefaultFontLeak(TestTTypeFontLeak): iterations = 100 mem_limit = 1024 # k - def test_leak(self): + def test_leak(self) -> None: default_font = ImageFont.load_default() self._test_font(default_font) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index e6abede07..0f1eabdce 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from pathlib import PosixPath import pytest @@ -20,7 +21,7 @@ message = "hello, world" pytestmark = skip_unless_feature("zlib") -def save_font(request, tmp_path): +def save_font(request: pytest.FixtureRequest, tmp_path: PosixPath) -> str: with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) assert isinstance(font, FontFile.FontFile) @@ -29,7 +30,7 @@ def save_font(request, tmp_path): tempname = str(tmp_path / "temp.pil") - def delete_tempfile(): + def delete_tempfile() -> None: try: os.remove(tempname[:-4] + ".pbm") except OSError: @@ -47,11 +48,11 @@ def save_font(request, tmp_path): return tempname -def test_sanity(request, tmp_path): +def test_sanity(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: save_font(request, tmp_path) -def test_less_than_256_characters(): +def test_less_than_256_characters() -> None: with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) assert isinstance(font, FontFile.FontFile) @@ -59,13 +60,13 @@ def test_less_than_256_characters(): assert len([_f for _f in font.glyph if _f]) == 127 -def test_invalid_file(): +def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): PcfFontFile.PcfFontFile(fp) -def test_draw(request, tmp_path): +def test_draw(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) im = Image.new("L", (130, 30), "white") @@ -74,7 +75,7 @@ def test_draw(request, tmp_path): assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0) -def test_textsize(request, tmp_path): +def test_textsize(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) for i in range(255): @@ -90,7 +91,9 @@ def test_textsize(request, tmp_path): assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20) -def _test_high_characters(request, tmp_path, message): +def _test_high_characters( + request: pytest.FixtureRequest, tmp_path: PosixPath, message: str | bytes +) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) im = Image.new("L", (750, 30), "white") @@ -99,7 +102,7 @@ def _test_high_characters(request, tmp_path, message): assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0) -def test_high_characters(request, tmp_path): +def test_high_characters(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: message = "".join(chr(i + 1) for i in range(140, 232)) _test_high_characters(request, tmp_path, message) # accept bytes instances. diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 4c2d7185e..9dfaa404e 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from pathlib import PosixPath import pytest @@ -14,38 +15,40 @@ from .helper import ( fontname = "Tests/fonts/ter-x20b.pcf" -charsets = { - "iso8859-1": { - "glyph_count": 223, - "message": "hello, world", - "image1": "Tests/images/test_draw_pbm_ter_en_target.png", - }, - "iso8859-2": { - "glyph_count": 223, - "message": "witaj świecie", - "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", - }, - "cp1250": { - "glyph_count": 250, - "message": "witaj świecie", - "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", - }, +charsets: dict[str, tuple[int, str, str]] = { + "iso8859-1": ( + 223, + "hello, world", + "Tests/images/test_draw_pbm_ter_en_target.png", + ), + "iso8859-2": ( + 223, + "witaj świecie", + "Tests/images/test_draw_pbm_ter_pl_target.png", + ), + "cp1250": ( + 250, + "witaj świecie", + "Tests/images/test_draw_pbm_ter_pl_target.png", + ), } pytestmark = skip_unless_feature("zlib") -def save_font(request, tmp_path, encoding): +def save_font( + request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str +) -> str: with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file, encoding) assert isinstance(font, FontFile.FontFile) # check the number of characters in the font - assert len([_f for _f in font.glyph if _f]) == charsets[encoding]["glyph_count"] + assert len([_f for _f in font.glyph if _f]) == charsets[encoding][0] tempname = str(tmp_path / "temp.pil") - def delete_tempfile(): + def delete_tempfile() -> None: try: os.remove(tempname[:-4] + ".pbm") except OSError: @@ -64,23 +67,29 @@ def save_font(request, tmp_path, encoding): @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) -def test_sanity(request, tmp_path, encoding): +def test_sanity( + request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str +) -> None: save_font(request, tmp_path, encoding) @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) -def test_draw(request, tmp_path, encoding): +def test_draw( + request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str +) -> None: tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") draw = ImageDraw.Draw(im) - message = charsets[encoding]["message"].encode(encoding) + message = charsets[encoding][1].encode(encoding) draw.text((0, 0), message, "black", font=font) - assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) + assert_image_similar_tofile(im, charsets[encoding][2], 0) @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) -def test_textsize(request, tmp_path, encoding): +def test_textsize( + request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str +) -> None: tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) for i in range(255): @@ -90,7 +99,7 @@ def test_textsize(request, tmp_path, encoding): assert dy == 20 assert dx in (0, 10) assert font.getlength(bytearray([i])) == dx - message = charsets[encoding]["message"].encode(encoding) + message = charsets[encoding][1].encode(encoding) for i in range(len(message)): msg = message[: i + 1] assert font.getlength(msg) == len(msg) * 10 From d96c196c48a27fc327eb1fcb7296ee47b7f25e0f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:42:03 +0200 Subject: [PATCH 010/157] Only cache on GHA, remove debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- depends/install_imagequant.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 0fe8cbdba..3adae91a5 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -19,18 +19,20 @@ else pushd $archive/imagequant-sys - time cargo install cargo-c - time cargo cinstall --prefix=/usr --destdir=. + cargo install cargo-c + cargo cinstall --prefix=/usr --destdir=. # Copy into place sudo cp usr/lib/libimagequant.so* /usr/lib/ sudo cp usr/include/libimagequant.h /usr/include/ - # Copy to cache - rm -rf ~/cache-$archive_name - mkdir ~/cache-$archive_name - cp usr/lib/libimagequant.so* ~/cache-$archive_name/ - cp usr/include/libimagequant.h ~/cache-$archive_name/ + if [ -n "$GITHUB_ACTIONS" ]; then + # Copy to cache + rm -rf ~/cache-$archive_name + mkdir ~/cache-$archive_name + cp usr/lib/libimagequant.so* ~/cache-$archive_name/ + cp usr/include/libimagequant.h ~/cache-$archive_name/ + fi popd From 2521ec4732311ffd85444253d755e3acee89c10f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Jan 2024 22:08:45 +1100 Subject: [PATCH 011/157] Restored charsets dictionary --- Tests/test_font_pcf_charsets.py | 53 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 9dfaa404e..cb77128ef 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -15,22 +15,22 @@ from .helper import ( fontname = "Tests/fonts/ter-x20b.pcf" -charsets: dict[str, tuple[int, str, str]] = { - "iso8859-1": ( - 223, - "hello, world", - "Tests/images/test_draw_pbm_ter_en_target.png", - ), - "iso8859-2": ( - 223, - "witaj świecie", - "Tests/images/test_draw_pbm_ter_pl_target.png", - ), - "cp1250": ( - 250, - "witaj świecie", - "Tests/images/test_draw_pbm_ter_pl_target.png", - ), +charsets: dict[str, dict[str, int | str]] = { + "iso8859-1": { + "glyph_count": 223, + "message": "hello, world", + "image1": "Tests/images/test_draw_pbm_ter_en_target.png", + }, + "iso8859-2": { + "glyph_count": 223, + "message": "witaj świecie", + "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", + }, + "cp1250": { + "glyph_count": 250, + "message": "witaj świecie", + "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", + }, } @@ -44,7 +44,7 @@ def save_font( font = PcfFontFile.PcfFontFile(test_file, encoding) assert isinstance(font, FontFile.FontFile) # check the number of characters in the font - assert len([_f for _f in font.glyph if _f]) == charsets[encoding][0] + assert len([_f for _f in font.glyph if _f]) == charsets[encoding]["glyph_count"] tempname = str(tmp_path / "temp.pil") @@ -81,9 +81,14 @@ def test_draw( font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") draw = ImageDraw.Draw(im) - message = charsets[encoding][1].encode(encoding) - draw.text((0, 0), message, "black", font=font) - assert_image_similar_tofile(im, charsets[encoding][2], 0) + + message = charsets[encoding]["message"] + assert isinstance(message, str) + draw.text((0, 0), message.encode(encoding), "black", font=font) + + expected_path = charsets[encoding]["image1"] + assert isinstance(expected_path, str) + assert_image_similar_tofile(im, expected_path, 0) @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) @@ -99,8 +104,10 @@ def test_textsize( assert dy == 20 assert dx in (0, 10) assert font.getlength(bytearray([i])) == dx - message = charsets[encoding][1].encode(encoding) - for i in range(len(message)): - msg = message[: i + 1] + message = charsets[encoding]["message"] + assert isinstance(message, str) + message_bytes = message.encode(encoding) + for i in range(len(message_bytes)): + msg = message_bytes[: i + 1] assert font.getlength(msg) == len(msg) * 10 assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20) From 231d54b9df90dcce1cdbb9928f8d21899eed8153 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:37:37 +0200 Subject: [PATCH 012/157] Replace io.BytesIO in type hints --- docs/reference/internal_design.rst | 4 ++-- docs/reference/internal_modules.rst | 12 ++++++++++++ src/PIL/GdImageFile.py | 7 +++++-- src/PIL/MpegImagePlugin.py | 5 ++--- src/PIL/_typing.py | 16 +++++++++++++++- src/PIL/_util.py | 4 ++-- 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst index 2e2d3322f..99a18e9ea 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -1,5 +1,5 @@ -Internal Reference Docs -======================= +Internal Reference +================== .. toctree:: :maxdepth: 2 diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index f2932c322..c3cc70060 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -33,6 +33,18 @@ Internal Modules Provides a convenient way to import type hints that are not available on some Python versions. +.. py:class:: FileDescriptor + + Typing alias. + +.. py:class:: StrOrBytesPath + + Typing alias. + +.. py:class:: SupportsRead + + An object that supports the read method. + .. py:data:: TypeGuard :value: typing.TypeGuard diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 7bb4736af..315ac6d6c 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -27,11 +27,12 @@ """ from __future__ import annotations -from io import BytesIO +from typing import IO from . import ImageFile, ImagePalette, UnidentifiedImageError from ._binary import i16be as i16 from ._binary import i32be as i32 +from ._typing import FileDescriptor, StrOrBytesPath class GdImageFile(ImageFile.ImageFile): @@ -80,7 +81,9 @@ class GdImageFile(ImageFile.ImageFile): ] -def open(fp: BytesIO, mode: str = "r") -> GdImageFile: +def open( + fp: StrOrBytesPath | FileDescriptor | IO[bytes], mode: str = "r" +) -> GdImageFile: """ Load texture from a GD image file. diff --git a/src/PIL/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py index b9e9243e5..1565612f8 100644 --- a/src/PIL/MpegImagePlugin.py +++ b/src/PIL/MpegImagePlugin.py @@ -14,17 +14,16 @@ # from __future__ import annotations -from io import BytesIO - from . import Image, ImageFile from ._binary import i8 +from ._typing import SupportsRead # # Bitstream parser class BitStream: - def __init__(self, fp: BytesIO) -> None: + def __init__(self, fp: SupportsRead[bytes]) -> None: self.fp = fp self.bits = 0 self.bitbuffer = 0 diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 608b2b41f..6eb25c1c1 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -1,6 +1,8 @@ from __future__ import annotations +import os import sys +from typing import Protocol, TypeVar, Union if sys.version_info >= (3, 10): from typing import TypeGuard @@ -15,4 +17,16 @@ else: return bool -__all__ = ["TypeGuard"] +_T_co = TypeVar("_T_co", covariant=True) + + +class SupportsRead(Protocol[_T_co]): + def read(self, __length: int = ...) -> _T_co: + ... + + +FileDescriptor = int +StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] + + +__all__ = ["FileDescriptor", "TypeGuard", "StrOrBytesPath", "SupportsRead"] diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 13f369cca..4ecdc4bd3 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -4,10 +4,10 @@ import os from pathlib import Path from typing import Any, NoReturn -from ._typing import TypeGuard +from ._typing import StrOrBytesPath, TypeGuard -def is_path(f: Any) -> TypeGuard[bytes | str | Path]: +def is_path(f: Any) -> TypeGuard[StrOrBytesPath]: return isinstance(f, (bytes, str, Path)) From 16fd934b007d7090fc32ff4a1ad13182a32bf612 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jan 2024 09:55:25 +1100 Subject: [PATCH 013/157] Use TypedDict --- Tests/test_font_pcf_charsets.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index cb77128ef..894d4eb56 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -2,6 +2,7 @@ from __future__ import annotations import os from pathlib import PosixPath +from typing import TypedDict import pytest @@ -15,7 +16,14 @@ from .helper import ( fontname = "Tests/fonts/ter-x20b.pcf" -charsets: dict[str, dict[str, int | str]] = { + +class Charset(TypedDict): + glyph_count: int + message: str + image1: str + + +charsets: dict[str, Charset] = { "iso8859-1": { "glyph_count": 223, "message": "hello, world", @@ -81,14 +89,9 @@ def test_draw( font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") draw = ImageDraw.Draw(im) - - message = charsets[encoding]["message"] - assert isinstance(message, str) - draw.text((0, 0), message.encode(encoding), "black", font=font) - - expected_path = charsets[encoding]["image1"] - assert isinstance(expected_path, str) - assert_image_similar_tofile(im, expected_path, 0) + message = charsets[encoding]["message"].encode(encoding) + draw.text((0, 0), message, "black", font=font) + assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) @@ -104,10 +107,8 @@ def test_textsize( assert dy == 20 assert dx in (0, 10) assert font.getlength(bytearray([i])) == dx - message = charsets[encoding]["message"] - assert isinstance(message, str) - message_bytes = message.encode(encoding) - for i in range(len(message_bytes)): - msg = message_bytes[: i + 1] + message = charsets[encoding]["message"].encode(encoding) + for i in range(len(message)): + msg = message[: i + 1] assert font.getlength(msg) == len(msg) * 10 assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20) From 4814bee6c0b99b4e00fa08a5d15663f8238f063a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jan 2024 21:42:36 +1100 Subject: [PATCH 014/157] Use Path instead of PosixPath --- Tests/check_j2k_overflow.py | 4 ++-- Tests/check_large_memory.py | 8 ++++---- Tests/check_large_memory_numpy.py | 8 ++++---- Tests/test_font_pcf.py | 14 +++++++------- Tests/test_font_pcf_charsets.py | 16 +++++----------- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 8a85783fc..dbdd5a4f5 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,13 +1,13 @@ from __future__ import annotations -from pathlib import PosixPath +from pathlib import Path import pytest from PIL import Image -def test_j2k_overflow(tmp_path: PosixPath) -> None: +def test_j2k_overflow(tmp_path: Path) -> None: im = Image.new("RGBA", (1024, 131584)) target = str(tmp_path / "temp.jpc") with pytest.raises(OSError): diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 2c8c77800..a9ce79e57 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from pathlib import PosixPath +from pathlib import Path from types import ModuleType import pytest @@ -31,18 +31,18 @@ XDIM = 48000 pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system") -def _write_png(tmp_path: PosixPath, xdim: int, ydim: int) -> None: +def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: f = str(tmp_path / "temp.png") im = Image.new("L", (xdim, ydim), 0) im.save(f) -def test_large(tmp_path: PosixPath) -> None: +def test_large(tmp_path: Path) -> None: """succeeded prepatch""" _write_png(tmp_path, XDIM, YDIM) -def test_2gpx(tmp_path: PosixPath) -> None: +def test_2gpx(tmp_path: Path) -> None: """failed prepatch""" _write_png(tmp_path, XDIM, XDIM) diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index 8609fe6d0..f4ca8d0aa 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from pathlib import PosixPath +from pathlib import Path import pytest @@ -25,7 +25,7 @@ XDIM = 48000 pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system") -def _write_png(tmp_path: PosixPath, xdim: int, ydim: int) -> None: +def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: dtype = np.uint8 a = np.zeros((xdim, ydim), dtype=dtype) f = str(tmp_path / "temp.png") @@ -33,11 +33,11 @@ def _write_png(tmp_path: PosixPath, xdim: int, ydim: int) -> None: im.save(f) -def test_large(tmp_path: PosixPath) -> None: +def test_large(tmp_path: Path) -> None: """succeeded prepatch""" _write_png(tmp_path, XDIM, YDIM) -def test_2gpx(tmp_path: PosixPath) -> None: +def test_2gpx(tmp_path: Path) -> None: """failed prepatch""" _write_png(tmp_path, XDIM, XDIM) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 0f1eabdce..997809e46 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from pathlib import PosixPath +from pathlib import Path import pytest @@ -21,7 +21,7 @@ message = "hello, world" pytestmark = skip_unless_feature("zlib") -def save_font(request: pytest.FixtureRequest, tmp_path: PosixPath) -> str: +def save_font(request: pytest.FixtureRequest, tmp_path: Path) -> str: with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) assert isinstance(font, FontFile.FontFile) @@ -48,7 +48,7 @@ def save_font(request: pytest.FixtureRequest, tmp_path: PosixPath) -> str: return tempname -def test_sanity(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: +def test_sanity(request: pytest.FixtureRequest, tmp_path: Path) -> None: save_font(request, tmp_path) @@ -66,7 +66,7 @@ def test_invalid_file() -> None: PcfFontFile.PcfFontFile(fp) -def test_draw(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: +def test_draw(request: pytest.FixtureRequest, tmp_path: Path) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) im = Image.new("L", (130, 30), "white") @@ -75,7 +75,7 @@ def test_draw(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0) -def test_textsize(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: +def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) for i in range(255): @@ -92,7 +92,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: def _test_high_characters( - request: pytest.FixtureRequest, tmp_path: PosixPath, message: str | bytes + request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes ) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) @@ -102,7 +102,7 @@ def _test_high_characters( assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0) -def test_high_characters(request: pytest.FixtureRequest, tmp_path: PosixPath) -> None: +def test_high_characters(request: pytest.FixtureRequest, tmp_path: Path) -> None: message = "".join(chr(i + 1) for i in range(140, 232)) _test_high_characters(request, tmp_path, message) # accept bytes instances. diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 894d4eb56..895458d9d 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from pathlib import PosixPath +from pathlib import Path from typing import TypedDict import pytest @@ -45,9 +45,7 @@ charsets: dict[str, Charset] = { pytestmark = skip_unless_feature("zlib") -def save_font( - request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str -) -> str: +def save_font(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> str: with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file, encoding) assert isinstance(font, FontFile.FontFile) @@ -75,16 +73,12 @@ def save_font( @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) -def test_sanity( - request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str -) -> None: +def test_sanity(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> None: save_font(request, tmp_path, encoding) @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) -def test_draw( - request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str -) -> None: +def test_draw(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> None: tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") @@ -96,7 +90,7 @@ def test_draw( @pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) def test_textsize( - request: pytest.FixtureRequest, tmp_path: PosixPath, encoding: str + request: pytest.FixtureRequest, tmp_path: Path, encoding: str ) -> None: tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) From e3932b7dbaf6aff8ef4b7a24007f4de07477ec91 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:58:41 +0200 Subject: [PATCH 015/157] Exclude from coverage: empty bodies in protocols or abstract methods --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 46df3f90d..5678e4566 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,6 +10,9 @@ exclude_also = if DEBUG: # Don't complain about compatibility code for missing optional dependencies except ImportError + # Empty bodies in protocols or abstract methods + ^\s*def [a-zA-Z0-9_]+\(.*\)(\s*->.*)?:\s*\.\.\.(\s*#.*)?$ + ^\s*\.\.\.(\s*#.*)?$ [run] omit = From b3a7ae065c4f34b345ecaa3b019bbd1d24e7922c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Jan 2024 06:40:03 +1100 Subject: [PATCH 016/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 62ae2a68b..7d80eec03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Do not support using test-image-results to upload images after test failures #7739 + [radarhere] + +- Changed ImageMath.ops to be static #7721 + [radarhere] + - Fix APNG info after seeking backwards more than twice #7701 [esoma, radarhere] From ddb7df0ec6b5852e509dbf00675a3866ca00bd66 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Jan 2024 22:18:46 +1100 Subject: [PATCH 017/157] Added type hints --- Tests/test_image_convert.py | 50 +++++++++++----------- Tests/test_image_copy.py | 6 +-- Tests/test_image_crop.py | 14 +++---- Tests/test_image_frombytes.py | 2 +- Tests/test_image_fromqimage.py | 15 +++---- Tests/test_image_getbands.py | 2 +- Tests/test_image_getbbox.py | 12 +++--- Tests/test_image_getcolors.py | 6 +-- Tests/test_image_getdata.py | 6 +-- Tests/test_image_getim.py | 2 +- Tests/test_image_getprojection.py | 2 +- Tests/test_image_histogram.py | 4 +- Tests/test_image_point.py | 8 ++-- Tests/test_image_putalpha.py | 6 +-- Tests/test_image_quantize.py | 24 +++++------ Tests/test_image_resize.py | 70 +++++++++++++++++++++---------- Tests/test_image_split.py | 12 +++--- Tests/test_image_tobitmap.py | 2 +- Tests/test_image_tobytes.py | 2 +- Tests/test_image_transpose.py | 19 +++++---- 20 files changed, 149 insertions(+), 115 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index d4ddc2a31..f154de123 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image @@ -7,8 +9,8 @@ from PIL import Image from .helper import assert_image, assert_image_equal, assert_image_similar, hopper -def test_sanity(): - def convert(im, mode): +def test_sanity() -> None: + def convert(im: Image.Image, mode: str) -> None: out = im.convert(mode) assert out.mode == mode assert out.size == im.size @@ -40,13 +42,13 @@ def test_sanity(): convert(im, output_mode) -def test_unsupported_conversion(): +def test_unsupported_conversion() -> None: im = hopper() with pytest.raises(ValueError): im.convert("INVALID") -def test_default(): +def test_default() -> None: im = hopper("P") assert im.mode == "P" converted_im = im.convert() @@ -62,18 +64,18 @@ def test_default(): # ref https://github.com/python-pillow/Pillow/issues/274 -def _test_float_conversion(im): +def _test_float_conversion(im: Image.Image) -> None: orig = im.getpixel((5, 5)) converted = im.convert("F").getpixel((5, 5)) assert orig == converted -def test_8bit(): +def test_8bit() -> None: with Image.open("Tests/images/hopper.jpg") as im: _test_float_conversion(im.convert("L")) -def test_16bit(): +def test_16bit() -> None: with Image.open("Tests/images/16bit.cropped.tif") as im: _test_float_conversion(im) @@ -83,19 +85,19 @@ def test_16bit(): assert im_i16.getpixel((0, 0)) == 65535 -def test_16bit_workaround(): +def test_16bit_workaround() -> None: with Image.open("Tests/images/16bit.cropped.tif") as im: _test_float_conversion(im.convert("I")) -def test_opaque(): +def test_opaque() -> None: alpha = hopper("P").convert("PA").getchannel("A") solid = Image.new("L", (128, 128), 255) assert_image_equal(alpha, solid) -def test_rgba_p(): +def test_rgba_p() -> None: im = hopper("RGBA") im.putalpha(hopper("L")) @@ -105,14 +107,14 @@ def test_rgba_p(): assert_image_similar(im, comparable, 20) -def test_rgba(): +def test_rgba() -> None: with Image.open("Tests/images/transparent.png") as im: assert im.mode == "RGBA" assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5) -def test_trns_p(tmp_path): +def test_trns_p(tmp_path: Path) -> None: im = hopper("P") im.info["transparency"] = 0 @@ -131,7 +133,7 @@ def test_trns_p(tmp_path): @pytest.mark.parametrize("mode", ("LA", "PA", "RGBA")) -def test_trns_p_transparency(mode): +def test_trns_p_transparency(mode: str) -> None: # Arrange im = hopper("P") im.info["transparency"] = 128 @@ -148,7 +150,7 @@ def test_trns_p_transparency(mode): assert converted_im.palette is None -def test_trns_l(tmp_path): +def test_trns_l(tmp_path: Path) -> None: im = hopper("L") im.info["transparency"] = 128 @@ -171,7 +173,7 @@ def test_trns_l(tmp_path): im_p.save(f) -def test_trns_RGB(tmp_path): +def test_trns_RGB(tmp_path: Path) -> None: im = hopper("RGB") im.info["transparency"] = im.getpixel((0, 0)) @@ -201,7 +203,7 @@ def test_trns_RGB(tmp_path): @pytest.mark.parametrize("convert_mode", ("L", "LA", "I")) -def test_l_macro_rounding(convert_mode): +def test_l_macro_rounding(convert_mode: str) -> None: for mode in ("P", "PA"): im = Image.new(mode, (1, 1)) im.palette.getcolor((0, 1, 2)) @@ -214,7 +216,7 @@ def test_l_macro_rounding(convert_mode): assert converted_color == 1 -def test_gif_with_rgba_palette_to_p(): +def test_gif_with_rgba_palette_to_p() -> None: # See https://github.com/python-pillow/Pillow/issues/2433 with Image.open("Tests/images/hopper.gif") as im: im.info["transparency"] = 255 @@ -226,7 +228,7 @@ def test_gif_with_rgba_palette_to_p(): im_p.load() -def test_p_la(): +def test_p_la() -> None: im = hopper("RGBA") alpha = hopper("L") im.putalpha(alpha) @@ -236,7 +238,7 @@ def test_p_la(): assert_image_similar(alpha, comparable, 5) -def test_p2pa_alpha(): +def test_p2pa_alpha() -> None: with Image.open("Tests/images/tiny.png") as im: assert im.mode == "P" @@ -250,13 +252,13 @@ def test_p2pa_alpha(): assert im_a.getpixel((x, y)) == alpha -def test_p2pa_palette(): +def test_p2pa_palette() -> None: with Image.open("Tests/images/tiny.png") as im: im_pa = im.convert("PA") assert im_pa.getpalette() == im.getpalette() -def test_matrix_illegal_conversion(): +def test_matrix_illegal_conversion() -> None: # Arrange im = hopper("CMYK") # fmt: off @@ -272,7 +274,7 @@ def test_matrix_illegal_conversion(): im.convert(mode="CMYK", matrix=matrix) -def test_matrix_wrong_mode(): +def test_matrix_wrong_mode() -> None: # Arrange im = hopper("L") # fmt: off @@ -289,7 +291,7 @@ def test_matrix_wrong_mode(): @pytest.mark.parametrize("mode", ("RGB", "L")) -def test_matrix_xyz(mode): +def test_matrix_xyz(mode: str) -> None: # Arrange im = hopper("RGB") im.info["transparency"] = (255, 0, 0) @@ -317,7 +319,7 @@ def test_matrix_xyz(mode): assert converted_im.info["transparency"] == 105 -def test_matrix_identity(): +def test_matrix_identity() -> None: # Arrange im = hopper("RGB") # fmt: off diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index 3a26ef96e..027e5338b 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -10,7 +10,7 @@ from .helper import hopper, skip_unless_feature @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) -def test_copy(mode): +def test_copy(mode: str) -> None: cropped_coordinates = (10, 10, 20, 20) cropped_size = (10, 10) @@ -39,7 +39,7 @@ def test_copy(mode): assert out.size == cropped_size -def test_copy_zero(): +def test_copy_zero() -> None: im = Image.new("RGB", (0, 0)) out = im.copy() assert out.mode == im.mode @@ -47,7 +47,7 @@ def test_copy_zero(): @skip_unless_feature("libtiff") -def test_deepcopy(): +def test_deepcopy() -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: out = copy.deepcopy(im) assert out.size == (590, 88) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 5e02a3b0d..d095364ba 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -8,7 +8,7 @@ from .helper import assert_image_equal, hopper @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) -def test_crop(mode): +def test_crop(mode: str) -> None: im = hopper(mode) assert_image_equal(im.crop(), im) @@ -17,8 +17,8 @@ def test_crop(mode): assert cropped.size == (50, 50) -def test_wide_crop(): - def crop(*bbox): +def test_wide_crop() -> None: + def crop(*bbox: int) -> tuple[int, ...]: i = im.crop(bbox) h = i.histogram() while h and not h[-1]: @@ -47,14 +47,14 @@ def test_wide_crop(): @pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2))) -def test_negative_crop(box): +def test_negative_crop(box: tuple[int, int, int, int]) -> None: im = Image.new("RGB", (10, 10)) with pytest.raises(ValueError): im.crop(box) -def test_crop_float(): +def test_crop_float() -> None: # Check cropping floats are rounded to nearest integer # https://github.com/python-pillow/Pillow/issues/1744 @@ -69,7 +69,7 @@ def test_crop_float(): assert cropped.size == (3, 5) -def test_crop_crash(): +def test_crop_crash() -> None: # Image.crop crashes prepatch with an access violation # apparently a use after free on Windows, see # https://github.com/python-pillow/Pillow/issues/1077 @@ -87,7 +87,7 @@ def test_crop_crash(): img.load() -def test_crop_zero(): +def test_crop_zero() -> None: im = Image.new("RGB", (0, 0), "white") cropped = im.crop((0, 0, 0, 0)) diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index 5d5e9f2df..6474daba1 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -8,7 +8,7 @@ from .helper import assert_image_equal, hopper @pytest.mark.parametrize("data_type", ("bytes", "memoryview")) -def test_sanity(data_type): +def test_sanity(data_type) -> None: im1 = hopper() data = im1.tobytes() diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 76b576da5..ea31a9de9 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,6 +1,7 @@ from __future__ import annotations import warnings +from typing import Generator import pytest @@ -18,7 +19,7 @@ pytestmark = pytest.mark.skipif( @pytest.fixture -def test_images(): +def test_images() -> Generator[Image.Image, None, None]: ims = [ hopper(), Image.open("Tests/images/transparent.png"), @@ -31,7 +32,7 @@ def test_images(): im.close() -def roundtrip(expected): +def roundtrip(expected: Image.Image) -> None: # PIL -> Qt intermediate = expected.toqimage() # Qt -> PIL @@ -43,26 +44,26 @@ def roundtrip(expected): assert_image_equal(result, expected.convert("RGB")) -def test_sanity_1(test_images): +def test_sanity_1(test_images: Generator[Image.Image, None, None]) -> None: for im in test_images: roundtrip(im.convert("1")) -def test_sanity_rgb(test_images): +def test_sanity_rgb(test_images: Generator[Image.Image, None, None]) -> None: for im in test_images: roundtrip(im.convert("RGB")) -def test_sanity_rgba(test_images): +def test_sanity_rgba(test_images: Generator[Image.Image, None, None]) -> None: for im in test_images: roundtrip(im.convert("RGBA")) -def test_sanity_l(test_images): +def test_sanity_l(test_images: Generator[Image.Image, None, None]) -> None: for im in test_images: roundtrip(im.convert("L")) -def test_sanity_p(test_images): +def test_sanity_p(test_images: Generator[Image.Image, None, None]) -> None: for im in test_images: roundtrip(im.convert("P")) diff --git a/Tests/test_image_getbands.py b/Tests/test_image_getbands.py index 64339e2cd..887553fc0 100644 --- a/Tests/test_image_getbands.py +++ b/Tests/test_image_getbands.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import Image -def test_getbands(): +def test_getbands() -> None: assert Image.new("1", (1, 1)).getbands() == ("1",) assert Image.new("L", (1, 1)).getbands() == ("L",) assert Image.new("I", (1, 1)).getbands() == ("I",) diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py index b18a7202e..18c6f6579 100644 --- a/Tests/test_image_getbbox.py +++ b/Tests/test_image_getbbox.py @@ -7,13 +7,13 @@ from PIL import Image from .helper import hopper -def test_sanity(): +def test_sanity() -> None: bbox = hopper().getbbox() assert isinstance(bbox, tuple) -def test_bbox(): - def check(im, fill_color): +def test_bbox() -> None: + def check(im: Image.Image, fill_color: int | tuple[int, ...]) -> None: assert im.getbbox() is None im.paste(fill_color, (10, 25, 90, 75)) @@ -34,8 +34,8 @@ def test_bbox(): check(im, 255) for mode in ("RGBA", "RGBa"): - for color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)): - im = Image.new(mode, (100, 100), color) + for rgba_color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)): + im = Image.new(mode, (100, 100), rgba_color) check(im, (255, 255, 255, 255)) for mode in ("La", "LA", "PA"): @@ -45,7 +45,7 @@ def test_bbox(): @pytest.mark.parametrize("mode", ("RGBA", "RGBa", "La", "LA", "PA")) -def test_bbox_alpha_only_false(mode): +def test_bbox_alpha_only_false(mode: str) -> None: im = Image.new(mode, (100, 100)) assert im.getbbox(alpha_only=False) is None diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py index 17460fa93..8f8870f4f 100644 --- a/Tests/test_image_getcolors.py +++ b/Tests/test_image_getcolors.py @@ -3,8 +3,8 @@ from __future__ import annotations from .helper import hopper -def test_getcolors(): - def getcolors(mode, limit=None): +def test_getcolors() -> None: + def getcolors(mode: str, limit: int | None = None) -> int | None: im = hopper(mode) if limit: colors = im.getcolors(limit) @@ -39,7 +39,7 @@ def test_getcolors(): # -------------------------------------------------------------------- -def test_pack(): +def test_pack() -> None: # Pack problems for small tables (@PIL209) im = hopper().quantize(3).convert("RGB") diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py index ace64279b..ac27400be 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -5,7 +5,7 @@ from PIL import Image from .helper import hopper -def test_sanity(): +def test_sanity() -> None: data = hopper().getdata() len(data) @@ -14,8 +14,8 @@ def test_sanity(): assert data[0] == (20, 20, 70) -def test_roundtrip(): - def getdata(mode): +def test_roundtrip() -> None: + def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]: im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST) data = im.getdata() return data[0], len(data), len(list(data)) diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index bc8a7485e..9afa02b0a 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -3,7 +3,7 @@ from __future__ import annotations from .helper import hopper -def test_sanity(): +def test_sanity() -> None: im = hopper() type_repr = repr(type(im.getim())) diff --git a/Tests/test_image_getprojection.py b/Tests/test_image_getprojection.py index e90f5f505..2b5a758ed 100644 --- a/Tests/test_image_getprojection.py +++ b/Tests/test_image_getprojection.py @@ -5,7 +5,7 @@ from PIL import Image from .helper import hopper -def test_sanity(): +def test_sanity() -> None: im = hopper() projection = im.getprojection() diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py index 3ac6649e0..dbd55d4c2 100644 --- a/Tests/test_image_histogram.py +++ b/Tests/test_image_histogram.py @@ -3,8 +3,8 @@ from __future__ import annotations from .helper import hopper -def test_histogram(): - def histogram(mode): +def test_histogram() -> None: + def histogram(mode: str) -> tuple[int, int, int]: h = hopper(mode).histogram() return len(h), min(h), max(h) diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 2232b9442..05f209351 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -5,7 +5,7 @@ import pytest from .helper import assert_image_equal, hopper -def test_sanity(): +def test_sanity() -> None: im = hopper() with pytest.raises(ValueError): @@ -39,7 +39,7 @@ def test_sanity(): im.point(lambda x: x // 2) -def test_16bit_lut(): +def test_16bit_lut() -> None: """Tests for 16 bit -> 8 bit lut for converting I->L images see https://github.com/python-pillow/Pillow/issues/440 """ @@ -47,7 +47,7 @@ def test_16bit_lut(): im.point(list(range(256)) * 256, "L") -def test_f_lut(): +def test_f_lut() -> None: """Tests for floating point lut of 8bit gray image""" im = hopper("L") lut = [0.5 * float(x) for x in range(256)] @@ -58,7 +58,7 @@ def test_f_lut(): assert_image_equal(out.convert("L"), im.point(int_lut, "L")) -def test_f_mode(): +def test_f_mode() -> None: im = hopper("F") with pytest.raises(ValueError): im.point(None) diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py index c44b048d5..2c92911d1 100644 --- a/Tests/test_image_putalpha.py +++ b/Tests/test_image_putalpha.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import Image -def test_interface(): +def test_interface() -> None: im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) assert im.getpixel((0, 0)) == (1, 2, 3, 0) @@ -17,7 +17,7 @@ def test_interface(): assert im.getpixel((0, 0)) == (1, 2, 3, 5) -def test_promote(): +def test_promote() -> None: im = Image.new("L", (1, 1), 1) assert im.getpixel((0, 0)) == 1 @@ -40,7 +40,7 @@ def test_promote(): assert im.getpixel((0, 0)) == (1, 2, 3, 4) -def test_readonly(): +def test_readonly() -> None: im = Image.new("RGB", (1, 1), (1, 2, 3)) im.readonly = 1 diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 1475b027b..873a9bb5d 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -8,7 +8,7 @@ from PIL import Image, features from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature -def test_sanity(): +def test_sanity() -> None: image = hopper() converted = image.quantize() assert converted.mode == "P" @@ -21,7 +21,7 @@ def test_sanity(): @skip_unless_feature("libimagequant") -def test_libimagequant_quantize(): +def test_libimagequant_quantize() -> None: image = hopper() if is_ppc64le(): libimagequant = parse_version(features.version_feature("libimagequant")) @@ -33,7 +33,7 @@ def test_libimagequant_quantize(): assert len(converted.getcolors()) == 100 -def test_octree_quantize(): +def test_octree_quantize() -> None: image = hopper() converted = image.quantize(100, Image.Quantize.FASTOCTREE) assert converted.mode == "P" @@ -41,7 +41,7 @@ def test_octree_quantize(): assert len(converted.getcolors()) == 100 -def test_rgba_quantize(): +def test_rgba_quantize() -> None: image = hopper("RGBA") with pytest.raises(ValueError): image.quantize(method=0) @@ -49,7 +49,7 @@ def test_rgba_quantize(): assert image.quantize().convert().mode == "RGBA" -def test_quantize(): +def test_quantize() -> None: with Image.open("Tests/images/caption_6_33_22.png") as image: image = image.convert("RGB") converted = image.quantize() @@ -57,7 +57,7 @@ def test_quantize(): assert_image_similar(converted.convert("RGB"), image, 1) -def test_quantize_no_dither(): +def test_quantize_no_dither() -> None: image = hopper() with Image.open("Tests/images/caption_6_33_22.png") as palette: palette = palette.convert("P") @@ -67,7 +67,7 @@ def test_quantize_no_dither(): assert converted.palette.palette == palette.palette.palette -def test_quantize_no_dither2(): +def test_quantize_no_dither2() -> None: im = Image.new("RGB", (9, 1)) im.putdata([(p,) * 3 for p in range(0, 36, 4)]) @@ -83,7 +83,7 @@ def test_quantize_no_dither2(): assert px[x, 0] == (0 if x < 5 else 1) -def test_quantize_dither_diff(): +def test_quantize_dither_diff() -> None: image = hopper() with Image.open("Tests/images/caption_6_33_22.png") as palette: palette = palette.convert("P") @@ -94,14 +94,14 @@ def test_quantize_dither_diff(): assert dither.tobytes() != nodither.tobytes() -def test_colors(): +def test_colors() -> None: im = hopper() colors = 2 converted = im.quantize(colors) assert len(converted.palette.palette) == colors * len("RGB") -def test_transparent_colors_equal(): +def test_transparent_colors_equal() -> None: im = Image.new("RGBA", (1, 2), (0, 0, 0, 0)) px = im.load() px[0, 1] = (255, 255, 255, 0) @@ -120,7 +120,7 @@ def test_transparent_colors_equal(): (Image.Quantize.FASTOCTREE, (0, 0, 0, 0)), ), ) -def test_palette(method, color): +def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None: im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color) converted = im.quantize(method=method) @@ -128,7 +128,7 @@ def test_palette(method, color): assert converted_px[0, 0] == converted.palette.colors[color] -def test_small_palette(): +def test_small_palette() -> None: # Arrange im = hopper() diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index aedcf4a09..bd45ee893 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -4,6 +4,8 @@ Tests for resize functionality. from __future__ import annotations from itertools import permutations +from pathlib import Path +from typing import Generator import pytest @@ -19,7 +21,9 @@ from .helper import ( class TestImagingCoreResize: - def resize(self, im, size, f): + def resize( + self, im: Image.Image, size: tuple[int, int], f: Image.Resampling + ) -> Image.Image: # Image class independent version of resize. im.load() return im._new(im.im.resize(size, f)) @@ -27,14 +31,14 @@ class TestImagingCoreResize: @pytest.mark.parametrize( "mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16") ) - def test_nearest_mode(self, mode): + def test_nearest_mode(self, mode: str) -> None: im = hopper(mode) r = self.resize(im, (15, 12), Image.Resampling.NEAREST) assert r.mode == mode assert r.size == (15, 12) assert r.im.bands == im.im.bands - def test_convolution_modes(self): + def test_convolution_modes(self) -> None: with pytest.raises(ValueError): self.resize(hopper("1"), (15, 12), Image.Resampling.BILINEAR) with pytest.raises(ValueError): @@ -59,7 +63,7 @@ class TestImagingCoreResize: Image.Resampling.LANCZOS, ), ) - def test_reduce_filters(self, resample): + def test_reduce_filters(self, resample: Image.Resampling) -> None: r = self.resize(hopper("RGB"), (15, 12), resample) assert r.mode == "RGB" assert r.size == (15, 12) @@ -75,7 +79,7 @@ class TestImagingCoreResize: Image.Resampling.LANCZOS, ), ) - def test_enlarge_filters(self, resample): + def test_enlarge_filters(self, resample: Image.Resampling) -> None: r = self.resize(hopper("RGB"), (212, 195), resample) assert r.mode == "RGB" assert r.size == (212, 195) @@ -99,7 +103,9 @@ class TestImagingCoreResize: ("LA", ("filled", "dirty")), ), ) - def test_endianness(self, resample, mode, channels_set): + def test_endianness( + self, resample: Image.Resampling, mode: str, channels_set: tuple[str, ...] + ) -> None: # Make an image with one colored pixel, in one channel. # When resized, that channel should be the same as a GS image. # Other channels should be unaffected. @@ -139,17 +145,17 @@ class TestImagingCoreResize: Image.Resampling.LANCZOS, ), ) - def test_enlarge_zero(self, resample): + def test_enlarge_zero(self, resample: Image.Resampling) -> None: r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample) assert r.mode == "RGB" assert r.size == (212, 195) assert r.getdata()[0] == (0, 0, 0) - def test_unknown_filter(self): + def test_unknown_filter(self) -> None: with pytest.raises(ValueError): self.resize(hopper(), (10, 10), 9) - def test_cross_platform(self, tmp_path): + def test_cross_platform(self, tmp_path: Path) -> None: # This test is intended for only check for consistent behaviour across # platforms. So if a future Pillow change requires that the test file # be updated, that is okay. @@ -162,7 +168,7 @@ class TestImagingCoreResize: @pytest.fixture -def gradients_image(): +def gradients_image() -> Generator[Image.Image, None, None]: with Image.open("Tests/images/radial_gradients.png") as im: im.load() try: @@ -172,7 +178,7 @@ def gradients_image(): class TestReducingGapResize: - def test_reducing_gap_values(self, gradients_image): + def test_reducing_gap_values(self, gradients_image: Image.Image) -> None: ref = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, reducing_gap=None ) @@ -191,7 +197,12 @@ class TestReducingGapResize: "box, epsilon", ((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)), ) - def test_reducing_gap_1(self, gradients_image, box, epsilon): + def test_reducing_gap_1( + self, + gradients_image: Image.Image, + box: tuple[float, float, float, float], + epsilon: float, + ) -> None: ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 @@ -206,7 +217,12 @@ class TestReducingGapResize: "box, epsilon", ((None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1)), ) - def test_reducing_gap_2(self, gradients_image, box, epsilon): + def test_reducing_gap_2( + self, + gradients_image: Image.Image, + box: tuple[float, float, float, float], + epsilon: float, + ) -> None: ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 @@ -221,7 +237,12 @@ class TestReducingGapResize: "box, epsilon", ((None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5)), ) - def test_reducing_gap_3(self, gradients_image, box, epsilon): + def test_reducing_gap_3( + self, + gradients_image: Image.Image, + box: tuple[float, float, float, float], + epsilon: float, + ) -> None: ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 @@ -233,7 +254,9 @@ class TestReducingGapResize: assert_image_similar(ref, im, epsilon) @pytest.mark.parametrize("box", (None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256))) - def test_reducing_gap_8(self, gradients_image, box): + def test_reducing_gap_8( + self, gradients_image: Image.Image, box: tuple[float, float, float, float] + ) -> None: ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0 @@ -245,7 +268,12 @@ class TestReducingGapResize: "box, epsilon", (((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)), ) - def test_box_filter(self, gradients_image, box, epsilon): + def test_box_filter( + self, + gradients_image: Image.Image, + box: tuple[float, float, float, float], + epsilon: float, + ) -> None: ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box) im = gradients_image.resize( (52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0 @@ -255,8 +283,8 @@ class TestReducingGapResize: class TestImageResize: - def test_resize(self): - def resize(mode, size): + def test_resize(self) -> None: + def resize(mode: str, size: tuple[int, int]) -> None: out = hopper(mode).resize(size) assert out.mode == mode assert out.size == size @@ -271,7 +299,7 @@ class TestImageResize: im.resize((10, 10), "unknown") @skip_unless_feature("libtiff") - def test_load_first(self): + def test_load_first(self) -> None: # load() may change the size of the image # Test that resize() is calling it before getting the size with Image.open("Tests/images/g4_orientation_5.tif") as im: @@ -279,13 +307,13 @@ class TestImageResize: assert im.size == (64, 64) @pytest.mark.parametrize("mode", ("L", "RGB", "I", "F")) - def test_default_filter_bicubic(self, mode): + def test_default_filter_bicubic(self, mode: str) -> None: im = hopper(mode) assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20)) @pytest.mark.parametrize( "mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16") ) - def test_default_filter_nearest(self, mode): + def test_default_filter_nearest(self, mode: str) -> None: im = hopper(mode) assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20)) diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index c39a100e7..3385f81f5 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, features @@ -7,8 +9,8 @@ from PIL import Image, features from .helper import assert_image_equal, hopper -def test_split(): - def split(mode): +def test_split() -> None: + def split(mode: str) -> list[tuple[str, int, int]]: layers = hopper(mode).split() return [(i.mode, i.size[0], i.size[1]) for i in layers] @@ -36,18 +38,18 @@ def test_split(): @pytest.mark.parametrize( "mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr") ) -def test_split_merge(mode): +def test_split_merge(mode: str) -> None: expected = Image.merge(mode, hopper(mode).split()) assert_image_equal(hopper(mode), expected) -def test_split_open(tmp_path): +def test_split_open(tmp_path: Path) -> None: if features.check("zlib"): test_file = str(tmp_path / "temp.png") else: test_file = str(tmp_path / "temp.pcx") - def split_open(mode): + def split_open(mode: str) -> int: hopper(mode).save(test_file) with Image.open(test_file) as im: return len(im.split()) diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index 89a41cf8e..f7a3cc41d 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -5,7 +5,7 @@ import pytest from .helper import assert_image_equal, fromstring, hopper -def test_sanity(): +def test_sanity() -> None: with pytest.raises(ValueError): hopper().tobitmap() diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py index 8f15adac0..d32b6c09b 100644 --- a/Tests/test_image_tobytes.py +++ b/Tests/test_image_tobytes.py @@ -3,6 +3,6 @@ from __future__ import annotations from .helper import hopper -def test_sanity(): +def test_sanity() -> None: data = hopper().tobytes() assert isinstance(data, bytes) diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index 01bf5a839..d384d8141 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -2,6 +2,7 @@ from __future__ import annotations import pytest +from PIL import Image from PIL.Image import Transpose from . import helper @@ -14,7 +15,7 @@ HOPPER = { @pytest.mark.parametrize("mode", HOPPER) -def test_flip_left_right(mode): +def test_flip_left_right(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.FLIP_LEFT_RIGHT) assert out.mode == mode @@ -28,7 +29,7 @@ def test_flip_left_right(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_flip_top_bottom(mode): +def test_flip_top_bottom(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.FLIP_TOP_BOTTOM) assert out.mode == mode @@ -42,7 +43,7 @@ def test_flip_top_bottom(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_rotate_90(mode): +def test_rotate_90(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.ROTATE_90) assert out.mode == mode @@ -56,7 +57,7 @@ def test_rotate_90(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_rotate_180(mode): +def test_rotate_180(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.ROTATE_180) assert out.mode == mode @@ -70,7 +71,7 @@ def test_rotate_180(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_rotate_270(mode): +def test_rotate_270(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.ROTATE_270) assert out.mode == mode @@ -84,7 +85,7 @@ def test_rotate_270(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_transpose(mode): +def test_transpose(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.TRANSPOSE) assert out.mode == mode @@ -98,7 +99,7 @@ def test_transpose(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_tranverse(mode): +def test_tranverse(mode: str) -> None: im = HOPPER[mode] out = im.transpose(Transpose.TRANSVERSE) assert out.mode == mode @@ -112,10 +113,10 @@ def test_tranverse(mode): @pytest.mark.parametrize("mode", HOPPER) -def test_roundtrip(mode): +def test_roundtrip(mode: str) -> None: im = HOPPER[mode] - def transpose(first, second): + def transpose(first: Transpose, second: Transpose) -> Image.Image: return im.transpose(first).transpose(second) assert_image_equal( From 945253672a74415807b5f685f54ebb1533ff468e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:11:18 +0200 Subject: [PATCH 018/157] Handle os.PathLike in is_path --- src/PIL/ImageFont.py | 2 +- src/PIL/_util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index a63b73b33..9eecad1ca 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -230,7 +230,7 @@ class FreeTypeFont: ) if is_path(font): - if isinstance(font, Path): + if isinstance(font, os.PathLike): font = str(font) if sys.platform == "win32": font_bytes_path = font if isinstance(font, bytes) else font.encode() diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 4ecdc4bd3..b649500ab 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -8,7 +8,7 @@ from ._typing import StrOrBytesPath, TypeGuard def is_path(f: Any) -> TypeGuard[StrOrBytesPath]: - return isinstance(f, (bytes, str, Path)) + return isinstance(f, (bytes, str, os.PathLike)) def is_directory(f: Any) -> TypeGuard[bytes | str | Path]: From f613a9213f4edc7b58ac84a4793223c8e4fd9191 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:15:19 +0200 Subject: [PATCH 019/157] Parameterise test --- Tests/test_util.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 3395ef753..71a862569 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,27 +1,14 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import _util -def test_is_path(): - # Arrange - fp = "filename.ext" - - # Act - it_is = _util.is_path(fp) - - # Assert - assert it_is - - -def test_path_obj_is_path(): - # Arrange - from pathlib import Path - - test_path = Path("filename.ext") - +@pytest.mark.parametrize("test_path", ["filename.ext", Path("filename.ext")]) +def test_is_path(test_path): # Act it_is = _util.is_path(test_path) From 16d4068b42f0a6069e14b2327302df713dabbfed Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:17:13 +0200 Subject: [PATCH 020/157] Test os.PathLike that's not pathlib.Path --- Tests/test_util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 71a862569..617e5f7c6 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,13 +1,15 @@ from __future__ import annotations -from pathlib import Path +from pathlib import Path, PurePath import pytest from PIL import _util -@pytest.mark.parametrize("test_path", ["filename.ext", Path("filename.ext")]) +@pytest.mark.parametrize( + "test_path", ["filename.ext", Path("filename.ext"), PurePath("filename.ext")] +) def test_is_path(test_path): # Act it_is = _util.is_path(test_path) From d631afc266c3c1214e12373d3ad0d16978867a7f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 26 Jan 2024 20:46:58 +0200 Subject: [PATCH 021/157] Use os.fspath instead of isinstance and str --- src/PIL/ImageFont.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9eecad1ca..1feaf447a 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -230,8 +230,7 @@ class FreeTypeFont: ) if is_path(font): - if isinstance(font, os.PathLike): - font = str(font) + font = os.fspath(font) if sys.platform == "win32": font_bytes_path = font if isinstance(font, bytes) else font.encode() try: From 737314923fd1abe8cea2b1986626302215436481 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 15:19:43 +1100 Subject: [PATCH 022/157] Added type hints --- Tests/test_000_sanity.py | 2 +- Tests/test_binary.py | 6 +++--- Tests/test_file_cur.py | 4 ++-- Tests/test_file_ftex.py | 6 +++--- Tests/test_file_gbr.py | 8 ++++---- Tests/test_file_gd.py | 6 +++--- Tests/test_file_gimppalette.py | 4 ++-- Tests/test_file_imt.py | 4 ++-- Tests/test_file_mcidas.py | 4 ++-- Tests/test_file_pcd.py | 2 +- Tests/test_file_pixar.py | 4 ++-- Tests/test_file_qoi.py | 4 ++-- Tests/test_file_wal.py | 4 ++-- Tests/test_file_webp_lossless.py | 4 +++- Tests/test_file_xpm.py | 6 +++--- Tests/test_file_xvthumb.py | 6 +++--- Tests/test_fontfile.py | 4 +++- Tests/test_format_lab.py | 6 +++--- Tests/test_lib_image.py | 2 +- Tests/test_locale.py | 2 +- Tests/test_main.py | 2 +- Tests/test_pyroma.py | 2 +- Tests/test_uploader.py | 4 ++-- Tests/test_util.py | 14 ++++++++------ Tests/test_webp_leaks.py | 4 ++-- 25 files changed, 60 insertions(+), 54 deletions(-) diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index f64216bca..c3926250f 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import Image -def test_sanity(): +def test_sanity() -> None: # Make sure we have the binary extension Image.core.new("L", (100, 100)) diff --git a/Tests/test_binary.py b/Tests/test_binary.py index 41fb93fcf..d19799a09 100644 --- a/Tests/test_binary.py +++ b/Tests/test_binary.py @@ -3,12 +3,12 @@ from __future__ import annotations from PIL import _binary -def test_standard(): +def test_standard() -> None: assert _binary.i8(b"*") == 42 assert _binary.o8(42) == b"*" -def test_little_endian(): +def test_little_endian() -> None: assert _binary.i16le(b"\xff\xff\x00\x00") == 65535 assert _binary.i32le(b"\xff\xff\x00\x00") == 65535 @@ -16,7 +16,7 @@ def test_little_endian(): assert _binary.o32le(65535) == b"\xff\xff\x00\x00" -def test_big_endian(): +def test_big_endian() -> None: assert _binary.i16be(b"\x00\x00\xff\xff") == 0 assert _binary.i32be(b"\x00\x00\xff\xff") == 65535 diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 27b2bc914..dbf1b866d 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -7,7 +7,7 @@ from PIL import CurImagePlugin, Image TEST_FILE = "Tests/images/deerstalker.cur" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: assert im.size == (32, 32) assert isinstance(im, CurImagePlugin.CurImageFile) @@ -17,7 +17,7 @@ def test_sanity(): assert im.getpixel((16, 16)) == (84, 87, 86, 255) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 0f9154e3d..0c544245a 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -7,18 +7,18 @@ from PIL import FtexImagePlugin, Image from .helper import assert_image_equal_tofile, assert_image_similar -def test_load_raw(): +def test_load_raw() -> None: with Image.open("Tests/images/ftex_uncompressed.ftu") as im: assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png") -def test_load_dxt1(): +def test_load_dxt1() -> None: with Image.open("Tests/images/ftex_dxt1.ftc") as im: with Image.open("Tests/images/ftex_dxt1.png") as target: assert_image_similar(im, target.convert("RGBA"), 15) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index d84004e14..be98b08f2 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -7,12 +7,12 @@ from PIL import GbrImagePlugin, Image from .helper import assert_image_equal_tofile -def test_gbr_file(): +def test_gbr_file() -> None: with Image.open("Tests/images/gbr.gbr") as im: assert_image_equal_tofile(im, "Tests/images/gbr.png") -def test_load(): +def test_load() -> None: with Image.open("Tests/images/gbr.gbr") as im: assert im.load()[0, 0] == (0, 0, 0, 0) @@ -20,14 +20,14 @@ def test_load(): assert im.load()[0, 0] == (0, 0, 0, 0) -def test_multiple_load_operations(): +def test_multiple_load_operations() -> None: with Image.open("Tests/images/gbr.gbr") as im: im.load() im.load() assert_image_equal_tofile(im, "Tests/images/gbr.png") -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index e7db54fb4..d512df284 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -7,18 +7,18 @@ from PIL import GdImageFile, UnidentifiedImageError TEST_GD_FILE = "Tests/images/hopper.gd" -def test_sanity(): +def test_sanity() -> None: with GdImageFile.open(TEST_GD_FILE) as im: assert im.size == (128, 128) assert im.format == "GD" -def test_bad_mode(): +def test_bad_mode() -> None: with pytest.raises(ValueError): GdImageFile.open(TEST_GD_FILE, "bad mode") -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(UnidentifiedImageError): diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index 28855c28a..e8d5f1705 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -5,7 +5,7 @@ import pytest from PIL.GimpPaletteFile import GimpPaletteFile -def test_sanity(): +def test_sanity() -> None: with open("Tests/images/test.gpl", "rb") as fp: GimpPaletteFile(fp) @@ -22,7 +22,7 @@ def test_sanity(): GimpPaletteFile(fp) -def test_get_palette(): +def test_get_palette() -> None: # Arrange with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp: palette_file = GimpPaletteFile(fp) diff --git a/Tests/test_file_imt.py b/Tests/test_file_imt.py index aa13d4407..6957dfa0a 100644 --- a/Tests/test_file_imt.py +++ b/Tests/test_file_imt.py @@ -9,13 +9,13 @@ from PIL import Image, ImtImagePlugin from .helper import assert_image_equal_tofile -def test_sanity(): +def test_sanity() -> None: with Image.open("Tests/images/bw_gradient.imt") as im: assert_image_equal_tofile(im, "Tests/images/bw_gradient.png") @pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n")) -def test_invalid_file(data): +def test_invalid_file(data: bytes) -> None: with io.BytesIO(data) as fp: with pytest.raises(SyntaxError): ImtImagePlugin.ImtImageFile(fp) diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 73eba5cc8..2c94fdc39 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -7,14 +7,14 @@ from PIL import Image, McIdasImagePlugin from .helper import assert_image_equal_tofile -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): McIdasImagePlugin.McIdasImageFile(invalid_file) -def test_valid_file(): +def test_valid_file() -> None: # Arrange # https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8 # https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/ diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 1a37c6ab3..81a316fc1 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import Image -def test_load_raw(): +def test_load_raw() -> None: with Image.open("Tests/images/hopper.pcd") as im: im.load() # should not segfault. diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index c6ddc54e7..8f208cfbf 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -9,7 +9,7 @@ from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.pxr" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: im.load() assert im.mode == "RGB" @@ -21,7 +21,7 @@ def test_sanity(): assert_image_similar(im, im2, 4.8) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_qoi.py b/Tests/test_file_qoi.py index 6dc468754..fd4b981ce 100644 --- a/Tests/test_file_qoi.py +++ b/Tests/test_file_qoi.py @@ -7,7 +7,7 @@ from PIL import Image, QoiImagePlugin from .helper import assert_image_equal_tofile -def test_sanity(): +def test_sanity() -> None: with Image.open("Tests/images/hopper.qoi") as im: assert im.mode == "RGB" assert im.size == (128, 128) @@ -23,7 +23,7 @@ def test_sanity(): assert_image_equal_tofile(im, "Tests/images/pil123rgba.png") -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index 7acec9759..b34975e83 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -7,7 +7,7 @@ from .helper import assert_image_equal_tofile TEST_FILE = "Tests/images/hopper.wal" -def test_open(): +def test_open() -> None: with WalImageFile.open(TEST_FILE) as im: assert im.format == "WAL" assert im.format_description == "Quake2 Texture" @@ -19,7 +19,7 @@ def test_open(): assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") -def test_load(): +def test_load() -> None: with WalImageFile.open(TEST_FILE) as im: assert im.load()[0, 0] == 122 diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 08c80973a..32e29de56 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image @@ -10,7 +12,7 @@ _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") RGB_MODE = "RGB" -def test_write_lossless_rgb(tmp_path): +def test_write_lossless_rgb(tmp_path: Path) -> None: if _webp.WebPDecoderVersion() < 0x0200: pytest.skip("lossless not included") diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 529a45580..26afe93f4 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -9,7 +9,7 @@ from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.xpm" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: im.load() assert im.mode == "P" @@ -20,14 +20,14 @@ def test_sanity(): assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): XpmImagePlugin.XpmImageFile(invalid_file) -def test_load_read(): +def test_load_read() -> None: # Arrange with Image.open(TEST_FILE) as im: dummy_bytes = 1 diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index b87494eba..6b8115930 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -9,7 +9,7 @@ from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.p7" -def test_open(): +def test_open() -> None: # Act with Image.open(TEST_FILE) as im: # Assert @@ -20,7 +20,7 @@ def test_open(): assert_image_similar(im, im_hopper, 9) -def test_unexpected_eof(): +def test_unexpected_eof() -> None: # Test unexpected EOF reading XV thumbnail file # Arrange bad_file = "Tests/images/hopper_bad.p7" @@ -30,7 +30,7 @@ def test_unexpected_eof(): XVThumbImagePlugin.XVThumbImageFile(bad_file) -def test_invalid_file(): +def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_fontfile.py b/Tests/test_fontfile.py index eda8fb812..206499a04 100644 --- a/Tests/test_fontfile.py +++ b/Tests/test_fontfile.py @@ -1,11 +1,13 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import FontFile -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: tempname = str(tmp_path / "temp.pil") font = FontFile.FontFile() diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index a55620e09..4fcc37e88 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import Image -def test_white(): +def test_white() -> None: with Image.open("Tests/images/lab.tif") as i: i.load() @@ -24,7 +24,7 @@ def test_white(): assert list(b) == [128] * 100 -def test_green(): +def test_green() -> None: # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS # == RGB: 0, 152, 117 with Image.open("Tests/images/lab-green.tif") as i: @@ -32,7 +32,7 @@ def test_green(): assert k == (128, 28, 128) -def test_red(): +def test_red() -> None: # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS # == RGB: 255, 0, 124 with Image.open("Tests/images/lab-red.tif") as i: diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 1c642e4c9..31548bbc9 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -5,7 +5,7 @@ import pytest from PIL import Image -def test_setmode(): +def test_setmode() -> None: im = Image.new("L", (1, 1), 255) im.im.setmode("1") assert im.im.getpixel((0, 0)) == 255 diff --git a/Tests/test_locale.py b/Tests/test_locale.py index db9557d7b..1c8b84a2b 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -24,7 +24,7 @@ from PIL import Image path = "Tests/images/hopper.jpg" -def test_sanity(): +def test_sanity() -> None: with Image.open(path): pass try: diff --git a/Tests/test_main.py b/Tests/test_main.py index 9f61a0c81..46259f1dc 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -5,7 +5,7 @@ import subprocess import sys -def test_main(): +def test_main() -> None: out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8") lines = out.splitlines() assert lines[0] == "-" * 68 diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index c2cea08ca..c2f7fe22e 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -7,7 +7,7 @@ from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") -def test_pyroma(): +def test_pyroma() -> None: # Arrange data = pyroma.projectdata.get_data(".") diff --git a/Tests/test_uploader.py b/Tests/test_uploader.py index 75326288f..d55ceb4be 100644 --- a/Tests/test_uploader.py +++ b/Tests/test_uploader.py @@ -3,13 +3,13 @@ from __future__ import annotations from .helper import assert_image_equal, assert_image_similar, hopper -def check_upload_equal(): +def check_upload_equal() -> None: result = hopper("P").convert("RGB") target = hopper("RGB") assert_image_equal(result, target) -def check_upload_similar(): +def check_upload_similar() -> None: result = hopper("P").convert("RGB") target = hopper("RGB") assert_image_similar(result, target, 0) diff --git a/Tests/test_util.py b/Tests/test_util.py index 3395ef753..b47ca8827 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,11 +1,13 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import _util -def test_is_path(): +def test_is_path() -> None: # Arrange fp = "filename.ext" @@ -16,7 +18,7 @@ def test_is_path(): assert it_is -def test_path_obj_is_path(): +def test_path_obj_is_path() -> None: # Arrange from pathlib import Path @@ -29,7 +31,7 @@ def test_path_obj_is_path(): assert it_is -def test_is_not_path(tmp_path): +def test_is_not_path(tmp_path: Path) -> None: # Arrange with (tmp_path / "temp.ext").open("w") as fp: pass @@ -41,7 +43,7 @@ def test_is_not_path(tmp_path): assert not it_is_not -def test_is_directory(): +def test_is_directory() -> None: # Arrange directory = "Tests" @@ -52,7 +54,7 @@ def test_is_directory(): assert it_is -def test_is_not_directory(): +def test_is_not_directory() -> None: # Arrange text = "abc" @@ -63,7 +65,7 @@ def test_is_not_directory(): assert not it_is_not -def test_deferred_error(): +def test_deferred_error() -> None: # Arrange # Act diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index 0f51abc95..626fe427c 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -14,11 +14,11 @@ class TestWebPLeaks(PillowLeakTestCase): mem_limit = 3 * 1024 # kb iterations = 100 - def test_leak_load(self): + def test_leak_load(self) -> None: with open(test_file, "rb") as f: im_data = f.read() - def core(): + def core() -> None: with Image.open(BytesIO(im_data)) as im: im.load() From cd640e5df27c6c3c58d8cc63c16cba71c237b9a4 Mon Sep 17 00:00:00 2001 From: Nicola Guerrera Date: Mon, 22 Jan 2024 15:19:59 +0100 Subject: [PATCH 023/157] Refactor grabclipboard() for x11 and wayland MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simpified logic and made it more robust against edge cases ( see the `allowed_errors` list ). Doing error checking this way, makes the behaviour of this function for x11 and wayland platforms more silimar to darwin and windows systems. fix typo src/PIL/ImageGrab.py Co-authored-by: Ondrej Baranovič fix typo src/PIL/ImageGrab.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> ImageGrab: \added debian edge case to comment --- src/PIL/ImageGrab.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index a4993d3d4..1cb02f5f9 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -149,18 +149,7 @@ def grabclipboard(): session_type = None if shutil.which("wl-paste") and session_type in ("wayland", None): - output = subprocess.check_output(["wl-paste", "-l"]).decode() - mimetypes = output.splitlines() - if "image/png" in mimetypes: - mimetype = "image/png" - elif mimetypes: - mimetype = mimetypes[0] - else: - mimetype = None - - args = ["wl-paste"] - if mimetype: - args.extend(["-t", mimetype]) + args = ["wl-paste", "-t", "image"] elif shutil.which("xclip") and session_type in ("x11", None): args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] else: @@ -168,10 +157,19 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr - if err: - msg = f"{args[0]} error: {err.strip().decode()}" + err = p.stderr.decode() + if p.returncode != 0: + allowed_errors = [ + "Nothing is copied", # wl-paste, when the clipboard is empty + "not available", # wl-paste/debian xclip, when an image isn't available + "cannot convert", # xclip, when an image isn't available + "There is no owner", # xclip, when the clipboard isn't initialized + ] + if any(e in err for e in allowed_errors): + return None + msg = f"{args[0]} error: {err.strip() if err else 'Unknown error'}" raise ChildProcessError(msg) + data = io.BytesIO(p.stdout) im = Image.open(data) im.load() From b81341ae7e62a246adabc40982d2b81ed3b7542d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 20:15:10 +1100 Subject: [PATCH 024/157] Only decode stderr when necessary --- src/PIL/ImageGrab.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 1cb02f5f9..730351c0d 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -157,17 +157,21 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr.decode() + err = p.stderr if p.returncode != 0: allowed_errors = [ - "Nothing is copied", # wl-paste, when the clipboard is empty - "not available", # wl-paste/debian xclip, when an image isn't available - "cannot convert", # xclip, when an image isn't available - "There is no owner", # xclip, when the clipboard isn't initialized + # wl-paste, when the clipboard is empty + b"Nothing is copied", + # wl-paste/debian xclip, when an image isn't available + b"not available", + # xclip, when an image isn't available + b"cannot convert", + # xclip, when the clipboard isn't initialized + b"There is no owner", ] if any(e in err for e in allowed_errors): return None - msg = f"{args[0]} error: {err.strip() if err else 'Unknown error'}" + msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" raise ChildProcessError(msg) data = io.BytesIO(p.stdout) From d2d9240de4cafee650f11c085a7ec321240a8e3e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 19:26:55 +1100 Subject: [PATCH 025/157] Do not declare variable until necessary --- src/PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 730351c0d..ca27b520c 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -157,7 +157,6 @@ def grabclipboard(): raise NotImplementedError(msg) p = subprocess.run(args, capture_output=True) - err = p.stderr if p.returncode != 0: allowed_errors = [ # wl-paste, when the clipboard is empty @@ -169,6 +168,7 @@ def grabclipboard(): # xclip, when the clipboard isn't initialized b"There is no owner", ] + err = p.stderr if any(e in err for e in allowed_errors): return None msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" From 6998f3476843e2f8da00eb23545aab55dc280006 Mon Sep 17 00:00:00 2001 From: Nicola Guerrera Date: Sat, 27 Jan 2024 12:08:16 +0100 Subject: [PATCH 026/157] Rearrange error handling Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageGrab.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index ca27b520c..a2c7a9351 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -171,7 +171,9 @@ def grabclipboard(): err = p.stderr if any(e in err for e in allowed_errors): return None - msg = f"{args[0]} error: {err.strip().decode() if err else 'Unknown error'}" + msg = f"{args[0]} error" + if err: + msg += f": {err.strip().decode()}" raise ChildProcessError(msg) data = io.BytesIO(p.stdout) From d3205fae192ec10497326aacb7325f5880d07b04 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jan 2024 22:54:01 +1100 Subject: [PATCH 027/157] Simplified code --- src/PIL/ImageGrab.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index a2c7a9351..c04be521f 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -158,7 +158,8 @@ def grabclipboard(): p = subprocess.run(args, capture_output=True) if p.returncode != 0: - allowed_errors = [ + err = p.stderr + for silent_error in [ # wl-paste, when the clipboard is empty b"Nothing is copied", # wl-paste/debian xclip, when an image isn't available @@ -167,10 +168,9 @@ def grabclipboard(): b"cannot convert", # xclip, when the clipboard isn't initialized b"There is no owner", - ] - err = p.stderr - if any(e in err for e in allowed_errors): - return None + ]: + if err in silent_error: + return None msg = f"{args[0]} error" if err: msg += f": {err.strip().decode()}" From 61d47c3dfa200b186ecacd7b9a5090cedb5523b6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:06:06 +0200 Subject: [PATCH 028/157] More support for arbitrary os.PathLike --- Tests/test_image.py | 3 +-- docs/reference/open_files.rst | 2 +- src/PIL/Image.py | 16 ++++++---------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index dd989ad99..84189df54 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -7,6 +7,7 @@ import shutil import sys import tempfile import warnings +from pathlib import Path import pytest @@ -161,8 +162,6 @@ class TestImage: pass def test_pathlib(self, tmp_path): - from PIL.Image import Path - with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im: assert im.mode == "P" assert im.size == (10, 10) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index f31941c9a..730c8da5b 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -3,7 +3,7 @@ File Handling in Pillow ======================= -When opening a file as an image, Pillow requires a filename, ``pathlib.Path`` +When opening a file as an image, Pillow requires a filename, ``os.PathLike`` object, or a file-like object. Pillow uses the filename or ``Path`` to open a file, so for the rest of this article, they will all be treated as a file-like object. diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 553f36703..48125b317 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -39,7 +39,6 @@ import tempfile import warnings from collections.abc import Callable, MutableMapping from enum import IntEnum -from pathlib import Path try: from defusedxml import ElementTree @@ -2370,7 +2369,7 @@ class Image: implement the ``seek``, ``tell``, and ``write`` methods, and be opened in binary mode. - :param fp: A filename (string), pathlib.Path object or file object. + :param fp: A filename (string), os.PathLike object or file object. :param format: Optional format override. If omitted, the format to use is determined from the filename extension. If a file object was used instead of a filename, this @@ -2385,11 +2384,8 @@ class Image: filename = "" open_fp = False - if isinstance(fp, Path): - filename = str(fp) - open_fp = True - elif is_path(fp): - filename = fp + if is_path(fp): + filename = os.fspath(fp) open_fp = True elif fp == sys.stdout: try: @@ -3206,7 +3202,7 @@ def open(fp, mode="r", formats=None) -> Image: :py:meth:`~PIL.Image.Image.load` method). See :py:func:`~PIL.Image.new`. See :ref:`file-handling`. - :param fp: A filename (string), pathlib.Path object or a file object. + :param fp: A filename (string), os.PathLike object or a file object. The file object must implement ``file.read``, ``file.seek``, and ``file.tell`` methods, and be opened in binary mode. The file object will also seek to zero @@ -3244,8 +3240,8 @@ def open(fp, mode="r", formats=None) -> Image: exclusive_fp = False filename = "" - if isinstance(fp, Path): - filename = str(fp.resolve()) + if isinstance(fp, os.PathLike): + filename = os.path.realpath(os.fspath(fp)) elif is_path(fp): filename = fp From 52e51e12b950aac7a5bd5593ea9fc2981490c2d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 21:20:28 +0000 Subject: [PATCH 029/157] Update dependency cibuildwheel to v2.16.4 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index dd61634cd..867543ebd 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.16.2 +cibuildwheel==2.16.4 From 529487c244c64ee93ed7601eac9a9bbc3194827f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:48:39 +0200 Subject: [PATCH 030/157] Remove execute bit from setup.py --- setup.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 setup.py diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 From 866c26957d521ec02c6dc86c686800fe0a18a4d6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:37:24 +0200 Subject: [PATCH 031/157] Add check-shebang-scripts-are-executable to pre-commit --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6adc75b49..5ce0c9a17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,7 @@ repos: rev: v4.5.0 hooks: - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable - id: check-merge-conflict - id: check-json - id: check-toml From 0669532898c9cbb45ceebbeefb317dfcc97eaac1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:43:03 +0200 Subject: [PATCH 032/157] Remove shebangs --- Tests/check_fli_oob.py | 1 - Tests/images/create_eps.gnuplot | 2 -- Tests/oss-fuzz/fuzz_pillow.py | 2 -- setup.py | 1 - 4 files changed, 6 deletions(-) diff --git a/Tests/check_fli_oob.py b/Tests/check_fli_oob.py index ac46ff1eb..e0057a2c2 100644 --- a/Tests/check_fli_oob.py +++ b/Tests/check_fli_oob.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations from PIL import Image diff --git a/Tests/images/create_eps.gnuplot b/Tests/images/create_eps.gnuplot index 4d7e29877..57a3c8c97 100644 --- a/Tests/images/create_eps.gnuplot +++ b/Tests/images/create_eps.gnuplot @@ -1,5 +1,3 @@ -#!/usr/bin/gnuplot - #This is the script that was used to create our sample EPS files #We used the following version of the gnuplot program #G N U P L O T diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index e6e99d415..9137391b6 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -1,5 +1,3 @@ -#!/usr/bin/python3 - # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/setup.py b/setup.py index 1bf0bcff5..1bbd2c05c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # > pyroma . # ------------------------------ # Checking . From 76955bbaf7ee718c743da8ba1866e5c98b69f272 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:43:51 +0200 Subject: [PATCH 033/157] Remove shebang and execute bit --- Tests/check_jp2_overflow.py | 2 -- 1 file changed, 2 deletions(-) mode change 100755 => 100644 Tests/check_jp2_overflow.py diff --git a/Tests/check_jp2_overflow.py b/Tests/check_jp2_overflow.py old mode 100755 new mode 100644 index 5adbb84b6..954d68bf7 --- a/Tests/check_jp2_overflow.py +++ b/Tests/check_jp2_overflow.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # Reproductions/tests for OOB read errors in FliDecode.c # When run in python, all of these images should fail for From 139320be3a121dbc38d51baefda8d0b97441314d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jan 2024 19:44:42 +1100 Subject: [PATCH 034/157] Pin to Python 3.9.16-1 --- .github/workflows/test-cygwin.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 9c3eb0924..b5c8c39aa 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@v4 - name: Install Cygwin - uses: cygwin/cygwin-install-action@v4 + uses: egor-tensin/setup-cygwin@v4 with: platform: x86_64 packages: > @@ -69,6 +69,7 @@ jobs: make netpbm perl + python39=3.9.16-1 python3${{ matrix.python-minor-version }}-cffi python3${{ matrix.python-minor-version }}-cython python3${{ matrix.python-minor-version }}-devel @@ -86,7 +87,7 @@ jobs: - name: Select Python version run: | - ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3 + ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/cygwin/bin/python3 - name: Get latest NumPy version id: latest-numpy From 40fceedfba5d79cde4891ec70e69aee961cd3165 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jan 2024 22:22:13 +1100 Subject: [PATCH 035/157] brew remove libxau --- .github/workflows/wheels-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 3ec314873..50ac2e18e 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -131,13 +131,13 @@ untar pillow-depends-main.zip if [[ -n "$IS_MACOS" ]]; then # webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb - # libxdmcp causes an issue on macOS < 11 + # libxau and libxdmcp cause an issue on macOS < 11 # if php is installed, brew tries to reinstall these after installing openblas # remove cairo to fix building harfbuzz on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64 # remove zstd to avoid inclusion on x86_64 # curl from brew requires zstd, use system curl - brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript zstd + brew remove --ignore-dependencies webp libpng libtiff libxcb libxau libxdmcp curl php cairo lcms2 ghostscript zstd brew install pkg-config fi From b374f2679c4a0b102a1bc59b177a1a6b5cd0e1be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jan 2024 22:57:17 +1100 Subject: [PATCH 036/157] Build libxcb dependencies on macOS x86-64 --- .github/workflows/wheels-dependencies.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 50ac2e18e..26bf2f6d6 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -72,13 +72,11 @@ function build { build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then - if [[ "$CIBW_ARCHS" == "arm64" ]]; then - build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto - build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib - build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist - if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then - cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc - fi + build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto + build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib + build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist + if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then + cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc fi else sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc From 7c6d8066452621f1adc54ca21305563e5fef99d5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:26:20 +0200 Subject: [PATCH 037/157] Test on macOS M1 where available --- .github/workflows/test.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e23f5c5b..ae84a4d8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: fail-fast: false matrix: os: [ - "macos-latest", + "macos-14", "ubuntu-latest", ] python-version: [ @@ -50,11 +50,21 @@ jobs: "3.8", ] include: - - python-version: "3.9" + - python-version: "3.11" PYTHONOPTIMIZE: 1 REVERSE: "--reverse" - - python-version: "3.8" + - python-version: "3.10" PYTHONOPTIMIZE: 2 + # M1 only available for 3.10+ + - os: "macos-latest" + python-version: "3.9" + - os: "macos-latest" + python-version: "3.8" + exclude: + - os: "macos-14" + python-version: "3.9" + - os: "macos-14" + python-version: "3.8" runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} @@ -141,7 +151,7 @@ jobs: - name: Upload coverage uses: codecov/codecov-action@v3 with: - flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} + flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} gcov: true From d65f7b5ef7ef32905078e10af4471ce8bfa54c9f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:10:22 +0200 Subject: [PATCH 038/157] brew install ghostscript --- .github/workflows/macos-install.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index f41324c4b..28124d7f7 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,7 +2,16 @@ set -e -brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm +brew install \ + freetype \ + ghostscript \ + libimagequant \ + libjpeg \ + libraqm \ + libtiff \ + little-cms2 \ + openjpeg \ + webp export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" # TODO Update condition when cffi supports 3.13 From 1dad1b87ed77e283d8fb10eee1e61e66c5173eba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 00:17:32 +0000 Subject: [PATCH 039/157] Update dependency cibuildwheel to v2.16.5 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 867543ebd..ccd6d87ed 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.16.4 +cibuildwheel==2.16.5 From 39cbd4f0f1bf4f40229f50aa5480b4b25eaae1a5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:31:03 +1100 Subject: [PATCH 040/157] Expanded error message strings --- src/PIL/ImageGrab.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index c04be521f..b888e66f1 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -163,11 +163,11 @@ def grabclipboard(): # wl-paste, when the clipboard is empty b"Nothing is copied", # wl-paste/debian xclip, when an image isn't available - b"not available", + b" not available", # xclip, when an image isn't available - b"cannot convert", + b"cannot convert ", # xclip, when the clipboard isn't initialized - b"There is no owner", + b"xclip: Error: There is no owner for the ", ]: if err in silent_error: return None From 5efa2ade222785979c1b085be09eff5ee738c42c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:53:27 +1100 Subject: [PATCH 041/157] Added test --- Tests/test_imagegrab.py | 12 ++++++++++++ src/PIL/ImageGrab.py | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 9d3d40398..efef4d908 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -119,3 +119,15 @@ $ms = new-object System.IO.MemoryStream(, $bytes) subprocess.call(["wl-copy"], stdin=fp) im = ImageGrab.grabclipboard() assert_image_equal_tofile(im, image_path) + + @pytest.mark.skipif( + ( + sys.platform != "linux" + or not all(shutil.which(cmd) for cmd in ("wl-paste", "wl-copy")) + ), + reason="Linux with wl-clipboard only", + ) + @pytest.mark.parametrize("arg", ("text", "--clear")) + def test_grabclipboard_wl_clipboard_errors(self, arg): + subprocess.call(["wl-copy", arg]) + assert ImageGrab.grabclipboard() is None diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index b888e66f1..17f5750b1 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -162,7 +162,11 @@ def grabclipboard(): for silent_error in [ # wl-paste, when the clipboard is empty b"Nothing is copied", - # wl-paste/debian xclip, when an image isn't available + # Ubuntu/Debian wl-paste, when the clipboard is empty + b"No selection", + # Ubuntu/Debian wl-paste, when an image isn't available + b"No suitable type of content copied", + # wl-paste or Ubuntu/Debian xclip, when an image isn't available b" not available", # xclip, when an image isn't available b"cannot convert ", From d57b5e827cfd0e9850a074a4ba27e9f5ad0c9910 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jan 2024 16:49:44 +1100 Subject: [PATCH 042/157] Corrected check --- src/PIL/ImageGrab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 17f5750b1..3f3be706d 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -173,7 +173,7 @@ def grabclipboard(): # xclip, when the clipboard isn't initialized b"xclip: Error: There is no owner for the ", ]: - if err in silent_error: + if silent_error in err: return None msg = f"{args[0]} error" if err: From 4a4b90c3652d5036ab8d7d140763fa1eeef62985 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:12:58 +0200 Subject: [PATCH 043/157] Autotype tests (#7756) * autotyping: --none-return * autotyping: --scalar-return * autotyping: --int-param * autotyping: --float-param * autotyping: --str-param * autotyping: --annotate-named-param tmp_path:pathlib.Path --- Tests/bench_cffi_access.py | 8 +- Tests/test_bmp_reference.py | 6 +- Tests/test_box_blur.py | 34 +++--- Tests/test_color_lut.py | 52 ++++----- Tests/test_core_resources.py | 32 ++--- Tests/test_decompression_bomb.py | 28 ++--- Tests/test_deprecate.py | 14 +-- Tests/test_features.py | 30 ++--- Tests/test_file_apng.py | 52 +++++---- Tests/test_file_blp.py | 14 ++- Tests/test_file_bmp.py | 35 +++--- Tests/test_file_bufrstub.py | 16 +-- Tests/test_file_container.py | 20 ++-- Tests/test_file_dcx.py | 20 ++-- Tests/test_file_dds.py | 53 ++++----- Tests/test_file_eps.py | 71 ++++++------ Tests/test_file_fits.py | 10 +- Tests/test_file_fli.py | 30 ++--- Tests/test_file_fpx.py | 8 +- Tests/test_file_gif.py | 173 +++++++++++++-------------- Tests/test_file_gimpgradient.py | 20 ++-- Tests/test_file_gribstub.py | 16 +-- Tests/test_file_hdf5stub.py | 16 +-- Tests/test_file_icns.py | 23 ++-- Tests/test_file_ico.py | 33 +++--- Tests/test_file_im.py | 29 ++--- Tests/test_file_iptc.py | 18 +-- Tests/test_file_jpeg.py | 155 +++++++++++++------------ Tests/test_file_jpeg2k.py | 77 ++++++------ Tests/test_file_libtiff.py | 167 +++++++++++++------------- Tests/test_file_libtiff_small.py | 7 +- Tests/test_file_mic.py | 14 +-- Tests/test_file_mpo.py | 44 +++---- Tests/test_file_msp.py | 17 +-- Tests/test_file_palm.py | 13 ++- Tests/test_file_pcx.py | 32 ++--- Tests/test_file_pdf.py | 35 +++--- Tests/test_file_png.py | 109 ++++++++--------- Tests/test_file_ppm.py | 51 ++++---- Tests/test_file_psd.py | 34 +++--- Tests/test_file_sgi.py | 24 ++-- Tests/test_file_spider.py | 35 +++--- Tests/test_file_sun.py | 6 +- Tests/test_file_tar.py | 8 +- Tests/test_file_tga.py | 31 ++--- Tests/test_file_tiff.py | 151 ++++++++++++------------ Tests/test_file_tiff_metadata.py | 49 ++++---- Tests/test_file_webp.py | 41 +++---- Tests/test_file_webp_alpha.py | 14 ++- Tests/test_file_webp_animated.py | 18 +-- Tests/test_file_webp_metadata.py | 17 +-- Tests/test_file_wmf.py | 16 +-- Tests/test_file_xbm.py | 13 ++- Tests/test_format_hsv.py | 8 +- Tests/test_image.py | 155 +++++++++++++------------ Tests/test_image_access.py | 44 +++---- Tests/test_image_array.py | 10 +- Tests/test_image_draft.py | 6 +- Tests/test_image_entropy.py | 2 +- Tests/test_image_filter.py | 24 ++-- Tests/test_image_getextrema.py | 4 +- Tests/test_image_getpalette.py | 4 +- Tests/test_image_load.py | 10 +- Tests/test_image_mode.py | 4 +- Tests/test_image_paste.py | 28 ++--- Tests/test_image_putdata.py | 20 ++-- Tests/test_image_putpalette.py | 12 +- Tests/test_image_reduce.py | 40 ++++--- Tests/test_image_resample.py | 80 ++++++------- Tests/test_image_rotate.py | 30 ++--- Tests/test_image_thumbnail.py | 20 ++-- Tests/test_image_transform.py | 38 +++--- Tests/test_imagechops.py | 60 +++++----- Tests/test_imagecms.py | 71 ++++++------ Tests/test_imagecolor.py | 12 +- Tests/test_imagedraw.py | 174 ++++++++++++++-------------- Tests/test_imagedraw2.py | 24 ++-- Tests/test_imageenhance.py | 8 +- Tests/test_imagefile.py | 58 +++++----- Tests/test_imagefont.py | 122 +++++++++---------- Tests/test_imagefontctl.py | 40 +++---- Tests/test_imagefontpil.py | 16 +-- Tests/test_imagegrab.py | 16 +-- Tests/test_imagemath.py | 50 ++++---- Tests/test_imagemorph.py | 40 ++++--- Tests/test_imageops.py | 50 ++++---- Tests/test_imageops_usm.py | 10 +- Tests/test_imagepalette.py | 30 ++--- Tests/test_imagepath.py | 24 ++-- Tests/test_imageqt.py | 8 +- Tests/test_imagesequence.py | 20 ++-- Tests/test_imageshow.py | 18 +-- Tests/test_imagestat.py | 6 +- Tests/test_imagetk.py | 12 +- Tests/test_imagewin.py | 16 +-- Tests/test_imagewin_pointers.py | 3 +- Tests/test_lib_pack.py | 80 ++++++------- Tests/test_map.py | 6 +- Tests/test_mode_i16.py | 10 +- Tests/test_numpy.py | 32 ++--- Tests/test_pdfparser.py | 10 +- Tests/test_pickle.py | 17 +-- Tests/test_psdraw.py | 7 +- Tests/test_qt_image_qapplication.py | 8 +- Tests/test_qt_image_toqimage.py | 4 +- Tests/test_sgi_crash.py | 2 +- Tests/test_shell_injection.py | 11 +- Tests/test_tiff_ifdrational.py | 11 +- 108 files changed, 1866 insertions(+), 1798 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index ad15a9739..c4ab3bdcc 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -9,21 +9,21 @@ from .helper import hopper # Not running this test by default. No DOS against CI. -def iterate_get(size, access): +def iterate_get(size, access) -> None: (w, h) = size for x in range(w): for y in range(h): access[(x, y)] -def iterate_set(size, access): +def iterate_set(size, access) -> None: (w, h) = size for x in range(w): for y in range(h): access[(x, y)] = (x % 256, y % 256, 0) -def timer(func, label, *args): +def timer(func, label, *args) -> None: iterations = 5000 starttime = time.time() for x in range(iterations): @@ -38,7 +38,7 @@ def timer(func, label, *args): ) -def test_direct(): +def test_direct() -> None: im = hopper() im.load() # im = Image.new("RGB", (2000, 2000), (1, 3, 2)) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 0da41e858..22ac9443e 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -10,13 +10,13 @@ from .helper import assert_image_similar base = os.path.join("Tests", "images", "bmp") -def get_files(d, ext=".bmp"): +def get_files(d, ext: str = ".bmp"): return [ os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f ] -def test_bad(): +def test_bad() -> None: """These shouldn't crash/dos, but they shouldn't return anything either""" for f in get_files("b"): @@ -56,7 +56,7 @@ def test_questionable(): raise -def test_good(): +def test_good() -> None: """These should all work. There's a set of target files in the html directory that we can compare against.""" diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index 461e6aaac..dfedb48d9 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -16,18 +16,18 @@ sample.putdata(sum([ # fmt: on -def test_imageops_box_blur(): +def test_imageops_box_blur() -> None: i = sample.filter(ImageFilter.BoxBlur(1)) assert i.mode == sample.mode assert i.size == sample.size assert isinstance(i, Image.Image) -def box_blur(image, radius=1, n=1): +def box_blur(image, radius: int = 1, n: int = 1): return image._new(image.im.box_blur((radius, radius), n)) -def assert_image(im, data, delta=0): +def assert_image(im, data, delta: int = 0) -> None: it = iter(im.getdata()) for data_row in data: im_row = [next(it) for _ in range(im.size[0])] @@ -37,7 +37,7 @@ def assert_image(im, data, delta=0): next(it) -def assert_blur(im, radius, data, passes=1, delta=0): +def assert_blur(im, radius, data, passes: int = 1, delta: int = 0) -> None: # check grayscale image assert_image(box_blur(im, radius, passes), data, delta) rgba = Image.merge("RGBA", (im, im, im, im)) @@ -45,7 +45,7 @@ def assert_blur(im, radius, data, passes=1, delta=0): assert_image(band, data, delta) -def test_color_modes(): +def test_color_modes() -> None: with pytest.raises(ValueError): box_blur(sample.convert("1")) with pytest.raises(ValueError): @@ -65,7 +65,7 @@ def test_color_modes(): box_blur(sample.convert("YCbCr")) -def test_radius_0(): +def test_radius_0() -> None: assert_blur( sample, 0, @@ -81,7 +81,7 @@ def test_radius_0(): ) -def test_radius_0_02(): +def test_radius_0_02() -> None: assert_blur( sample, 0.02, @@ -98,7 +98,7 @@ def test_radius_0_02(): ) -def test_radius_0_05(): +def test_radius_0_05() -> None: assert_blur( sample, 0.05, @@ -115,7 +115,7 @@ def test_radius_0_05(): ) -def test_radius_0_1(): +def test_radius_0_1() -> None: assert_blur( sample, 0.1, @@ -132,7 +132,7 @@ def test_radius_0_1(): ) -def test_radius_0_5(): +def test_radius_0_5() -> None: assert_blur( sample, 0.5, @@ -149,7 +149,7 @@ def test_radius_0_5(): ) -def test_radius_1(): +def test_radius_1() -> None: assert_blur( sample, 1, @@ -166,7 +166,7 @@ def test_radius_1(): ) -def test_radius_1_5(): +def test_radius_1_5() -> None: assert_blur( sample, 1.5, @@ -183,7 +183,7 @@ def test_radius_1_5(): ) -def test_radius_bigger_then_half(): +def test_radius_bigger_then_half() -> None: assert_blur( sample, 3, @@ -200,7 +200,7 @@ def test_radius_bigger_then_half(): ) -def test_radius_bigger_then_width(): +def test_radius_bigger_then_width() -> None: assert_blur( sample, 10, @@ -215,7 +215,7 @@ def test_radius_bigger_then_width(): ) -def test_extreme_large_radius(): +def test_extreme_large_radius() -> None: assert_blur( sample, 600, @@ -230,7 +230,7 @@ def test_extreme_large_radius(): ) -def test_two_passes(): +def test_two_passes() -> None: assert_blur( sample, 1, @@ -248,7 +248,7 @@ def test_two_passes(): ) -def test_three_passes(): +def test_three_passes() -> None: assert_blur( sample, 1, diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index fcd1169ef..e6c8d7819 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -41,7 +41,7 @@ class TestColorLut3DCoreAPI: [item for sublist in table for item in sublist], ) - def test_wrong_args(self): + def test_wrong_args(self) -> None: im = Image.new("RGB", (10, 10), 0) with pytest.raises(ValueError, match="filter"): @@ -101,7 +101,7 @@ class TestColorLut3DCoreAPI: with pytest.raises(TypeError): im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16) - def test_correct_args(self): + def test_correct_args(self) -> None: im = Image.new("RGB", (10, 10), 0) im.im.color_lut_3d( @@ -136,7 +136,7 @@ class TestColorLut3DCoreAPI: *self.generate_identity_table(3, (3, 3, 65)), ) - def test_wrong_mode(self): + def test_wrong_mode(self) -> None: with pytest.raises(ValueError, match="wrong mode"): im = Image.new("L", (10, 10), 0) im.im.color_lut_3d( @@ -167,7 +167,7 @@ class TestColorLut3DCoreAPI: "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) ) - def test_correct_mode(self): + def test_correct_mode(self) -> None: im = Image.new("RGBA", (10, 10), 0) im.im.color_lut_3d( "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) @@ -188,7 +188,7 @@ class TestColorLut3DCoreAPI: "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) ) - def test_identities(self): + def test_identities(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGB", @@ -224,7 +224,7 @@ class TestColorLut3DCoreAPI: ), ) - def test_identities_4_channels(self): + def test_identities_4_channels(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGB", @@ -247,7 +247,7 @@ class TestColorLut3DCoreAPI: ), ) - def test_copy_alpha_channel(self): + def test_copy_alpha_channel(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGBA", @@ -270,7 +270,7 @@ class TestColorLut3DCoreAPI: ), ) - def test_channels_order(self): + def test_channels_order(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGB", @@ -295,7 +295,7 @@ class TestColorLut3DCoreAPI: ]))) # fmt: on - def test_overflow(self): + def test_overflow(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGB", @@ -348,7 +348,7 @@ class TestColorLut3DCoreAPI: class TestColorLut3DFilter: - def test_wrong_args(self): + def test_wrong_args(self) -> None: with pytest.raises(ValueError, match="should be either an integer"): ImageFilter.Color3DLUT("small", [1]) @@ -376,7 +376,7 @@ class TestColorLut3DFilter: with pytest.raises(ValueError, match="Only 3 or 4 output"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2) - def test_convert_table(self): + def test_convert_table(self) -> None: lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) assert tuple(lut.size) == (2, 2, 2) assert lut.name == "Color 3D LUT" @@ -394,7 +394,7 @@ class TestColorLut3DFilter: assert lut.table == list(range(4)) * 8 @pytest.mark.skipif(numpy is None, reason="NumPy not installed") - def test_numpy_sources(self): + def test_numpy_sources(self) -> None: table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) with pytest.raises(ValueError, match="should have either channels"): lut = ImageFilter.Color3DLUT((5, 6, 7), table) @@ -427,7 +427,7 @@ class TestColorLut3DFilter: assert lut.table[0] == 33 @pytest.mark.skipif(numpy is None, reason="NumPy not installed") - def test_numpy_formats(self): + def test_numpy_formats(self) -> None: g = Image.linear_gradient("L") im = Image.merge( "RGB", @@ -466,7 +466,7 @@ class TestColorLut3DFilter: lut.table = numpy.array(lut.table, dtype=numpy.int8) im.filter(lut) - def test_repr(self): + def test_repr(self) -> None: lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) assert repr(lut) == "" @@ -484,7 +484,7 @@ class TestColorLut3DFilter: class TestGenerateColorLut3D: - def test_wrong_channels_count(self): + def test_wrong_channels_count(self) -> None: with pytest.raises(ValueError, match="3 or 4 output channels"): ImageFilter.Color3DLUT.generate( 5, channels=2, callback=lambda r, g, b: (r, g, b) @@ -498,7 +498,7 @@ class TestGenerateColorLut3D: 5, channels=4, callback=lambda r, g, b: (r, g, b) ) - def test_3_channels(self): + def test_3_channels(self) -> None: lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) assert tuple(lut.size) == (5, 5, 5) assert lut.name == "Color 3D LUT" @@ -508,7 +508,7 @@ class TestGenerateColorLut3D: 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0] # fmt: on - def test_4_channels(self): + def test_4_channels(self) -> None: lut = ImageFilter.Color3DLUT.generate( 5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2) ) @@ -521,7 +521,7 @@ class TestGenerateColorLut3D: ] # fmt: on - def test_apply(self): + def test_apply(self) -> None: lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) g = Image.linear_gradient("L") @@ -537,7 +537,7 @@ class TestGenerateColorLut3D: class TestTransformColorLut3D: - def test_wrong_args(self): + def test_wrong_args(self) -> None: source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) with pytest.raises(ValueError, match="Only 3 or 4 output"): @@ -552,7 +552,7 @@ class TestTransformColorLut3D: with pytest.raises(TypeError): source.transform(lambda r, g, b, a: (r, g, b)) - def test_target_mode(self): + def test_target_mode(self) -> None: source = ImageFilter.Color3DLUT.generate( 2, lambda r, g, b: (r, g, b), target_mode="HSV" ) @@ -563,7 +563,7 @@ class TestTransformColorLut3D: lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB") assert lut.mode == "RGB" - def test_3_to_3_channels(self): + def test_3_to_3_channels(self) -> None: source = ImageFilter.Color3DLUT.generate((3, 4, 5), lambda r, g, b: (r, g, b)) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b)) assert tuple(lut.size) == tuple(source.size) @@ -571,7 +571,7 @@ class TestTransformColorLut3D: assert lut.table != source.table assert lut.table[:10] == [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] - def test_3_to_4_channels(self): + def test_3_to_4_channels(self) -> None: source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b)) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b, 1), channels=4) assert tuple(lut.size) == tuple(source.size) @@ -583,7 +583,7 @@ class TestTransformColorLut3D: 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1] # fmt: on - def test_4_to_3_channels(self): + def test_4_to_3_channels(self) -> None: source = ImageFilter.Color3DLUT.generate( (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4 ) @@ -599,7 +599,7 @@ class TestTransformColorLut3D: 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0] # fmt: on - def test_4_to_4_channels(self): + def test_4_to_4_channels(self) -> None: source = ImageFilter.Color3DLUT.generate( (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4 ) @@ -613,7 +613,7 @@ class TestTransformColorLut3D: 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5] # fmt: on - def test_with_normals_3_channels(self): + def test_with_normals_3_channels(self) -> None: source = ImageFilter.Color3DLUT.generate( (6, 5, 4), lambda r, g, b: (r * r, g * g, b * b) ) @@ -629,7 +629,7 @@ class TestTransformColorLut3D: 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0] # fmt: on - def test_with_normals_4_channels(self): + def test_with_normals_4_channels(self) -> None: source = ImageFilter.Color3DLUT.generate( (3, 6, 5), lambda r, g, b: (r * r, g * g, b * b, 1), channels=4 ) diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index d3f76fdb1..5eabe8f11 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -9,7 +9,7 @@ from PIL import Image from .helper import is_pypy -def test_get_stats(): +def test_get_stats() -> None: # Create at least one image Image.new("RGB", (10, 10)) @@ -22,7 +22,7 @@ def test_get_stats(): assert "blocks_cached" in stats -def test_reset_stats(): +def test_reset_stats() -> None: Image.core.reset_stats() stats = Image.core.get_stats() @@ -35,19 +35,19 @@ def test_reset_stats(): class TestCoreMemory: - def teardown_method(self): + def teardown_method(self) -> None: # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) Image.core.set_blocks_max(0) Image.core.clear_cache() - def test_get_alignment(self): + def test_get_alignment(self) -> None: alignment = Image.core.get_alignment() assert alignment > 0 - def test_set_alignment(self): + def test_set_alignment(self) -> None: for i in [1, 2, 4, 8, 16, 32]: Image.core.set_alignment(i) alignment = Image.core.get_alignment() @@ -63,12 +63,12 @@ class TestCoreMemory: with pytest.raises(ValueError): Image.core.set_alignment(3) - def test_get_block_size(self): + def test_get_block_size(self) -> None: block_size = Image.core.get_block_size() assert block_size >= 4096 - def test_set_block_size(self): + def test_set_block_size(self) -> None: for i in [4096, 2 * 4096, 3 * 4096]: Image.core.set_block_size(i) block_size = Image.core.get_block_size() @@ -84,7 +84,7 @@ class TestCoreMemory: with pytest.raises(ValueError): Image.core.set_block_size(4000) - def test_set_block_size_stats(self): + def test_set_block_size_stats(self) -> None: Image.core.reset_stats() Image.core.set_blocks_max(0) Image.core.set_block_size(4096) @@ -96,12 +96,12 @@ class TestCoreMemory: if not is_pypy(): assert stats["freed_blocks"] >= 64 - def test_get_blocks_max(self): + def test_get_blocks_max(self) -> None: blocks_max = Image.core.get_blocks_max() assert blocks_max >= 0 - def test_set_blocks_max(self): + def test_set_blocks_max(self) -> None: for i in [0, 1, 10]: Image.core.set_blocks_max(i) blocks_max = Image.core.get_blocks_max() @@ -117,7 +117,7 @@ class TestCoreMemory: Image.core.set_blocks_max(2**29) @pytest.mark.skipif(is_pypy(), reason="Images not collected") - def test_set_blocks_max_stats(self): + def test_set_blocks_max_stats(self) -> None: Image.core.reset_stats() Image.core.set_blocks_max(128) Image.core.set_block_size(4096) @@ -132,7 +132,7 @@ class TestCoreMemory: assert stats["blocks_cached"] == 64 @pytest.mark.skipif(is_pypy(), reason="Images not collected") - def test_clear_cache_stats(self): + def test_clear_cache_stats(self) -> None: Image.core.reset_stats() Image.core.clear_cache() Image.core.set_blocks_max(128) @@ -149,7 +149,7 @@ class TestCoreMemory: assert stats["freed_blocks"] >= 48 assert stats["blocks_cached"] == 16 - def test_large_images(self): + def test_large_images(self) -> None: Image.core.reset_stats() Image.core.set_blocks_max(0) Image.core.set_block_size(4096) @@ -166,14 +166,14 @@ class TestCoreMemory: class TestEnvVars: - def teardown_method(self): + def teardown_method(self) -> None: # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) Image.core.set_blocks_max(0) Image.core.clear_cache() - def test_units(self): + def test_units(self) -> None: Image._apply_env_variables({"PILLOW_BLOCKS_MAX": "2K"}) assert Image.core.get_blocks_max() == 2 * 1024 Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) @@ -187,6 +187,6 @@ class TestEnvVars: {"PILLOW_BLOCKS_MAX": "wat"}, ), ) - def test_warnings(self, var): + def test_warnings(self, var) -> None: with pytest.warns(UserWarning): Image._apply_env_variables(var) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index d3049eff1..9c21efa45 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -12,16 +12,16 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS class TestDecompressionBomb: - def teardown_method(self, method): + def teardown_method(self, method) -> None: Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT - def test_no_warning_small_file(self): + def test_no_warning_small_file(self) -> None: # Implicit assert: no warning. # A warning would cause a failure. with Image.open(TEST_FILE): pass - def test_no_warning_no_limit(self): + def test_no_warning_no_limit(self) -> None: # Arrange # Turn limit off Image.MAX_IMAGE_PIXELS = None @@ -33,7 +33,7 @@ class TestDecompressionBomb: with Image.open(TEST_FILE): pass - def test_warning(self): + def test_warning(self) -> None: # Set limit to trigger warning on the test file Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 @@ -42,7 +42,7 @@ class TestDecompressionBomb: with Image.open(TEST_FILE): pass - def test_exception(self): + def test_exception(self) -> None: # Set limit to trigger exception on the test file Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1 @@ -51,22 +51,22 @@ class TestDecompressionBomb: with Image.open(TEST_FILE): pass - def test_exception_ico(self): + def test_exception_ico(self) -> None: with pytest.raises(Image.DecompressionBombError): with Image.open("Tests/images/decompression_bomb.ico"): pass - def test_exception_gif(self): + def test_exception_gif(self) -> None: with pytest.raises(Image.DecompressionBombError): with Image.open("Tests/images/decompression_bomb.gif"): pass - def test_exception_gif_extents(self): + def test_exception_gif_extents(self) -> None: with Image.open("Tests/images/decompression_bomb_extents.gif") as im: with pytest.raises(Image.DecompressionBombError): im.seek(1) - def test_exception_gif_zero_width(self): + def test_exception_gif_zero_width(self) -> None: # Set limit to trigger exception on the test file Image.MAX_IMAGE_PIXELS = 4 * 64 * 128 assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128 @@ -75,7 +75,7 @@ class TestDecompressionBomb: with Image.open("Tests/images/zero_width.gif"): pass - def test_exception_bmp(self): + def test_exception_bmp(self) -> None: with pytest.raises(Image.DecompressionBombError): with Image.open("Tests/images/bmp/b/reallybig.bmp"): pass @@ -83,15 +83,15 @@ class TestDecompressionBomb: class TestDecompressionCrop: @classmethod - def setup_class(cls): + def setup_class(cls) -> None: width, height = 128, 128 Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 @classmethod - def teardown_class(cls): + def teardown_class(cls) -> None: Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT - def test_enlarge_crop(self): + def test_enlarge_crop(self) -> None: # Crops can extend the extents, therefore we should have the # same decompression bomb warnings on them. with hopper() as src: @@ -99,7 +99,7 @@ class TestDecompressionCrop: with pytest.warns(Image.DecompressionBombWarning): src.crop(box) - def test_crop_decompression_checks(self): + def test_crop_decompression_checks(self) -> None: im = Image.new("RGB", (100, 100)) for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)): diff --git a/Tests/test_deprecate.py b/Tests/test_deprecate.py index 6c7f509a7..6ffc8f6f5 100644 --- a/Tests/test_deprecate.py +++ b/Tests/test_deprecate.py @@ -20,12 +20,12 @@ from PIL import _deprecate ), ], ) -def test_version(version, expected): +def test_version(version, expected) -> None: with pytest.warns(DeprecationWarning, match=expected): _deprecate.deprecate("Old thing", version, "new thing") -def test_unknown_version(): +def test_unknown_version() -> None: expected = r"Unknown removal version: 12345. Update PIL\._deprecate\?" with pytest.raises(ValueError, match=expected): _deprecate.deprecate("Old thing", 12345, "new thing") @@ -46,13 +46,13 @@ def test_unknown_version(): ), ], ) -def test_old_version(deprecated, plural, expected): +def test_old_version(deprecated, plural, expected) -> None: expected = r"" with pytest.raises(RuntimeError, match=expected): _deprecate.deprecate(deprecated, 1, plural=plural) -def test_plural(): +def test_plural() -> None: expected = ( r"Old things are deprecated and will be removed in Pillow 11 \(2024-10-15\)\. " r"Use new thing instead\." @@ -61,7 +61,7 @@ def test_plural(): _deprecate.deprecate("Old things", 11, "new thing", plural=True) -def test_replacement_and_action(): +def test_replacement_and_action() -> None: expected = "Use only one of 'replacement' and 'action'" with pytest.raises(ValueError, match=expected): _deprecate.deprecate( @@ -76,7 +76,7 @@ def test_replacement_and_action(): "Upgrade to new thing.", ], ) -def test_action(action): +def test_action(action) -> None: expected = ( r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. " r"Upgrade to new thing\." @@ -85,7 +85,7 @@ def test_action(action): _deprecate.deprecate("Old thing", 11, action=action) -def test_no_replacement_or_action(): +def test_no_replacement_or_action() -> None: expected = ( r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)" ) diff --git a/Tests/test_features.py b/Tests/test_features.py index b90c1d25f..de74e9c18 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -15,7 +15,7 @@ except ImportError: pass -def test_check(): +def test_check() -> None: # Check the correctness of the convenience function for module in features.modules: assert features.check_module(module) == features.check(module) @@ -25,11 +25,11 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) -def test_version(): +def test_version() -> None: # Check the correctness of the convenience function # and the format of version numbers - def test(name, function): + def test(name, function) -> None: version = features.version(name) if not features.check(name): assert version is None @@ -47,56 +47,56 @@ def test_version(): @skip_unless_feature("webp") -def test_webp_transparency(): +def test_webp_transparency() -> None: assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() assert features.check("transp_webp") == _webp.HAVE_TRANSPARENCY @skip_unless_feature("webp") -def test_webp_mux(): +def test_webp_mux() -> None: assert features.check("webp_mux") == _webp.HAVE_WEBPMUX @skip_unless_feature("webp") -def test_webp_anim(): +def test_webp_anim() -> None: assert features.check("webp_anim") == _webp.HAVE_WEBPANIM @skip_unless_feature("libjpeg_turbo") -def test_libjpeg_turbo_version(): +def test_libjpeg_turbo_version() -> None: assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo")) @skip_unless_feature("libimagequant") -def test_libimagequant_version(): +def test_libimagequant_version() -> None: assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) @pytest.mark.parametrize("feature", features.modules) -def test_check_modules(feature): +def test_check_modules(feature) -> None: assert features.check_module(feature) in [True, False] @pytest.mark.parametrize("feature", features.codecs) -def test_check_codecs(feature): +def test_check_codecs(feature) -> None: assert features.check_codec(feature) in [True, False] -def test_check_warns_on_nonexistent(): +def test_check_warns_on_nonexistent() -> None: with pytest.warns(UserWarning) as cm: has_feature = features.check("typo") assert has_feature is False assert str(cm[-1].message) == "Unknown feature 'typo'." -def test_supported_modules(): +def test_supported_modules() -> None: assert isinstance(features.get_supported_modules(), list) assert isinstance(features.get_supported_codecs(), list) assert isinstance(features.get_supported_features(), list) assert isinstance(features.get_supported(), list) -def test_unsupported_codec(): +def test_unsupported_codec() -> None: # Arrange codec = "unsupported_codec" # Act / Assert @@ -106,7 +106,7 @@ def test_unsupported_codec(): features.version_codec(codec) -def test_unsupported_module(): +def test_unsupported_module() -> None: # Arrange module = "unsupported_module" # Act / Assert @@ -116,7 +116,7 @@ def test_unsupported_module(): features.version_module(module) -def test_pilinfo(): +def test_pilinfo() -> None: buf = io.StringIO() features.pilinfo(buf) out = buf.getvalue() diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 23263b5d4..f9edf6e98 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, ImageSequence, PngImagePlugin @@ -8,7 +10,7 @@ from PIL import Image, ImageSequence, PngImagePlugin # APNG browser support tests and fixtures via: # https://philip.html5.org/tests/apng/tests.html # (referenced from https://wiki.mozilla.org/APNG_Specification) -def test_apng_basic(): +def test_apng_basic() -> None: with Image.open("Tests/images/apng/single_frame.png") as im: assert not im.is_animated assert im.n_frames == 1 @@ -45,14 +47,14 @@ def test_apng_basic(): "filename", ("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"), ) -def test_apng_fdat(filename): +def test_apng_fdat(filename) -> None: with Image.open(filename) as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_dispose(): +def test_apng_dispose() -> None: with Image.open("Tests/images/apng/dispose_op_none.png") as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) @@ -84,7 +86,7 @@ def test_apng_dispose(): assert im.getpixel((64, 32)) == (0, 0, 0, 0) -def test_apng_dispose_region(): +def test_apng_dispose_region() -> None: with Image.open("Tests/images/apng/dispose_op_none_region.png") as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) @@ -106,7 +108,7 @@ def test_apng_dispose_region(): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_dispose_op_previous_frame(): +def test_apng_dispose_op_previous_frame() -> None: # Test that the dispose settings being used are from the previous frame # # Image created with: @@ -131,14 +133,14 @@ def test_apng_dispose_op_previous_frame(): assert im.getpixel((0, 0)) == (255, 0, 0, 255) -def test_apng_dispose_op_background_p_mode(): +def test_apng_dispose_op_background_p_mode() -> None: with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im: im.seek(1) im.load() assert im.size == (128, 64) -def test_apng_blend(): +def test_apng_blend() -> None: with Image.open("Tests/images/apng/blend_op_source_solid.png") as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) @@ -165,20 +167,20 @@ def test_apng_blend(): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_blend_transparency(): +def test_apng_blend_transparency() -> None: with Image.open("Tests/images/blend_transparency.png") as im: im.seek(1) assert im.getpixel((0, 0)) == (255, 0, 0) -def test_apng_chunk_order(): +def test_apng_chunk_order() -> None: with Image.open("Tests/images/apng/fctl_actl.png") as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_delay(): +def test_apng_delay() -> None: with Image.open("Tests/images/apng/delay.png") as im: im.seek(1) assert im.info.get("duration") == 500.0 @@ -218,7 +220,7 @@ def test_apng_delay(): assert im.info.get("duration") == 1000.0 -def test_apng_num_plays(): +def test_apng_num_plays() -> None: with Image.open("Tests/images/apng/num_plays.png") as im: assert im.info.get("loop") == 0 @@ -226,7 +228,7 @@ def test_apng_num_plays(): assert im.info.get("loop") == 1 -def test_apng_mode(): +def test_apng_mode() -> None: with Image.open("Tests/images/apng/mode_16bit.png") as im: assert im.mode == "RGBA" im.seek(im.n_frames - 1) @@ -267,7 +269,7 @@ def test_apng_mode(): assert im.getpixel((64, 32)) == (0, 0, 255, 128) -def test_apng_chunk_errors(): +def test_apng_chunk_errors() -> None: with Image.open("Tests/images/apng/chunk_no_actl.png") as im: assert not im.is_animated @@ -292,7 +294,7 @@ def test_apng_chunk_errors(): im.seek(im.n_frames - 1) -def test_apng_syntax_errors(): +def test_apng_syntax_errors() -> None: with pytest.warns(UserWarning): with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: assert not im.is_animated @@ -336,14 +338,14 @@ def test_apng_syntax_errors(): "sequence_fdat_fctl.png", ), ) -def test_apng_sequence_errors(test_file): +def test_apng_sequence_errors(test_file) -> None: with pytest.raises(SyntaxError): with Image.open(f"Tests/images/apng/{test_file}") as im: im.seek(im.n_frames - 1) im.load() -def test_apng_save(tmp_path): +def test_apng_save(tmp_path: Path) -> None: with Image.open("Tests/images/apng/single_frame.png") as im: test_file = str(tmp_path / "temp.png") im.save(test_file, save_all=True) @@ -374,7 +376,7 @@ def test_apng_save(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_save_alpha(tmp_path): +def test_apng_save_alpha(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.png") im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) @@ -388,7 +390,7 @@ def test_apng_save_alpha(tmp_path): assert reloaded.getpixel((0, 0)) == (255, 0, 0, 127) -def test_apng_save_split_fdat(tmp_path): +def test_apng_save_split_fdat(tmp_path: Path) -> None: # test to make sure we do not generate sequence errors when writing # frames with image data spanning multiple fdAT chunks (in this case # both the default image and first animation frame will span multiple @@ -412,7 +414,7 @@ def test_apng_save_split_fdat(tmp_path): assert exception is None -def test_apng_save_duration_loop(tmp_path): +def test_apng_save_duration_loop(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.png") with Image.open("Tests/images/apng/delay.png") as im: frames = [] @@ -475,7 +477,7 @@ def test_apng_save_duration_loop(tmp_path): assert im.info["duration"] == 600 -def test_apng_save_disposal(tmp_path): +def test_apng_save_disposal(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.png") size = (128, 64) red = Image.new("RGBA", size, (255, 0, 0, 255)) @@ -576,7 +578,7 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((64, 32)) == (0, 0, 0, 0) -def test_apng_save_disposal_previous(tmp_path): +def test_apng_save_disposal_previous(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.png") size = (128, 64) blue = Image.new("RGBA", size, (0, 0, 255, 255)) @@ -598,7 +600,7 @@ def test_apng_save_disposal_previous(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_save_blend(tmp_path): +def test_apng_save_blend(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.png") size = (128, 64) red = Image.new("RGBA", size, (255, 0, 0, 255)) @@ -666,7 +668,7 @@ def test_apng_save_blend(tmp_path): assert im.getpixel((0, 0)) == (0, 255, 0, 255) -def test_seek_after_close(): +def test_seek_after_close() -> None: im = Image.open("Tests/images/apng/delay.png") im.seek(1) im.close() @@ -678,7 +680,9 @@ def test_seek_after_close(): @pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) @pytest.mark.parametrize("default_image", (True, False)) @pytest.mark.parametrize("duplicate", (True, False)) -def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path): +def test_different_modes_in_later_frames( + mode, default_image, duplicate, tmp_path: Path +) -> None: test_file = str(tmp_path / "temp.png") im = Image.new("L", (1, 1)) diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 27ff7ab66..3904d3bc5 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image @@ -12,7 +14,7 @@ from .helper import ( ) -def test_load_blp1(): +def test_load_blp1() -> None: with Image.open("Tests/images/blp/blp1_jpeg.blp") as im: assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png") @@ -20,22 +22,22 @@ def test_load_blp1(): im.load() -def test_load_blp2_raw(): +def test_load_blp2_raw() -> None: with Image.open("Tests/images/blp/blp2_raw.blp") as im: assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png") -def test_load_blp2_dxt1(): +def test_load_blp2_dxt1() -> None: with Image.open("Tests/images/blp/blp2_dxt1.blp") as im: assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png") -def test_load_blp2_dxt1a(): +def test_load_blp2_dxt1a() -> None: with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: f = str(tmp_path / "temp.blp") for version in ("BLP1", "BLP2"): @@ -69,7 +71,7 @@ def test_save(tmp_path): "Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp", ], ) -def test_crashes(test_file): +def test_crashes(test_file) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 225fb28ba..c36466e02 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +from pathlib import Path import pytest @@ -14,8 +15,8 @@ from .helper import ( ) -def test_sanity(tmp_path): - def roundtrip(im): +def test_sanity(tmp_path: Path) -> None: + def roundtrip(im) -> None: outfile = str(tmp_path / "temp.bmp") im.save(outfile, "BMP") @@ -35,20 +36,20 @@ def test_sanity(tmp_path): roundtrip(hopper("RGB")) -def test_invalid_file(): +def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): BmpImagePlugin.BmpImageFile(fp) -def test_fallback_if_mmap_errors(): +def test_fallback_if_mmap_errors() -> None: # This image has been truncated, # so that the buffer is not large enough when using mmap with Image.open("Tests/images/mmap_error.bmp") as im: assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp") -def test_save_to_bytes(): +def test_save_to_bytes() -> None: output = io.BytesIO() im = hopper() im.save(output, "BMP") @@ -60,7 +61,7 @@ def test_save_to_bytes(): assert reloaded.format == "BMP" -def test_small_palette(tmp_path): +def test_small_palette(tmp_path: Path) -> None: im = Image.new("P", (1, 1)) colors = [0, 0, 0, 125, 125, 125, 255, 255, 255] im.putpalette(colors) @@ -72,7 +73,7 @@ def test_small_palette(tmp_path): assert reloaded.getpalette() == colors -def test_save_too_large(tmp_path): +def test_save_too_large(tmp_path: Path) -> None: outfile = str(tmp_path / "temp.bmp") with Image.new("RGB", (1, 1)) as im: im._size = (37838, 37838) @@ -80,7 +81,7 @@ def test_save_too_large(tmp_path): im.save(outfile) -def test_dpi(): +def test_dpi() -> None: dpi = (72, 72) output = io.BytesIO() @@ -92,7 +93,7 @@ def test_dpi(): assert reloaded.info["dpi"] == (72.008961115161, 72.008961115161) -def test_save_bmp_with_dpi(tmp_path): +def test_save_bmp_with_dpi(tmp_path: Path) -> None: # Test for #1301 # Arrange outfile = str(tmp_path / "temp.jpg") @@ -110,7 +111,7 @@ def test_save_bmp_with_dpi(tmp_path): assert reloaded.format == "JPEG" -def test_save_float_dpi(tmp_path): +def test_save_float_dpi(tmp_path: Path) -> None: outfile = str(tmp_path / "temp.bmp") with Image.open("Tests/images/hopper.bmp") as im: im.save(outfile, dpi=(72.21216100543306, 72.21216100543306)) @@ -118,7 +119,7 @@ def test_save_float_dpi(tmp_path): assert reloaded.info["dpi"] == (72.21216100543306, 72.21216100543306) -def test_load_dib(): +def test_load_dib() -> None: # test for #1293, Imagegrab returning Unsupported Bitfields Format with Image.open("Tests/images/clipboard.dib") as im: assert im.format == "DIB" @@ -127,7 +128,7 @@ def test_load_dib(): assert_image_equal_tofile(im, "Tests/images/clipboard_target.png") -def test_save_dib(tmp_path): +def test_save_dib(tmp_path: Path) -> None: outfile = str(tmp_path / "temp.dib") with Image.open("Tests/images/clipboard.dib") as im: @@ -139,7 +140,7 @@ def test_save_dib(tmp_path): assert_image_equal(im, reloaded) -def test_rgba_bitfields(): +def test_rgba_bitfields() -> None: # This test image has been manually hexedited # to change the bitfield compression in the header from XBGR to RGBA with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: @@ -157,7 +158,7 @@ def test_rgba_bitfields(): ) -def test_rle8(): +def test_rle8() -> None: with Image.open("Tests/images/hopper_rle8.bmp") as im: assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) @@ -177,7 +178,7 @@ def test_rle8(): im.load() -def test_rle4(): +def test_rle4() -> None: with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im: assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) @@ -193,7 +194,7 @@ def test_rle4(): ("Tests/images/bmp/g/pal8rle.bmp", 1064), ), ) -def test_rle8_eof(file_name, length): +def test_rle8_eof(file_name, length) -> None: with open(file_name, "rb") as fp: data = fp.read(length) with Image.open(io.BytesIO(data)) as im: @@ -201,7 +202,7 @@ def test_rle8_eof(file_name, length): im.load() -def test_offset(): +def test_offset() -> None: # This image has been hexedited # to exclude the palette size from the pixel data offset with Image.open("Tests/images/pal8_offset.bmp") as im: diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 45081832e..3dd24533a 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import BufrStubImagePlugin, Image @@ -9,7 +11,7 @@ from .helper import hopper TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d" -def test_open(): +def test_open() -> None: # Act with Image.open(TEST_FILE) as im: # Assert @@ -20,7 +22,7 @@ def test_open(): assert im.size == (1, 1) -def test_invalid_file(): +def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" @@ -29,7 +31,7 @@ def test_invalid_file(): BufrStubImagePlugin.BufrStubImageFile(invalid_file) -def test_load(): +def test_load() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler @@ -37,7 +39,7 @@ def test_load(): im.load() -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: # Arrange im = hopper() tmpfile = str(tmp_path / "temp.bufr") @@ -47,13 +49,13 @@ def test_save(tmp_path): im.save(tmpfile) -def test_handler(tmp_path): +def test_handler(tmp_path: Path) -> None: class TestHandler: opened = False loaded = False saved = False - def open(self, im): + def open(self, im) -> None: self.opened = True def load(self, im): @@ -61,7 +63,7 @@ def test_handler(tmp_path): im.fp.close() return Image.new("RGB", (1, 1)) - def save(self, im, fp, filename): + def save(self, im, fp, filename) -> None: self.saved = True handler = TestHandler() diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 95a5b2337..4dba4be5d 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -9,19 +9,19 @@ from .helper import hopper TEST_FILE = "Tests/images/dummy.container" -def test_sanity(): +def test_sanity() -> None: dir(Image) dir(ContainerIO) -def test_isatty(): +def test_isatty() -> None: with hopper() as im: container = ContainerIO.ContainerIO(im, 0, 0) assert container.isatty() is False -def test_seek_mode_0(): +def test_seek_mode_0() -> None: # Arrange mode = 0 with open(TEST_FILE, "rb") as fh: @@ -35,7 +35,7 @@ def test_seek_mode_0(): assert container.tell() == 33 -def test_seek_mode_1(): +def test_seek_mode_1() -> None: # Arrange mode = 1 with open(TEST_FILE, "rb") as fh: @@ -49,7 +49,7 @@ def test_seek_mode_1(): assert container.tell() == 66 -def test_seek_mode_2(): +def test_seek_mode_2() -> None: # Arrange mode = 2 with open(TEST_FILE, "rb") as fh: @@ -64,7 +64,7 @@ def test_seek_mode_2(): @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_n0(bytesmode): +def test_read_n0(bytesmode) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -80,7 +80,7 @@ def test_read_n0(bytesmode): @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_n(bytesmode): +def test_read_n(bytesmode) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -96,7 +96,7 @@ def test_read_n(bytesmode): @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_eof(bytesmode): +def test_read_eof(bytesmode) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -112,7 +112,7 @@ def test_read_eof(bytesmode): @pytest.mark.parametrize("bytesmode", (True, False)) -def test_readline(bytesmode): +def test_readline(bytesmode) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 0, 120) @@ -127,7 +127,7 @@ def test_readline(bytesmode): @pytest.mark.parametrize("bytesmode", (True, False)) -def test_readlines(bytesmode): +def test_readlines(bytesmode) -> None: # Arrange expected = [ "This is line 1\n", diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index cba7c10bf..65337cad9 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -12,7 +12,7 @@ from .helper import assert_image_equal, hopper, is_pypy TEST_FILE = "Tests/images/hopper.dcx" -def test_sanity(): +def test_sanity() -> None: # Arrange # Act @@ -25,8 +25,8 @@ def test_sanity(): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(TEST_FILE) im.load() @@ -34,26 +34,26 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(TEST_FILE) im.load() im.close() -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(TEST_FILE) as im: im.load() -def test_invalid_file(): +def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): DcxImagePlugin.DcxImageFile(fp) -def test_tell(): +def test_tell() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act @@ -63,13 +63,13 @@ def test_tell(): assert frame == 0 -def test_n_frames(): +def test_n_frames() -> None: with Image.open(TEST_FILE) as im: assert im.n_frames == 1 assert not im.is_animated -def test_eoferror(): +def test_eoferror() -> None: with Image.open(TEST_FILE) as im: n_frames = im.n_frames @@ -82,7 +82,7 @@ def test_eoferror(): im.seek(n_frames - 1) -def test_seek_too_far(): +def test_seek_too_far() -> None: # Arrange with Image.open(TEST_FILE) as im: frame = 999 # too big on purpose diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 7064b74c0..09ee8986a 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -2,6 +2,7 @@ from __future__ import annotations from io import BytesIO +from pathlib import Path import pytest @@ -46,7 +47,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds" TEST_FILE_DX10_BC1_TYPELESS, ), ) -def test_sanity_dxt1_bc1(image_path): +def test_sanity_dxt1_bc1(image_path) -> None: """Check DXT1 and BC1 images can be opened""" with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") @@ -60,7 +61,7 @@ def test_sanity_dxt1_bc1(image_path): assert_image_equal(im, target) -def test_sanity_dxt3(): +def test_sanity_dxt3() -> None: """Check DXT3 images can be opened""" with Image.open(TEST_FILE_DXT3) as im: @@ -73,7 +74,7 @@ def test_sanity_dxt3(): assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png")) -def test_sanity_dxt5(): +def test_sanity_dxt5() -> None: """Check DXT5 images can be opened""" with Image.open(TEST_FILE_DXT5) as im: @@ -94,7 +95,7 @@ def test_sanity_dxt5(): TEST_FILE_BC4U, ), ) -def test_sanity_ati1_bc4u(image_path): +def test_sanity_ati1_bc4u(image_path) -> None: """Check ATI1 and BC4U images can be opened""" with Image.open(image_path) as im: @@ -115,7 +116,7 @@ def test_sanity_ati1_bc4u(image_path): TEST_FILE_DX10_BC4_TYPELESS, ), ) -def test_dx10_bc4(image_path): +def test_dx10_bc4(image_path) -> None: """Check DX10 BC4 images can be opened""" with Image.open(image_path) as im: @@ -136,7 +137,7 @@ def test_dx10_bc4(image_path): TEST_FILE_BC5U, ), ) -def test_sanity_ati2_bc5u(image_path): +def test_sanity_ati2_bc5u(image_path) -> None: """Check ATI2 and BC5U images can be opened""" with Image.open(image_path) as im: @@ -160,7 +161,7 @@ def test_sanity_ati2_bc5u(image_path): (TEST_FILE_BC5S, TEST_FILE_BC5S), ), ) -def test_dx10_bc5(image_path, expected_path): +def test_dx10_bc5(image_path, expected_path) -> None: """Check DX10 BC5 images can be opened""" with Image.open(image_path) as im: @@ -174,7 +175,7 @@ def test_dx10_bc5(image_path, expected_path): @pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS)) -def test_dx10_bc6h(image_path): +def test_dx10_bc6h(image_path) -> None: """Check DX10 BC6H/BC6HS images can be opened""" with Image.open(image_path) as im: @@ -187,7 +188,7 @@ def test_dx10_bc6h(image_path): assert_image_equal_tofile(im, image_path.replace(".dds", ".png")) -def test_dx10_bc7(): +def test_dx10_bc7() -> None: """Check DX10 images can be opened""" with Image.open(TEST_FILE_DX10_BC7) as im: @@ -200,7 +201,7 @@ def test_dx10_bc7(): assert_image_equal_tofile(im, TEST_FILE_DX10_BC7.replace(".dds", ".png")) -def test_dx10_bc7_unorm_srgb(): +def test_dx10_bc7_unorm_srgb() -> None: """Check DX10 unsigned normalized integer images can be opened""" with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im: @@ -216,7 +217,7 @@ def test_dx10_bc7_unorm_srgb(): ) -def test_dx10_r8g8b8a8(): +def test_dx10_r8g8b8a8() -> None: """Check DX10 images can be opened""" with Image.open(TEST_FILE_DX10_R8G8B8A8) as im: @@ -229,7 +230,7 @@ def test_dx10_r8g8b8a8(): assert_image_equal_tofile(im, TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) -def test_dx10_r8g8b8a8_unorm_srgb(): +def test_dx10_r8g8b8a8_unorm_srgb() -> None: """Check DX10 unsigned normalized integer images can be opened""" with Image.open(TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB) as im: @@ -255,7 +256,7 @@ def test_dx10_r8g8b8a8_unorm_srgb(): ("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA), ], ) -def test_uncompressed(mode, size, test_file): +def test_uncompressed(mode, size, test_file) -> None: """Check uncompressed images can be opened""" with Image.open(test_file) as im: @@ -266,7 +267,7 @@ def test_uncompressed(mode, size, test_file): assert_image_equal_tofile(im, test_file.replace(".dds", ".png")) -def test__accept_true(): +def test__accept_true() -> None: """Check valid prefix""" # Arrange prefix = b"DDS etc" @@ -278,7 +279,7 @@ def test__accept_true(): assert output -def test__accept_false(): +def test__accept_false() -> None: """Check invalid prefix""" # Arrange prefix = b"something invalid" @@ -290,19 +291,19 @@ def test__accept_false(): assert not output -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): DdsImagePlugin.DdsImageFile(invalid_file) -def test_short_header(): +def test_short_header() -> None: """Check a short header""" with open(TEST_FILE_DXT5, "rb") as f: img_file = f.read() - def short_header(): + def short_header() -> None: with Image.open(BytesIO(img_file[:119])): pass # pragma: no cover @@ -310,13 +311,13 @@ def test_short_header(): short_header() -def test_short_file(): +def test_short_file() -> None: """Check that the appropriate error is thrown for a short file""" with open(TEST_FILE_DXT5, "rb") as f: img_file = f.read() - def short_file(): + def short_file() -> None: with Image.open(BytesIO(img_file[:-100])) as im: im.load() @@ -324,7 +325,7 @@ def test_short_file(): short_file() -def test_dxt5_colorblock_alpha_issue_4142(): +def test_dxt5_colorblock_alpha_issue_4142() -> None: """Check that colorblocks are decoded correctly in DXT5""" with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im: @@ -339,12 +340,12 @@ def test_dxt5_colorblock_alpha_issue_4142(): assert px[2] != 0 -def test_palette(): +def test_palette() -> None: with Image.open("Tests/images/palette.dds") as im: assert_image_equal_tofile(im, "Tests/images/transparent.gif") -def test_unsupported_bitcount(): +def test_unsupported_bitcount() -> None: with pytest.raises(OSError): with Image.open("Tests/images/unsupported_bitcount.dds"): pass @@ -357,13 +358,13 @@ def test_unsupported_bitcount(): "Tests/images/unimplemented_pfflags.dds", ), ) -def test_not_implemented(test_file): +def test_not_implemented(test_file) -> None: with pytest.raises(NotImplementedError): with Image.open(test_file): pass -def test_save_unsupported_mode(tmp_path): +def test_save_unsupported_mode(tmp_path: Path) -> None: out = str(tmp_path / "temp.dds") im = hopper("HSV") with pytest.raises(OSError): @@ -379,7 +380,7 @@ def test_save_unsupported_mode(tmp_path): ("RGBA", "Tests/images/pil123rgba.png"), ], ) -def test_save(mode, test_file, tmp_path): +def test_save(mode, test_file, tmp_path: Path) -> None: out = str(tmp_path / "temp.dds") with Image.open(test_file) as im: assert im.mode == mode diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 8def9a435..06f927c7b 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +from pathlib import Path import pytest @@ -83,7 +84,7 @@ simple_eps_file_with_long_binary_data = ( ("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252))) ) @pytest.mark.parametrize("scale", (1, 2)) -def test_sanity(filename, size, scale): +def test_sanity(filename, size, scale) -> None: expected_size = tuple(s * scale for s in size) with Image.open(filename) as image: image.load(scale=scale) @@ -93,7 +94,7 @@ def test_sanity(filename, size, scale): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_load(): +def test_load() -> None: with Image.open(FILE1) as im: assert im.load()[0, 0] == (255, 255, 255) @@ -101,7 +102,7 @@ def test_load(): assert im.load()[0, 0] == (255, 255, 255) -def test_binary(): +def test_binary() -> None: if HAS_GHOSTSCRIPT: assert EpsImagePlugin.gs_binary is not None else: @@ -115,41 +116,41 @@ def test_binary(): assert EpsImagePlugin.gs_windows_binary is not None -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): EpsImagePlugin.EpsImageFile(invalid_file) -def test_binary_header_only(): +def test_binary_header_only() -> None: data = io.BytesIO(simple_binary_header) with pytest.raises(SyntaxError, match='EPS header missing "%!PS-Adobe" comment'): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_missing_version_comment(prefix): +def test_missing_version_comment(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version)) with pytest.raises(SyntaxError): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_missing_boundingbox_comment(prefix): +def test_missing_boundingbox_comment(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox)) with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_invalid_boundingbox_comment(prefix): +def test_invalid_boundingbox_comment(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox)) with pytest.raises(OSError, match="cannot determine EPS bounding box"): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix): +def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix) -> None: data = io.BytesIO( prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata) ) @@ -160,21 +161,21 @@ def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix): @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_ascii_comment_too_long(prefix): +def test_ascii_comment_too_long(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment)) with pytest.raises(SyntaxError, match="not an EPS file"): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_long_binary_data(prefix): +def test_long_binary_data(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data)) EpsImagePlugin.EpsImageFile(data) @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_load_long_binary_data(prefix): +def test_load_long_binary_data(prefix) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data)) with Image.open(data) as img: img.load() @@ -187,7 +188,7 @@ def test_load_long_binary_data(prefix): pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_cmyk(): +def test_cmyk() -> None: with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: assert cmyk_image.mode == "CMYK" assert cmyk_image.size == (100, 100) @@ -203,7 +204,7 @@ def test_cmyk(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_showpage(): +def test_showpage() -> None: # See https://github.com/python-pillow/Pillow/issues/2615 with Image.open("Tests/images/reqd_showpage.eps") as plot_image: with Image.open("Tests/images/reqd_showpage.png") as target: @@ -214,7 +215,7 @@ def test_showpage(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_transparency(): +def test_transparency() -> None: with Image.open("Tests/images/reqd_showpage.eps") as plot_image: plot_image.load(transparency=True) assert plot_image.mode == "RGBA" @@ -225,7 +226,7 @@ def test_transparency(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_file_object(tmp_path): +def test_file_object(tmp_path: Path) -> None: # issue 479 with Image.open(FILE1) as image1: with open(str(tmp_path / "temp.eps"), "wb") as fh: @@ -233,7 +234,7 @@ def test_file_object(tmp_path): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_bytesio_object(): +def test_bytesio_object() -> None: with open(FILE1, "rb") as f: img_bytes = io.BytesIO(f.read()) @@ -246,12 +247,12 @@ def test_bytesio_object(): assert_image_similar(img, image1_scale1_compare, 5) -def test_1_mode(): +def test_1_mode() -> None: with Image.open("Tests/images/1.eps") as im: assert im.mode == "1" -def test_image_mode_not_supported(tmp_path): +def test_image_mode_not_supported(tmp_path: Path) -> None: im = hopper("RGBA") tmpfile = str(tmp_path / "temp.eps") with pytest.raises(ValueError): @@ -260,7 +261,7 @@ def test_image_mode_not_supported(tmp_path): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @skip_unless_feature("zlib") -def test_render_scale1(): +def test_render_scale1() -> None: # We need png support for these render test # Zero bounding box @@ -282,7 +283,7 @@ def test_render_scale1(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @skip_unless_feature("zlib") -def test_render_scale2(): +def test_render_scale2() -> None: # We need png support for these render test # Zero bounding box @@ -304,7 +305,7 @@ def test_render_scale2(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps")) -def test_resize(filename): +def test_resize(filename) -> None: with Image.open(filename) as im: new_size = (100, 100) im = im.resize(new_size) @@ -313,7 +314,7 @@ def test_resize(filename): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("filename", (FILE1, FILE2)) -def test_thumbnail(filename): +def test_thumbnail(filename) -> None: # Issue #619 with Image.open(filename) as im: new_size = (100, 100) @@ -321,20 +322,20 @@ def test_thumbnail(filename): assert max(im.size) == max(new_size) -def test_read_binary_preview(): +def test_read_binary_preview() -> None: # Issue 302 # open image with binary preview with Image.open(FILE3): pass -def test_readline_psfile(tmp_path): +def test_readline_psfile(tmp_path: Path) -> None: # check all the freaking line endings possible from the spec # test_string = u'something\r\nelse\n\rbaz\rbif\n' line_endings = ["\r\n", "\n", "\n\r", "\r"] strings = ["something", "else", "baz", "bif"] - def _test_readline(t, ending): + def _test_readline(t, ending) -> None: ending = "Failure with line ending: %s" % ( "".join("%s" % ord(s) for s in ending) ) @@ -343,13 +344,13 @@ def test_readline_psfile(tmp_path): assert t.readline().strip("\r\n") == "baz", ending assert t.readline().strip("\r\n") == "bif", ending - def _test_readline_io_psfile(test_string, ending): + def _test_readline_io_psfile(test_string, ending) -> None: f = io.BytesIO(test_string.encode("latin-1")) with pytest.warns(DeprecationWarning): t = EpsImagePlugin.PSFile(f) _test_readline(t, ending) - def _test_readline_file_psfile(test_string, ending): + def _test_readline_file_psfile(test_string, ending) -> None: f = str(tmp_path / "temp.txt") with open(f, "wb") as w: w.write(test_string.encode("latin-1")) @@ -365,7 +366,7 @@ def test_readline_psfile(tmp_path): _test_readline_file_psfile(s, ending) -def test_psfile_deprecation(): +def test_psfile_deprecation() -> None: with pytest.warns(DeprecationWarning): EpsImagePlugin.PSFile(None) @@ -375,7 +376,7 @@ def test_psfile_deprecation(): "line_ending", (b"\r\n", b"\n", b"\n\r", b"\r"), ) -def test_readline(prefix, line_ending): +def test_readline(prefix, line_ending) -> None: simple_file = prefix + line_ending.join(simple_eps_file_with_comments) data = io.BytesIO(simple_file) test_file = EpsImagePlugin.EpsImageFile(data) @@ -393,14 +394,14 @@ def test_readline(prefix, line_ending): "Tests/images/illuCS6_preview.eps", ), ) -def test_open_eps(filename): +def test_open_eps(filename) -> None: # https://github.com/python-pillow/Pillow/issues/1104 with Image.open(filename) as img: assert img.mode == "RGB" @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_emptyline(): +def test_emptyline() -> None: # Test file includes an empty line in the header data emptyline_file = "Tests/images/zero_bb_emptyline.eps" @@ -416,14 +417,14 @@ def test_emptyline(): "test_file", ["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], ) -def test_timeout(test_file): +def test_timeout(test_file) -> None: with open(test_file, "rb") as f: with pytest.raises(Image.UnidentifiedImageError): with Image.open(f): pass -def test_bounding_box_in_trailer(): +def test_bounding_box_in_trailer() -> None: # Check bounding boxes are parsed in the same way # when specified in the header and the trailer with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open( @@ -432,7 +433,7 @@ def test_bounding_box_in_trailer(): assert trailer_image.size == header_image.size -def test_eof_before_bounding_box(): +def test_eof_before_bounding_box() -> None: with pytest.raises(OSError): with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"): pass diff --git a/Tests/test_file_fits.py b/Tests/test_file_fits.py index 7444eb673..cce0b05cd 100644 --- a/Tests/test_file_fits.py +++ b/Tests/test_file_fits.py @@ -11,7 +11,7 @@ from .helper import assert_image_equal, hopper TEST_FILE = "Tests/images/hopper.fits" -def test_open(): +def test_open() -> None: # Act with Image.open(TEST_FILE) as im: # Assert @@ -22,7 +22,7 @@ def test_open(): assert_image_equal(im, hopper("L")) -def test_invalid_file(): +def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" @@ -31,14 +31,14 @@ def test_invalid_file(): FitsImagePlugin.FitsImageFile(invalid_file) -def test_truncated_fits(): +def test_truncated_fits() -> None: # No END to headers image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE" with pytest.raises(OSError): FitsImagePlugin.FitsImageFile(BytesIO(image_data)) -def test_naxis_zero(): +def test_naxis_zero() -> None: # This test image has been manually hexedited # to set the number of data axes to zero with pytest.raises(ValueError): @@ -46,7 +46,7 @@ def test_naxis_zero(): pass -def test_comment(): +def test_comment() -> None: image_data = b"SIMPLE = T / comment string" with pytest.raises(OSError): FitsImagePlugin.FitsImageFile(BytesIO(image_data)) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 00377e0c9..a673d4af8 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -16,7 +16,7 @@ static_test_file = "Tests/images/hopper.fli" animated_test_file = "Tests/images/a.fli" -def test_sanity(): +def test_sanity() -> None: with Image.open(static_test_file) as im: im.load() assert im.mode == "P" @@ -33,8 +33,8 @@ def test_sanity(): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(static_test_file) im.load() @@ -42,14 +42,14 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(static_test_file) im.load() im.close() -def test_seek_after_close(): +def test_seek_after_close() -> None: im = Image.open(animated_test_file) im.seek(1) im.close() @@ -58,13 +58,13 @@ def test_seek_after_close(): im.seek(0) -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(static_test_file) as im: im.load() -def test_tell(): +def test_tell() -> None: # Arrange with Image.open(static_test_file) as im: # Act @@ -74,20 +74,20 @@ def test_tell(): assert frame == 0 -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): FliImagePlugin.FliImageFile(invalid_file) -def test_palette_chunk_second(): +def test_palette_chunk_second() -> None: with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im: with Image.open(static_test_file) as expected: assert_image_equal(im.convert("RGB"), expected.convert("RGB")) -def test_n_frames(): +def test_n_frames() -> None: with Image.open(static_test_file) as im: assert im.n_frames == 1 assert not im.is_animated @@ -97,7 +97,7 @@ def test_n_frames(): assert im.is_animated -def test_eoferror(): +def test_eoferror() -> None: with Image.open(animated_test_file) as im: n_frames = im.n_frames @@ -110,7 +110,7 @@ def test_eoferror(): im.seek(n_frames - 1) -def test_seek_tell(): +def test_seek_tell() -> None: with Image.open(animated_test_file) as im: layer_number = im.tell() assert layer_number == 0 @@ -132,7 +132,7 @@ def test_seek_tell(): assert layer_number == 1 -def test_seek(): +def test_seek() -> None: with Image.open(animated_test_file) as im: im.seek(50) @@ -147,7 +147,7 @@ def test_seek(): ], ) @pytest.mark.timeout(timeout=3) -def test_timeouts(test_file): +def test_timeouts(test_file) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): @@ -160,7 +160,7 @@ def test_timeouts(test_file): "Tests/images/crash-5762152299364352.fli", ], ) -def test_crash(test_file): +def test_crash(test_file) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index d710070c0..e32f30a01 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -11,7 +11,7 @@ FpxImagePlugin = pytest.importorskip( ) -def test_sanity(): +def test_sanity() -> None: with Image.open("Tests/images/input_bw_one_band.fpx") as im: assert im.mode == "L" assert im.size == (70, 46) @@ -20,7 +20,7 @@ def test_sanity(): assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png") -def test_close(): +def test_close() -> None: with Image.open("Tests/images/input_bw_one_band.fpx") as im: pass assert im.ole.fp.closed @@ -30,7 +30,7 @@ def test_close(): assert im.ole.fp.closed -def test_invalid_file(): +def test_invalid_file() -> None: # Test an invalid OLE file invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): @@ -42,7 +42,7 @@ def test_invalid_file(): FpxImagePlugin.FpxImageFile(ole_file) -def test_fpx_invalid_number_of_bands(): +def test_fpx_invalid_number_of_bands() -> None: with pytest.raises(OSError, match="Invalid number of bands"): with Image.open("Tests/images/input_bw_five_bands.fpx"): pass diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3e19940aa..3f550fd11 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -2,6 +2,7 @@ from __future__ import annotations import warnings from io import BytesIO +from pathlib import Path import pytest @@ -23,7 +24,7 @@ with open(TEST_GIF, "rb") as f: data = f.read() -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_GIF) as im: im.load() assert im.mode == "P" @@ -33,8 +34,8 @@ def test_sanity(): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(TEST_GIF) im.load() @@ -42,14 +43,14 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(TEST_GIF) im.load() im.close() -def test_seek_after_close(): +def test_seek_after_close() -> None: im = Image.open("Tests/images/iss634.gif") im.load() im.close() @@ -62,20 +63,20 @@ def test_seek_after_close(): im.seek(1) -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(TEST_GIF) as im: im.load() -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): GifImagePlugin.GifImageFile(invalid_file) -def test_l_mode_transparency(): +def test_l_mode_transparency() -> None: with Image.open("Tests/images/no_palette_with_transparency.gif") as im: assert im.mode == "L" assert im.load()[0, 0] == 128 @@ -86,7 +87,7 @@ def test_l_mode_transparency(): assert im.load()[0, 0] == 128 -def test_l_mode_after_rgb(): +def test_l_mode_after_rgb() -> None: with Image.open("Tests/images/no_palette_after_rgb.gif") as im: im.seek(1) assert im.mode == "RGB" @@ -95,13 +96,13 @@ def test_l_mode_after_rgb(): assert im.mode == "RGB" -def test_palette_not_needed_for_second_frame(): +def test_palette_not_needed_for_second_frame() -> None: with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im: im.seek(1) assert_image_similar(im, hopper("L").convert("RGB"), 8) -def test_strategy(): +def test_strategy() -> None: with Image.open("Tests/images/iss634.gif") as im: expected_rgb_always = im.convert("RGB") @@ -142,7 +143,7 @@ def test_strategy(): GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST -def test_optimize(): +def test_optimize() -> None: def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) filename = BytesIO() @@ -177,7 +178,7 @@ def test_optimize(): (4, 513, 256), ), ) -def test_optimize_correctness(colors, size, expected_palette_length): +def test_optimize_correctness(colors, size, expected_palette_length) -> None: # 256 color Palette image, posterize to > 128 and < 128 levels. # Size bigger and smaller than 512x512. # Check the palette for number of colors allocated. @@ -199,14 +200,14 @@ def test_optimize_correctness(colors, size, expected_palette_length): assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) -def test_optimize_full_l(): +def test_optimize_full_l() -> None: im = Image.frombytes("L", (16, 16), bytes(range(256))) test_file = BytesIO() im.save(test_file, "GIF", optimize=True) assert im.mode == "L" -def test_optimize_if_palette_can_be_reduced_by_half(): +def test_optimize_if_palette_can_be_reduced_by_half() -> None: im = Image.new("P", (8, 1)) im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150)) for i in range(8): @@ -219,7 +220,7 @@ def test_optimize_if_palette_can_be_reduced_by_half(): assert len(reloaded.palette.palette) // 3 == colors -def test_full_palette_second_frame(tmp_path): +def test_full_palette_second_frame(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("P", (1, 256)) @@ -240,7 +241,7 @@ def test_full_palette_second_frame(tmp_path): reloaded.getpixel((0, i)) == i -def test_roundtrip(tmp_path): +def test_roundtrip(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = hopper() im.save(out) @@ -248,7 +249,7 @@ def test_roundtrip(tmp_path): assert_image_similar(reread.convert("RGB"), im, 50) -def test_roundtrip2(tmp_path): +def test_roundtrip2(tmp_path: Path) -> None: # see https://github.com/python-pillow/Pillow/issues/403 out = str(tmp_path / "temp.gif") with Image.open(TEST_GIF) as im: @@ -258,7 +259,7 @@ def test_roundtrip2(tmp_path): assert_image_similar(reread.convert("RGB"), hopper(), 50) -def test_roundtrip_save_all(tmp_path): +def test_roundtrip_save_all(tmp_path: Path) -> None: # Single frame image out = str(tmp_path / "temp.gif") im = hopper() @@ -275,7 +276,7 @@ def test_roundtrip_save_all(tmp_path): assert reread.n_frames == 5 -def test_roundtrip_save_all_1(tmp_path): +def test_roundtrip_save_all_1(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("1", (1, 1)) im2 = Image.new("1", (1, 1), 1) @@ -296,7 +297,7 @@ def test_roundtrip_save_all_1(tmp_path): ("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), ), ) -def test_loading_multiple_palettes(path, mode): +def test_loading_multiple_palettes(path, mode) -> None: with Image.open(path) as im: assert im.mode == "P" first_frame_colors = im.palette.colors.keys() @@ -314,7 +315,7 @@ def test_loading_multiple_palettes(path, mode): assert im.load()[24, 24] not in first_frame_colors -def test_headers_saving_for_animated_gifs(tmp_path): +def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None: important_headers = ["background", "version", "duration", "loop"] # Multiframe image with Image.open("Tests/images/dispose_bgnd.gif") as im: @@ -327,7 +328,7 @@ def test_headers_saving_for_animated_gifs(tmp_path): assert info[header] == reread.info[header] -def test_palette_handling(tmp_path): +def test_palette_handling(tmp_path: Path) -> None: # see https://github.com/python-pillow/Pillow/issues/513 with Image.open(TEST_GIF) as im: @@ -343,7 +344,7 @@ def test_palette_handling(tmp_path): assert_image_similar(im, reloaded.convert("RGB"), 10) -def test_palette_434(tmp_path): +def test_palette_434(tmp_path: Path) -> None: # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): @@ -368,7 +369,7 @@ def test_palette_434(tmp_path): @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") -def test_save_netpbm_bmp_mode(tmp_path): +def test_save_netpbm_bmp_mode(tmp_path: Path) -> None: with Image.open(TEST_GIF) as img: img = img.convert("RGB") @@ -379,7 +380,7 @@ def test_save_netpbm_bmp_mode(tmp_path): @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") -def test_save_netpbm_l_mode(tmp_path): +def test_save_netpbm_l_mode(tmp_path: Path) -> None: with Image.open(TEST_GIF) as img: img = img.convert("L") @@ -389,7 +390,7 @@ def test_save_netpbm_l_mode(tmp_path): assert_image_similar(img, reloaded.convert("L"), 0) -def test_seek(): +def test_seek() -> None: with Image.open("Tests/images/dispose_none.gif") as img: frame_count = 0 try: @@ -400,7 +401,7 @@ def test_seek(): assert frame_count == 5 -def test_seek_info(): +def test_seek_info() -> None: with Image.open("Tests/images/iss634.gif") as im: info = im.info.copy() @@ -410,7 +411,7 @@ def test_seek_info(): assert im.info == info -def test_seek_rewind(): +def test_seek_rewind() -> None: with Image.open("Tests/images/iss634.gif") as im: im.seek(2) im.seek(1) @@ -428,7 +429,7 @@ def test_seek_rewind(): ("Tests/images/iss634.gif", 42), ), ) -def test_n_frames(path, n_frames): +def test_n_frames(path, n_frames) -> None: # Test is_animated before n_frames with Image.open(path) as im: assert im.is_animated == (n_frames != 1) @@ -439,7 +440,7 @@ def test_n_frames(path, n_frames): assert im.is_animated == (n_frames != 1) -def test_no_change(): +def test_no_change() -> None: # Test n_frames does not change the image with Image.open("Tests/images/dispose_bgnd.gif") as im: im.seek(1) @@ -460,7 +461,7 @@ def test_no_change(): assert_image_equal(im, expected) -def test_eoferror(): +def test_eoferror() -> None: with Image.open(TEST_GIF) as im: n_frames = im.n_frames @@ -473,13 +474,13 @@ def test_eoferror(): im.seek(n_frames - 1) -def test_first_frame_transparency(): +def test_first_frame_transparency() -> None: with Image.open("Tests/images/first_frame_transparency.gif") as im: px = im.load() assert px[0, 0] == im.info["transparency"] -def test_dispose_none(): +def test_dispose_none() -> None: with Image.open("Tests/images/dispose_none.gif") as img: try: while True: @@ -489,7 +490,7 @@ def test_dispose_none(): pass -def test_dispose_none_load_end(): +def test_dispose_none_load_end() -> None: # Test image created with: # # im = Image.open("transparent.gif") @@ -502,7 +503,7 @@ def test_dispose_none_load_end(): assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png") -def test_dispose_background(): +def test_dispose_background() -> None: with Image.open("Tests/images/dispose_bgnd.gif") as img: try: while True: @@ -512,7 +513,7 @@ def test_dispose_background(): pass -def test_dispose_background_transparency(): +def test_dispose_background_transparency() -> None: with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: img.seek(2) px = img.load() @@ -540,7 +541,7 @@ def test_dispose_background_transparency(): ), ), ) -def test_transparent_dispose(loading_strategy, expected_colors): +def test_transparent_dispose(loading_strategy, expected_colors) -> None: GifImagePlugin.LOADING_STRATEGY = loading_strategy try: with Image.open("Tests/images/transparent_dispose.gif") as img: @@ -553,7 +554,7 @@ def test_transparent_dispose(loading_strategy, expected_colors): GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST -def test_dispose_previous(): +def test_dispose_previous() -> None: with Image.open("Tests/images/dispose_prev.gif") as img: try: while True: @@ -563,7 +564,7 @@ def test_dispose_previous(): pass -def test_dispose_previous_first_frame(): +def test_dispose_previous_first_frame() -> None: with Image.open("Tests/images/dispose_prev_first_frame.gif") as im: im.seek(1) assert_image_equal_tofile( @@ -571,7 +572,7 @@ def test_dispose_previous_first_frame(): ) -def test_previous_frame_loaded(): +def test_previous_frame_loaded() -> None: with Image.open("Tests/images/dispose_none.gif") as img: img.load() img.seek(1) @@ -582,7 +583,7 @@ def test_previous_frame_loaded(): assert_image_equal(img_skipped, img) -def test_save_dispose(tmp_path): +def test_save_dispose(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im_list = [ Image.new("L", (100, 100), "#000"), @@ -610,7 +611,7 @@ def test_save_dispose(tmp_path): assert img.disposal_method == i + 1 -def test_dispose2_palette(tmp_path): +def test_dispose2_palette(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") # Four colors: white, gray, black, red @@ -641,7 +642,7 @@ def test_dispose2_palette(tmp_path): assert rgb_img.getpixel((50, 50)) == circle -def test_dispose2_diff(tmp_path): +def test_dispose2_diff(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") # 4 frames: red/blue, red/red, blue/blue, red/blue @@ -683,7 +684,7 @@ def test_dispose2_diff(tmp_path): assert rgb_img.getpixel((1, 1)) == (255, 255, 255, 0) -def test_dispose2_background(tmp_path): +def test_dispose2_background(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im_list = [] @@ -709,7 +710,7 @@ def test_dispose2_background(tmp_path): assert im.getpixel((0, 0)) == (255, 0, 0) -def test_dispose2_background_frame(tmp_path): +def test_dispose2_background_frame(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im_list = [Image.new("RGBA", (1, 20))] @@ -727,7 +728,7 @@ def test_dispose2_background_frame(tmp_path): assert im.n_frames == 3 -def test_transparency_in_second_frame(tmp_path): +def test_transparency_in_second_frame(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") with Image.open("Tests/images/different_transparency.gif") as im: assert im.info["transparency"] == 0 @@ -747,7 +748,7 @@ def test_transparency_in_second_frame(tmp_path): ) -def test_no_transparency_in_second_frame(): +def test_no_transparency_in_second_frame() -> None: with Image.open("Tests/images/iss634.gif") as img: # Seek to the second frame img.seek(img.tell() + 1) @@ -757,7 +758,7 @@ def test_no_transparency_in_second_frame(): assert img.histogram()[255] == 0 -def test_remapped_transparency(tmp_path): +def test_remapped_transparency(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("P", (1, 2)) @@ -773,7 +774,7 @@ def test_remapped_transparency(tmp_path): assert reloaded.info["transparency"] == reloaded.getpixel((0, 1)) -def test_duration(tmp_path): +def test_duration(tmp_path: Path) -> None: duration = 1000 out = str(tmp_path / "temp.gif") @@ -787,7 +788,7 @@ def test_duration(tmp_path): assert reread.info["duration"] == duration -def test_multiple_duration(tmp_path): +def test_multiple_duration(tmp_path: Path) -> None: duration_list = [1000, 2000, 3000] out = str(tmp_path / "temp.gif") @@ -822,7 +823,7 @@ def test_multiple_duration(tmp_path): pass -def test_roundtrip_info_duration(tmp_path): +def test_roundtrip_info_duration(tmp_path: Path) -> None: duration_list = [100, 500, 500] out = str(tmp_path / "temp.gif") @@ -839,7 +840,7 @@ def test_roundtrip_info_duration(tmp_path): ] == duration_list -def test_roundtrip_info_duration_combined(tmp_path): +def test_roundtrip_info_duration_combined(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") with Image.open("Tests/images/duplicate_frame.gif") as im: assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [ @@ -855,7 +856,7 @@ def test_roundtrip_info_duration_combined(tmp_path): ] == [1000, 2000] -def test_identical_frames(tmp_path): +def test_identical_frames(tmp_path: Path) -> None: duration_list = [1000, 1500, 2000, 4000] out = str(tmp_path / "temp.gif") @@ -888,7 +889,7 @@ def test_identical_frames(tmp_path): 1500, ), ) -def test_identical_frames_to_single_frame(duration, tmp_path): +def test_identical_frames_to_single_frame(duration, tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im_list = [ Image.new("L", (100, 100), "#000"), @@ -905,7 +906,7 @@ def test_identical_frames_to_single_frame(duration, tmp_path): assert reread.info["duration"] == 4500 -def test_loop_none(tmp_path): +def test_loop_none(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("L", (100, 100), "#000") im.save(out, loop=None) @@ -913,7 +914,7 @@ def test_loop_none(tmp_path): assert "loop" not in reread.info -def test_number_of_loops(tmp_path): +def test_number_of_loops(tmp_path: Path) -> None: number_of_loops = 2 out = str(tmp_path / "temp.gif") @@ -931,7 +932,7 @@ def test_number_of_loops(tmp_path): assert im.info["loop"] == 2 -def test_background(tmp_path): +def test_background(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("L", (100, 100), "#000") im.info["background"] = 1 @@ -940,7 +941,7 @@ def test_background(tmp_path): assert reread.info["background"] == im.info["background"] -def test_webp_background(tmp_path): +def test_webp_background(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") # Test opaque WebP background @@ -955,7 +956,7 @@ def test_webp_background(tmp_path): im.save(out) -def test_comment(tmp_path): +def test_comment(tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" @@ -975,7 +976,7 @@ def test_comment(tmp_path): assert reread.info["version"] == b"GIF89a" -def test_comment_over_255(tmp_path): +def test_comment_over_255(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("L", (100, 100), "#000") comment = b"Test comment text" @@ -990,18 +991,18 @@ def test_comment_over_255(tmp_path): assert reread.info["version"] == b"GIF89a" -def test_zero_comment_subblocks(): +def test_zero_comment_subblocks() -> None: with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: assert_image_equal_tofile(im, TEST_GIF) -def test_read_multiple_comment_blocks(): +def test_read_multiple_comment_blocks() -> None: with Image.open("Tests/images/multiple_comments.gif") as im: # Multiple comment blocks in a frame are separated not concatenated assert im.info["comment"] == b"Test comment 1\nTest comment 2" -def test_empty_string_comment(tmp_path): +def test_empty_string_comment(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") with Image.open("Tests/images/chi.gif") as im: assert "comment" in im.info @@ -1014,7 +1015,7 @@ def test_empty_string_comment(tmp_path): assert "comment" not in frame.info -def test_retain_comment_in_subsequent_frames(tmp_path): +def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None: # Test that a comment block at the beginning is kept with Image.open("Tests/images/chi.gif") as im: for frame in ImageSequence.Iterator(im): @@ -1045,10 +1046,10 @@ def test_retain_comment_in_subsequent_frames(tmp_path): assert frame.info["comment"] == b"Test" -def test_version(tmp_path): +def test_version(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") - def assert_version_after_save(im, version): + def assert_version_after_save(im, version) -> None: im.save(out) with Image.open(out) as reread: assert reread.info["version"] == version @@ -1075,7 +1076,7 @@ def test_version(tmp_path): assert_version_after_save(im, b"GIF87a") -def test_append_images(tmp_path): +def test_append_images(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") # Test appending single frame images @@ -1104,7 +1105,7 @@ def test_append_images(tmp_path): assert reread.n_frames == 10 -def test_transparent_optimize(tmp_path): +def test_transparent_optimize(tmp_path: Path) -> None: # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses # transparency. # Need a palette that isn't using the 0 color, @@ -1124,7 +1125,7 @@ def test_transparent_optimize(tmp_path): assert reloaded.info["transparency"] == reloaded.getpixel((252, 0)) -def test_removed_transparency(tmp_path): +def test_removed_transparency(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("RGB", (256, 1)) @@ -1139,7 +1140,7 @@ def test_removed_transparency(tmp_path): assert "transparency" not in reloaded.info -def test_rgb_transparency(tmp_path): +def test_rgb_transparency(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") # Single frame @@ -1161,7 +1162,7 @@ def test_rgb_transparency(tmp_path): assert "transparency" not in reloaded.info -def test_rgba_transparency(tmp_path): +def test_rgba_transparency(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = hopper("P") @@ -1172,13 +1173,13 @@ def test_rgba_transparency(tmp_path): assert_image_equal(hopper("P").convert("RGB"), reloaded) -def test_background_outside_palettte(tmp_path): +def test_background_outside_palettte(tmp_path: Path) -> None: with Image.open("Tests/images/background_outside_palette.gif") as im: im.seek(1) assert im.info["background"] == 255 -def test_bbox(tmp_path): +def test_bbox(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("RGB", (100, 100), "#fff") @@ -1189,7 +1190,7 @@ def test_bbox(tmp_path): assert reread.n_frames == 2 -def test_bbox_alpha(tmp_path): +def test_bbox_alpha(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") im = Image.new("RGBA", (1, 2), (255, 0, 0, 255)) @@ -1201,7 +1202,7 @@ def test_bbox_alpha(tmp_path): assert reread.n_frames == 2 -def test_palette_save_L(tmp_path): +def test_palette_save_L(tmp_path: Path) -> None: # Generate an L mode image with a separate palette im = hopper("P") @@ -1215,7 +1216,7 @@ def test_palette_save_L(tmp_path): assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) -def test_palette_save_P(tmp_path): +def test_palette_save_P(tmp_path: Path) -> None: im = Image.new("P", (1, 2)) im.putpixel((0, 1), 1) @@ -1229,7 +1230,7 @@ def test_palette_save_P(tmp_path): assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6) -def test_palette_save_duplicate_entries(tmp_path): +def test_palette_save_duplicate_entries(tmp_path: Path) -> None: im = Image.new("P", (1, 2)) im.putpixel((0, 1), 1) @@ -1242,7 +1243,7 @@ def test_palette_save_duplicate_entries(tmp_path): assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0) -def test_palette_save_all_P(tmp_path): +def test_palette_save_all_P(tmp_path: Path) -> None: frames = [] colors = ((255, 0, 0), (0, 255, 0)) for color in colors: @@ -1265,7 +1266,7 @@ def test_palette_save_all_P(tmp_path): assert im.palette.palette == im.global_palette.palette -def test_palette_save_ImagePalette(tmp_path): +def test_palette_save_ImagePalette(tmp_path: Path) -> None: # Pass in a different palette, as an ImagePalette.ImagePalette # effectively the same as test_palette_save_P @@ -1280,7 +1281,7 @@ def test_palette_save_ImagePalette(tmp_path): assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) -def test_save_I(tmp_path): +def test_save_I(tmp_path: Path) -> None: # Test saving something that would trigger the auto-convert to 'L' im = hopper("I") @@ -1292,7 +1293,7 @@ def test_save_I(tmp_path): assert_image_equal(reloaded.convert("L"), im.convert("L")) -def test_getdata(): +def test_getdata() -> None: # Test getheader/getdata against legacy values. # Create a 'P' image with holes in the palette. im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) @@ -1320,7 +1321,7 @@ def test_getdata(): GifImagePlugin._FORCE_OPTIMIZE = False -def test_lzw_bits(): +def test_lzw_bits() -> None: # see https://github.com/python-pillow/Pillow/issues/2811 with Image.open("Tests/images/issue_2811.gif") as im: assert im.tile[0][3][0] == 11 # LZW bits @@ -1328,7 +1329,7 @@ def test_lzw_bits(): im.load() -def test_extents(): +def test_extents() -> None: with Image.open("Tests/images/test_extents.gif") as im: assert im.size == (100, 100) @@ -1340,7 +1341,7 @@ def test_extents(): assert im.size == (150, 150) -def test_missing_background(): +def test_missing_background() -> None: # The Global Color Table Flag isn't set, so there is no background color index, # but the disposal method is "Restore to background color" with Image.open("Tests/images/missing_background.gif") as im: @@ -1348,7 +1349,7 @@ def test_missing_background(): assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") -def test_saving_rgba(tmp_path): +def test_saving_rgba(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") with Image.open("Tests/images/transparent.png") as im: im.save(out) diff --git a/Tests/test_file_gimpgradient.py b/Tests/test_file_gimpgradient.py index ceea1edd3..006ee952d 100644 --- a/Tests/test_file_gimpgradient.py +++ b/Tests/test_file_gimpgradient.py @@ -3,7 +3,7 @@ from __future__ import annotations from PIL import GimpGradientFile, ImagePalette -def test_linear_pos_le_middle(): +def test_linear_pos_le_middle() -> None: # Arrange middle = 0.5 pos = 0.25 @@ -15,7 +15,7 @@ def test_linear_pos_le_middle(): assert ret == 0.25 -def test_linear_pos_le_small_middle(): +def test_linear_pos_le_small_middle() -> None: # Arrange middle = 1e-11 pos = 1e-12 @@ -27,7 +27,7 @@ def test_linear_pos_le_small_middle(): assert ret == 0.0 -def test_linear_pos_gt_middle(): +def test_linear_pos_gt_middle() -> None: # Arrange middle = 0.5 pos = 0.75 @@ -39,7 +39,7 @@ def test_linear_pos_gt_middle(): assert ret == 0.75 -def test_linear_pos_gt_small_middle(): +def test_linear_pos_gt_small_middle() -> None: # Arrange middle = 1 - 1e-11 pos = 1 - 1e-12 @@ -51,7 +51,7 @@ def test_linear_pos_gt_small_middle(): assert ret == 1.0 -def test_curved(): +def test_curved() -> None: # Arrange middle = 0.5 pos = 0.75 @@ -63,7 +63,7 @@ def test_curved(): assert ret == 0.75 -def test_sine(): +def test_sine() -> None: # Arrange middle = 0.5 pos = 0.75 @@ -75,7 +75,7 @@ def test_sine(): assert ret == 0.8535533905932737 -def test_sphere_increasing(): +def test_sphere_increasing() -> None: # Arrange middle = 0.5 pos = 0.75 @@ -87,7 +87,7 @@ def test_sphere_increasing(): assert round(abs(ret - 0.9682458365518543), 7) == 0 -def test_sphere_decreasing(): +def test_sphere_decreasing() -> None: # Arrange middle = 0.5 pos = 0.75 @@ -99,7 +99,7 @@ def test_sphere_decreasing(): assert ret == 0.3385621722338523 -def test_load_via_imagepalette(): +def test_load_via_imagepalette() -> None: # Arrange test_file = "Tests/images/gimp_gradient.ggr" @@ -112,7 +112,7 @@ def test_load_via_imagepalette(): assert palette[1] == "RGBA" -def test_load_1_3_via_imagepalette(): +def test_load_1_3_via_imagepalette() -> None: # Arrange # GIMP 1.3 gradient files contain a name field test_file = "Tests/images/gimp_gradient_with_name.ggr" diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index a4ce6dde6..4945468be 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import GribStubImagePlugin, Image @@ -9,7 +11,7 @@ from .helper import hopper TEST_FILE = "Tests/images/WAlaska.wind.7days.grb" -def test_open(): +def test_open() -> None: # Act with Image.open(TEST_FILE) as im: # Assert @@ -20,7 +22,7 @@ def test_open(): assert im.size == (1, 1) -def test_invalid_file(): +def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" @@ -29,7 +31,7 @@ def test_invalid_file(): GribStubImagePlugin.GribStubImageFile(invalid_file) -def test_load(): +def test_load() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler @@ -37,7 +39,7 @@ def test_load(): im.load() -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: # Arrange im = hopper() tmpfile = str(tmp_path / "temp.grib") @@ -47,13 +49,13 @@ def test_save(tmp_path): im.save(tmpfile) -def test_handler(tmp_path): +def test_handler(tmp_path: Path) -> None: class TestHandler: opened = False loaded = False saved = False - def open(self, im): + def open(self, im) -> None: self.opened = True def load(self, im): @@ -61,7 +63,7 @@ def test_handler(tmp_path): im.fp.close() return Image.new("RGB", (1, 1)) - def save(self, im, fp, filename): + def save(self, im, fp, filename) -> None: self.saved = True handler = TestHandler() diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 727644617..ac3d40bf2 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Hdf5StubImagePlugin, Image @@ -7,7 +9,7 @@ from PIL import Hdf5StubImagePlugin, Image TEST_FILE = "Tests/images/hdf5.h5" -def test_open(): +def test_open() -> None: # Act with Image.open(TEST_FILE) as im: # Assert @@ -18,7 +20,7 @@ def test_open(): assert im.size == (1, 1) -def test_invalid_file(): +def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" @@ -27,7 +29,7 @@ def test_invalid_file(): Hdf5StubImagePlugin.HDF5StubImageFile(invalid_file) -def test_load(): +def test_load() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler @@ -35,7 +37,7 @@ def test_load(): im.load() -def test_save(): +def test_save() -> None: # Arrange with Image.open(TEST_FILE) as im: dummy_fp = None @@ -48,13 +50,13 @@ def test_save(): Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename) -def test_handler(tmp_path): +def test_handler(tmp_path: Path) -> None: class TestHandler: opened = False loaded = False saved = False - def open(self, im): + def open(self, im) -> None: self.opened = True def load(self, im): @@ -62,7 +64,7 @@ def test_handler(tmp_path): im.fp.close() return Image.new("RGB", (1, 1)) - def save(self, im, fp, filename): + def save(self, im, fp, filename) -> None: self.saved = True handler = TestHandler() diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 314fa8008..488984aef 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -3,6 +3,7 @@ from __future__ import annotations import io import os import warnings +from pathlib import Path import pytest @@ -14,7 +15,7 @@ from .helper import assert_image_equal, assert_image_similar_tofile, skip_unless TEST_FILE = "Tests/images/pillow.icns" -def test_sanity(): +def test_sanity() -> None: # Loading this icon by default should result in the largest size # (512x512@2x) being loaded with Image.open(TEST_FILE) as im: @@ -27,7 +28,7 @@ def test_sanity(): assert im.format == "ICNS" -def test_load(): +def test_load() -> None: with Image.open(TEST_FILE) as im: assert im.load()[0, 0] == (0, 0, 0, 0) @@ -35,7 +36,7 @@ def test_load(): assert im.load()[0, 0] == (0, 0, 0, 0) -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.icns") with Image.open(TEST_FILE) as im: @@ -52,7 +53,7 @@ def test_save(tmp_path): assert _binary.i32be(fp.read(4)) == file_length -def test_save_append_images(tmp_path): +def test_save_append_images(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.icns") provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) @@ -67,7 +68,7 @@ def test_save_append_images(tmp_path): assert_image_equal(reread, provided_im) -def test_save_fp(): +def test_save_fp() -> None: fp = io.BytesIO() with Image.open(TEST_FILE) as im: @@ -79,7 +80,7 @@ def test_save_fp(): assert reread.format == "ICNS" -def test_sizes(): +def test_sizes() -> None: # Check that we can load all of the sizes, and that the final pixel # dimensions are as expected with Image.open(TEST_FILE) as im: @@ -96,7 +97,7 @@ def test_sizes(): im.size = (1, 1) -def test_older_icon(): +def test_older_icon() -> None: # This icon was made with Icon Composer rather than iconutil; it still # uses PNG rather than JP2, however (since it was made on 10.9). with Image.open("Tests/images/pillow2.icns") as im: @@ -111,7 +112,7 @@ def test_older_icon(): @skip_unless_feature("jpg_2000") -def test_jp2_icon(): +def test_jp2_icon() -> None: # This icon uses JPEG 2000 images instead of the PNG images. # The advantage of doing this is that OS X 10.5 supports JPEG 2000 # but not PNG; some commercial software therefore does just this. @@ -127,7 +128,7 @@ def test_jp2_icon(): assert im2.size == (wr, hr) -def test_getimage(): +def test_getimage() -> None: with open(TEST_FILE, "rb") as fp: icns_file = IcnsImagePlugin.IcnsFile(fp) @@ -140,14 +141,14 @@ def test_getimage(): assert im.size == (512, 512) -def test_not_an_icns_file(): +def test_not_an_icns_file() -> None: with io.BytesIO(b"invalid\n") as fp: with pytest.raises(SyntaxError): IcnsImagePlugin.IcnsFile(fp) @skip_unless_feature("jpg_2000") -def test_icns_decompression_bomb(): +def test_icns_decompression_bomb() -> None: with Image.open( "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" ) as im: diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 99b3048d1..65f090931 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -2,6 +2,7 @@ from __future__ import annotations import io import os +from pathlib import Path import pytest @@ -12,7 +13,7 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_ICO_FILE = "Tests/images/hopper.ico" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_ICO_FILE) as im: im.load() assert im.mode == "RGBA" @@ -21,29 +22,29 @@ def test_sanity(): assert im.get_format_mimetype() == "image/x-icon" -def test_load(): +def test_load() -> None: with Image.open(TEST_ICO_FILE) as im: assert im.load()[0, 0] == (1, 1, 9, 255) -def test_mask(): +def test_mask() -> None: with Image.open("Tests/images/hopper_mask.ico") as im: assert_image_equal_tofile(im, "Tests/images/hopper_mask.png") -def test_black_and_white(): +def test_black_and_white() -> None: with Image.open("Tests/images/black_and_white.ico") as im: assert im.mode == "RGBA" assert im.size == (16, 16) -def test_invalid_file(): +def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): IcoImagePlugin.IcoImageFile(fp) -def test_save_to_bytes(): +def test_save_to_bytes() -> None: output = io.BytesIO() im = hopper() im.save(output, "ico", sizes=[(32, 32), (64, 64)]) @@ -73,7 +74,7 @@ def test_save_to_bytes(): ) -def test_getpixel(tmp_path): +def test_getpixel(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.ico") im = hopper() @@ -86,7 +87,7 @@ def test_getpixel(tmp_path): assert reloaded.getpixel((0, 0)) == (18, 20, 62) -def test_no_duplicates(tmp_path): +def test_no_duplicates(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.ico") temp_file2 = str(tmp_path / "temp2.ico") @@ -100,7 +101,7 @@ def test_no_duplicates(tmp_path): assert os.path.getsize(temp_file) == os.path.getsize(temp_file2) -def test_different_bit_depths(tmp_path): +def test_different_bit_depths(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.ico") temp_file2 = str(tmp_path / "temp2.ico") @@ -134,7 +135,7 @@ def test_different_bit_depths(tmp_path): @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA")) -def test_save_to_bytes_bmp(mode): +def test_save_to_bytes_bmp(mode) -> None: output = io.BytesIO() im = hopper(mode) im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)]) @@ -162,13 +163,13 @@ def test_save_to_bytes_bmp(mode): assert_image_equal(reloaded, im) -def test_incorrect_size(): +def test_incorrect_size() -> None: with Image.open(TEST_ICO_FILE) as im: with pytest.raises(ValueError): im.size = (1, 1) -def test_save_256x256(tmp_path): +def test_save_256x256(tmp_path: Path) -> None: """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" # Arrange with Image.open("Tests/images/hopper_256x256.ico") as im: @@ -181,7 +182,7 @@ def test_save_256x256(tmp_path): assert im_saved.size == (256, 256) -def test_only_save_relevant_sizes(tmp_path): +def test_only_save_relevant_sizes(tmp_path: Path) -> None: """Issue #2266 https://github.com/python-pillow/Pillow/issues/2266 Should save in 16x16, 24x24, 32x32, 48x48 sizes and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes @@ -197,7 +198,7 @@ def test_only_save_relevant_sizes(tmp_path): assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)} -def test_save_append_images(tmp_path): +def test_save_append_images(tmp_path: Path) -> None: # append_images should be used for scaled down versions of the image im = hopper("RGBA") provided_im = Image.new("RGBA", (32, 32), (255, 0, 0)) @@ -211,7 +212,7 @@ def test_save_append_images(tmp_path): assert_image_equal(reread, provided_im) -def test_unexpected_size(): +def test_unexpected_size() -> None: # This image has been manually hexedited to state that it is 16x32 # while the image within is still 16x16 with pytest.warns(UserWarning): @@ -219,7 +220,7 @@ def test_unexpected_size(): assert im.size == (16, 16) -def test_draw_reloaded(tmp_path): +def test_draw_reloaded(tmp_path: Path) -> None: with Image.open(TEST_ICO_FILE) as im: outfile = str(tmp_path / "temp_saved_hopper_draw.ico") diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index a031b3e88..f932069b9 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -2,6 +2,7 @@ from __future__ import annotations import filecmp import warnings +from pathlib import Path import pytest @@ -13,7 +14,7 @@ from .helper import assert_image_equal_tofile, hopper, is_pypy TEST_IM = "Tests/images/hopper.im" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_IM) as im: im.load() assert im.mode == "RGB" @@ -21,7 +22,7 @@ def test_sanity(): assert im.format == "IM" -def test_name_limit(tmp_path): +def test_name_limit(tmp_path: Path) -> None: out = str(tmp_path / ("name_limit_test" * 7 + ".im")) with Image.open(TEST_IM) as im: im.save(out) @@ -29,8 +30,8 @@ def test_name_limit(tmp_path): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(TEST_IM) im.load() @@ -38,20 +39,20 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(TEST_IM) im.load() im.close() -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(TEST_IM) as im: im.load() -def test_tell(): +def test_tell() -> None: # Arrange with Image.open(TEST_IM) as im: # Act @@ -61,13 +62,13 @@ def test_tell(): assert frame == 0 -def test_n_frames(): +def test_n_frames() -> None: with Image.open(TEST_IM) as im: assert im.n_frames == 1 assert not im.is_animated -def test_eoferror(): +def test_eoferror() -> None: with Image.open(TEST_IM) as im: n_frames = im.n_frames @@ -81,14 +82,14 @@ def test_eoferror(): @pytest.mark.parametrize("mode", ("RGB", "P", "PA")) -def test_roundtrip(mode, tmp_path): +def test_roundtrip(mode, tmp_path: Path) -> None: out = str(tmp_path / "temp.im") im = hopper(mode) im.save(out) assert_image_equal_tofile(im, out) -def test_small_palette(tmp_path): +def test_small_palette(tmp_path: Path) -> None: im = Image.new("P", (1, 1)) colors = [0, 1, 2] im.putpalette(colors) @@ -100,19 +101,19 @@ def test_small_palette(tmp_path): assert reloaded.getpalette() == colors + [0] * 765 -def test_save_unsupported_mode(tmp_path): +def test_save_unsupported_mode(tmp_path: Path) -> None: out = str(tmp_path / "temp.im") im = hopper("HSV") with pytest.raises(ValueError): im.save(out) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): ImImagePlugin.ImImageFile(invalid_file) -def test_number(): +def test_number() -> None: assert ImImagePlugin.number("1.2") == 1.2 diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index a2c50ecef..9c0969437 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -12,7 +12,7 @@ from .helper import assert_image_equal, hopper TEST_FILE = "Tests/images/iptc.jpg" -def test_open(): +def test_open() -> None: expected = Image.new("L", (1, 1)) f = BytesIO( @@ -24,7 +24,7 @@ def test_open(): assert_image_equal(im, expected) -def test_getiptcinfo_jpg_none(): +def test_getiptcinfo_jpg_none() -> None: # Arrange with hopper() as im: # Act @@ -34,7 +34,7 @@ def test_getiptcinfo_jpg_none(): assert iptc is None -def test_getiptcinfo_jpg_found(): +def test_getiptcinfo_jpg_found() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act @@ -46,7 +46,7 @@ def test_getiptcinfo_jpg_found(): assert iptc[(2, 101)] == b"Hungary" -def test_getiptcinfo_fotostation(): +def test_getiptcinfo_fotostation() -> None: # Arrange with open(TEST_FILE, "rb") as fp: data = bytearray(fp.read()) @@ -63,7 +63,7 @@ def test_getiptcinfo_fotostation(): pytest.fail("FotoStation tag not found") -def test_getiptcinfo_zero_padding(): +def test_getiptcinfo_zero_padding() -> None: # Arrange with Image.open(TEST_FILE) as im: im.info["photoshop"][0x0404] += b"\x00\x00\x00" @@ -76,7 +76,7 @@ def test_getiptcinfo_zero_padding(): assert len(iptc) == 3 -def test_getiptcinfo_tiff_none(): +def test_getiptcinfo_tiff_none() -> None: # Arrange with Image.open("Tests/images/hopper.tif") as im: # Act @@ -86,7 +86,7 @@ def test_getiptcinfo_tiff_none(): assert iptc is None -def test_i(): +def test_i() -> None: # Arrange c = b"a" @@ -98,7 +98,7 @@ def test_i(): assert ret == 97 -def test_dump(monkeypatch): +def test_dump(monkeypatch) -> None: # Arrange c = b"abc" # Temporarily redirect stdout @@ -113,6 +113,6 @@ def test_dump(monkeypatch): assert mystdout.getvalue() == "61 62 63 \n" -def test_pad_deprecation(): +def test_pad_deprecation() -> None: with pytest.warns(DeprecationWarning): assert IptcImagePlugin.PAD == b"\0\0\0\0" diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 232e51f91..ff278d4c1 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -4,6 +4,7 @@ import os import re import warnings from io import BytesIO +from pathlib import Path import pytest @@ -50,7 +51,7 @@ class TestFileJpeg: im.bytes = test_bytes # for testing only return im - def gen_random_image(self, size, mode="RGB"): + def gen_random_image(self, size, mode: str = "RGB"): """Generates a very hard to compress file :param size: tuple :param mode: optional image mode @@ -58,7 +59,7 @@ class TestFileJpeg: """ return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode))) - def test_sanity(self): + def test_sanity(self) -> None: # internal version number assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) @@ -70,13 +71,13 @@ class TestFileJpeg: assert im.get_format_mimetype() == "image/jpeg" @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero(self, size, tmp_path): + def test_zero(self, size, tmp_path: Path) -> None: f = str(tmp_path / "temp.jpg") im = Image.new("RGB", size) with pytest.raises(ValueError): im.save(f) - def test_app(self): + def test_app(self) -> None: # Test APP/COM reader (@PIL135) with Image.open(TEST_FILE) as im: assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") @@ -89,7 +90,7 @@ class TestFileJpeg: assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" assert im.app["COM"] == im.info["comment"] - def test_comment_write(self): + def test_comment_write(self) -> None: with Image.open(TEST_FILE) as im: assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" @@ -115,7 +116,7 @@ class TestFileJpeg: comment = comment.encode() assert reloaded.info["comment"] == comment - def test_cmyk(self): + def test_cmyk(self) -> None: # Test CMYK handling. Thanks to Tim and Charlie for test data, # Michael for getting me to look one more time. f = "Tests/images/pil_sample_cmyk.jpg" @@ -143,7 +144,7 @@ class TestFileJpeg: ) assert k > 0.9 - def test_rgb(self): + def test_rgb(self) -> None: def getchannels(im): return tuple(v[0] for v in im.layer) @@ -160,7 +161,7 @@ class TestFileJpeg: "test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], ) - def test_dpi(self, test_image_path): + def test_dpi(self, test_image_path) -> None: def test(xdpi, ydpi=None): with Image.open(test_image_path) as im: im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) @@ -174,7 +175,7 @@ class TestFileJpeg: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_icc(self, tmp_path): + def test_icc(self, tmp_path: Path) -> None: # Test ICC support with Image.open("Tests/images/rgb.jpg") as im1: icc_profile = im1.info["icc_profile"] @@ -206,7 +207,7 @@ class TestFileJpeg: ImageFile.MAXBLOCK * 4 + 3, # large block ), ) - def test_icc_big(self, n): + def test_icc_big(self, n) -> None: # Make sure that the "extra" support handles large blocks # The ICC APP marker can store 65519 bytes per marker, so # using a 4-byte test code should allow us to detect out of @@ -219,7 +220,7 @@ class TestFileJpeg: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_large_icc_meta(self, tmp_path): + def test_large_icc_meta(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than # Image.MAXBLOCK or the image size. @@ -243,7 +244,7 @@ class TestFileJpeg: f = str(tmp_path / "temp3.jpg") im.save(f, progressive=True, quality=94, exif=b" " * 43668) - def test_optimize(self): + def test_optimize(self) -> None: im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), optimize=0) im3 = self.roundtrip(hopper(), optimize=1) @@ -252,14 +253,14 @@ class TestFileJpeg: assert im1.bytes >= im2.bytes assert im1.bytes >= im3.bytes - def test_optimize_large_buffer(self, tmp_path): + def test_optimize_large_buffer(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/148 f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", optimize=True) - def test_progressive(self): + def test_progressive(self) -> None: im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), progressive=False) im3 = self.roundtrip(hopper(), progressive=True) @@ -270,25 +271,25 @@ class TestFileJpeg: assert_image_equal(im1, im3) assert im1.bytes >= im3.bytes - def test_progressive_large_buffer(self, tmp_path): + def test_progressive_large_buffer(self, tmp_path: Path) -> None: f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", progressive=True) - def test_progressive_large_buffer_highest_quality(self, tmp_path): + def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None: f = str(tmp_path / "temp.jpg") im = self.gen_random_image((255, 255)) # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) - def test_progressive_cmyk_buffer(self): + def test_progressive_cmyk_buffer(self) -> None: # Issue 2272, quality 90 cmyk image is tripping the large buffer bug. f = BytesIO() im = self.gen_random_image((256, 256), "CMYK") im.save(f, format="JPEG", progressive=True, quality=94) - def test_large_exif(self, tmp_path): + def test_large_exif(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/148 f = str(tmp_path / "temp.jpg") im = hopper() @@ -297,12 +298,12 @@ class TestFileJpeg: with pytest.raises(ValueError): im.save(f, "JPEG", quality=90, exif=b"1" * 65534) - def test_exif_typeerror(self): + def test_exif_typeerror(self) -> None: with Image.open("Tests/images/exif_typeerror.jpg") as im: # Should not raise a TypeError im._getexif() - def test_exif_gps(self, tmp_path): + def test_exif_gps(self, tmp_path: Path) -> None: expected_exif_gps = { 0: b"\x00\x00\x00\x01", 2: 4294967295, @@ -327,7 +328,7 @@ class TestFileJpeg: exif = reloaded._getexif() assert exif[gps_index] == expected_exif_gps - def test_empty_exif_gps(self): + def test_empty_exif_gps(self) -> None: with Image.open("Tests/images/empty_gps_ifd.jpg") as im: exif = im.getexif() del exif[0x8769] @@ -345,7 +346,7 @@ class TestFileJpeg: # Assert that it was transposed assert 0x0112 not in exif - def test_exif_equality(self): + def test_exif_equality(self) -> None: # In 7.2.0, Exif rationals were changed to be read as # TiffImagePlugin.IFDRational. This class had a bug in __eq__, # breaking the self-equality of Exif data @@ -355,7 +356,7 @@ class TestFileJpeg: exifs.append(im._getexif()) assert exifs[0] == exifs[1] - def test_exif_rollback(self): + def test_exif_rollback(self) -> None: # rolling back exif support in 3.1 to pre-3.0 formatting. # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility # this test passes on 2.9 and 3.1, but not 3.0 @@ -390,12 +391,12 @@ class TestFileJpeg: for tag, value in expected_exif.items(): assert value == exif[tag] - def test_exif_gps_typeerror(self): + def test_exif_gps_typeerror(self) -> None: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: # Should not raise a TypeError im._getexif() - def test_progressive_compat(self): + def test_progressive_compat(self) -> None: im1 = self.roundtrip(hopper()) assert not im1.info.get("progressive") assert not im1.info.get("progression") @@ -416,7 +417,7 @@ class TestFileJpeg: assert im3.info.get("progressive") assert im3.info.get("progression") - def test_quality(self): + def test_quality(self) -> None: im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), quality=50) assert_image(im1, im2.mode, im2.size) @@ -426,12 +427,12 @@ class TestFileJpeg: assert_image(im1, im3.mode, im3.size) assert im2.bytes > im3.bytes - def test_smooth(self): + def test_smooth(self) -> None: im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), smooth=100) assert_image(im1, im2.mode, im2.size) - def test_subsampling(self): + def test_subsampling(self) -> None: def getsampling(im): layer = im.layer return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] @@ -463,23 +464,23 @@ class TestFileJpeg: with pytest.raises(TypeError): self.roundtrip(hopper(), subsampling="1:1:1") - def test_exif(self): + def test_exif(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: info = im._getexif() assert info[305] == "Adobe Photoshop CS Macintosh" - def test_get_child_images(self): + def test_get_child_images(self) -> None: with Image.open("Tests/images/flower.jpg") as im: ims = im.get_child_images() assert len(ims) == 1 assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1) - def test_mp(self): + def test_mp(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: assert im._getmp() is None - def test_quality_keep(self, tmp_path): + def test_quality_keep(self, tmp_path: Path) -> None: # RGB with Image.open("Tests/images/hopper.jpg") as im: f = str(tmp_path / "temp.jpg") @@ -493,13 +494,13 @@ class TestFileJpeg: f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") - def test_junk_jpeg_header(self): + def test_junk_jpeg_header(self) -> None: # https://github.com/python-pillow/Pillow/issues/630 filename = "Tests/images/junk_jpeg_header.jpg" with Image.open(filename): pass - def test_ff00_jpeg_header(self): + def test_ff00_jpeg_header(self) -> None: filename = "Tests/images/jpeg_ff00_header.jpg" with Image.open(filename): pass @@ -507,7 +508,7 @@ class TestFileJpeg: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_truncated_jpeg_should_read_all_the_data(self): + def test_truncated_jpeg_should_read_all_the_data(self) -> None: filename = "Tests/images/truncated_jpeg.jpg" ImageFile.LOAD_TRUNCATED_IMAGES = True with Image.open(filename) as im: @@ -515,7 +516,7 @@ class TestFileJpeg: ImageFile.LOAD_TRUNCATED_IMAGES = False assert im.getbbox() is not None - def test_truncated_jpeg_throws_oserror(self): + def test_truncated_jpeg_throws_oserror(self) -> None: filename = "Tests/images/truncated_jpeg.jpg" with Image.open(filename) as im: with pytest.raises(OSError): @@ -528,8 +529,8 @@ class TestFileJpeg: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_qtables(self, tmp_path): - def _n_qtables_helper(n, test_file): + def test_qtables(self, tmp_path: Path) -> None: + def _n_qtables_helper(n, test_file) -> None: with Image.open(test_file) as im: f = str(tmp_path / "temp.jpg") im.save(f, qtables=[[n] * 64] * n) @@ -637,24 +638,24 @@ class TestFileJpeg: with pytest.raises(ValueError): self.roundtrip(im, qtables=[[1, 2, 3, 4]]) - def test_load_16bit_qtables(self): + def test_load_16bit_qtables(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: assert len(im.quantization) == 2 assert len(im.quantization[0]) == 64 assert max(im.quantization[0]) > 255 - def test_save_multiple_16bit_qtables(self): + def test_save_multiple_16bit_qtables(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: im2 = self.roundtrip(im, qtables="keep") assert im.quantization == im2.quantization - def test_save_single_16bit_qtable(self): + def test_save_single_16bit_qtable(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) assert len(im2.quantization) == 1 assert im2.quantization[0] == im.quantization[0] - def test_save_low_quality_baseline_qtables(self): + def test_save_low_quality_baseline_qtables(self) -> None: with Image.open(TEST_FILE) as im: im2 = self.roundtrip(im, quality=10) assert len(im2.quantization) == 2 @@ -665,7 +666,7 @@ class TestFileJpeg: "blocks, rows, markers", ((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)), ) - def test_restart_markers(self, blocks, rows, markers): + def test_restart_markers(self, blocks, rows, markers) -> None: im = Image.new("RGB", (32, 32)) # 16 MCUs out = BytesIO() im.save( @@ -679,20 +680,20 @@ class TestFileJpeg: assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") - def test_load_djpeg(self): + def test_load_djpeg(self) -> None: with Image.open(TEST_FILE) as img: img.load_djpeg() assert_image_similar_tofile(img, TEST_FILE, 5) @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") - def test_save_cjpeg(self, tmp_path): + def test_save_cjpeg(self, tmp_path: Path) -> None: with Image.open(TEST_FILE) as img: tempfile = str(tmp_path / "temp.jpg") JpegImagePlugin._save_cjpeg(img, 0, tempfile) # Default save quality is 75%, so a tiny bit of difference is alright assert_image_similar_tofile(img, tempfile, 17) - def test_no_duplicate_0x1001_tag(self): + def test_no_duplicate_0x1001_tag(self) -> None: # Arrange tag_ids = {v: k for k, v in ExifTags.TAGS.items()} @@ -700,7 +701,7 @@ class TestFileJpeg: assert tag_ids["RelatedImageWidth"] == 0x1001 assert tag_ids["RelatedImageLength"] == 0x1002 - def test_MAXBLOCK_scaling(self, tmp_path): + def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None: im = self.gen_random_image((512, 512)) f = str(tmp_path / "temp.jpeg") im.save(f, quality=100, optimize=True) @@ -711,7 +712,7 @@ class TestFileJpeg: reloaded.save(f, quality="keep", progressive=True) reloaded.save(f, quality="keep", optimize=True) - def test_bad_mpo_header(self): + def test_bad_mpo_header(self) -> None: """Treat unknown MPO as JPEG""" # Arrange @@ -723,20 +724,20 @@ class TestFileJpeg: assert im.format == "JPEG" @pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) - def test_save_correct_modes(self, mode): + def test_save_correct_modes(self, mode) -> None: out = BytesIO() img = Image.new(mode, (20, 20)) img.save(out, "JPEG") @pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P")) - def test_save_wrong_modes(self, mode): + def test_save_wrong_modes(self, mode) -> None: # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() img = Image.new(mode, (20, 20)) with pytest.raises(OSError): img.save(out, "JPEG") - def test_save_tiff_with_dpi(self, tmp_path): + def test_save_tiff_with_dpi(self, tmp_path: Path) -> None: # Arrange outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: @@ -748,7 +749,7 @@ class TestFileJpeg: reloaded.load() assert im.info["dpi"] == reloaded.info["dpi"] - def test_save_dpi_rounding(self, tmp_path): + def test_save_dpi_rounding(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: im.save(outfile, dpi=(72.2, 72.2)) @@ -761,7 +762,7 @@ class TestFileJpeg: with Image.open(outfile) as reloaded: assert reloaded.info["dpi"] == (73, 73) - def test_dpi_tuple_from_exif(self): + def test_dpi_tuple_from_exif(self) -> None: # Arrange # This Photoshop CC 2017 image has DPI in EXIF not metadata # EXIF XResolution is (2000000, 10000) @@ -769,7 +770,7 @@ class TestFileJpeg: # Act / Assert assert im.info.get("dpi") == (200, 200) - def test_dpi_int_from_exif(self): + def test_dpi_int_from_exif(self) -> None: # Arrange # This image has DPI in EXIF not metadata # EXIF XResolution is 72 @@ -777,7 +778,7 @@ class TestFileJpeg: # Act / Assert assert im.info.get("dpi") == (72, 72) - def test_dpi_from_dpcm_exif(self): + def test_dpi_from_dpcm_exif(self) -> None: # Arrange # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm: # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg @@ -785,7 +786,7 @@ class TestFileJpeg: # Act / Assert assert im.info.get("dpi") == (508, 508) - def test_dpi_exif_zero_division(self): + def test_dpi_exif_zero_division(self) -> None: # Arrange # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0: # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg @@ -794,7 +795,7 @@ class TestFileJpeg: # This should return the default, and not raise a ZeroDivisionError assert im.info.get("dpi") == (72, 72) - def test_dpi_exif_string(self): + def test_dpi_exif_string(self) -> None: # Arrange # 0x011A tag in this exif contains string '300300\x02' with Image.open("Tests/images/broken_exif_dpi.jpg") as im: @@ -802,14 +803,14 @@ class TestFileJpeg: # This should return the default assert im.info.get("dpi") == (72, 72) - def test_dpi_exif_truncated(self): + def test_dpi_exif_truncated(self) -> None: # Arrange with Image.open("Tests/images/truncated_exif_dpi.jpg") as im: # Act / Assert # This should return the default assert im.info.get("dpi") == (72, 72) - def test_no_dpi_in_exif(self): + def test_no_dpi_in_exif(self) -> None: # Arrange # This is photoshop-200dpi.jpg with resolution removed from EXIF: # exiftool "-*resolution*"= photoshop-200dpi.jpg @@ -819,7 +820,7 @@ class TestFileJpeg: # https://exiv2.org/tags.html assert im.info.get("dpi") == (72, 72) - def test_invalid_exif(self): + def test_invalid_exif(self) -> None: # This is no-dpi-in-exif with the tiff header of the exif block # hexedited from MM * to FF FF FF FF with Image.open("Tests/images/invalid-exif.jpg") as im: @@ -830,7 +831,7 @@ class TestFileJpeg: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_exif_x_resolution(self, tmp_path): + def test_exif_x_resolution(self, tmp_path: Path) -> None: with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() assert exif[282] == 180 @@ -842,14 +843,14 @@ class TestFileJpeg: with Image.open(out) as reloaded: assert reloaded.getexif()[282] == 180 - def test_invalid_exif_x_resolution(self): + def test_invalid_exif_x_resolution(self) -> None: # When no x or y resolution is defined in EXIF with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im: # This should return the default, and not a ValueError or # OSError for an unidentified image. assert im.info.get("dpi") == (72, 72) - def test_ifd_offset_exif(self): + def test_ifd_offset_exif(self) -> None: # Arrange # This image has been manually hexedited to have an IFD offset of 10, # in contrast to normal 8 @@ -857,14 +858,14 @@ class TestFileJpeg: # Act / Assert assert im._getexif()[306] == "2017:03:13 23:03:09" - def test_multiple_exif(self): + def test_multiple_exif(self) -> None: with Image.open("Tests/images/multiple_exif.jpg") as im: assert im.info["exif"] == b"Exif\x00\x00firstsecond" @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_photoshop(self): + def test_photoshop(self) -> None: with Image.open("Tests/images/photoshop-200dpi.jpg") as im: assert im.info["photoshop"][0x03ED] == { "XResolution": 200.0, @@ -881,14 +882,14 @@ class TestFileJpeg: with Image.open("Tests/images/app13.jpg") as im: assert "photoshop" not in im.info - def test_photoshop_malformed_and_multiple(self): + def test_photoshop_malformed_and_multiple(self) -> None: with Image.open("Tests/images/app13-multiple.jpg") as im: assert "photoshop" in im.info assert 24 == len(im.info["photoshop"]) apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"] assert [65504, 24] == apps_13_lengths - def test_adobe_transform(self): + def test_adobe_transform(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: assert im.info["adobe_transform"] == 1 @@ -902,11 +903,11 @@ class TestFileJpeg: assert "adobe" in im.info assert "adobe_transform" not in im.info - def test_icc_after_SOF(self): + def test_icc_after_SOF(self) -> None: with Image.open("Tests/images/icc-after-SOF.jpg") as im: assert im.info["icc_profile"] == b"profile" - def test_jpeg_magic_number(self): + def test_jpeg_magic_number(self) -> None: size = 4097 buffer = BytesIO(b"\xFF" * size) # Many xFF bytes buffer.max_pos = 0 @@ -925,7 +926,7 @@ class TestFileJpeg: # Assert the entire file has not been read assert 0 < buffer.max_pos < size - def test_getxmp(self): + def test_getxmp(self) -> None: with Image.open("Tests/images/xmp_test.jpg") as im: if ElementTree is None: with pytest.warns( @@ -954,7 +955,7 @@ class TestFileJpeg: with Image.open("Tests/images/hopper.jpg") as im: assert im.getxmp() == {} - def test_getxmp_no_prefix(self): + def test_getxmp_no_prefix(self) -> None: with Image.open("Tests/images/xmp_no_prefix.jpg") as im: if ElementTree is None: with pytest.warns( @@ -965,7 +966,7 @@ class TestFileJpeg: else: assert im.getxmp() == {"xmpmeta": {"key": "value"}} - def test_getxmp_padded(self): + def test_getxmp_padded(self) -> None: with Image.open("Tests/images/xmp_padded.jpg") as im: if ElementTree is None: with pytest.warns( @@ -977,7 +978,7 @@ class TestFileJpeg: assert im.getxmp() == {"xmpmeta": None} @pytest.mark.timeout(timeout=1) - def test_eof(self): + def test_eof(self) -> None: # Even though this decoder never says that it is finished # the image should still end when there is no new data class InfiniteMockPyDecoder(ImageFile.PyDecoder): @@ -1000,7 +1001,7 @@ class TestFileJpeg: im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_separate_tables(self): + def test_separate_tables(self) -> None: im = hopper() data = [] # [interchange, tables-only, image-only] for streamtype in range(3): @@ -1022,14 +1023,14 @@ class TestFileJpeg: with Image.open(BytesIO(data[1] + data[2])) as combined_im: assert_image_equal(interchange_im, combined_im) - def test_repr_jpeg(self): + def test_repr_jpeg(self) -> None: im = hopper() with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg: assert repr_jpeg.format == "JPEG" assert_image_similar(im, repr_jpeg, 17) - def test_repr_jpeg_error_returns_none(self): + def test_repr_jpeg_error_returns_none(self) -> None: im = hopper("F") assert im._repr_jpeg_() is None @@ -1038,7 +1039,7 @@ class TestFileJpeg: @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") class TestFileCloseW32: - def test_fd_leak(self, tmp_path): + def test_fd_leak(self, tmp_path: Path) -> None: tmpfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 94b02c9ff..e3f1fa8fd 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -3,6 +3,7 @@ from __future__ import annotations import os import re from io import BytesIO +from pathlib import Path import pytest @@ -46,7 +47,7 @@ def roundtrip(im, **options): return im -def test_sanity(): +def test_sanity() -> None: # Internal version number assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000")) @@ -59,20 +60,20 @@ def test_sanity(): assert im.get_format_mimetype() == "image/jp2" -def test_jpf(): +def test_jpf() -> None: with Image.open("Tests/images/balloon.jpf") as im: assert im.format == "JPEG2000" assert im.get_format_mimetype() == "image/jpx" -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) -def test_bytesio(): +def test_bytesio() -> None: with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) assert_image_similar_tofile(test_card, data, 1.0e-3) @@ -82,7 +83,7 @@ def test_bytesio(): # PIL (they were made using Adobe Photoshop) -def test_lossless(tmp_path): +def test_lossless(tmp_path: Path) -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: im.load() outfile = str(tmp_path / "temp_test-card.png") @@ -90,54 +91,54 @@ def test_lossless(tmp_path): assert_image_similar(im, test_card, 1.0e-3) -def test_lossy_tiled(): +def test_lossy_tiled() -> None: assert_image_similar_tofile( test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0 ) -def test_lossless_rt(): +def test_lossless_rt() -> None: im = roundtrip(test_card) assert_image_equal(im, test_card) -def test_lossy_rt(): +def test_lossy_rt() -> None: im = roundtrip(test_card, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) -def test_tiled_rt(): +def test_tiled_rt() -> None: im = roundtrip(test_card, tile_size=(128, 128)) assert_image_equal(im, test_card) -def test_tiled_offset_rt(): +def test_tiled_offset_rt() -> None: im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) assert_image_equal(im, test_card) -def test_tiled_offset_too_small(): +def test_tiled_offset_too_small() -> None: with pytest.raises(ValueError): roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) -def test_irreversible_rt(): +def test_irreversible_rt() -> None: im = roundtrip(test_card, irreversible=True, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) -def test_prog_qual_rt(): +def test_prog_qual_rt() -> None: im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") assert_image_similar(im, test_card, 2.0) -def test_prog_res_rt(): +def test_prog_res_rt() -> None: im = roundtrip(test_card, num_resolutions=8, progression="RLCP") assert_image_equal(im, test_card) @pytest.mark.parametrize("num_resolutions", range(2, 6)) -def test_default_num_resolutions(num_resolutions): +def test_default_num_resolutions(num_resolutions) -> None: d = 1 << (num_resolutions - 1) im = test_card.resize((d - 1, d - 1)) with pytest.raises(OSError): @@ -146,7 +147,7 @@ def test_default_num_resolutions(num_resolutions): assert_image_equal(im, reloaded) -def test_reduce(): +def test_reduce() -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: assert callable(im.reduce) @@ -160,7 +161,7 @@ def test_reduce(): assert im.size == (40, 30) -def test_load_dpi(): +def test_load_dpi() -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: assert im.info["dpi"] == (71.9836, 71.9836) @@ -168,7 +169,7 @@ def test_load_dpi(): assert "dpi" not in im.info -def test_restricted_icc_profile(): +def test_restricted_icc_profile() -> None: ImageFile.LOAD_TRUNCATED_IMAGES = True try: # JPEG2000 image with a restricted ICC profile and a known colorspace @@ -178,7 +179,7 @@ def test_restricted_icc_profile(): ImageFile.LOAD_TRUNCATED_IMAGES = False -def test_header_errors(): +def test_header_errors() -> None: for path in ( "Tests/images/invalid_header_length.jp2", "Tests/images/not_enough_data.jp2", @@ -192,7 +193,7 @@ def test_header_errors(): pass -def test_layers_type(tmp_path): +def test_layers_type(tmp_path: Path) -> None: outfile = str(tmp_path / "temp_layers.jp2") for quality_layers in [[100, 50, 10], (100, 50, 10), None]: test_card.save(outfile, quality_layers=quality_layers) @@ -202,7 +203,7 @@ def test_layers_type(tmp_path): test_card.save(outfile, quality_layers=quality_layers) -def test_layers(): +def test_layers() -> None: out = BytesIO() test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") out.seek(0) @@ -232,7 +233,7 @@ def test_layers(): ("foo.jp2", {"no_jp2": False}, 4, b"jP"), ), ) -def test_no_jp2(name, args, offset, data): +def test_no_jp2(name, args, offset, data) -> None: out = BytesIO() if name: out.name = name @@ -241,7 +242,7 @@ def test_no_jp2(name, args, offset, data): assert out.read(2) == data -def test_mct(): +def test_mct() -> None: # Three component for val in (0, 1): out = BytesIO() @@ -262,7 +263,7 @@ def test_mct(): assert_image_similar(im, jp2, 1.0e-3) -def test_sgnd(tmp_path): +def test_sgnd(tmp_path: Path) -> None: outfile = str(tmp_path / "temp.jp2") im = Image.new("L", (1, 1)) @@ -277,7 +278,7 @@ def test_sgnd(tmp_path): @pytest.mark.parametrize("ext", (".j2k", ".jp2")) -def test_rgba(ext): +def test_rgba(ext) -> None: # Arrange with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im: # Act @@ -288,47 +289,47 @@ def test_rgba(ext): @pytest.mark.parametrize("ext", (".j2k", ".jp2")) -def test_16bit_monochrome_has_correct_mode(ext): +def test_16bit_monochrome_has_correct_mode(ext) -> None: with Image.open("Tests/images/16bit.cropped" + ext) as im: im.load() assert im.mode == "I;16" -def test_16bit_monochrome_jp2_like_tiff(): +def test_16bit_monochrome_jp2_like_tiff() -> None: with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3) -def test_16bit_monochrome_j2k_like_tiff(): +def test_16bit_monochrome_j2k_like_tiff() -> None: with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3) -def test_16bit_j2k_roundtrips(): +def test_16bit_j2k_roundtrips() -> None: with Image.open("Tests/images/16bit.cropped.j2k") as j2k: im = roundtrip(j2k) assert_image_equal(im, j2k) -def test_16bit_jp2_roundtrips(): +def test_16bit_jp2_roundtrips() -> None: with Image.open("Tests/images/16bit.cropped.jp2") as jp2: im = roundtrip(jp2) assert_image_equal(im, jp2) -def test_issue_6194(): +def test_issue_6194() -> None: with Image.open("Tests/images/issue_6194.j2k") as im: assert im.getpixel((5, 5)) == 31 -def test_unbound_local(): +def test_unbound_local() -> None: # prepatch, a malformed jp2 file could cause an UnboundLocalError exception. with pytest.raises(OSError): with Image.open("Tests/images/unbound_variable.jp2"): pass -def test_parser_feed(): +def test_parser_feed() -> None: # Arrange with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = f.read() @@ -345,7 +346,7 @@ def test_parser_feed(): not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) @pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2")) -def test_subsampling_decode(name): +def test_subsampling_decode(name) -> None: test = f"{EXTRA_DIR}/{name}.jp2" reference = f"{EXTRA_DIR}/{name}.ppm" @@ -361,7 +362,7 @@ def test_subsampling_decode(name): assert_image_similar(im, expected, epsilon) -def test_comment(): +def test_comment() -> None: with Image.open("Tests/images/comment.jp2") as im: assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" @@ -372,7 +373,7 @@ def test_comment(): pass -def test_save_comment(): +def test_save_comment() -> None: for comment in ("Created by Pillow", b"Created by Pillow"): out = BytesIO() test_card.save(out, "JPEG2000", comment=comment) @@ -399,7 +400,7 @@ def test_save_comment(): "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k", ], ) -def test_crashes(test_file): +def test_crashes(test_file) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: # Valgrind should not complain here @@ -410,7 +411,7 @@ def test_crashes(test_file): @skip_unless_feature_version("jpg_2000", "2.4.0") -def test_plt_marker(): +def test_plt_marker() -> None: # Search the start of the codesteam for PLT out = BytesIO() test_card.save(out, "JPEG2000", no_jp2=True, plt=True) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 494253c87..1386034e5 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -7,6 +7,7 @@ import os import re import sys from collections import namedtuple +from pathlib import Path import pytest @@ -26,7 +27,7 @@ from .helper import ( @skip_unless_feature("libtiff") class LibTiffTestCase: - def _assert_noerr(self, tmp_path, im): + def _assert_noerr(self, tmp_path: Path, im) -> None: """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit assert im.mode == "1" @@ -50,10 +51,10 @@ class LibTiffTestCase: class TestFileLibTiff(LibTiffTestCase): - def test_version(self): + def test_version(self) -> None: assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) - def test_g4_tiff(self, tmp_path): + def test_g4_tiff(self, tmp_path: Path) -> None: """Test the ordinary file path load path""" test_file = "Tests/images/hopper_g4_500.tif" @@ -61,12 +62,12 @@ class TestFileLibTiff(LibTiffTestCase): assert im.size == (500, 500) self._assert_noerr(tmp_path, im) - def test_g4_large(self, tmp_path): + def test_g4_large(self, tmp_path: Path) -> None: test_file = "Tests/images/pport_g4.tif" with Image.open(test_file) as im: self._assert_noerr(tmp_path, im) - def test_g4_tiff_file(self, tmp_path): + def test_g4_tiff_file(self, tmp_path: Path) -> None: """Testing the string load path""" test_file = "Tests/images/hopper_g4_500.tif" @@ -75,7 +76,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im.size == (500, 500) self._assert_noerr(tmp_path, im) - def test_g4_tiff_bytesio(self, tmp_path): + def test_g4_tiff_bytesio(self, tmp_path: Path) -> None: """Testing the stringio loading code path""" test_file = "Tests/images/hopper_g4_500.tif" s = io.BytesIO() @@ -86,7 +87,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im.size == (500, 500) self._assert_noerr(tmp_path, im) - def test_g4_non_disk_file_object(self, tmp_path): + def test_g4_non_disk_file_object(self, tmp_path: Path) -> None: """Testing loading from non-disk non-BytesIO file object""" test_file = "Tests/images/hopper_g4_500.tif" s = io.BytesIO() @@ -98,18 +99,18 @@ class TestFileLibTiff(LibTiffTestCase): assert im.size == (500, 500) self._assert_noerr(tmp_path, im) - def test_g4_eq_png(self): + def test_g4_eq_png(self) -> None: """Checking that we're actually getting the data that we expect""" with Image.open("Tests/images/hopper_bw_500.png") as png: assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif") # see https://github.com/python-pillow/Pillow/issues/279 - def test_g4_fillorder_eq_png(self): + def test_g4_fillorder_eq_png(self) -> None: """Checking that we're actually getting the data that we expect""" with Image.open("Tests/images/g4-fillorder-test.tif") as g4: assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png") - def test_g4_write(self, tmp_path): + def test_g4_write(self, tmp_path: Path) -> None: """Checking to see that the saved image is the same as what we wrote""" test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as orig: @@ -128,7 +129,7 @@ class TestFileLibTiff(LibTiffTestCase): assert orig.tobytes() != reread.tobytes() - def test_adobe_deflate_tiff(self): + def test_adobe_deflate_tiff(self) -> None: test_file = "Tests/images/tiff_adobe_deflate.tif" with Image.open(test_file) as im: assert im.mode == "RGB" @@ -139,7 +140,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") @pytest.mark.parametrize("legacy_api", (False, True)) - def test_write_metadata(self, legacy_api, tmp_path): + def test_write_metadata(self, legacy_api, tmp_path: Path) -> None: """Test metadata writing through libtiff""" f = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper_g4.tif") as img: @@ -184,7 +185,7 @@ class TestFileLibTiff(LibTiffTestCase): assert field in reloaded, f"{field} not in metadata" @pytest.mark.valgrind_known_error(reason="Known invalid metadata") - def test_additional_metadata(self, tmp_path): + def test_additional_metadata(self, tmp_path: Path) -> None: # these should not crash. Seriously dummy data, most of it doesn't make # any sense, so we're running up against limits where we're asking # libtiff to do stupid things. @@ -241,7 +242,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False - def test_custom_metadata(self, tmp_path): + def test_custom_metadata(self, tmp_path: Path) -> None: tc = namedtuple("test_case", "value,type,supported_by_default") custom = { 37000 + k: v @@ -283,7 +284,7 @@ class TestFileLibTiff(LibTiffTestCase): for libtiff in libtiffs: TiffImagePlugin.WRITE_LIBTIFF = libtiff - def check_tags(tiffinfo): + def check_tags(tiffinfo) -> None: im = hopper() out = str(tmp_path / "temp.tif") @@ -322,7 +323,7 @@ class TestFileLibTiff(LibTiffTestCase): ) TiffImagePlugin.WRITE_LIBTIFF = False - def test_subifd(self, tmp_path): + def test_subifd(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/g4_orientation_6.tif") as im: im.tag_v2[SUBIFD] = 10000 @@ -330,7 +331,7 @@ class TestFileLibTiff(LibTiffTestCase): # Should not segfault im.save(outfile) - def test_xmlpacket_tag(self, tmp_path): + def test_xmlpacket_tag(self, tmp_path: Path) -> None: TiffImagePlugin.WRITE_LIBTIFF = True out = str(tmp_path / "temp.tif") @@ -341,7 +342,7 @@ class TestFileLibTiff(LibTiffTestCase): if 700 in reloaded.tag_v2: assert reloaded.tag_v2[700] == b"xmlpacket tag" - def test_int_dpi(self, tmp_path): + def test_int_dpi(self, tmp_path: Path) -> None: # issue #1765 im = hopper("RGB") out = str(tmp_path / "temp.tif") @@ -351,7 +352,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert reloaded.info["dpi"] == (72.0, 72.0) - def test_g3_compression(self, tmp_path): + def test_g3_compression(self, tmp_path: Path) -> None: with Image.open("Tests/images/hopper_g4_500.tif") as i: out = str(tmp_path / "temp.tif") i.save(out, compression="group3") @@ -360,7 +361,7 @@ class TestFileLibTiff(LibTiffTestCase): assert reread.info["compression"] == "group3" assert_image_equal(reread, i) - def test_little_endian(self, tmp_path): + def test_little_endian(self, tmp_path: Path) -> None: with Image.open("Tests/images/16bit.deflate.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16" @@ -379,7 +380,7 @@ class TestFileLibTiff(LibTiffTestCase): # UNDONE - libtiff defaults to writing in native endian, so # on big endian, we'll get back mode = 'I;16B' here. - def test_big_endian(self, tmp_path): + def test_big_endian(self, tmp_path: Path) -> None: with Image.open("Tests/images/16bit.MM.deflate.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16B" @@ -396,7 +397,7 @@ class TestFileLibTiff(LibTiffTestCase): assert reread.info["compression"] == im.info["compression"] assert reread.getpixel((0, 0)) == 480 - def test_g4_string_info(self, tmp_path): + def test_g4_string_info(self, tmp_path: Path) -> None: """Tests String data in info directory""" test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as orig: @@ -409,7 +410,7 @@ class TestFileLibTiff(LibTiffTestCase): assert "temp.tif" == reread.tag_v2[269] assert "temp.tif" == reread.tag[269][0] - def test_12bit_rawmode(self): + def test_12bit_rawmode(self) -> None: """Are we generating the same interpretation of the image as Imagemagick is?""" TiffImagePlugin.READ_LIBTIFF = True @@ -424,7 +425,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") - def test_blur(self, tmp_path): + def test_blur(self, tmp_path: Path) -> None: # test case from irc, how to do blur on b/w image # and save to compressed tif. out = str(tmp_path / "temp.tif") @@ -436,7 +437,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, out) - def test_compressions(self, tmp_path): + def test_compressions(self, tmp_path: Path) -> None: # Test various tiff compressions and assert similar image content but reduced # file sizes. im = hopper("RGB") @@ -462,7 +463,7 @@ class TestFileLibTiff(LibTiffTestCase): assert size_compressed > size_jpeg assert size_jpeg > size_jpeg_30 - def test_tiff_jpeg_compression(self, tmp_path): + def test_tiff_jpeg_compression(self, tmp_path: Path) -> None: im = hopper("RGB") out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_jpeg") @@ -470,7 +471,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert reloaded.info["compression"] == "jpeg" - def test_tiff_deflate_compression(self, tmp_path): + def test_tiff_deflate_compression(self, tmp_path: Path) -> None: im = hopper("RGB") out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_deflate") @@ -478,7 +479,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert reloaded.info["compression"] == "tiff_adobe_deflate" - def test_quality(self, tmp_path): + def test_quality(self, tmp_path: Path) -> None: im = hopper("RGB") out = str(tmp_path / "temp.tif") @@ -493,7 +494,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, compression="jpeg", quality=0) im.save(out, compression="jpeg", quality=100) - def test_cmyk_save(self, tmp_path): + def test_cmyk_save(self, tmp_path: Path) -> None: im = hopper("CMYK") out = str(tmp_path / "temp.tif") @@ -501,7 +502,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, out) @pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000"))) - def test_palette_save(self, im, tmp_path): + def test_palette_save(self, im, tmp_path: Path) -> None: out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True @@ -513,14 +514,14 @@ class TestFileLibTiff(LibTiffTestCase): assert len(reloaded.tag_v2[320]) == 768 @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) - def test_bw_compression_w_rgb(self, compression, tmp_path): + def test_bw_compression_w_rgb(self, compression, tmp_path: Path) -> None: im = hopper("RGB") out = str(tmp_path / "temp.tif") with pytest.raises(OSError): im.save(out, compression=compression) - def test_fp_leak(self): + def test_fp_leak(self) -> None: im = Image.open("Tests/images/hopper_g4_500.tif") fn = im.fp.fileno() @@ -534,7 +535,7 @@ class TestFileLibTiff(LibTiffTestCase): with pytest.raises(OSError): os.close(fn) - def test_multipage(self): + def test_multipage(self) -> None: # issue #862 TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/multipage.tiff") as im: @@ -557,7 +558,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False - def test_multipage_nframes(self): + def test_multipage_nframes(self) -> None: # issue #862 TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/multipage.tiff") as im: @@ -570,7 +571,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False - def test_multipage_seek_backwards(self): + def test_multipage_seek_backwards(self) -> None: TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/multipage.tiff") as im: im.seek(1) @@ -581,14 +582,14 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False - def test__next(self): + def test__next(self) -> None: TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/hopper.tif") as im: assert not im.tag.next im.load() assert not im.tag.next - def test_4bit(self): + def test_4bit(self) -> None: # Arrange test_file = "Tests/images/hopper_gray_4bpp.tif" original = hopper("L") @@ -603,7 +604,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im.mode == "L" assert_image_similar(im, original, 7.3) - def test_gray_semibyte_per_pixel(self): + def test_gray_semibyte_per_pixel(self) -> None: test_files = ( ( 24.8, # epsilon @@ -636,7 +637,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im2.mode == "L" assert_image_equal(im, im2) - def test_save_bytesio(self): + def test_save_bytesio(self) -> None: # PR 1011 # Test TIFF saving to io.BytesIO() object. @@ -646,7 +647,7 @@ class TestFileLibTiff(LibTiffTestCase): # Generate test image pilim = hopper() - def save_bytesio(compression=None): + def save_bytesio(compression=None) -> None: buffer_io = io.BytesIO() pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) @@ -661,7 +662,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False - def test_save_ycbcr(self, tmp_path): + def test_save_ycbcr(self, tmp_path: Path) -> None: im = hopper("YCbCr") outfile = str(tmp_path / "temp.tif") im.save(outfile, compression="jpeg") @@ -670,7 +671,7 @@ class TestFileLibTiff(LibTiffTestCase): assert reloaded.tag_v2[530] == (1, 1) assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) - def test_exif_ifd(self, tmp_path): + def test_exif_ifd(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/tiff_adobe_deflate.tif") as im: assert im.tag_v2[34665] == 125456 @@ -680,7 +681,7 @@ class TestFileLibTiff(LibTiffTestCase): if Image.core.libtiff_support_custom_tags: assert reloaded.tag_v2[34665] == 125456 - def test_crashing_metadata(self, tmp_path): + def test_crashing_metadata(self, tmp_path: Path) -> None: # issue 1597 with Image.open("Tests/images/rdf.tif") as im: out = str(tmp_path / "temp.tif") @@ -690,7 +691,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, format="TIFF") TiffImagePlugin.WRITE_LIBTIFF = False - def test_page_number_x_0(self, tmp_path): + def test_page_number_x_0(self, tmp_path: Path) -> None: # Issue 973 # Test TIFF with tag 297 (Page Number) having value of 0 0. # The first number is the current page number. @@ -704,7 +705,7 @@ class TestFileLibTiff(LibTiffTestCase): # Should not divide by zero im.save(outfile) - def test_fd_duplication(self, tmp_path): + def test_fd_duplication(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/1651 tmpfile = str(tmp_path / "temp.tif") @@ -718,7 +719,7 @@ class TestFileLibTiff(LibTiffTestCase): # Should not raise PermissionError. os.remove(tmpfile) - def test_read_icc(self): + def test_read_icc(self) -> None: with Image.open("Tests/images/hopper.iccprofile.tif") as img: icc = img.info.get("icc_profile") assert icc is not None @@ -729,8 +730,8 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False assert icc == icc_libtiff - def test_write_icc(self, tmp_path): - def check_write(libtiff): + def test_write_icc(self, tmp_path: Path) -> None: + def check_write(libtiff) -> None: TiffImagePlugin.WRITE_LIBTIFF = libtiff with Image.open("Tests/images/hopper.iccprofile.tif") as img: @@ -749,7 +750,7 @@ class TestFileLibTiff(LibTiffTestCase): for libtiff in libtiffs: check_write(libtiff) - def test_multipage_compression(self): + def test_multipage_compression(self) -> None: with Image.open("Tests/images/compression.tif") as im: im.seek(0) assert im._compression == "tiff_ccitt" @@ -765,7 +766,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im.size == (10, 10) im.load() - def test_save_tiff_with_jpegtables(self, tmp_path): + def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None: # Arrange outfile = str(tmp_path / "temp.tif") @@ -777,7 +778,7 @@ class TestFileLibTiff(LibTiffTestCase): # Should not raise UnicodeDecodeError or anything else im.save(outfile) - def test_16bit_RGB_tiff(self): + def test_16bit_RGB_tiff(self) -> None: with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im: assert im.mode == "RGB" assert im.size == (100, 40) @@ -793,7 +794,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - def test_16bit_RGBa_tiff(self): + def test_16bit_RGBa_tiff(self) -> None: with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im: assert im.mode == "RGBA" assert im.size == (100, 40) @@ -805,7 +806,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") @skip_unless_feature("jpg") - def test_gimp_tiff(self): + def test_gimp_tiff(self) -> None: # Read TIFF JPEG images from GIMP [@PIL168] filename = "Tests/images/pil168.tif" with Image.open(filename) as im: @@ -818,14 +819,14 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/pil168.png") - def test_sampleformat(self): + def test_sampleformat(self) -> None: # https://github.com/python-pillow/Pillow/issues/1466 with Image.open("Tests/images/copyleft.tiff") as im: assert im.mode == "RGB" assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") - def test_sampleformat_write(self, tmp_path): + def test_sampleformat_write(self, tmp_path: Path) -> None: im = Image.new("F", (1, 1)) out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True @@ -874,7 +875,7 @@ class TestFileLibTiff(LibTiffTestCase): sys.stderr.write(captured.err) raise - def test_lzw(self): + def test_lzw(self) -> None: with Image.open("Tests/images/hopper_lzw.tif") as im: assert im.mode == "RGB" assert im.size == (128, 128) @@ -882,12 +883,12 @@ class TestFileLibTiff(LibTiffTestCase): im2 = hopper() assert_image_similar(im, im2, 5) - def test_strip_cmyk_jpeg(self): + def test_strip_cmyk_jpeg(self) -> None: infile = "Tests/images/tiff_strip_cmyk_jpeg.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) - def test_strip_cmyk_16l_jpeg(self): + def test_strip_cmyk_16l_jpeg(self) -> None: infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @@ -895,7 +896,7 @@ class TestFileLibTiff(LibTiffTestCase): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_strip_ycbcr_jpeg_2x2_sampling(self): + def test_strip_ycbcr_jpeg_2x2_sampling(self) -> None: infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2) @@ -903,12 +904,12 @@ class TestFileLibTiff(LibTiffTestCase): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_strip_ycbcr_jpeg_1x1_sampling(self): + def test_strip_ycbcr_jpeg_1x1_sampling(self) -> None: infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) - def test_tiled_cmyk_jpeg(self): + def test_tiled_cmyk_jpeg(self) -> None: infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @@ -916,7 +917,7 @@ class TestFileLibTiff(LibTiffTestCase): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_tiled_ycbcr_jpeg_1x1_sampling(self): + def test_tiled_ycbcr_jpeg_1x1_sampling(self) -> None: infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) @@ -924,45 +925,45 @@ class TestFileLibTiff(LibTiffTestCase): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_tiled_ycbcr_jpeg_2x2_sampling(self): + def test_tiled_ycbcr_jpeg_2x2_sampling(self) -> None: infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5) - def test_strip_planar_rgb(self): + def test_strip_planar_rgb(self) -> None: # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff infile = "Tests/images/tiff_strip_planar_lzw.tiff" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_tiled_planar_rgb(self): + def test_tiled_planar_rgb(self) -> None: # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff infile = "Tests/images/tiff_tiled_planar_lzw.tiff" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_tiled_planar_16bit_RGB(self): + def test_tiled_planar_16bit_RGB(self) -> None: # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - def test_strip_planar_16bit_RGB(self): + def test_strip_planar_16bit_RGB(self) -> None: # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - def test_tiled_planar_16bit_RGBa(self): + def test_tiled_planar_16bit_RGBa(self) -> None: # gdal_translate -co TILED=yes \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ # tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - def test_strip_planar_16bit_RGBa(self): + def test_strip_planar_16bit_RGBa(self) -> None: # gdal_translate -co TILED=no \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff @@ -970,7 +971,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") @pytest.mark.parametrize("compression", (None, "jpeg")) - def test_block_tile_tags(self, compression, tmp_path): + def test_block_tile_tags(self, compression, tmp_path: Path) -> None: im = hopper() out = str(tmp_path / "temp.tif") @@ -986,11 +987,11 @@ class TestFileLibTiff(LibTiffTestCase): for tag in tags: assert tag not in reloaded.getexif() - def test_old_style_jpeg(self): + def test_old_style_jpeg(self) -> None: with Image.open("Tests/images/old-style-jpeg-compression.tif") as im: assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") - def test_open_missing_samplesperpixel(self): + def test_open_missing_samplesperpixel(self) -> None: with Image.open( "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif" ) as im: @@ -1019,21 +1020,21 @@ class TestFileLibTiff(LibTiffTestCase): ), ], ) - def test_wrong_bits_per_sample(self, file_name, mode, size, tile): + def test_wrong_bits_per_sample(self, file_name, mode, size, tile) -> None: with Image.open("Tests/images/" + file_name) as im: assert im.mode == mode assert im.size == size assert im.tile == tile im.load() - def test_no_rows_per_strip(self): + def test_no_rows_per_strip(self) -> None: # This image does not have a RowsPerStrip TIFF tag infile = "Tests/images/no_rows_per_strip.tif" with Image.open(infile) as im: im.load() assert im.size == (950, 975) - def test_orientation(self): + def test_orientation(self) -> None: with Image.open("Tests/images/g4_orientation_1.tif") as base_im: for i in range(2, 9): with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: @@ -1044,7 +1045,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar(base_im, im, 0.7) - def test_exif_transpose(self): + def test_exif_transpose(self) -> None: with Image.open("Tests/images/g4_orientation_1.tif") as base_im: for i in range(2, 9): with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: @@ -1053,7 +1054,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar(base_im, im, 0.7) @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") - def test_sampleformat_not_corrupted(self): + def test_sampleformat_not_corrupted(self) -> None: # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted # when saving to a new file. # Pillow 6.0 fails with "OSError: cannot identify image file". @@ -1074,7 +1075,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im: im.load() - def test_realloc_overflow(self): + def test_realloc_overflow(self) -> None: TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: with pytest.raises(OSError) as e: @@ -1085,7 +1086,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) - def test_save_multistrip(self, compression, tmp_path): + def test_save_multistrip(self, compression, tmp_path: Path) -> None: im = hopper("RGB").resize((256, 256)) out = str(tmp_path / "temp.tif") im.save(out, compression=compression) @@ -1095,7 +1096,7 @@ class TestFileLibTiff(LibTiffTestCase): assert len(im.tag_v2[STRIPOFFSETS]) > 1 @pytest.mark.parametrize("argument", (True, False)) - def test_save_single_strip(self, argument, tmp_path): + def test_save_single_strip(self, argument, tmp_path: Path) -> None: im = hopper("RGB").resize((256, 256)) out = str(tmp_path / "temp.tif") @@ -1113,13 +1114,13 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.STRIP_SIZE = 65536 @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) - def test_save_zero(self, compression, tmp_path): + def test_save_zero(self, compression, tmp_path: Path) -> None: im = Image.new("RGB", (0, 0)) out = str(tmp_path / "temp.tif") with pytest.raises(SystemError): im.save(out, compression=compression) - def test_save_many_compressed(self, tmp_path): + def test_save_many_compressed(self, tmp_path: Path) -> None: im = hopper() out = str(tmp_path / "temp.tif") for _ in range(10000): @@ -1133,7 +1134,7 @@ class TestFileLibTiff(LibTiffTestCase): ("Tests/images/child_ifd_jpeg.tiff", (20,)), ), ) - def test_get_child_images(self, path, sizes): + def test_get_child_images(self, path, sizes) -> None: with Image.open(path) as im: ims = im.get_child_images() diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 171e4a3f8..ac5270eac 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -1,6 +1,7 @@ from __future__ import annotations from io import BytesIO +from pathlib import Path from PIL import Image @@ -17,7 +18,7 @@ class TestFileLibTiffSmall(LibTiffTestCase): file just before reading in libtiff. These tests remain to ensure that it stays fixed.""" - def test_g4_hopper_file(self, tmp_path): + def test_g4_hopper_file(self, tmp_path: Path) -> None: """Testing the open file load path""" test_file = "Tests/images/hopper_g4.tif" @@ -26,7 +27,7 @@ class TestFileLibTiffSmall(LibTiffTestCase): assert im.size == (128, 128) self._assert_noerr(tmp_path, im) - def test_g4_hopper_bytesio(self, tmp_path): + def test_g4_hopper_bytesio(self, tmp_path: Path) -> None: """Testing the bytesio loading code path""" test_file = "Tests/images/hopper_g4.tif" s = BytesIO() @@ -37,7 +38,7 @@ class TestFileLibTiffSmall(LibTiffTestCase): assert im.size == (128, 128) self._assert_noerr(tmp_path, im) - def test_g4_hopper(self, tmp_path): + def test_g4_hopper(self, tmp_path: Path) -> None: """The 128x128 lena image failed for some reason.""" test_file = "Tests/images/hopper_g4.tif" diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 8c43f7d7a..9a6f13ea3 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -13,7 +13,7 @@ pytestmark = skip_unless_feature("libtiff") TEST_FILE = "Tests/images/hopper.mic" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: im.load() assert im.mode == "RGBA" @@ -28,22 +28,22 @@ def test_sanity(): assert_image_similar(im, im2, 10) -def test_n_frames(): +def test_n_frames() -> None: with Image.open(TEST_FILE) as im: assert im.n_frames == 1 -def test_is_animated(): +def test_is_animated() -> None: with Image.open(TEST_FILE) as im: assert not im.is_animated -def test_tell(): +def test_tell() -> None: with Image.open(TEST_FILE) as im: assert im.tell() == 0 -def test_seek(): +def test_seek() -> None: with Image.open(TEST_FILE) as im: im.seek(0) assert im.tell() == 0 @@ -53,7 +53,7 @@ def test_seek(): assert im.tell() == 0 -def test_close(): +def test_close() -> None: with Image.open(TEST_FILE) as im: pass assert im.ole.fp.closed @@ -63,7 +63,7 @@ def test_close(): assert im.ole.fp.closed -def test_invalid_file(): +def test_invalid_file() -> None: # Test an invalid OLE file invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index c7121ea28..55b04a1e0 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -30,7 +30,7 @@ def roundtrip(im, **options): @pytest.mark.parametrize("test_file", test_files) -def test_sanity(test_file): +def test_sanity(test_file) -> None: with Image.open(test_file) as im: im.load() assert im.mode == "RGB" @@ -39,8 +39,8 @@ def test_sanity(test_file): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(test_files[0]) im.load() @@ -48,14 +48,14 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(test_files[0]) im.load() im.close() -def test_seek_after_close(): +def test_seek_after_close() -> None: im = Image.open(test_files[0]) im.close() @@ -63,14 +63,14 @@ def test_seek_after_close(): im.seek(1) -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(test_files[0]) as im: im.load() @pytest.mark.parametrize("test_file", test_files) -def test_app(test_file): +def test_app(test_file) -> None: # Test APP/COM reader (@PIL135) with Image.open(test_file) as im: assert im.applist[0][0] == "APP1" @@ -82,7 +82,7 @@ def test_app(test_file): @pytest.mark.parametrize("test_file", test_files) -def test_exif(test_file): +def test_exif(test_file) -> None: with Image.open(test_file) as im_original: im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif()) @@ -93,7 +93,7 @@ def test_exif(test_file): assert info[34665] == 188 -def test_frame_size(): +def test_frame_size() -> None: # This image has been hexedited to contain a different size # in the EXIF data of the second frame with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: @@ -106,7 +106,7 @@ def test_frame_size(): assert im.size == (640, 480) -def test_ignore_frame_size(): +def test_ignore_frame_size() -> None: # Ignore the different size of the second frame # since this is not a "Large Thumbnail" image with Image.open("Tests/images/ignore_frame_size.mpo") as im: @@ -120,7 +120,7 @@ def test_ignore_frame_size(): assert im.size == (64, 64) -def test_parallax(): +def test_parallax() -> None: # Nintendo with Image.open("Tests/images/sugarshack.mpo") as im: exif = im.getexif() @@ -133,7 +133,7 @@ def test_parallax(): assert exif.get_ifd(0x927C)[0xB211] == -3.125 -def test_reload_exif_after_seek(): +def test_reload_exif_after_seek() -> None: with Image.open("Tests/images/sugarshack.mpo") as im: exif = im.getexif() del exif[296] @@ -143,14 +143,14 @@ def test_reload_exif_after_seek(): @pytest.mark.parametrize("test_file", test_files) -def test_mp(test_file): +def test_mp(test_file) -> None: with Image.open(test_file) as im: mpinfo = im._getmp() assert mpinfo[45056] == b"0100" assert mpinfo[45057] == 2 -def test_mp_offset(): +def test_mp_offset() -> None: # This image has been manually hexedited to have an IFD offset of 10 # in APP2 data, in contrast to normal 8 with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: @@ -159,7 +159,7 @@ def test_mp_offset(): assert mpinfo[45057] == 2 -def test_mp_no_data(): +def test_mp_no_data() -> None: # This image has been manually hexedited to have the second frame # beyond the end of the file with Image.open("Tests/images/sugarshack_no_data.mpo") as im: @@ -168,7 +168,7 @@ def test_mp_no_data(): @pytest.mark.parametrize("test_file", test_files) -def test_mp_attribute(test_file): +def test_mp_attribute(test_file) -> None: with Image.open(test_file) as im: mpinfo = im._getmp() for frame_number, mpentry in enumerate(mpinfo[0xB002]): @@ -185,7 +185,7 @@ def test_mp_attribute(test_file): @pytest.mark.parametrize("test_file", test_files) -def test_seek(test_file): +def test_seek(test_file) -> None: with Image.open(test_file) as im: assert im.tell() == 0 # prior to first image raises an error, both blatant and borderline @@ -209,13 +209,13 @@ def test_seek(test_file): assert im.tell() == 0 -def test_n_frames(): +def test_n_frames() -> None: with Image.open("Tests/images/sugarshack.mpo") as im: assert im.n_frames == 2 assert im.is_animated -def test_eoferror(): +def test_eoferror() -> None: with Image.open("Tests/images/sugarshack.mpo") as im: n_frames = im.n_frames @@ -229,7 +229,7 @@ def test_eoferror(): @pytest.mark.parametrize("test_file", test_files) -def test_image_grab(test_file): +def test_image_grab(test_file) -> None: with Image.open(test_file) as im: assert im.tell() == 0 im0 = im.tobytes() @@ -244,7 +244,7 @@ def test_image_grab(test_file): @pytest.mark.parametrize("test_file", test_files) -def test_save(test_file): +def test_save(test_file) -> None: with Image.open(test_file) as im: assert im.tell() == 0 jpg0 = roundtrip(im) @@ -255,7 +255,7 @@ def test_save(test_file): assert_image_similar(im, jpg1, 30) -def test_save_all(): +def test_save_all() -> None: for test_file in test_files: with Image.open(test_file) as im: im_reloaded = roundtrip(im, save_all=True) diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 9037ea33b..f9f81d114 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from pathlib import Path import pytest @@ -13,7 +14,7 @@ EXTRA_DIR = "Tests/images/picins" YA_EXTRA_DIR = "Tests/images/msp" -def test_sanity(tmp_path): +def test_sanity(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.msp") hopper("1").save(test_file) @@ -25,14 +26,14 @@ def test_sanity(tmp_path): assert im.format == "MSP" -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): MspImagePlugin.MspImageFile(invalid_file) -def test_bad_checksum(): +def test_bad_checksum() -> None: # Arrange # This was created by forcing Pillow to save with checksum=0 bad_checksum = "Tests/images/hopper_bad_checksum.msp" @@ -42,7 +43,7 @@ def test_bad_checksum(): MspImagePlugin.MspImageFile(bad_checksum) -def test_open_windows_v1(): +def test_open_windows_v1() -> None: # Arrange # Act with Image.open(TEST_FILE) as im: @@ -51,7 +52,7 @@ def test_open_windows_v1(): assert isinstance(im, MspImagePlugin.MspImageFile) -def _assert_file_image_equal(source_path, target_path): +def _assert_file_image_equal(source_path, target_path) -> None: with Image.open(source_path) as im: assert_image_equal_tofile(im, target_path) @@ -59,7 +60,7 @@ def _assert_file_image_equal(source_path, target_path): @pytest.mark.skipif( not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) -def test_open_windows_v2(): +def test_open_windows_v2() -> None: files = ( os.path.join(EXTRA_DIR, f) for f in os.listdir(EXTRA_DIR) @@ -72,7 +73,7 @@ def test_open_windows_v2(): @pytest.mark.skipif( not os.path.exists(YA_EXTRA_DIR), reason="Even More Extra image files not installed" ) -def test_msp_v2(): +def test_msp_v2() -> None: for f in os.listdir(YA_EXTRA_DIR): if ".MSP" not in f: continue @@ -80,7 +81,7 @@ def test_msp_v2(): _assert_file_image_equal(path, path.replace(".MSP", ".png")) -def test_cannot_save_wrong_mode(tmp_path): +def test_cannot_save_wrong_mode(tmp_path: Path) -> None: # Arrange im = hopper() filename = str(tmp_path / "temp.msp") diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index eba694153..55041a4b2 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,6 +2,7 @@ from __future__ import annotations import os.path import subprocess +from pathlib import Path import pytest @@ -10,7 +11,7 @@ from PIL import Image from .helper import assert_image_equal, hopper, magick_command -def helper_save_as_palm(tmp_path, mode): +def helper_save_as_palm(tmp_path: Path, mode) -> None: # Arrange im = hopper(mode) outfile = str(tmp_path / ("temp_" + mode + ".palm")) @@ -23,7 +24,7 @@ def helper_save_as_palm(tmp_path, mode): assert os.path.getsize(outfile) > 0 -def open_with_magick(magick, tmp_path, f): +def open_with_magick(magick, tmp_path: Path, f): outfile = str(tmp_path / "temp.png") rc = subprocess.call( magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT @@ -32,7 +33,7 @@ def open_with_magick(magick, tmp_path, f): return Image.open(outfile) -def roundtrip(tmp_path, mode): +def roundtrip(tmp_path: Path, mode) -> None: magick = magick_command() if not magick: return @@ -45,7 +46,7 @@ def roundtrip(tmp_path, mode): assert_image_equal(converted, im) -def test_monochrome(tmp_path): +def test_monochrome(tmp_path: Path) -> None: # Arrange mode = "1" @@ -55,7 +56,7 @@ def test_monochrome(tmp_path): @pytest.mark.xfail(reason="Palm P image is wrong") -def test_p_mode(tmp_path): +def test_p_mode(tmp_path: Path) -> None: # Arrange mode = "P" @@ -65,6 +66,6 @@ def test_p_mode(tmp_path): @pytest.mark.parametrize("mode", ("L", "RGB")) -def test_oserror(tmp_path, mode): +def test_oserror(tmp_path: Path, mode) -> None: with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 2565e0b6d..a2486be40 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, ImageFile, PcxImagePlugin @@ -7,7 +9,7 @@ from PIL import Image, ImageFile, PcxImagePlugin from .helper import assert_image_equal, hopper -def _roundtrip(tmp_path, im): +def _roundtrip(tmp_path: Path, im) -> None: f = str(tmp_path / "temp.pcx") im.save(f) with Image.open(f) as im2: @@ -18,7 +20,7 @@ def _roundtrip(tmp_path, im): assert_image_equal(im2, im) -def test_sanity(tmp_path): +def test_sanity(tmp_path: Path) -> None: for mode in ("1", "L", "P", "RGB"): _roundtrip(tmp_path, hopper(mode)) @@ -34,7 +36,7 @@ def test_sanity(tmp_path): im.save(f) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): @@ -42,7 +44,7 @@ def test_invalid_file(): @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB")) -def test_odd(tmp_path, mode): +def test_odd(tmp_path: Path, mode) -> None: # See issue #523, odd sized images should have a stride that's even. # Not that ImageMagick or GIMP write PCX that way. # We were not handling properly. @@ -51,7 +53,7 @@ def test_odd(tmp_path, mode): _roundtrip(tmp_path, hopper(mode).resize((511, 511))) -def test_odd_read(): +def test_odd_read() -> None: # Reading an image with an odd stride, making it malformed with Image.open("Tests/images/odd_stride.pcx") as im: im.load() @@ -59,7 +61,7 @@ def test_odd_read(): assert im.size == (371, 150) -def test_pil184(): +def test_pil184() -> None: # Check reading of files where xmin/xmax is not zero. test_file = "Tests/images/pil184.pcx" @@ -71,7 +73,7 @@ def test_pil184(): assert im.histogram()[0] + im.histogram()[255] == 447 * 144 -def test_1px_width(tmp_path): +def test_1px_width(tmp_path: Path) -> None: im = Image.new("L", (1, 256)) px = im.load() for y in range(256): @@ -79,7 +81,7 @@ def test_1px_width(tmp_path): _roundtrip(tmp_path, im) -def test_large_count(tmp_path): +def test_large_count(tmp_path: Path) -> None: im = Image.new("L", (256, 1)) px = im.load() for x in range(256): @@ -87,7 +89,7 @@ def test_large_count(tmp_path): _roundtrip(tmp_path, im) -def _test_buffer_overflow(tmp_path, im, size=1024): +def _test_buffer_overflow(tmp_path: Path, im, size: int = 1024) -> None: _last = ImageFile.MAXBLOCK ImageFile.MAXBLOCK = size try: @@ -96,7 +98,7 @@ def _test_buffer_overflow(tmp_path, im, size=1024): ImageFile.MAXBLOCK = _last -def test_break_in_count_overflow(tmp_path): +def test_break_in_count_overflow(tmp_path: Path) -> None: im = Image.new("L", (256, 5)) px = im.load() for y in range(4): @@ -105,7 +107,7 @@ def test_break_in_count_overflow(tmp_path): _test_buffer_overflow(tmp_path, im) -def test_break_one_in_loop(tmp_path): +def test_break_one_in_loop(tmp_path: Path) -> None: im = Image.new("L", (256, 5)) px = im.load() for y in range(5): @@ -114,7 +116,7 @@ def test_break_one_in_loop(tmp_path): _test_buffer_overflow(tmp_path, im) -def test_break_many_in_loop(tmp_path): +def test_break_many_in_loop(tmp_path: Path) -> None: im = Image.new("L", (256, 5)) px = im.load() for y in range(4): @@ -125,7 +127,7 @@ def test_break_many_in_loop(tmp_path): _test_buffer_overflow(tmp_path, im) -def test_break_one_at_end(tmp_path): +def test_break_one_at_end(tmp_path: Path) -> None: im = Image.new("L", (256, 5)) px = im.load() for y in range(5): @@ -135,7 +137,7 @@ def test_break_one_at_end(tmp_path): _test_buffer_overflow(tmp_path, im) -def test_break_many_at_end(tmp_path): +def test_break_many_at_end(tmp_path: Path) -> None: im = Image.new("L", (256, 5)) px = im.load() for y in range(5): @@ -147,7 +149,7 @@ def test_break_many_at_end(tmp_path): _test_buffer_overflow(tmp_path, im) -def test_break_padding(tmp_path): +def test_break_padding(tmp_path: Path) -> None: im = Image.new("L", (257, 5)) px = im.load() for y in range(5): diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 30c54c963..65a93c138 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -5,6 +5,7 @@ import os import os.path import tempfile import time +from pathlib import Path import pytest @@ -13,7 +14,7 @@ from PIL import Image, PdfParser, features from .helper import hopper, mark_if_feature_version, skip_unless_feature -def helper_save_as_pdf(tmp_path, mode, **kwargs): +def helper_save_as_pdf(tmp_path: Path, mode, **kwargs): # Arrange im = hopper(mode) outfile = str(tmp_path / ("temp_" + mode + ".pdf")) @@ -40,17 +41,17 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs): @pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) -def test_save(tmp_path, mode): +def test_save(tmp_path: Path, mode) -> None: helper_save_as_pdf(tmp_path, mode) @skip_unless_feature("jpg_2000") @pytest.mark.parametrize("mode", ("LA", "RGBA")) -def test_save_alpha(tmp_path, mode): +def test_save_alpha(tmp_path: Path, mode) -> None: helper_save_as_pdf(tmp_path, mode) -def test_p_alpha(tmp_path): +def test_p_alpha(tmp_path: Path) -> None: # Arrange outfile = str(tmp_path / "temp.pdf") with Image.open("Tests/images/pil123p.png") as im: @@ -66,7 +67,7 @@ def test_p_alpha(tmp_path): assert b"\n/SMask " in contents -def test_monochrome(tmp_path): +def test_monochrome(tmp_path: Path) -> None: # Arrange mode = "1" @@ -75,7 +76,7 @@ def test_monochrome(tmp_path): assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000) -def test_unsupported_mode(tmp_path): +def test_unsupported_mode(tmp_path: Path) -> None: im = hopper("PA") outfile = str(tmp_path / "temp_PA.pdf") @@ -83,7 +84,7 @@ def test_unsupported_mode(tmp_path): im.save(outfile) -def test_resolution(tmp_path): +def test_resolution(tmp_path: Path) -> None: im = hopper() outfile = str(tmp_path / "temp.pdf") @@ -111,7 +112,7 @@ def test_resolution(tmp_path): {"dpi": (75, 150), "resolution": 200}, ), ) -def test_dpi(params, tmp_path): +def test_dpi(params, tmp_path: Path) -> None: im = hopper() outfile = str(tmp_path / "temp.pdf") @@ -135,7 +136,7 @@ def test_dpi(params, tmp_path): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) -def test_save_all(tmp_path): +def test_save_all(tmp_path: Path) -> None: # Single frame image helper_save_as_pdf(tmp_path, "RGB", save_all=True) @@ -171,7 +172,7 @@ def test_save_all(tmp_path): assert os.path.getsize(outfile) > 0 -def test_multiframe_normal_save(tmp_path): +def test_multiframe_normal_save(tmp_path: Path) -> None: # Test saving a multiframe image without save_all with Image.open("Tests/images/dispose_bgnd.gif") as im: outfile = str(tmp_path / "temp.pdf") @@ -181,7 +182,7 @@ def test_multiframe_normal_save(tmp_path): assert os.path.getsize(outfile) > 0 -def test_pdf_open(tmp_path): +def test_pdf_open(tmp_path: Path) -> None: # fail on a buffer full of null bytes with pytest.raises(PdfParser.PdfFormatError): PdfParser.PdfParser(buf=bytearray(65536)) @@ -218,14 +219,14 @@ def test_pdf_open(tmp_path): assert not hopper_pdf.should_close_file -def test_pdf_append_fails_on_nonexistent_file(): +def test_pdf_append_fails_on_nonexistent_file() -> None: im = hopper("RGB") with tempfile.TemporaryDirectory() as temp_dir: with pytest.raises(OSError): im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) -def check_pdf_pages_consistency(pdf): +def check_pdf_pages_consistency(pdf) -> None: pages_info = pdf.read_indirect(pdf.pages_ref) assert b"Parent" not in pages_info assert b"Kids" in pages_info @@ -243,7 +244,7 @@ def check_pdf_pages_consistency(pdf): assert kids_not_used == [] -def test_pdf_append(tmp_path): +def test_pdf_append(tmp_path: Path) -> None: # make a PDF file pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser") @@ -294,7 +295,7 @@ def test_pdf_append(tmp_path): check_pdf_pages_consistency(pdf) -def test_pdf_info(tmp_path): +def test_pdf_info(tmp_path: Path) -> None: # make a PDF file pdf_filename = helper_save_as_pdf( tmp_path, @@ -323,7 +324,7 @@ def test_pdf_info(tmp_path): check_pdf_pages_consistency(pdf) -def test_pdf_append_to_bytesio(): +def test_pdf_append_to_bytesio() -> None: im = hopper("RGB") f = io.BytesIO() im.save(f, format="PDF") @@ -338,7 +339,7 @@ def test_pdf_append_to_bytesio(): @pytest.mark.timeout(1) @pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower") @pytest.mark.parametrize("newline", (b"\r", b"\n")) -def test_redos(newline): +def test_redos(newline) -> None: malicious = b" trailer<<>>" + newline * 3456 # This particular exception isn't relevant here. diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index ae2a4772b..0f1d96365 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -5,6 +5,7 @@ import sys import warnings import zlib from io import BytesIO +from pathlib import Path import pytest @@ -79,7 +80,7 @@ class TestFilePng: png.crc(cid, s) return chunks - def test_sanity(self, tmp_path): + def test_sanity(self, tmp_path: Path) -> None: # internal version number assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib")) @@ -102,13 +103,13 @@ class TestFilePng: reloaded = reloaded.convert(mode) assert_image_equal(reloaded, im) - def test_invalid_file(self): + def test_invalid_file(self) -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): PngImagePlugin.PngImageFile(invalid_file) - def test_broken(self): + def test_broken(self) -> None: # Check reading of totally broken files. In this case, the test # file was checked into Subversion as a text file. @@ -117,7 +118,7 @@ class TestFilePng: with Image.open(test_file): pass - def test_bad_text(self): + def test_bad_text(self) -> None: # Make sure PIL can read malformed tEXt chunks (@PIL152) im = load(HEAD + chunk(b"tEXt") + TAIL) @@ -135,7 +136,7 @@ class TestFilePng: im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL) assert im.info == {"spam": "egg\x00"} - def test_bad_ztxt(self): + def test_bad_ztxt(self) -> None: # Test reading malformed zTXt chunks (python-pillow/Pillow#318) im = load(HEAD + chunk(b"zTXt") + TAIL) @@ -156,7 +157,7 @@ class TestFilePng: im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL) assert im.info == {"spam": "egg"} - def test_bad_itxt(self): + def test_bad_itxt(self) -> None: im = load(HEAD + chunk(b"iTXt") + TAIL) assert im.info == {} @@ -200,7 +201,7 @@ class TestFilePng: assert im.info["spam"].lang == "en" assert im.info["spam"].tkey == "Spam" - def test_interlace(self): + def test_interlace(self) -> None: test_file = "Tests/images/pil123p.png" with Image.open(test_file) as im: assert_image(im, "P", (162, 150)) @@ -215,7 +216,7 @@ class TestFilePng: im.load() - def test_load_transparent_p(self): + def test_load_transparent_p(self) -> None: test_file = "Tests/images/pil123p.png" with Image.open(test_file) as im: assert_image(im, "P", (162, 150)) @@ -225,7 +226,7 @@ class TestFilePng: # image has 124 unique alpha values assert len(im.getchannel("A").getcolors()) == 124 - def test_load_transparent_rgb(self): + def test_load_transparent_rgb(self) -> None: test_file = "Tests/images/rgb_trns.png" with Image.open(test_file) as im: assert im.info["transparency"] == (0, 255, 52) @@ -237,7 +238,7 @@ class TestFilePng: # image has 876 transparent pixels assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_palette(self, tmp_path): + def test_save_p_transparent_palette(self, tmp_path: Path) -> None: in_file = "Tests/images/pil123p.png" with Image.open(in_file) as im: # 'transparency' contains a byte string with the opacity for @@ -258,7 +259,7 @@ class TestFilePng: # image has 124 unique alpha values assert len(im.getchannel("A").getcolors()) == 124 - def test_save_p_single_transparency(self, tmp_path): + def test_save_p_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/p_trns_single.png" with Image.open(in_file) as im: # pixel value 164 is full transparent @@ -281,7 +282,7 @@ class TestFilePng: # image has 876 transparent pixels assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_black(self, tmp_path): + def test_save_p_transparent_black(self, tmp_path: Path) -> None: # check if solid black image with full transparency # is supported (check for #1838) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) @@ -299,7 +300,7 @@ class TestFilePng: assert_image(im, "RGBA", (10, 10)) assert im.getcolors() == [(100, (0, 0, 0, 0))] - def test_save_grayscale_transparency(self, tmp_path): + def test_save_grayscale_transparency(self, tmp_path: Path) -> None: for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): in_file = "Tests/images/" + mode.lower() + "_trns.png" with Image.open(in_file) as im: @@ -320,13 +321,13 @@ class TestFilePng: test_im_rgba = test_im.convert("RGBA") assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent - def test_save_rgb_single_transparency(self, tmp_path): + def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/caption_6_33_22.png" with Image.open(in_file) as im: test_file = str(tmp_path / "temp.png") im.save(test_file) - def test_load_verify(self): + def test_load_verify(self) -> None: # Check open/load/verify exception (@PIL150) with Image.open(TEST_PNG_FILE) as im: @@ -339,7 +340,7 @@ class TestFilePng: with pytest.raises(RuntimeError): im.verify() - def test_verify_struct_error(self): + def test_verify_struct_error(self) -> None: # Check open/load/verify exception (#1755) # offsets to test, -10: breaks in i32() in read. (OSError) @@ -355,7 +356,7 @@ class TestFilePng: with pytest.raises((OSError, SyntaxError)): im.verify() - def test_verify_ignores_crc_error(self): + def test_verify_ignores_crc_error(self) -> None: # check ignores crc errors in ancillary chunks chunk_data = chunk(b"tEXt", b"spam") @@ -372,7 +373,7 @@ class TestFilePng: finally: ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_verify_not_ignores_crc_error_in_required_chunk(self): + def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None: # check does not ignore crc errors in required chunks image_data = MAGIC + IHDR[:-1] + b"q" + TAIL @@ -384,18 +385,18 @@ class TestFilePng: finally: ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_roundtrip_dpi(self): + def test_roundtrip_dpi(self) -> None: # Check dpi roundtripping with Image.open(TEST_PNG_FILE) as im: im = roundtrip(im, dpi=(100.33, 100.33)) assert im.info["dpi"] == (100.33, 100.33) - def test_load_float_dpi(self): + def test_load_float_dpi(self) -> None: with Image.open(TEST_PNG_FILE) as im: assert im.info["dpi"] == (95.9866, 95.9866) - def test_roundtrip_text(self): + def test_roundtrip_text(self) -> None: # Check text roundtripping with Image.open(TEST_PNG_FILE) as im: @@ -407,7 +408,7 @@ class TestFilePng: assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"} assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} - def test_roundtrip_itxt(self): + def test_roundtrip_itxt(self) -> None: # Check iTXt roundtripping im = Image.new("RGB", (32, 32)) @@ -423,7 +424,7 @@ class TestFilePng: assert im.text["eggs"].lang == "en" assert im.text["eggs"].tkey == "Eggs" - def test_nonunicode_text(self): + def test_nonunicode_text(self) -> None: # Check so that non-Unicode text is saved as a tEXt rather than iTXt im = Image.new("RGB", (32, 32)) @@ -432,10 +433,10 @@ class TestFilePng: im = roundtrip(im, pnginfo=info) assert isinstance(im.info["Text"], str) - def test_unicode_text(self): + def test_unicode_text(self) -> None: # Check preservation of non-ASCII characters - def rt_text(value): + def rt_text(value) -> None: im = Image.new("RGB", (32, 32)) info = PngImagePlugin.PngInfo() info.add_text("Text", value) @@ -448,7 +449,7 @@ class TestFilePng: rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00)) rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined - def test_scary(self): + def test_scary(self) -> None: # Check reading of evil PNG file. For information, see: # http://scary.beasts.org/security/CESA-2004-001.txt # The first byte is removed from pngtest_bad.png @@ -462,7 +463,7 @@ class TestFilePng: with Image.open(pngfile): pass - def test_trns_rgb(self): + def test_trns_rgb(self) -> None: # Check writing and reading of tRNS chunks for RGB images. # Independent file sample provided by Sebastian Spaeth. @@ -477,7 +478,7 @@ class TestFilePng: im = roundtrip(im, transparency=(0, 1, 2)) assert im.info["transparency"] == (0, 1, 2) - def test_trns_p(self, tmp_path): + def test_trns_p(self, tmp_path: Path) -> None: # Check writing a transparency of 0, issue #528 im = hopper("P") im.info["transparency"] = 0 @@ -490,13 +491,13 @@ class TestFilePng: assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) - def test_trns_null(self): + def test_trns_null(self) -> None: # Check reading images with null tRNS value, issue #1239 test_file = "Tests/images/tRNS_null_1x1.png" with Image.open(test_file) as im: assert im.info["transparency"] == 0 - def test_save_icc_profile(self): + def test_save_icc_profile(self) -> None: with Image.open("Tests/images/icc_profile_none.png") as im: assert im.info["icc_profile"] is None @@ -506,40 +507,40 @@ class TestFilePng: im = roundtrip(im, icc_profile=expected_icc) assert im.info["icc_profile"] == expected_icc - def test_discard_icc_profile(self): + def test_discard_icc_profile(self) -> None: with Image.open("Tests/images/icc_profile.png") as im: assert "icc_profile" in im.info im = roundtrip(im, icc_profile=None) assert "icc_profile" not in im.info - def test_roundtrip_icc_profile(self): + def test_roundtrip_icc_profile(self) -> None: with Image.open("Tests/images/icc_profile.png") as im: expected_icc = im.info["icc_profile"] im = roundtrip(im) assert im.info["icc_profile"] == expected_icc - def test_roundtrip_no_icc_profile(self): + def test_roundtrip_no_icc_profile(self) -> None: with Image.open("Tests/images/icc_profile_none.png") as im: assert im.info["icc_profile"] is None im = roundtrip(im) assert "icc_profile" not in im.info - def test_repr_png(self): + def test_repr_png(self) -> None: im = hopper() with Image.open(BytesIO(im._repr_png_())) as repr_png: assert repr_png.format == "PNG" assert_image_equal(im, repr_png) - def test_repr_png_error_returns_none(self): + def test_repr_png_error_returns_none(self) -> None: im = hopper("F") assert im._repr_png_() is None - def test_chunk_order(self, tmp_path): + def test_chunk_order(self, tmp_path: Path) -> None: with Image.open("Tests/images/icc_profile.png") as im: test_file = str(tmp_path / "temp.png") im.convert("P").save(test_file, dpi=(100, 100)) @@ -560,17 +561,17 @@ class TestFilePng: # pHYs - before IDAT assert chunks.index(b"pHYs") < chunks.index(b"IDAT") - def test_getchunks(self): + def test_getchunks(self) -> None: im = hopper() chunks = PngImagePlugin.getchunks(im) assert len(chunks) == 3 - def test_read_private_chunks(self): + def test_read_private_chunks(self) -> None: with Image.open("Tests/images/exif.png") as im: assert im.private_chunks == [(b"orNT", b"\x01")] - def test_roundtrip_private_chunk(self): + def test_roundtrip_private_chunk(self) -> None: # Check private chunk roundtripping with Image.open(TEST_PNG_FILE) as im: @@ -588,7 +589,7 @@ class TestFilePng: (b"prIV", b"VALUE3", True), ] - def test_textual_chunks_after_idat(self): + def test_textual_chunks_after_idat(self) -> None: with Image.open("Tests/images/hopper.png") as im: assert "comment" in im.text for k, v in { @@ -615,7 +616,7 @@ class TestFilePng: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} - def test_padded_idat(self): + def test_padded_idat(self) -> None: # This image has been manually hexedited # so that the IDAT chunk has padding at the end # Set MAXBLOCK to the length of the actual data @@ -635,7 +636,7 @@ class TestFilePng: @pytest.mark.parametrize( "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT") ) - def test_truncated_chunks(self, cid): + def test_truncated_chunks(self, cid) -> None: fp = BytesIO() with PngImagePlugin.PngStream(fp) as png: with pytest.raises(ValueError): @@ -645,7 +646,7 @@ class TestFilePng: png.call(cid, 0, 0) ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_specify_bits(self, tmp_path): + def test_specify_bits(self, tmp_path: Path) -> None: im = hopper("P") out = str(tmp_path / "temp.png") @@ -654,7 +655,7 @@ class TestFilePng: with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 48 - def test_plte_length(self, tmp_path): + def test_plte_length(self, tmp_path: Path) -> None: im = Image.new("P", (1, 1)) im.putpalette((1, 1, 1)) @@ -664,7 +665,7 @@ class TestFilePng: with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 3 - def test_getxmp(self): + def test_getxmp(self) -> None: with Image.open("Tests/images/color_snakes.png") as im: if ElementTree is None: with pytest.warns( @@ -679,7 +680,7 @@ class TestFilePng: assert description["PixelXDimension"] == "10" assert description["subject"]["Seq"] is None - def test_exif(self): + def test_exif(self) -> None: # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: exif = im._getexif() @@ -705,7 +706,7 @@ class TestFilePng: exif = im.getexif() assert exif[274] == 3 - def test_exif_save(self, tmp_path): + def test_exif_save(self, tmp_path: Path) -> None: # Test exif is not saved from info test_file = str(tmp_path / "temp.png") with Image.open("Tests/images/exif.png") as im: @@ -725,7 +726,7 @@ class TestFilePng: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_exif_from_jpg(self, tmp_path): + def test_exif_from_jpg(self, tmp_path: Path) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: test_file = str(tmp_path / "temp.png") im.save(test_file, exif=im.getexif()) @@ -734,7 +735,7 @@ class TestFilePng: exif = reloaded._getexif() assert exif[305] == "Adobe Photoshop CS Macintosh" - def test_exif_argument(self, tmp_path): + def test_exif_argument(self, tmp_path: Path) -> None: with Image.open(TEST_PNG_FILE) as im: test_file = str(tmp_path / "temp.png") im.save(test_file, exif=b"exifstring") @@ -742,11 +743,11 @@ class TestFilePng: with Image.open(test_file) as reloaded: assert reloaded.info["exif"] == b"Exif\x00\x00exifstring" - def test_tell(self): + def test_tell(self) -> None: with Image.open(TEST_PNG_FILE) as im: assert im.tell() == 0 - def test_seek(self): + def test_seek(self) -> None: with Image.open(TEST_PNG_FILE) as im: im.seek(0) @@ -754,7 +755,7 @@ class TestFilePng: im.seek(1) @pytest.mark.parametrize("buffer", (True, False)) - def test_save_stdout(self, buffer): + def test_save_stdout(self, buffer) -> None: old_stdout = sys.stdout if buffer: @@ -786,7 +787,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): mem_limit = 2 * 1024 # max increase in K iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs - def test_leak_load(self): + def test_leak_load(self) -> None: with open("Tests/images/hopper.png", "rb") as f: DATA = BytesIO(f.read(16 * 1024)) @@ -794,7 +795,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): with Image.open(DATA) as im: im.load() - def core(): + def core() -> None: with Image.open(DATA) as im: im.load() diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 32de42ed4..94f66ee7d 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -2,6 +2,7 @@ from __future__ import annotations import sys from io import BytesIO +from pathlib import Path import pytest @@ -18,7 +19,7 @@ from .helper import ( TEST_FILE = "Tests/images/hopper.ppm" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: assert im.mode == "RGB" assert im.size == (128, 128) @@ -69,7 +70,7 @@ def test_sanity(): ), ), ) -def test_arbitrary_maxval(data, mode, pixels): +def test_arbitrary_maxval(data, mode, pixels) -> None: fp = BytesIO(data) with Image.open(fp) as im: assert im.size == (3, 1) @@ -79,7 +80,7 @@ def test_arbitrary_maxval(data, mode, pixels): assert tuple(px[x, 0] for x in range(3)) == pixels -def test_16bit_pgm(): +def test_16bit_pgm() -> None: with Image.open("Tests/images/16_bit_binary.pgm") as im: assert im.mode == "I" assert im.size == (20, 100) @@ -88,7 +89,7 @@ def test_16bit_pgm(): assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png") -def test_16bit_pgm_write(tmp_path): +def test_16bit_pgm_write(tmp_path: Path) -> None: with Image.open("Tests/images/16_bit_binary.pgm") as im: filename = str(tmp_path / "temp.pgm") im.save(filename, "PPM") @@ -96,7 +97,7 @@ def test_16bit_pgm_write(tmp_path): assert_image_equal_tofile(im, filename) -def test_pnm(tmp_path): +def test_pnm(tmp_path: Path) -> None: with Image.open("Tests/images/hopper.pnm") as im: assert_image_similar(im, hopper(), 0.0001) @@ -106,7 +107,7 @@ def test_pnm(tmp_path): assert_image_equal_tofile(im, filename) -def test_pfm(tmp_path): +def test_pfm(tmp_path: Path) -> None: with Image.open("Tests/images/hopper.pfm") as im: assert im.info["scale"] == 1.0 assert_image_equal(im, hopper("F")) @@ -117,7 +118,7 @@ def test_pfm(tmp_path): assert_image_equal_tofile(im, filename) -def test_pfm_big_endian(tmp_path): +def test_pfm_big_endian(tmp_path: Path) -> None: with Image.open("Tests/images/hopper_be.pfm") as im: assert im.info["scale"] == 2.5 assert_image_equal(im, hopper("F")) @@ -138,7 +139,7 @@ def test_pfm_big_endian(tmp_path): b"Pf 1 1 -0.0 \0\0\0\0", ], ) -def test_pfm_invalid(data): +def test_pfm_invalid(data) -> None: with pytest.raises(ValueError): with Image.open(BytesIO(data)): pass @@ -161,12 +162,12 @@ def test_pfm_invalid(data): ), ), ) -def test_plain(plain_path, raw_path): +def test_plain(plain_path, raw_path) -> None: with Image.open(plain_path) as im: assert_image_equal_tofile(im, raw_path) -def test_16bit_plain_pgm(): +def test_16bit_plain_pgm() -> None: # P2 with maxval 2 ** 16 - 1 with Image.open("Tests/images/hopper_16bit_plain.pgm") as im: assert im.mode == "I" @@ -185,7 +186,7 @@ def test_16bit_plain_pgm(): (b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6), ), ) -def test_plain_data_with_comment(tmp_path, header, data, comment_count): +def test_plain_data_with_comment(tmp_path: Path, header, data, comment_count) -> None: path1 = str(tmp_path / "temp1.ppm") path2 = str(tmp_path / "temp2.ppm") comment = b"# comment" * comment_count @@ -198,7 +199,7 @@ def test_plain_data_with_comment(tmp_path, header, data, comment_count): @pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n")) -def test_plain_truncated_data(tmp_path, data): +def test_plain_truncated_data(tmp_path: Path, data) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -209,7 +210,7 @@ def test_plain_truncated_data(tmp_path, data): @pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A")) -def test_plain_invalid_data(tmp_path, data): +def test_plain_invalid_data(tmp_path: Path, data) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -226,7 +227,7 @@ def test_plain_invalid_data(tmp_path, data): b"P3\n128 128\n255\n012345678910 0", # token too long ), ) -def test_plain_ppm_token_too_long(tmp_path, data): +def test_plain_ppm_token_too_long(tmp_path: Path, data) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -236,7 +237,7 @@ def test_plain_ppm_token_too_long(tmp_path, data): im.load() -def test_plain_ppm_value_too_large(tmp_path): +def test_plain_ppm_value_too_large(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P3\n128 128\n255\n256") @@ -246,12 +247,12 @@ def test_plain_ppm_value_too_large(tmp_path): im.load() -def test_magic(): +def test_magic() -> None: with pytest.raises(SyntaxError): PpmImagePlugin.PpmImageFile(fp=BytesIO(b"PyInvalid")) -def test_header_with_comments(tmp_path): +def test_header_with_comments(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n") @@ -260,7 +261,7 @@ def test_header_with_comments(tmp_path): assert im.size == (128, 128) -def test_non_integer_token(tmp_path): +def test_non_integer_token(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\nTEST") @@ -270,7 +271,7 @@ def test_non_integer_token(tmp_path): pass -def test_header_token_too_long(tmp_path): +def test_header_token_too_long(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n 01234567890") @@ -282,7 +283,7 @@ def test_header_token_too_long(tmp_path): assert str(e.value) == "Token too long in file header: 01234567890" -def test_truncated_file(tmp_path): +def test_truncated_file(tmp_path: Path) -> None: # Test EOF in header path = str(tmp_path / "temp.pgm") with open(path, "wb") as f: @@ -301,7 +302,7 @@ def test_truncated_file(tmp_path): im.load() -def test_not_enough_image_data(tmp_path): +def test_not_enough_image_data(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P2 1 2 255 255") @@ -312,7 +313,7 @@ def test_not_enough_image_data(tmp_path): @pytest.mark.parametrize("maxval", (b"0", b"65536")) -def test_invalid_maxval(maxval, tmp_path): +def test_invalid_maxval(maxval, tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n3 1 " + maxval) @@ -324,7 +325,7 @@ def test_invalid_maxval(maxval, tmp_path): assert str(e.value) == "maxval must be greater than 0 and less than 65536" -def test_neg_ppm(): +def test_neg_ppm() -> None: # Storage.c accepted negative values for xsize, ysize. the # internal open_ppm function didn't check for sanity but it # has been removed. The default opener doesn't accept negative @@ -335,7 +336,7 @@ def test_neg_ppm(): pass -def test_mimetypes(tmp_path): +def test_mimetypes(tmp_path: Path) -> None: path = str(tmp_path / "temp.pgm") with open(path, "wb") as f: @@ -350,7 +351,7 @@ def test_mimetypes(tmp_path): @pytest.mark.parametrize("buffer", (True, False)) -def test_save_stdout(buffer): +def test_save_stdout(buffer) -> None: old_stdout = sys.stdout if buffer: diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 16f049602..7eca8d9b1 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -11,7 +11,7 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_ test_file = "Tests/images/hopper.psd" -def test_sanity(): +def test_sanity() -> None: with Image.open(test_file) as im: im.load() assert im.mode == "RGB" @@ -24,8 +24,8 @@ def test_sanity(): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(test_file) im.load() @@ -33,27 +33,27 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(test_file) im.load() im.close() -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(test_file) as im: im.load() -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): PsdImagePlugin.PsdImageFile(invalid_file) -def test_n_frames(): +def test_n_frames() -> None: with Image.open("Tests/images/hopper_merged.psd") as im: assert im.n_frames == 1 assert not im.is_animated @@ -64,7 +64,7 @@ def test_n_frames(): assert im.is_animated -def test_eoferror(): +def test_eoferror() -> None: with Image.open(test_file) as im: # PSD seek index starts at 1 rather than 0 n_frames = im.n_frames + 1 @@ -78,7 +78,7 @@ def test_eoferror(): im.seek(n_frames - 1) -def test_seek_tell(): +def test_seek_tell() -> None: with Image.open(test_file) as im: layer_number = im.tell() assert layer_number == 1 @@ -95,30 +95,30 @@ def test_seek_tell(): assert layer_number == 2 -def test_seek_eoferror(): +def test_seek_eoferror() -> None: with Image.open(test_file) as im: with pytest.raises(EOFError): im.seek(-1) -def test_open_after_exclusive_load(): +def test_open_after_exclusive_load() -> None: with Image.open(test_file) as im: im.load() im.seek(im.tell() + 1) im.load() -def test_rgba(): +def test_rgba() -> None: with Image.open("Tests/images/rgba.psd") as im: assert_image_equal_tofile(im, "Tests/images/imagedraw_square.png") -def test_layer_skip(): +def test_layer_skip() -> None: with Image.open("Tests/images/five_channels.psd") as im: assert im.n_frames == 1 -def test_icc_profile(): +def test_icc_profile() -> None: with Image.open(test_file) as im: assert "icc_profile" in im.info @@ -126,12 +126,12 @@ def test_icc_profile(): assert len(icc_profile) == 3144 -def test_no_icc_profile(): +def test_no_icc_profile() -> None: with Image.open("Tests/images/hopper_merged.psd") as im: assert "icc_profile" not in im.info -def test_combined_larger_than_size(): +def test_combined_larger_than_size() -> None: # The combined size of the individual parts is larger than the # declared 'size' of the extra data field, resulting in a backwards seek. @@ -157,7 +157,7 @@ def test_combined_larger_than_size(): ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), ], ) -def test_crashes(test_file, raises): +def test_crashes(test_file, raises) -> None: with open(test_file, "rb") as f: with pytest.raises(raises): with Image.open(f): diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index bc45bbfd3..92aea0735 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, SgiImagePlugin @@ -12,7 +14,7 @@ from .helper import ( ) -def test_rgb(): +def test_rgb() -> None: # Created with ImageMagick then renamed: # convert hopper.ppm -compress None sgi:hopper.rgb test_file = "Tests/images/hopper.rgb" @@ -22,11 +24,11 @@ def test_rgb(): assert im.get_format_mimetype() == "image/rgb" -def test_rgb16(): +def test_rgb16() -> None: assert_image_equal_tofile(hopper(), "Tests/images/hopper16.rgb") -def test_l(): +def test_l() -> None: # Created with ImageMagick # convert hopper.ppm -monochrome -compress None sgi:hopper.bw test_file = "Tests/images/hopper.bw" @@ -36,7 +38,7 @@ def test_l(): assert im.get_format_mimetype() == "image/sgi" -def test_rgba(): +def test_rgba() -> None: # Created with ImageMagick: # convert transparent.png -compress None transparent.sgi test_file = "Tests/images/transparent.sgi" @@ -46,7 +48,7 @@ def test_rgba(): assert im.get_format_mimetype() == "image/sgi" -def test_rle(): +def test_rle() -> None: # Created with ImageMagick: # convert hopper.ppm hopper.sgi test_file = "Tests/images/hopper.sgi" @@ -55,22 +57,22 @@ def test_rle(): assert_image_equal_tofile(im, "Tests/images/hopper.rgb") -def test_rle16(): +def test_rle16() -> None: test_file = "Tests/images/tv16.sgi" with Image.open(test_file) as im: assert_image_equal_tofile(im, "Tests/images/tv.rgb") -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(ValueError): SgiImagePlugin.SgiImageFile(invalid_file) -def test_write(tmp_path): - def roundtrip(img): +def test_write(tmp_path: Path) -> None: + def roundtrip(img) -> None: out = str(tmp_path / "temp.sgi") img.save(out, format="sgi") assert_image_equal_tofile(img, out) @@ -89,7 +91,7 @@ def test_write(tmp_path): roundtrip(Image.new("L", (10, 1))) -def test_write16(tmp_path): +def test_write16(tmp_path: Path) -> None: test_file = "Tests/images/hopper16.rgb" with Image.open(test_file) as im: @@ -99,7 +101,7 @@ def test_write16(tmp_path): assert_image_equal_tofile(im, out) -def test_unsupported_mode(tmp_path): +def test_unsupported_mode(tmp_path: Path) -> None: im = hopper("LA") out = str(tmp_path / "temp.sgi") diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 42d833fb2..75fef1dc6 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -3,6 +3,7 @@ from __future__ import annotations import tempfile import warnings from io import BytesIO +from pathlib import Path import pytest @@ -13,7 +14,7 @@ from .helper import assert_image_equal_tofile, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" -def test_sanity(): +def test_sanity() -> None: with Image.open(TEST_FILE) as im: im.load() assert im.mode == "F" @@ -22,8 +23,8 @@ def test_sanity(): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): - def open(): +def test_unclosed_file() -> None: + def open() -> None: im = Image.open(TEST_FILE) im.load() @@ -31,20 +32,20 @@ def test_unclosed_file(): open() -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(TEST_FILE) im.load() im.close() -def test_context_manager(): +def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(TEST_FILE) as im: im.load() -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: # Arrange temp = str(tmp_path / "temp.spider") im = hopper() @@ -59,7 +60,7 @@ def test_save(tmp_path): assert im2.format == "SPIDER" -def test_tempfile(): +def test_tempfile() -> None: # Arrange im = hopper() @@ -75,11 +76,11 @@ def test_tempfile(): assert reloaded.format == "SPIDER" -def test_is_spider_image(): +def test_is_spider_image() -> None: assert SpiderImagePlugin.isSpiderImage(TEST_FILE) -def test_tell(): +def test_tell() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act @@ -89,13 +90,13 @@ def test_tell(): assert index == 0 -def test_n_frames(): +def test_n_frames() -> None: with Image.open(TEST_FILE) as im: assert im.n_frames == 1 assert not im.is_animated -def test_load_image_series(): +def test_load_image_series() -> None: # Arrange not_spider_file = "Tests/images/hopper.ppm" file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] @@ -109,7 +110,7 @@ def test_load_image_series(): assert img_list[0].size == (128, 128) -def test_load_image_series_no_input(): +def test_load_image_series_no_input() -> None: # Arrange file_list = None @@ -120,7 +121,7 @@ def test_load_image_series_no_input(): assert img_list is None -def test_is_int_not_a_number(): +def test_is_int_not_a_number() -> None: # Arrange not_a_number = "a" @@ -131,7 +132,7 @@ def test_is_int_not_a_number(): assert ret == 0 -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/invalid.spider" with pytest.raises(OSError): @@ -139,20 +140,20 @@ def test_invalid_file(): pass -def test_nonstack_file(): +def test_nonstack_file() -> None: with Image.open(TEST_FILE) as im: with pytest.raises(EOFError): im.seek(0) -def test_nonstack_dos(): +def test_nonstack_dos() -> None: with Image.open(TEST_FILE) as im: for i, frame in enumerate(ImageSequence.Iterator(im)): assert i <= 1, "Non-stack DOS file test failed" # for issue #4093 -def test_odd_size(): +def test_odd_size() -> None: data = BytesIO() width = 100 im = Image.new("F", (width, 64)) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 41f3b7d98..6cfff8730 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -11,7 +11,7 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper EXTRA_DIR = "Tests/images/sunraster" -def test_sanity(): +def test_sanity() -> None: # Arrange # Created with ImageMagick: convert hopper.jpg hopper.ras test_file = "Tests/images/hopper.ras" @@ -28,7 +28,7 @@ def test_sanity(): SunImagePlugin.SunImageFile(invalid_file) -def test_im1(): +def test_im1() -> None: with Image.open("Tests/images/sunraster.im1") as im: assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png") @@ -36,7 +36,7 @@ def test_im1(): @pytest.mark.skipif( not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) -def test_others(): +def test_others() -> None: files = ( os.path.join(EXTRA_DIR, f) for f in os.listdir(EXTRA_DIR) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 58226c330..44e78e972 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -19,7 +19,7 @@ TEST_TAR_FILE = "Tests/images/hopper.tar" ("jpg", "hopper.jpg", "JPEG"), ), ) -def test_sanity(codec, test_path, format): +def test_sanity(codec, test_path, format) -> None: if features.check(codec): with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: with Image.open(tar) as im: @@ -30,18 +30,18 @@ def test_sanity(codec, test_path, format): @pytest.mark.skipif(is_pypy(), reason="Requires CPython") -def test_unclosed_file(): +def test_unclosed_file() -> None: with pytest.warns(ResourceWarning): TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") -def test_close(): +def test_close() -> None: with warnings.catch_warnings(): tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() -def test_contextmanager(): +def test_contextmanager() -> None: with warnings.catch_warnings(): with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index eafb61d30..bd8e522c7 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,6 +3,7 @@ from __future__ import annotations import os from glob import glob from itertools import product +from pathlib import Path import pytest @@ -21,8 +22,8 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} @pytest.mark.parametrize("mode", _MODES) -def test_sanity(mode, tmp_path): - def roundtrip(original_im): +def test_sanity(mode, tmp_path: Path) -> None: + def roundtrip(original_im) -> None: out = str(tmp_path / "temp.tga") original_im.save(out, rle=rle) @@ -64,7 +65,7 @@ def test_sanity(mode, tmp_path): roundtrip(original_im) -def test_palette_depth_16(tmp_path): +def test_palette_depth_16(tmp_path: Path) -> None: with Image.open("Tests/images/p_16.tga") as im: assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png") @@ -74,7 +75,7 @@ def test_palette_depth_16(tmp_path): assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png") -def test_id_field(): +def test_id_field() -> None: # tga file with id field test_file = "Tests/images/tga_id_field.tga" @@ -84,7 +85,7 @@ def test_id_field(): assert im.size == (100, 100) -def test_id_field_rle(): +def test_id_field_rle() -> None: # tga file with id field test_file = "Tests/images/rgb32rle.tga" @@ -94,7 +95,7 @@ def test_id_field_rle(): assert im.size == (199, 199) -def test_cross_scan_line(): +def test_cross_scan_line() -> None: with Image.open("Tests/images/cross_scan_line.tga") as im: assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png") @@ -103,7 +104,7 @@ def test_cross_scan_line(): im.load() -def test_save(tmp_path): +def test_save(tmp_path: Path) -> None: test_file = "Tests/images/tga_id_field.tga" with Image.open(test_file) as im: out = str(tmp_path / "temp.tga") @@ -120,7 +121,7 @@ def test_save(tmp_path): assert test_im.size == (100, 100) -def test_small_palette(tmp_path): +def test_small_palette(tmp_path: Path) -> None: im = Image.new("P", (1, 1)) colors = [0, 0, 0] im.putpalette(colors) @@ -132,7 +133,7 @@ def test_small_palette(tmp_path): assert reloaded.getpalette() == colors -def test_save_wrong_mode(tmp_path): +def test_save_wrong_mode(tmp_path: Path) -> None: im = hopper("PA") out = str(tmp_path / "temp.tga") @@ -140,7 +141,7 @@ def test_save_wrong_mode(tmp_path): im.save(out) -def test_save_mapdepth(): +def test_save_mapdepth() -> None: # This image has been manually hexedited from 200x32_p_bl_raw.tga # to include an origin test_file = "Tests/images/200x32_p_bl_raw_origin.tga" @@ -148,7 +149,7 @@ def test_save_mapdepth(): assert_image_equal_tofile(im, "Tests/images/tga/common/200x32_p.png") -def test_save_id_section(tmp_path): +def test_save_id_section(tmp_path: Path) -> None: test_file = "Tests/images/rgb32rle.tga" with Image.open(test_file) as im: out = str(tmp_path / "temp.tga") @@ -179,7 +180,7 @@ def test_save_id_section(tmp_path): assert "id_section" not in test_im.info -def test_save_orientation(tmp_path): +def test_save_orientation(tmp_path: Path) -> None: test_file = "Tests/images/rgb32rle.tga" out = str(tmp_path / "temp.tga") with Image.open(test_file) as im: @@ -190,7 +191,7 @@ def test_save_orientation(tmp_path): assert test_im.info["orientation"] == 1 -def test_horizontal_orientations(): +def test_horizontal_orientations() -> None: # These images have been manually hexedited to have the relevant orientations with Image.open("Tests/images/rgb32rle_top_right.tga") as im: assert im.load()[90, 90][:3] == (0, 0, 0) @@ -199,7 +200,7 @@ def test_horizontal_orientations(): assert im.load()[90, 90][:3] == (0, 255, 0) -def test_save_rle(tmp_path): +def test_save_rle(tmp_path: Path) -> None: test_file = "Tests/images/rgb32rle.tga" with Image.open(test_file) as im: assert im.info["compression"] == "tga_rle" @@ -232,7 +233,7 @@ def test_save_rle(tmp_path): assert test_im.info["compression"] == "tga_rle" -def test_save_l_transparency(tmp_path): +def test_save_l_transparency(tmp_path: Path) -> None: # There are 559 transparent pixels in la.tga. num_transparent = 559 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f0995679b..a16b76e19 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,6 +3,7 @@ from __future__ import annotations import os import warnings from io import BytesIO +from pathlib import Path import pytest @@ -26,7 +27,7 @@ except ImportError: class TestFileTiff: - def test_sanity(self, tmp_path): + def test_sanity(self, tmp_path: Path) -> None: filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename) @@ -58,21 +59,21 @@ class TestFileTiff: pass @pytest.mark.skipif(is_pypy(), reason="Requires CPython") - def test_unclosed_file(self): - def open(): + def test_unclosed_file(self) -> None: + def open() -> None: im = Image.open("Tests/images/multipage.tiff") im.load() with pytest.warns(ResourceWarning): open() - def test_closed_file(self): + def test_closed_file(self) -> None: with warnings.catch_warnings(): im = Image.open("Tests/images/multipage.tiff") im.load() im.close() - def test_seek_after_close(self): + def test_seek_after_close(self) -> None: im = Image.open("Tests/images/multipage.tiff") im.close() @@ -81,12 +82,12 @@ class TestFileTiff: with pytest.raises(ValueError): im.seek(1) - def test_context_manager(self): + def test_context_manager(self) -> None: with warnings.catch_warnings(): with Image.open("Tests/images/multipage.tiff") as im: im.load() - def test_mac_tiff(self): + def test_mac_tiff(self) -> None: # Read RGBa images from macOS [@PIL136] filename = "Tests/images/pil136.tiff" @@ -98,7 +99,7 @@ class TestFileTiff: assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) - def test_bigtiff(self, tmp_path): + def test_bigtiff(self, tmp_path: Path) -> None: with Image.open("Tests/images/hopper_bigtiff.tif") as im: assert_image_equal_tofile(im, "Tests/images/hopper.tif") @@ -109,13 +110,13 @@ class TestFileTiff: outfile = str(tmp_path / "temp.tif") im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2) - def test_set_legacy_api(self): + def test_set_legacy_api(self) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() with pytest.raises(Exception) as e: ifd.legacy_api = None assert str(e.value) == "Not allowing setting of legacy api" - def test_xyres_tiff(self): + def test_xyres_tiff(self) -> None: filename = "Tests/images/pil168.tif" with Image.open(filename) as im: # legacy api @@ -128,7 +129,7 @@ class TestFileTiff: assert im.info["dpi"] == (72.0, 72.0) - def test_xyres_fallback_tiff(self): + def test_xyres_fallback_tiff(self) -> None: filename = "Tests/images/compression.tif" with Image.open(filename) as im: # v2 api @@ -142,7 +143,7 @@ class TestFileTiff: # Fallback "inch". assert im.info["dpi"] == (100.0, 100.0) - def test_int_resolution(self): + def test_int_resolution(self) -> None: filename = "Tests/images/pil168.tif" with Image.open(filename) as im: # Try to read a file where X,Y_RESOLUTION are ints @@ -155,14 +156,14 @@ class TestFileTiff: "resolution_unit, dpi", [(None, 72.8), (2, 72.8), (3, 184.912)], ) - def test_load_float_dpi(self, resolution_unit, dpi): + def test_load_float_dpi(self, resolution_unit, dpi) -> None: with Image.open( "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif" ) as im: assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit assert im.info["dpi"] == (dpi, dpi) - def test_save_float_dpi(self, tmp_path): + def test_save_float_dpi(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: dpi = (72.2, 72.2) @@ -171,7 +172,7 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert reloaded.info["dpi"] == dpi - def test_save_setting_missing_resolution(self): + def test_save_setting_missing_resolution(self) -> None: b = BytesIO() with Image.open("Tests/images/10ct_32bit_128.tiff") as im: im.save(b, format="tiff", resolution=123.45) @@ -179,7 +180,7 @@ class TestFileTiff: assert im.tag_v2[X_RESOLUTION] == 123.45 assert im.tag_v2[Y_RESOLUTION] == 123.45 - def test_invalid_file(self): + def test_invalid_file(self) -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): @@ -190,30 +191,30 @@ class TestFileTiff: TiffImagePlugin.TiffImageFile(invalid_file) TiffImagePlugin.PREFIXES.pop() - def test_bad_exif(self): + def test_bad_exif(self) -> None: with Image.open("Tests/images/hopper_bad_exif.jpg") as i: # Should not raise struct.error. with pytest.warns(UserWarning): i._getexif() - def test_save_rgba(self, tmp_path): + def test_save_rgba(self, tmp_path: Path) -> None: im = hopper("RGBA") outfile = str(tmp_path / "temp.tif") im.save(outfile) - def test_save_unsupported_mode(self, tmp_path): + def test_save_unsupported_mode(self, tmp_path: Path) -> None: im = hopper("HSV") outfile = str(tmp_path / "temp.tif") with pytest.raises(OSError): im.save(outfile) - def test_8bit_s(self): + def test_8bit_s(self) -> None: with Image.open("Tests/images/8bit.s.tif") as im: im.load() assert im.mode == "L" assert im.getpixel((50, 50)) == 184 - def test_little_endian(self): + def test_little_endian(self) -> None: with Image.open("Tests/images/16bit.cropped.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16" @@ -223,7 +224,7 @@ class TestFileTiff: assert b[0] == ord(b"\xe0") assert b[1] == ord(b"\x01") - def test_big_endian(self): + def test_big_endian(self) -> None: with Image.open("Tests/images/16bit.MM.cropped.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16B" @@ -233,7 +234,7 @@ class TestFileTiff: assert b[0] == ord(b"\x01") assert b[1] == ord(b"\xe0") - def test_16bit_r(self): + def test_16bit_r(self) -> None: with Image.open("Tests/images/16bit.r.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16" @@ -242,14 +243,14 @@ class TestFileTiff: assert b[0] == ord(b"\xe0") assert b[1] == ord(b"\x01") - def test_16bit_s(self): + def test_16bit_s(self) -> None: with Image.open("Tests/images/16bit.s.tif") as im: im.load() assert im.mode == "I" assert im.getpixel((0, 0)) == 32767 assert im.getpixel((0, 1)) == 0 - def test_12bit_rawmode(self): + def test_12bit_rawmode(self) -> None: """Are we generating the same interpretation of the image as Imagemagick is?""" @@ -262,7 +263,7 @@ class TestFileTiff: assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") - def test_32bit_float(self): + def test_32bit_float(self) -> None: # Issue 614, specific 32-bit float format path = "Tests/images/10ct_32bit_128.tiff" with Image.open(path) as im: @@ -271,7 +272,7 @@ class TestFileTiff: assert im.getpixel((0, 0)) == -0.4526388943195343 assert im.getextrema() == (-3.140936851501465, 3.140684127807617) - def test_unknown_pixel_mode(self): + def test_unknown_pixel_mode(self) -> None: with pytest.raises(OSError): with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): pass @@ -283,12 +284,12 @@ class TestFileTiff: ("Tests/images/multipage.tiff", 3), ), ) - def test_n_frames(self, path, n_frames): + def test_n_frames(self, path, n_frames) -> None: with Image.open(path) as im: assert im.n_frames == n_frames assert im.is_animated == (n_frames != 1) - def test_eoferror(self): + def test_eoferror(self) -> None: with Image.open("Tests/images/multipage-lastframe.tif") as im: n_frames = im.n_frames @@ -300,7 +301,7 @@ class TestFileTiff: # Test that seeking to the last frame does not raise an error im.seek(n_frames - 1) - def test_multipage(self): + def test_multipage(self) -> None: # issue #862 with Image.open("Tests/images/multipage.tiff") as im: # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue @@ -324,13 +325,13 @@ class TestFileTiff: assert im.size == (20, 20) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) - def test_multipage_last_frame(self): + def test_multipage_last_frame(self) -> None: with Image.open("Tests/images/multipage-lastframe.tif") as im: im.load() assert im.size == (20, 20) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) - def test_frame_order(self): + def test_frame_order(self) -> None: # A frame can't progress to itself after reading with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im: assert im.n_frames == 1 @@ -343,7 +344,7 @@ class TestFileTiff: with Image.open("Tests/images/multipage_out_of_order.tiff") as im: assert im.n_frames == 3 - def test___str__(self): + def test___str__(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: # Act @@ -352,7 +353,7 @@ class TestFileTiff: # Assert assert isinstance(ret, str) - def test_dict(self): + def test_dict(self) -> None: # Arrange filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: @@ -392,7 +393,7 @@ class TestFileTiff: } assert dict(im.tag) == legacy_tags - def test__delitem__(self): + def test__delitem__(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: len_before = len(dict(im.ifd)) @@ -401,36 +402,36 @@ class TestFileTiff: assert len_before == len_after + 1 @pytest.mark.parametrize("legacy_api", (False, True)) - def test_load_byte(self, legacy_api): + def test_load_byte(self, legacy_api) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abc" ret = ifd.load_byte(data, legacy_api) assert ret == b"abc" - def test_load_string(self): + def test_load_string(self) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abc\0" ret = ifd.load_string(data, False) assert ret == "abc" - def test_load_float(self): + def test_load_float(self) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdabcd" ret = ifd.load_float(data, False) assert ret == (1.6777999408082104e22, 1.6777999408082104e22) - def test_load_double(self): + def test_load_double(self) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdefghabcdefgh" ret = ifd.load_double(data, False) assert ret == (8.540883223036124e194, 8.540883223036124e194) - def test_ifd_tag_type(self): + def test_ifd_tag_type(self) -> None: with Image.open("Tests/images/ifd_tag_type.tiff") as im: assert 0x8825 in im.tag_v2 - def test_exif(self, tmp_path): - def check_exif(exif): + def test_exif(self, tmp_path: Path) -> None: + def check_exif(exif) -> None: assert sorted(exif.keys()) == [ 256, 257, @@ -481,7 +482,7 @@ class TestFileTiff: exif = im.getexif() check_exif(exif) - def test_modify_exif(self, tmp_path): + def test_modify_exif(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/ifd_tag_type.tiff") as im: exif = im.getexif() @@ -493,7 +494,7 @@ class TestFileTiff: exif = im.getexif() assert exif[264] == 100 - def test_reload_exif_after_seek(self): + def test_reload_exif_after_seek(self) -> None: with Image.open("Tests/images/multipage.tiff") as im: exif = im.getexif() del exif[256] @@ -501,7 +502,7 @@ class TestFileTiff: assert 256 in exif - def test_exif_frames(self): + def test_exif_frames(self) -> None: # Test that EXIF data can change across frames with Image.open("Tests/images/g4-multi.tiff") as im: assert im.getexif()[273] == (328, 815) @@ -510,7 +511,7 @@ class TestFileTiff: assert im.getexif()[273] == (1408, 1907) @pytest.mark.parametrize("mode", ("1", "L")) - def test_photometric(self, mode, tmp_path): + def test_photometric(self, mode, tmp_path: Path) -> None: filename = str(tmp_path / "temp.tif") im = hopper(mode) im.save(filename, tiffinfo={262: 0}) @@ -518,13 +519,13 @@ class TestFileTiff: assert reloaded.tag_v2[262] == 0 assert_image_equal(im, reloaded) - def test_seek(self): + def test_seek(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: im.seek(0) assert im.tell() == 0 - def test_seek_eof(self): + def test_seek_eof(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: assert im.tell() == 0 @@ -533,21 +534,21 @@ class TestFileTiff: with pytest.raises(EOFError): im.seek(1) - def test__limit_rational_int(self): + def test__limit_rational_int(self) -> None: from PIL.TiffImagePlugin import _limit_rational value = 34 ret = _limit_rational(value, 65536) assert ret == (34, 1) - def test__limit_rational_float(self): + def test__limit_rational_float(self) -> None: from PIL.TiffImagePlugin import _limit_rational value = 22.3 ret = _limit_rational(value, 65536) assert ret == (223, 10) - def test_4bit(self): + def test_4bit(self) -> None: test_file = "Tests/images/hopper_gray_4bpp.tif" original = hopper("L") with Image.open(test_file) as im: @@ -555,7 +556,7 @@ class TestFileTiff: assert im.mode == "L" assert_image_similar(im, original, 7.3) - def test_gray_semibyte_per_pixel(self): + def test_gray_semibyte_per_pixel(self) -> None: test_files = ( ( 24.8, # epsilon @@ -588,7 +589,7 @@ class TestFileTiff: assert im2.mode == "L" assert_image_equal(im, im2) - def test_with_underscores(self, tmp_path): + def test_with_underscores(self, tmp_path: Path) -> None: kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename, **kwargs) @@ -601,7 +602,7 @@ class TestFileTiff: assert im.tag_v2[X_RESOLUTION] == 72 assert im.tag_v2[Y_RESOLUTION] == 36 - def test_roundtrip_tiff_uint16(self, tmp_path): + def test_roundtrip_tiff_uint16(self, tmp_path: Path) -> None: # Test an image of all '0' values pixel_value = 0x1234 infile = "Tests/images/uint16_1_4660.tif" @@ -613,7 +614,7 @@ class TestFileTiff: assert_image_equal_tofile(im, tmpfile) - def test_rowsperstrip(self, tmp_path): + def test_rowsperstrip(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") im = hopper() im.save(outfile, tiffinfo={278: 256}) @@ -621,25 +622,25 @@ class TestFileTiff: with Image.open(outfile) as im: assert im.tag_v2[278] == 256 - def test_strip_raw(self): + def test_strip_raw(self) -> None: infile = "Tests/images/tiff_strip_raw.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_strip_planar_raw(self): + def test_strip_planar_raw(self) -> None: # gdal_translate -of GTiff -co INTERLEAVE=BAND \ # tiff_strip_raw.tif tiff_strip_planar_raw.tiff infile = "Tests/images/tiff_strip_planar_raw.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_strip_planar_raw_with_overviews(self): + def test_strip_planar_raw_with_overviews(self) -> None: # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_tiled_planar_raw(self): + def test_tiled_planar_raw(self) -> None: # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \ # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \ # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff @@ -647,7 +648,7 @@ class TestFileTiff: with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_planar_configuration_save(self, tmp_path): + def test_planar_configuration_save(self, tmp_path: Path) -> None: infile = "Tests/images/tiff_tiled_planar_raw.tif" with Image.open(infile) as im: assert im._planar_configuration == 2 @@ -659,7 +660,7 @@ class TestFileTiff: assert_image_equal_tofile(reloaded, infile) @pytest.mark.parametrize("mode", ("P", "PA")) - def test_palette(self, mode, tmp_path): + def test_palette(self, mode, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") im = hopper(mode) @@ -668,7 +669,7 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - def test_tiff_save_all(self): + def test_tiff_save_all(self) -> None: mp = BytesIO() with Image.open("Tests/images/multipage.tiff") as im: im.save(mp, format="tiff", save_all=True) @@ -698,7 +699,7 @@ class TestFileTiff: with Image.open(mp) as reread: assert reread.n_frames == 3 - def test_saving_icc_profile(self, tmp_path): + def test_saving_icc_profile(self, tmp_path: Path) -> None: # Tests saving TIFF with icc_profile set. # At the time of writing this will only work for non-compressed tiffs # as libtiff does not support embedded ICC profiles, @@ -712,7 +713,7 @@ class TestFileTiff: with Image.open(tmpfile) as reloaded: assert b"Dummy value" == reloaded.info["icc_profile"] - def test_save_icc_profile(self, tmp_path): + def test_save_icc_profile(self, tmp_path: Path) -> None: im = hopper() assert "icc_profile" not in im.info @@ -723,14 +724,14 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert reloaded.info["icc_profile"] == icc_profile - def test_save_bmp_compression(self, tmp_path): + def test_save_bmp_compression(self, tmp_path: Path) -> None: with Image.open("Tests/images/hopper.bmp") as im: assert im.info["compression"] == 0 outfile = str(tmp_path / "temp.tif") im.save(outfile) - def test_discard_icc_profile(self, tmp_path): + def test_discard_icc_profile(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/icc_profile.png") as im: @@ -741,7 +742,7 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert "icc_profile" not in reloaded.info - def test_getxmp(self): + def test_getxmp(self) -> None: with Image.open("Tests/images/lab.tif") as im: if ElementTree is None: with pytest.warns( @@ -756,7 +757,7 @@ class TestFileTiff: assert description[0]["format"] == "image/tiff" assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] - def test_get_photoshop_blocks(self): + def test_get_photoshop_blocks(self) -> None: with Image.open("Tests/images/lab.tif") as im: assert list(im.get_photoshop_blocks().keys()) == [ 1061, @@ -782,7 +783,7 @@ class TestFileTiff: 4001, ] - def test_tiff_chunks(self, tmp_path): + def test_tiff_chunks(self, tmp_path: Path) -> None: tmpfile = str(tmp_path / "temp.tif") im = hopper() @@ -803,7 +804,7 @@ class TestFileTiff: assert_image_equal_tofile(im, tmpfile) - def test_close_on_load_exclusive(self, tmp_path): + def test_close_on_load_exclusive(self, tmp_path: Path) -> None: # similar to test_fd_leak, but runs on unixlike os tmpfile = str(tmp_path / "temp.tif") @@ -816,7 +817,7 @@ class TestFileTiff: im.load() assert fp.closed - def test_close_on_load_nonexclusive(self, tmp_path): + def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None: tmpfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/uint16_1_4660.tif") as im: @@ -838,7 +839,7 @@ class TestFileTiff: not os.path.exists("Tests/images/string_dimension.tiff"), reason="Extra image files not installed", ) - def test_string_dimension(self): + def test_string_dimension(self) -> None: # Assert that an error is raised if one of the dimensions is a string with Image.open("Tests/images/string_dimension.tiff") as im: with pytest.raises(OSError): @@ -846,7 +847,7 @@ class TestFileTiff: @pytest.mark.timeout(6) @pytest.mark.filterwarnings("ignore:Truncated File Read") - def test_timeout(self): + def test_timeout(self) -> None: with Image.open("Tests/images/timeout-6646305047838720") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True im.load() @@ -859,7 +860,7 @@ class TestFileTiff: ], ) @pytest.mark.timeout(2) - def test_oom(self, test_file): + def test_oom(self, test_file) -> None: with pytest.raises(UnidentifiedImageError): with pytest.warns(UserWarning): with Image.open(test_file): @@ -868,7 +869,7 @@ class TestFileTiff: @pytest.mark.skipif(not is_win32(), reason="Windows only") class TestFileTiffW32: - def test_fd_leak(self, tmp_path): + def test_fd_leak(self, tmp_path: Path) -> None: tmpfile = str(tmp_path / "temp.tif") # this is an mmaped file. diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 06689bc90..bb6225d07 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -2,6 +2,7 @@ from __future__ import annotations import io import struct +from pathlib import Path import pytest @@ -13,7 +14,7 @@ from .helper import assert_deep_equal, hopper TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()} -def test_rt_metadata(tmp_path): +def test_rt_metadata(tmp_path: Path) -> None: """Test writing arbitrary metadata into the tiff image directory Use case is ImageJ private tags, one numeric, one arbitrary data. https://github.com/python-pillow/Pillow/issues/291 @@ -79,7 +80,7 @@ def test_rt_metadata(tmp_path): assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) -def test_read_metadata(): +def test_read_metadata() -> None: with Image.open("Tests/images/hopper_g4.tif") as img: assert { "YResolution": IFDRational(4294967295, 113653537), @@ -120,7 +121,7 @@ def test_read_metadata(): } == img.tag.named() -def test_write_metadata(tmp_path): +def test_write_metadata(tmp_path: Path) -> None: """Test metadata writing through the python code""" with Image.open("Tests/images/hopper.tif") as img: f = str(tmp_path / "temp.tiff") @@ -157,7 +158,7 @@ def test_write_metadata(tmp_path): assert value == reloaded[tag], f"{tag} didn't roundtrip" -def test_change_stripbytecounts_tag_type(tmp_path): +def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: out = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper.tif") as im: info = im.tag_v2 @@ -176,19 +177,19 @@ def test_change_stripbytecounts_tag_type(tmp_path): assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG -def test_no_duplicate_50741_tag(): +def test_no_duplicate_50741_tag() -> None: assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["BestQualityScale"] == 50780 -def test_iptc(tmp_path): +def test_iptc(tmp_path: Path) -> None: out = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper.Lab.tif") as im: im.save(out) @pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1"))) -def test_writing_other_types_to_ascii(value, expected, tmp_path): +def test_writing_other_types_to_ascii(value, expected, tmp_path: Path) -> None: info = TiffImagePlugin.ImageFileDirectory_v2() tag = TiffTags.TAGS_V2[271] @@ -205,7 +206,7 @@ def test_writing_other_types_to_ascii(value, expected, tmp_path): @pytest.mark.parametrize("value", (1, IFDRational(1))) -def test_writing_other_types_to_bytes(value, tmp_path): +def test_writing_other_types_to_bytes(value, tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() @@ -221,7 +222,7 @@ def test_writing_other_types_to_bytes(value, tmp_path): assert reloaded.tag_v2[700] == b"\x01" -def test_writing_other_types_to_undefined(tmp_path): +def test_writing_other_types_to_undefined(tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() @@ -237,7 +238,7 @@ def test_writing_other_types_to_undefined(tmp_path): assert reloaded.tag_v2[33723] == b"1" -def test_undefined_zero(tmp_path): +def test_undefined_zero(tmp_path: Path) -> None: # Check that the tag has not been changed since this test was created tag = TiffTags.TAGS_V2[45059] assert tag.type == TiffTags.UNDEFINED @@ -252,7 +253,7 @@ def test_undefined_zero(tmp_path): assert info[45059] == original -def test_empty_metadata(): +def test_empty_metadata() -> None: f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") head = f.read(8) info = TiffImagePlugin.ImageFileDirectory(head) @@ -261,7 +262,7 @@ def test_empty_metadata(): info.load(f) -def test_iccprofile(tmp_path): +def test_iccprofile(tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/1462 out = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper.iccprofile.tif") as im: @@ -272,7 +273,7 @@ def test_iccprofile(tmp_path): assert im.info["icc_profile"] == reloaded.info["icc_profile"] -def test_iccprofile_binary(): +def test_iccprofile_binary() -> None: # https://github.com/python-pillow/Pillow/issues/1526 # We should be able to load this, # but probably won't be able to save it. @@ -282,19 +283,19 @@ def test_iccprofile_binary(): assert im.info["icc_profile"] -def test_iccprofile_save_png(tmp_path): +def test_iccprofile_save_png(tmp_path: Path) -> None: with Image.open("Tests/images/hopper.iccprofile.tif") as im: outfile = str(tmp_path / "temp.png") im.save(outfile) -def test_iccprofile_binary_save_png(tmp_path): +def test_iccprofile_binary_save_png(tmp_path: Path) -> None: with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: outfile = str(tmp_path / "temp.png") im.save(outfile) -def test_exif_div_zero(tmp_path): +def test_exif_div_zero(tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() info[41988] = TiffImagePlugin.IFDRational(0, 0) @@ -307,7 +308,7 @@ def test_exif_div_zero(tmp_path): assert 0 == reloaded.tag_v2[41988].denominator -def test_ifd_unsigned_rational(tmp_path): +def test_ifd_unsigned_rational(tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() @@ -338,7 +339,7 @@ def test_ifd_unsigned_rational(tmp_path): assert 1 == reloaded.tag_v2[41493].denominator -def test_ifd_signed_rational(tmp_path): +def test_ifd_signed_rational(tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() @@ -381,7 +382,7 @@ def test_ifd_signed_rational(tmp_path): assert -1 == reloaded.tag_v2[37380].denominator -def test_ifd_signed_long(tmp_path): +def test_ifd_signed_long(tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() @@ -394,7 +395,7 @@ def test_ifd_signed_long(tmp_path): assert reloaded.tag_v2[37000] == -60000 -def test_empty_values(): +def test_empty_values() -> None: data = io.BytesIO( b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00" @@ -409,7 +410,7 @@ def test_empty_values(): assert 33432 in info -def test_photoshop_info(tmp_path): +def test_photoshop_info(tmp_path: Path) -> None: with Image.open("Tests/images/issue_2278.tif") as im: assert len(im.tag_v2[34377]) == 70 assert isinstance(im.tag_v2[34377], bytes) @@ -420,7 +421,7 @@ def test_photoshop_info(tmp_path): assert isinstance(reloaded.tag_v2[34377], bytes) -def test_too_many_entries(): +def test_too_many_entries() -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() # 277: ("SamplesPerPixel", SHORT, 1), @@ -432,7 +433,7 @@ def test_too_many_entries(): assert ifd[277] == 4 -def test_tag_group_data(): +def test_tag_group_data() -> None: base_ifd = TiffImagePlugin.ImageFileDirectory_v2() interop_ifd = TiffImagePlugin.ImageFileDirectory_v2(group=40965) for ifd in (base_ifd, interop_ifd): @@ -446,7 +447,7 @@ def test_tag_group_data(): assert base_ifd.tagtype[2] != interop_ifd.tagtype[256] -def test_empty_subifd(tmp_path): +def test_empty_subifd(tmp_path: Path) -> None: out = str(tmp_path / "temp.jpg") im = hopper() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index c49418ce3..249846da4 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -4,6 +4,7 @@ import io import re import sys import warnings +from pathlib import Path import pytest @@ -26,7 +27,7 @@ except ImportError: class TestUnsupportedWebp: - def test_unsupported(self): + def test_unsupported(self) -> None: if HAVE_WEBP: WebPImagePlugin.SUPPORTED = False @@ -42,15 +43,15 @@ class TestUnsupportedWebp: @skip_unless_feature("webp") class TestFileWebp: - def setup_method(self): + def setup_method(self) -> None: self.rgb_mode = "RGB" - def test_version(self): + def test_version(self) -> None: _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) - def test_read_rgb(self): + def test_read_rgb(self) -> None: """ Can we read a RGB mode WebP file without error? Does it have the bits we expect? @@ -67,7 +68,7 @@ class TestFileWebp: # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) - def _roundtrip(self, tmp_path, mode, epsilon, args={}): + def _roundtrip(self, tmp_path: Path, mode, epsilon, args={}) -> None: temp_file = str(tmp_path / "temp.webp") hopper(mode).save(temp_file, **args) @@ -93,7 +94,7 @@ class TestFileWebp: target = target.convert(self.rgb_mode) assert_image_similar(image, target, epsilon) - def test_write_rgb(self, tmp_path): + def test_write_rgb(self, tmp_path: Path) -> None: """ Can we write a RGB mode file to webp without error? Does it have the bits we expect? @@ -101,7 +102,7 @@ class TestFileWebp: self._roundtrip(tmp_path, self.rgb_mode, 12.5) - def test_write_method(self, tmp_path): + def test_write_method(self, tmp_path: Path) -> None: self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6}) buffer_no_args = io.BytesIO() @@ -112,7 +113,7 @@ class TestFileWebp: assert buffer_no_args.getbuffer() != buffer_method.getbuffer() @skip_unless_feature("webp_anim") - def test_save_all(self, tmp_path): + def test_save_all(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.webp") im = Image.new("RGB", (1, 1)) im2 = Image.new("RGB", (1, 1), "#f00") @@ -124,14 +125,14 @@ class TestFileWebp: reloaded.seek(1) assert_image_similar(im2, reloaded, 1) - def test_icc_profile(self, tmp_path): + def test_icc_profile(self, tmp_path: Path) -> None: self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) if _webp.HAVE_WEBPANIM: self._roundtrip( tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True} ) - def test_write_unsupported_mode_L(self, tmp_path): + def test_write_unsupported_mode_L(self, tmp_path: Path) -> None: """ Saving a black-and-white file to WebP format should work, and be similar to the original file. @@ -139,7 +140,7 @@ class TestFileWebp: self._roundtrip(tmp_path, "L", 10.0) - def test_write_unsupported_mode_P(self, tmp_path): + def test_write_unsupported_mode_P(self, tmp_path: Path) -> None: """ Saving a palette-based file to WebP format should work, and be similar to the original file. @@ -148,14 +149,14 @@ class TestFileWebp: self._roundtrip(tmp_path, "P", 50.0) @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") - def test_write_encoding_error_message(self, tmp_path): + def test_write_encoding_error_message(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.webp") im = Image.new("RGB", (15000, 15000)) with pytest.raises(ValueError) as e: im.save(temp_file, method=0) assert str(e.value) == "encoding error 6" - def test_WebPEncode_with_invalid_args(self): + def test_WebPEncode_with_invalid_args(self) -> None: """ Calling encoder functions with no arguments should result in an error. """ @@ -166,7 +167,7 @@ class TestFileWebp: with pytest.raises(TypeError): _webp.WebPEncode() - def test_WebPDecode_with_invalid_args(self): + def test_WebPDecode_with_invalid_args(self) -> None: """ Calling decoder functions with no arguments should result in an error. """ @@ -177,14 +178,14 @@ class TestFileWebp: with pytest.raises(TypeError): _webp.WebPDecode() - def test_no_resource_warning(self, tmp_path): + def test_no_resource_warning(self, tmp_path: Path) -> None: file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: temp_file = str(tmp_path / "temp.webp") with warnings.catch_warnings(): image.save(temp_file) - def test_file_pointer_could_be_reused(self): + def test_file_pointer_could_be_reused(self) -> None: file_path = "Tests/images/hopper.webp" with open(file_path, "rb") as blob: Image.open(blob).load() @@ -195,14 +196,14 @@ class TestFileWebp: (0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)), ) @skip_unless_feature("webp_anim") - def test_invalid_background(self, background, tmp_path): + def test_invalid_background(self, background, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.webp") im = hopper() with pytest.raises(OSError): im.save(temp_file, save_all=True, append_images=[im], background=background) @skip_unless_feature("webp_anim") - def test_background_from_gif(self, tmp_path): + def test_background_from_gif(self, tmp_path: Path) -> None: # Save L mode GIF with background with Image.open("Tests/images/no_palette_with_background.gif") as im: out_webp = str(tmp_path / "temp.webp") @@ -227,7 +228,7 @@ class TestFileWebp: assert difference < 5 @skip_unless_feature("webp_anim") - def test_duration(self, tmp_path): + def test_duration(self, tmp_path: Path) -> None: with Image.open("Tests/images/dispose_bgnd.gif") as im: assert im.info["duration"] == 1000 @@ -238,7 +239,7 @@ class TestFileWebp: reloaded.load() assert reloaded.info["duration"] == 1000 - def test_roundtrip_rgba_palette(self, tmp_path): + def test_roundtrip_rgba_palette(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.webp") im = Image.new("RGBA", (1, 1)).convert("P") assert im.mode == "P" diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index cfda35a09..a95434624 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image @@ -14,12 +16,12 @@ from .helper import ( _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") -def setup_module(): +def setup_module() -> None: if _webp.WebPDecoderBuggyAlpha(): pytest.skip("Buggy early version of WebP installed, not testing transparency") -def test_read_rgba(): +def test_read_rgba() -> None: """ Can we read an RGBA mode file without error? Does it have the bits we expect? @@ -39,7 +41,7 @@ def test_read_rgba(): assert_image_similar_tofile(image, "Tests/images/transparent.png", 20.0) -def test_write_lossless_rgb(tmp_path): +def test_write_lossless_rgb(tmp_path: Path) -> None: """ Can we write an RGBA mode file with lossless compression without error? Does it have the bits we expect? @@ -68,7 +70,7 @@ def test_write_lossless_rgb(tmp_path): assert_image_equal(image, pil_image) -def test_write_rgba(tmp_path): +def test_write_rgba(tmp_path: Path) -> None: """ Can we write a RGBA mode file to WebP without error. Does it have the bits we expect? @@ -99,7 +101,7 @@ def test_write_rgba(tmp_path): assert_image_similar(image, pil_image, 1.0) -def test_keep_rgb_values_when_transparent(tmp_path): +def test_keep_rgb_values_when_transparent(tmp_path: Path) -> None: """ Saving transparent pixels should retain their original RGB values when using the "exact" parameter. @@ -128,7 +130,7 @@ def test_keep_rgb_values_when_transparent(tmp_path): assert_image_equal(reloaded.convert("RGB"), image) -def test_write_unsupported_mode_PA(tmp_path): +def test_write_unsupported_mode_PA(tmp_path: Path) -> None: """ Saving a palette-based file with transparency to WebP format should work, and be similar to the original file. diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 426fe7a02..9a730f1f9 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from packaging.version import parse as parse_version @@ -18,7 +20,7 @@ pytestmark = [ ] -def test_n_frames(): +def test_n_frames() -> None: """Ensure that WebP format sets n_frames and is_animated attributes correctly.""" with Image.open("Tests/images/hopper.webp") as im: @@ -30,7 +32,7 @@ def test_n_frames(): assert im.is_animated -def test_write_animation_L(tmp_path): +def test_write_animation_L(tmp_path: Path) -> None: """ Convert an animated GIF to animated WebP, then compare the frame count, and first and last frames to ensure they're visually similar. @@ -60,13 +62,13 @@ def test_write_animation_L(tmp_path): assert_image_similar(im, orig.convert("RGBA"), 32.9) -def test_write_animation_RGB(tmp_path): +def test_write_animation_RGB(tmp_path: Path) -> None: """ Write an animated WebP from RGB frames, and ensure the frames are visually similar to the originals. """ - def check(temp_file): + def check(temp_file) -> None: with Image.open(temp_file) as im: assert im.n_frames == 2 @@ -105,7 +107,7 @@ def test_write_animation_RGB(tmp_path): check(temp_file2) -def test_timestamp_and_duration(tmp_path): +def test_timestamp_and_duration(tmp_path: Path) -> None: """ Try passing a list of durations, and make sure the encoded timestamps and durations are correct. @@ -136,7 +138,7 @@ def test_timestamp_and_duration(tmp_path): ts += durations[frame] -def test_float_duration(tmp_path): +def test_float_duration(tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.webp") with Image.open("Tests/images/iss634.apng") as im: assert im.info["duration"] == 70.0 @@ -148,7 +150,7 @@ def test_float_duration(tmp_path): assert reloaded.info["duration"] == 70 -def test_seeking(tmp_path): +def test_seeking(tmp_path: Path) -> None: """ Create an animated WebP file, and then try seeking through frames in reverse-order, verifying the timestamps and durations are correct. @@ -179,7 +181,7 @@ def test_seeking(tmp_path): ts -= dur -def test_seek_errors(): +def test_seek_errors() -> None: with Image.open("Tests/images/iss634.webp") as im: with pytest.raises(EOFError): im.seek(-1) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index deaf5e380..fea196941 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,6 +1,7 @@ from __future__ import annotations from io import BytesIO +from pathlib import Path import pytest @@ -19,7 +20,7 @@ except ImportError: ElementTree = None -def test_read_exif_metadata(): +def test_read_exif_metadata() -> None: file_path = "Tests/images/flower.webp" with Image.open(file_path) as image: assert image.format == "WEBP" @@ -37,7 +38,7 @@ def test_read_exif_metadata(): assert exif_data == expected_exif -def test_read_exif_metadata_without_prefix(): +def test_read_exif_metadata_without_prefix() -> None: with Image.open("Tests/images/flower2.webp") as im: # Assert prefix is not present assert im.info["exif"][:6] != b"Exif\x00\x00" @@ -49,7 +50,7 @@ def test_read_exif_metadata_without_prefix(): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) -def test_write_exif_metadata(): +def test_write_exif_metadata() -> None: file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() with Image.open(file_path) as image: @@ -63,7 +64,7 @@ def test_write_exif_metadata(): assert webp_exif == expected_exif[6:], "WebP EXIF didn't match" -def test_read_icc_profile(): +def test_read_icc_profile() -> None: file_path = "Tests/images/flower2.webp" with Image.open(file_path) as image: assert image.format == "WEBP" @@ -80,7 +81,7 @@ def test_read_icc_profile(): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) -def test_write_icc_metadata(): +def test_write_icc_metadata() -> None: file_path = "Tests/images/flower2.jpg" test_buffer = BytesIO() with Image.open(file_path) as image: @@ -100,7 +101,7 @@ def test_write_icc_metadata(): @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) -def test_read_no_exif(): +def test_read_no_exif() -> None: file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() with Image.open(file_path) as image: @@ -113,7 +114,7 @@ def test_read_no_exif(): assert not webp_image._getexif() -def test_getxmp(): +def test_getxmp() -> None: with Image.open("Tests/images/flower.webp") as im: assert "xmp" not in im.info assert im.getxmp() == {} @@ -133,7 +134,7 @@ def test_getxmp(): @skip_unless_feature("webp_anim") -def test_write_animated_metadata(tmp_path): +def test_write_animated_metadata(tmp_path: Path) -> None: iccp_data = b"" exif_data = b"" xmp_data = b"" diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 6e1d4c136..b43e3f296 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, WmfImagePlugin @@ -7,7 +9,7 @@ from PIL import Image, WmfImagePlugin from .helper import assert_image_similar_tofile, hopper -def test_load_raw(): +def test_load_raw() -> None: # Test basic EMF open and rendering with Image.open("Tests/images/drawing.emf") as im: if hasattr(Image.core, "drawwmf"): @@ -25,17 +27,17 @@ def test_load_raw(): assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) -def test_load(): +def test_load() -> None: with Image.open("Tests/images/drawing.emf") as im: if hasattr(Image.core, "drawwmf"): assert im.load()[0, 0] == (255, 255, 255) -def test_register_handler(tmp_path): +def test_register_handler(tmp_path: Path) -> None: class TestHandler: methodCalled = False - def save(self, im, fp, filename): + def save(self, im, fp, filename) -> None: self.methodCalled = True handler = TestHandler() @@ -51,12 +53,12 @@ def test_register_handler(tmp_path): WmfImagePlugin.register_handler(original_handler) -def test_load_float_dpi(): +def test_load_float_dpi() -> None: with Image.open("Tests/images/drawing.emf") as im: assert im.info["dpi"] == 1423.7668161434979 -def test_load_set_dpi(): +def test_load_set_dpi() -> None: with Image.open("Tests/images/drawing.wmf") as im: assert im.size == (82, 82) @@ -68,7 +70,7 @@ def test_load_set_dpi(): @pytest.mark.parametrize("ext", (".wmf", ".emf")) -def test_save(ext, tmp_path): +def test_save(ext, tmp_path: Path) -> None: im = hopper() tmpfile = str(tmp_path / ("temp" + ext)) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 69a0a1b38..44dd2541f 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,6 +1,7 @@ from __future__ import annotations from io import BytesIO +from pathlib import Path import pytest @@ -32,14 +33,14 @@ static char basic_bits[] = { """ -def test_pil151(): +def test_pil151() -> None: with Image.open(BytesIO(PIL151)) as im: im.load() assert im.mode == "1" assert im.size == (32, 32) -def test_open(): +def test_open() -> None: # Arrange # Created with `convert hopper.png hopper.xbm` filename = "Tests/images/hopper.xbm" @@ -51,7 +52,7 @@ def test_open(): assert im.size == (128, 128) -def test_open_filename_with_underscore(): +def test_open_filename_with_underscore() -> None: # Arrange # Created with `convert hopper.png hopper_underscore.xbm` filename = "Tests/images/hopper_underscore.xbm" @@ -63,14 +64,14 @@ def test_open_filename_with_underscore(): assert im.size == (128, 128) -def test_invalid_file(): +def test_invalid_file() -> None: invalid_file = "Tests/images/flower.jpg" with pytest.raises(SyntaxError): XbmImagePlugin.XbmImageFile(invalid_file) -def test_save_wrong_mode(tmp_path): +def test_save_wrong_mode(tmp_path: Path) -> None: im = hopper() out = str(tmp_path / "temp.xbm") @@ -78,7 +79,7 @@ def test_save_wrong_mode(tmp_path): im.save(out) -def test_hotspot(tmp_path): +def test_hotspot(tmp_path: Path) -> None: im = hopper("1") out = str(tmp_path / "temp.xbm") diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 6395ae4aa..73aaae6e7 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -21,7 +21,7 @@ def tuple_to_ints(tp): return int(x * 255.0), int(y * 255.0), int(z * 255.0) -def test_sanity(): +def test_sanity() -> None: Image.new("HSV", (100, 100)) @@ -78,7 +78,7 @@ def to_rgb_colorsys(im): return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") -def test_wedge(): +def test_wedge() -> None: src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR) im = src.convert("HSV") comparable = to_hsv_colorsys(src) @@ -110,7 +110,7 @@ def test_wedge(): ) -def test_convert(): +def test_convert() -> None: im = hopper("RGB").convert("HSV") comparable = to_hsv_colorsys(hopper("RGB")) @@ -128,7 +128,7 @@ def test_convert(): ) -def test_hsv_to_rgb(): +def test_hsv_to_rgb() -> None: comparable = to_hsv_colorsys(hopper("RGB")) converted = comparable.convert("RGB") comparable = to_rgb_colorsys(comparable) diff --git a/Tests/test_image.py b/Tests/test_image.py index dd989ad99..67a7d7eca 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -7,6 +7,7 @@ import shutil import sys import tempfile import warnings +from pathlib import Path import pytest @@ -60,19 +61,19 @@ class TestImage: "HSV", ), ) - def test_image_modes_success(self, mode): + def test_image_modes_success(self, mode) -> None: Image.new(mode, (1, 1)) @pytest.mark.parametrize("mode", ("", "bad", "very very long")) - def test_image_modes_fail(self, mode): + def test_image_modes_fail(self, mode) -> None: with pytest.raises(ValueError) as e: Image.new(mode, (1, 1)) assert str(e.value) == "unrecognized image mode" - def test_exception_inheritance(self): + def test_exception_inheritance(self) -> None: assert issubclass(UnidentifiedImageError, OSError) - def test_sanity(self): + def test_sanity(self) -> None: im = Image.new("L", (100, 100)) assert repr(im)[:45] == " None: class Pretty: - def text(self, text): + def text(self, text) -> None: self.pretty_output = text im = Image.new("L", (100, 100)) @@ -108,7 +109,7 @@ class TestImage: im._repr_pretty_(p, None) assert p.pretty_output == "" - def test_open_formats(self): + def test_open_formats(self) -> None: PNGFILE = "Tests/images/hopper.png" JPGFILE = "Tests/images/hopper.jpg" @@ -130,7 +131,7 @@ class TestImage: assert im.mode == "RGB" assert im.size == (128, 128) - def test_width_height(self): + def test_width_height(self) -> None: im = Image.new("RGB", (1, 2)) assert im.width == 1 assert im.height == 2 @@ -138,29 +139,29 @@ class TestImage: with pytest.raises(AttributeError): im.size = (3, 4) - def test_set_mode(self): + def test_set_mode(self) -> None: im = Image.new("RGB", (1, 1)) with pytest.raises(AttributeError): im.mode = "P" - def test_invalid_image(self): + def test_invalid_image(self) -> None: im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): with Image.open(im): pass - def test_bad_mode(self): + def test_bad_mode(self) -> None: with pytest.raises(ValueError): with Image.open("filename", "bad mode"): pass - def test_stringio(self): + def test_stringio(self) -> None: with pytest.raises(ValueError): with Image.open(io.StringIO()): pass - def test_pathlib(self, tmp_path): + def test_pathlib(self, tmp_path: Path) -> None: from PIL.Image import Path with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im: @@ -179,11 +180,11 @@ class TestImage: os.remove(temp_file) im.save(Path(temp_file)) - def test_fp_name(self, tmp_path): + def test_fp_name(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.jpg") class FP: - def write(self, b): + def write(self, b) -> None: pass fp = FP() @@ -192,7 +193,7 @@ class TestImage: im = hopper() im.save(fp) - def test_tempfile(self): + def test_tempfile(self) -> None: # see #1460, pathlib support breaks tempfile.TemporaryFile on py27 # Will error out on save on 3.0.0 im = hopper() @@ -201,13 +202,13 @@ class TestImage: fp.seek(0) assert_image_similar_tofile(im, fp, 20) - def test_unknown_extension(self, tmp_path): + def test_unknown_extension(self, tmp_path: Path) -> None: im = hopper() temp_file = str(tmp_path / "temp.unknown") with pytest.raises(ValueError): im.save(temp_file) - def test_internals(self): + def test_internals(self) -> None: im = Image.new("L", (100, 100)) im.readonly = 1 im._copy() @@ -222,7 +223,7 @@ class TestImage: sys.platform == "cygwin", reason="Test requires opening an mmaped file for writing", ) - def test_readonly_save(self, tmp_path): + def test_readonly_save(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.bmp") shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) @@ -230,7 +231,7 @@ class TestImage: assert im.readonly im.save(temp_file) - def test_dump(self, tmp_path): + def test_dump(self, tmp_path: Path) -> None: im = Image.new("L", (10, 10)) im._dump(str(tmp_path / "temp_L.ppm")) @@ -241,7 +242,7 @@ class TestImage: with pytest.raises(ValueError): im._dump(str(tmp_path / "temp_HSV.ppm")) - def test_comparison_with_other_type(self): + def test_comparison_with_other_type(self) -> None: # Arrange item = Image.new("RGB", (25, 25), "#000") num = 12 @@ -251,7 +252,7 @@ class TestImage: assert item is not None assert item != num - def test_expand_x(self): + def test_expand_x(self) -> None: # Arrange im = hopper() orig_size = im.size @@ -264,7 +265,7 @@ class TestImage: assert im.size[0] == orig_size[0] + 2 * xmargin assert im.size[1] == orig_size[1] + 2 * xmargin - def test_expand_xy(self): + def test_expand_xy(self) -> None: # Arrange im = hopper() orig_size = im.size @@ -278,12 +279,12 @@ class TestImage: assert im.size[0] == orig_size[0] + 2 * xmargin assert im.size[1] == orig_size[1] + 2 * ymargin - def test_getbands(self): + def test_getbands(self) -> None: # Assert assert hopper("RGB").getbands() == ("R", "G", "B") assert hopper("YCbCr").getbands() == ("Y", "Cb", "Cr") - def test_getchannel_wrong_params(self): + def test_getchannel_wrong_params(self) -> None: im = hopper() with pytest.raises(ValueError): @@ -295,7 +296,7 @@ class TestImage: with pytest.raises(ValueError): im.getchannel("1") - def test_getchannel(self): + def test_getchannel(self) -> None: im = hopper("YCbCr") Y, Cb, Cr = im.split() @@ -306,7 +307,7 @@ class TestImage: assert_image_equal(Cr, im.getchannel(2)) assert_image_equal(Cr, im.getchannel("Cr")) - def test_getbbox(self): + def test_getbbox(self) -> None: # Arrange im = hopper() @@ -316,7 +317,7 @@ class TestImage: # Assert assert bbox == (0, 0, 128, 128) - def test_ne(self): + def test_ne(self) -> None: # Arrange im1 = Image.new("RGB", (25, 25), "black") im2 = Image.new("RGB", (25, 25), "white") @@ -324,7 +325,7 @@ class TestImage: # Act / Assert assert im1 != im2 - def test_alpha_composite(self): + def test_alpha_composite(self) -> None: # https://stackoverflow.com/questions/3374878 # Arrange expected_colors = sorted( @@ -355,7 +356,7 @@ class TestImage: img_colors = sorted(img.getcolors()) assert img_colors == expected_colors - def test_alpha_inplace(self): + def test_alpha_inplace(self) -> None: src = Image.new("RGBA", (128, 128), "blue") over = Image.new("RGBA", (128, 128), "red") @@ -407,7 +408,7 @@ class TestImage: with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), (0, -1)) - def test_register_open_duplicates(self): + def test_register_open_duplicates(self) -> None: # Arrange factory, accept = Image.OPEN["JPEG"] id_length = len(Image.ID) @@ -418,7 +419,7 @@ class TestImage: # Assert assert len(Image.ID) == id_length - def test_registered_extensions_uninitialized(self): + def test_registered_extensions_uninitialized(self) -> None: # Arrange Image._initialized = 0 @@ -428,7 +429,7 @@ class TestImage: # Assert assert Image._initialized == 2 - def test_registered_extensions(self): + def test_registered_extensions(self) -> None: # Arrange # Open an image to trigger plugin registration with Image.open("Tests/images/rgb.jpg"): @@ -442,7 +443,7 @@ class TestImage: for ext in [".cur", ".icns", ".tif", ".tiff"]: assert ext in extensions - def test_effect_mandelbrot(self): + def test_effect_mandelbrot(self) -> None: # Arrange size = (512, 512) extent = (-3, -2.5, 2, 2.5) @@ -455,7 +456,7 @@ class TestImage: assert im.size == (512, 512) assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png") - def test_effect_mandelbrot_bad_arguments(self): + def test_effect_mandelbrot_bad_arguments(self) -> None: # Arrange size = (512, 512) # Get coordinates the wrong way round: @@ -467,7 +468,7 @@ class TestImage: with pytest.raises(ValueError): Image.effect_mandelbrot(size, extent, quality) - def test_effect_noise(self): + def test_effect_noise(self) -> None: # Arrange size = (100, 100) sigma = 128 @@ -485,7 +486,7 @@ class TestImage: p4 = im.getpixel((0, 4)) assert_not_all_same([p0, p1, p2, p3, p4]) - def test_effect_spread(self): + def test_effect_spread(self) -> None: # Arrange im = hopper() distance = 10 @@ -497,7 +498,7 @@ class TestImage: assert im.size == (128, 128) assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110) - def test_effect_spread_zero(self): + def test_effect_spread_zero(self) -> None: # Arrange im = hopper() distance = 0 @@ -508,7 +509,7 @@ class TestImage: # Assert assert_image_equal(im, im2) - def test_check_size(self): + def test_check_size(self) -> None: # Checking that the _check_size function throws value errors when we want it to with pytest.raises(ValueError): Image.new("RGB", 0) # not a tuple @@ -537,10 +538,10 @@ class TestImage: "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower" ) @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) - def test_empty_image(self, size): + def test_empty_image(self, size) -> None: Image.new("RGB", size) - def test_storage_neg(self): + def test_storage_neg(self) -> None: # Storage.c accepted negative values for xsize, ysize. Was # test_neg_ppm, but the core function for that has been # removed Calling directly into core to test the error in @@ -549,13 +550,13 @@ class TestImage: with pytest.raises(ValueError): Image.core.fill("RGB", (2, -2), (0, 0, 0)) - def test_one_item_tuple(self): + def test_one_item_tuple(self) -> None: for mode in ("I", "F", "L"): im = Image.new(mode, (100, 100), (5,)) px = im.load() assert px[0, 0] == 5 - def test_linear_gradient_wrong_mode(self): + def test_linear_gradient_wrong_mode(self) -> None: # Arrange wrong_mode = "RGB" @@ -564,7 +565,7 @@ class TestImage: Image.linear_gradient(wrong_mode) @pytest.mark.parametrize("mode", ("L", "P", "I", "F")) - def test_linear_gradient(self, mode): + def test_linear_gradient(self, mode) -> None: # Arrange target_file = "Tests/images/linear_gradient.png" @@ -580,7 +581,7 @@ class TestImage: target = target.convert(mode) assert_image_equal(im, target) - def test_radial_gradient_wrong_mode(self): + def test_radial_gradient_wrong_mode(self) -> None: # Arrange wrong_mode = "RGB" @@ -589,7 +590,7 @@ class TestImage: Image.radial_gradient(wrong_mode) @pytest.mark.parametrize("mode", ("L", "P", "I", "F")) - def test_radial_gradient(self, mode): + def test_radial_gradient(self, mode) -> None: # Arrange target_file = "Tests/images/radial_gradient.png" @@ -605,7 +606,7 @@ class TestImage: target = target.convert(mode) assert_image_equal(im, target) - def test_register_extensions(self): + def test_register_extensions(self) -> None: test_format = "a" exts = ["b", "c"] for ext in exts: @@ -621,7 +622,7 @@ class TestImage: assert ext_individual == ext_multiple - def test_remap_palette(self): + def test_remap_palette(self) -> None: # Test identity transform with Image.open("Tests/images/hopper.gif") as im: assert_image_equal(im, im.remap_palette(list(range(256)))) @@ -640,7 +641,7 @@ class TestImage: with pytest.raises(ValueError): im.remap_palette(None) - def test_remap_palette_transparency(self): + def test_remap_palette_transparency(self) -> None: im = Image.new("P", (1, 2), (0, 0, 0)) im.putpixel((0, 1), (255, 0, 0)) im.info["transparency"] = 0 @@ -655,7 +656,7 @@ class TestImage: im_remapped = im.remap_palette([1, 0]) assert "transparency" not in im_remapped.info - def test__new(self): + def test__new(self) -> None: im = hopper("RGB") im_p = hopper("P") @@ -664,7 +665,7 @@ class TestImage: blank_p.palette = None blank_pa.palette = None - def _make_new(base_image, image, palette_result=None): + def _make_new(base_image, image, palette_result=None) -> None: new_image = base_image._new(image.im) assert new_image.mode == image.mode assert new_image.size == image.size @@ -679,7 +680,7 @@ class TestImage: _make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette()) - def test_p_from_rgb_rgba(self): + def test_p_from_rgb_rgba(self) -> None: for mode, color in [ ("RGB", "#DDEEFF"), ("RGB", (221, 238, 255)), @@ -689,7 +690,7 @@ class TestImage: expected = Image.new(mode, (100, 100), color) assert_image_equal(im.convert(mode), expected) - def test_no_resource_warning_on_save(self, tmp_path): + def test_no_resource_warning_on_save(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/835 # Arrange test_file = "Tests/images/hopper.png" @@ -700,7 +701,7 @@ class TestImage: with warnings.catch_warnings(): im.save(temp_file) - def test_no_new_file_on_error(self, tmp_path): + def test_no_new_file_on_error(self, tmp_path: Path) -> None: temp_file = str(tmp_path / "temp.jpg") im = Image.new("RGB", (0, 0)) @@ -709,10 +710,10 @@ class TestImage: assert not os.path.exists(temp_file) - def test_load_on_nonexclusive_multiframe(self): + def test_load_on_nonexclusive_multiframe(self) -> None: with open("Tests/images/frozenpond.mpo", "rb") as fp: - def act(fp): + def act(fp) -> None: im = Image.open(fp) im.load() @@ -723,7 +724,7 @@ class TestImage: assert not fp.closed - def test_empty_exif(self): + def test_empty_exif(self) -> None: with Image.open("Tests/images/exif.png") as im: exif = im.getexif() assert dict(exif) @@ -739,7 +740,7 @@ class TestImage: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_exif_jpeg(self, tmp_path): + def test_exif_jpeg(self, tmp_path: Path) -> None: with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian exif = im.getexif() assert 258 not in exif @@ -785,7 +786,7 @@ class TestImage: @skip_unless_feature("webp") @skip_unless_feature("webp_anim") - def test_exif_webp(self, tmp_path): + def test_exif_webp(self, tmp_path: Path) -> None: with Image.open("Tests/images/hopper.webp") as im: exif = im.getexif() assert exif == {} @@ -795,7 +796,7 @@ class TestImage: exif[40963] = 455 exif[305] = "Pillow test" - def check_exif(): + def check_exif() -> None: with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() assert reloaded_exif[258] == 8 @@ -807,7 +808,7 @@ class TestImage: im.save(out, exif=exif, save_all=True) check_exif() - def test_exif_png(self, tmp_path): + def test_exif_png(self, tmp_path: Path) -> None: with Image.open("Tests/images/exif.png") as im: exif = im.getexif() assert exif == {274: 1} @@ -823,7 +824,7 @@ class TestImage: reloaded_exif = reloaded.getexif() assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"} - def test_exif_interop(self): + def test_exif_interop(self) -> None: with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() assert exif.get_ifd(0xA005) == { @@ -837,7 +838,7 @@ class TestImage: reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) - def test_exif_ifd1(self): + def test_exif_ifd1(self) -> None: with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() assert exif.get_ifd(ExifTags.IFD.IFD1) == { @@ -849,7 +850,7 @@ class TestImage: 283: 180.0, } - def test_exif_ifd(self): + def test_exif_ifd(self) -> None: with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() del exif.get_ifd(0x8769)[0xA005] @@ -858,7 +859,7 @@ class TestImage: reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) - def test_exif_load_from_fp(self): + def test_exif_load_from_fp(self) -> None: with Image.open("Tests/images/flower.jpg") as im: data = im.info["exif"] if data.startswith(b"Exif\x00\x00"): @@ -879,7 +880,7 @@ class TestImage: 34665: 196, } - def test_exif_hide_offsets(self): + def test_exif_hide_offsets(self) -> None: with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() @@ -905,18 +906,18 @@ class TestImage: assert exif.get_ifd(0xA005) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero_tobytes(self, size): + def test_zero_tobytes(self, size) -> None: im = Image.new("RGB", size) assert im.tobytes() == b"" @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero_frombytes(self, size): + def test_zero_frombytes(self, size) -> None: Image.frombytes("RGB", size, b"") im = Image.new("RGB", size) im.frombytes(b"") - def test_has_transparency_data(self): + def test_has_transparency_data(self) -> None: for mode in ("1", "L", "P", "RGB"): im = Image.new(mode, (1, 1)) assert not im.has_transparency_data @@ -941,7 +942,7 @@ class TestImage: assert im.palette.mode == "RGBA" assert im.has_transparency_data - def test_apply_transparency(self): + def test_apply_transparency(self) -> None: im = Image.new("P", (1, 1)) im.putpalette((0, 0, 0, 1, 1, 1)) assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1} @@ -970,7 +971,7 @@ class TestImage: im.apply_transparency() assert im.palette.colors[(27, 35, 6, 214)] == 24 - def test_constants(self): + def test_constants(self) -> None: for enum in ( Image.Transpose, Image.Transform, @@ -995,7 +996,7 @@ class TestImage: "01r_00.pcx", ], ) - def test_overrun(self, path): + def test_overrun(self, path) -> None: """For overrun completeness, test as: valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c """ @@ -1009,7 +1010,7 @@ class TestImage: assert buffer_overrun or truncated - def test_fli_overrun2(self): + def test_fli_overrun2(self) -> None: with Image.open("Tests/images/fli_overrun2.bin") as im: try: im.seek(1) @@ -1017,12 +1018,12 @@ class TestImage: except OSError as e: assert str(e) == "buffer overrun when reading image file" - def test_exit_fp(self): + def test_exit_fp(self) -> None: with Image.new("L", (1, 1)) as im: pass assert not hasattr(im, "fp") - def test_close_graceful(self, caplog): + def test_close_graceful(self, caplog) -> None: with Image.open("Tests/images/hopper.jpg") as im: copy = im.copy() with caplog.at_level(logging.DEBUG): @@ -1043,7 +1044,7 @@ def mock_encode(*args): class TestRegistry: - def test_encode_registry(self): + def test_encode_registry(self) -> None: Image.register_encoder("MOCK", mock_encode) assert "MOCK" in Image.ENCODERS @@ -1052,6 +1053,6 @@ class TestRegistry: assert isinstance(enc, MockEncoder) assert enc.args == ("RGB", "args", "extra") - def test_encode_registry_fail(self): + def test_encode_registry_fail(self) -> None: with pytest.raises(OSError): Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",)) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 4ae56fae0..00cd4e7a9 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -35,16 +35,16 @@ class AccessTest: _need_cffi_access = False @classmethod - def setup_class(cls): + def setup_class(cls) -> None: Image.USE_CFFI_ACCESS = cls._need_cffi_access @classmethod - def teardown_class(cls): + def teardown_class(cls) -> None: Image.USE_CFFI_ACCESS = cls._init_cffi_access class TestImagePutPixel(AccessTest): - def test_sanity(self): + def test_sanity(self) -> None: im1 = hopper() im2 = Image.new(im1.mode, im1.size, 0) @@ -81,7 +81,7 @@ class TestImagePutPixel(AccessTest): assert_image_equal(im1, im2) - def test_sanity_negative_index(self): + def test_sanity_negative_index(self) -> None: im1 = hopper() im2 = Image.new(im1.mode, im1.size, 0) @@ -119,7 +119,7 @@ class TestImagePutPixel(AccessTest): assert_image_equal(im1, im2) @pytest.mark.skipif(numpy is None, reason="NumPy not installed") - def test_numpy(self): + def test_numpy(self) -> None: im = hopper() pix = im.load() @@ -138,7 +138,7 @@ class TestImageGetPixel(AccessTest): return (16, 32, 49) return tuple(range(1, bands + 1)) - def check(self, mode, expected_color=None): + def check(self, mode, expected_color=None) -> None: if self._need_cffi_access and mode.startswith("BGR;"): pytest.skip("Support not added to deprecated module for BGR;* modes") @@ -222,10 +222,10 @@ class TestImageGetPixel(AccessTest): "YCbCr", ), ) - def test_basic(self, mode): + def test_basic(self, mode) -> None: self.check(mode) - def test_list(self): + def test_list(self) -> None: im = hopper() assert im.getpixel([0, 0]) == (20, 20, 70) @@ -233,14 +233,14 @@ class TestImageGetPixel(AccessTest): @pytest.mark.parametrize( "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1) ) - def test_signedness(self, mode, expected_color): + def test_signedness(self, mode, expected_color) -> None: # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* self.check(mode, expected_color) @pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) - def test_p_putpixel_rgb_rgba(self, mode, color): + def test_p_putpixel_rgb_rgba(self, mode, color) -> None: im = Image.new(mode, (1, 1)) im.putpixel((0, 0), color) @@ -264,7 +264,7 @@ class TestCffiGetPixel(TestImageGetPixel): class TestCffi(AccessTest): _need_cffi_access = True - def _test_get_access(self, im): + def _test_get_access(self, im) -> None: """Do we get the same thing as the old pixel access Using private interfaces, forcing a capi access and @@ -282,7 +282,7 @@ class TestCffi(AccessTest): with pytest.raises(ValueError): access[(access.xsize + 1, access.ysize + 1)] - def test_get_vs_c(self): + def test_get_vs_c(self) -> None: with pytest.warns(DeprecationWarning): rgb = hopper("RGB") rgb.load() @@ -301,7 +301,7 @@ class TestCffi(AccessTest): # im = Image.new('I;32B', (10, 10), 2**10) # self._test_get_access(im) - def _test_set_access(self, im, color): + def _test_set_access(self, im, color) -> None: """Are we writing the correct bits into the image? Using private interfaces, forcing a capi access and @@ -322,7 +322,7 @@ class TestCffi(AccessTest): with pytest.raises(ValueError): access[(0, 0)] = color - def test_set_vs_c(self): + def test_set_vs_c(self) -> None: rgb = hopper("RGB") with pytest.warns(DeprecationWarning): rgb.load() @@ -345,11 +345,11 @@ class TestCffi(AccessTest): # self._test_set_access(im, 2**13-1) @pytest.mark.filterwarnings("ignore::DeprecationWarning") - def test_not_implemented(self): + def test_not_implemented(self) -> None: assert PyAccess.new(hopper("BGR;15")) is None # ref https://github.com/python-pillow/Pillow/pull/2009 - def test_reference_counting(self): + def test_reference_counting(self) -> None: size = 10 for _ in range(10): @@ -361,7 +361,7 @@ class TestCffi(AccessTest): assert px[i, 0] == 0 @pytest.mark.parametrize("mode", ("P", "PA")) - def test_p_putpixel_rgb_rgba(self, mode): + def test_p_putpixel_rgb_rgba(self, mode) -> None: for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)): im = Image.new(mode, (1, 1)) with pytest.warns(DeprecationWarning): @@ -379,7 +379,7 @@ class TestImagePutPixelError(AccessTest): INVALID_TYPES = ["foo", 1.0, None] @pytest.mark.parametrize("mode", IMAGE_MODES1) - def test_putpixel_type_error1(self, mode): + def test_putpixel_type_error1(self, mode) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises(TypeError, match="color must be int or tuple"): @@ -402,14 +402,14 @@ class TestImagePutPixelError(AccessTest): ), ), ) - def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match): + def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match) -> None: im = hopper(mode) for band_number in band_numbers: with pytest.raises(TypeError, match=match): im.putpixel((0, 0), (0,) * band_number) @pytest.mark.parametrize("mode", IMAGE_MODES2) - def test_putpixel_type_error2(self, mode): + def test_putpixel_type_error2(self, mode) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises( @@ -418,7 +418,7 @@ class TestImagePutPixelError(AccessTest): im.putpixel((0, 0), v) @pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2) - def test_putpixel_overflow_error(self, mode): + def test_putpixel_overflow_error(self, mode) -> None: im = hopper(mode) with pytest.raises(OverflowError): im.putpixel((0, 0), 2**80) @@ -427,7 +427,7 @@ class TestImagePutPixelError(AccessTest): class TestEmbeddable: @pytest.mark.xfail(reason="failing test") @pytest.mark.skipif(not is_win32(), reason="requires Windows") - def test_embeddable(self): + def test_embeddable(self) -> None: import ctypes from setuptools.command.build_ext import new_compiler diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 0dacb3157..0125ab56a 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -12,12 +12,12 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed") im = hopper().resize((128, 100)) -def test_toarray(): +def test_toarray() -> None: def test(mode): ai = numpy.array(im.convert(mode)) return ai.shape, ai.dtype.str, ai.nbytes - def test_with_dtype(dtype): + def test_with_dtype(dtype) -> None: ai = numpy.array(im, dtype=dtype) assert ai.dtype == dtype @@ -46,11 +46,11 @@ def test_toarray(): numpy.array(im_truncated) -def test_fromarray(): +def test_fromarray() -> None: class Wrapper: """Class with API matching Image.fromarray""" - def __init__(self, img, arr_params): + def __init__(self, img, arr_params) -> None: self.img = img self.__array_interface__ = arr_params @@ -89,7 +89,7 @@ def test_fromarray(): Image.fromarray(wrapped) -def test_fromarray_palette(): +def test_fromarray_palette() -> None: # Arrange i = im.convert("L") a = numpy.array(i) diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 08c40af1f..54474311a 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -19,7 +19,7 @@ def draft_roundtrip(in_mode, in_size, req_mode, req_size): return im -def test_size(): +def test_size() -> None: for in_size, req_size, out_size in [ ((435, 361), (2048, 2048), (435, 361)), # bigger ((435, 361), (435, 361), (435, 361)), # same @@ -48,7 +48,7 @@ def test_size(): assert im.size == out_size -def test_mode(): +def test_mode() -> None: for in_mode, req_mode, out_mode in [ ("RGB", "1", "RGB"), ("RGB", "L", "L"), @@ -68,7 +68,7 @@ def test_mode(): assert im.mode == out_mode -def test_several_drafts(): +def test_several_drafts() -> None: im = draft_roundtrip("L", (128, 128), None, (64, 64)) im.draft(None, (64, 64)) im.load() diff --git a/Tests/test_image_entropy.py b/Tests/test_image_entropy.py index fce161224..01107ae6b 100644 --- a/Tests/test_image_entropy.py +++ b/Tests/test_image_entropy.py @@ -3,7 +3,7 @@ from __future__ import annotations from .helper import hopper -def test_entropy(): +def test_entropy() -> None: def entropy(mode): return hopper(mode).entropy() diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 3fa5dd242..2b6787933 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -36,7 +36,7 @@ from .helper import assert_image_equal, hopper ), ) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity(filter_to_apply, mode): +def test_sanity(filter_to_apply, mode) -> None: im = hopper(mode) if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter): out = im.filter(filter_to_apply) @@ -45,7 +45,7 @@ def test_sanity(filter_to_apply, mode): @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity_error(mode): +def test_sanity_error(mode) -> None: with pytest.raises(TypeError): im = hopper(mode) im.filter("hello") @@ -53,7 +53,7 @@ def test_sanity_error(mode): # crashes on small images @pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3))) -def test_crash(size): +def test_crash(size) -> None: im = Image.new("RGB", size) im.filter(ImageFilter.SMOOTH) @@ -67,7 +67,7 @@ def test_crash(size): ("RGB", ((4, 0, 0), (0, 0, 0))), ), ) -def test_modefilter(mode, expected): +def test_modefilter(mode, expected) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -90,7 +90,7 @@ def test_modefilter(mode, expected): ("F", (0.0, 4.0, 8.0)), ), ) -def test_rankfilter(mode, expected): +def test_rankfilter(mode, expected) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -106,7 +106,7 @@ def test_rankfilter(mode, expected): @pytest.mark.parametrize( "filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter) ) -def test_rankfilter_error(filter): +def test_rankfilter_error(filter) -> None: with pytest.raises(ValueError): im = Image.new("P", (3, 3), None) im.putdata(list(range(9))) @@ -117,27 +117,27 @@ def test_rankfilter_error(filter): im.filter(filter).getpixel((1, 1)) -def test_rankfilter_properties(): +def test_rankfilter_properties() -> None: rankfilter = ImageFilter.RankFilter(1, 2) assert rankfilter.size == 1 assert rankfilter.rank == 2 -def test_builtinfilter_p(): +def test_builtinfilter_p() -> None: builtin_filter = ImageFilter.BuiltinFilter() with pytest.raises(ValueError): builtin_filter.filter(hopper("P")) -def test_kernel_not_enough_coefficients(): +def test_kernel_not_enough_coefficients() -> None: with pytest.raises(ValueError): ImageFilter.Kernel((3, 3), (0, 0)) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_3x3(mode): +def test_consistency_3x3(mode) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -163,7 +163,7 @@ def test_consistency_3x3(mode): @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_5x5(mode): +def test_consistency_5x5(mode) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss_more" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -199,7 +199,7 @@ def test_consistency_5x5(mode): (2, -2), ), ) -def test_invalid_box_blur_filter(radius): +def test_invalid_box_blur_filter(radius) -> None: with pytest.raises(ValueError): ImageFilter.BoxBlur(radius) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 6bbc4da9a..0107fdcc4 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -5,7 +5,7 @@ from PIL import Image from .helper import hopper -def test_extrema(): +def test_extrema() -> None: def extrema(mode): return hopper(mode).getextrema() @@ -20,7 +20,7 @@ def test_extrema(): assert extrema("I;16") == (1, 255) -def test_true_16(): +def test_true_16() -> None: with Image.open("Tests/images/16_bit_noise.tif") as im: assert im.mode == "I;16" extrema = im.getextrema() diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 4340f46f6..e7304c98f 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -5,7 +5,7 @@ from PIL import Image from .helper import hopper -def test_palette(): +def test_palette() -> None: def palette(mode): p = hopper(mode).getpalette() if p: @@ -23,7 +23,7 @@ def test_palette(): assert palette("YCbCr") is None -def test_palette_rawmode(): +def test_palette_rawmode() -> None: im = Image.new("P", (1, 1)) im.putpalette((1, 2, 3)) diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index 36f8ba575..5b1a9ee2d 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -10,14 +10,14 @@ from PIL import Image from .helper import hopper -def test_sanity(): +def test_sanity() -> None: im = hopper() pix = im.load() assert pix[0, 0] == (20, 20, 70) -def test_close(): +def test_close() -> None: im = Image.open("Tests/images/hopper.gif") im.close() with pytest.raises(ValueError): @@ -26,7 +26,7 @@ def test_close(): im.getpixel((0, 0)) -def test_close_after_load(caplog): +def test_close_after_load(caplog) -> None: im = Image.open("Tests/images/hopper.gif") im.load() with caplog.at_level(logging.DEBUG): @@ -34,7 +34,7 @@ def test_close_after_load(caplog): assert len(caplog.records) == 0 -def test_contextmanager(): +def test_contextmanager() -> None: fn = None with Image.open("Tests/images/hopper.gif") as im: fn = im.fp.fileno() @@ -44,7 +44,7 @@ def test_contextmanager(): os.fstat(fn) -def test_contextmanager_non_exclusive_fp(): +def test_contextmanager_non_exclusive_fp() -> None: with open("Tests/images/hopper.gif", "rb") as fp: with Image.open(fp): pass diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 3c1d494fa..8e94aafc5 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -7,7 +7,7 @@ from PIL import Image, ImageMode from .helper import hopper -def test_sanity(): +def test_sanity() -> None: with hopper() as im: im.mode @@ -69,7 +69,7 @@ def test_sanity(): ) def test_properties( mode, expected_base, expected_type, expected_bands, expected_band_names -): +) -> None: assert Image.getmodebase(mode) == expected_base assert Image.getmodetype(mode) == expected_type assert Image.getmodebands(mode) == expected_bands diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index fd117f9db..34a2f8f3d 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -11,7 +11,7 @@ class TestImagingPaste: masks = {} size = 128 - def assert_9points_image(self, im, expected): + def assert_9points_image(self, im, expected) -> None: expected = [ point[0] if im.mode == "L" else point[: len(im.mode)] for point in expected ] @@ -29,7 +29,7 @@ class TestImagingPaste: ] assert actual == expected - def assert_9points_paste(self, im, im2, mask, expected): + def assert_9points_paste(self, im, im2, mask, expected) -> None: im3 = im.copy() im3.paste(im2, (0, 0), mask) self.assert_9points_image(im3, expected) @@ -106,7 +106,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_solid(self, mode): + def test_image_solid(self, mode) -> None: im = Image.new(mode, (200, 200), "red") im2 = getattr(self, "gradient_" + mode) @@ -116,7 +116,7 @@ class TestImagingPaste: assert_image_equal(im, im2) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_1(self, mode): + def test_image_mask_1(self, mode) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -138,7 +138,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_L(self, mode): + def test_image_mask_L(self, mode) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -160,7 +160,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_LA(self, mode): + def test_image_mask_LA(self, mode) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -182,7 +182,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_RGBA(self, mode): + def test_image_mask_RGBA(self, mode) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -204,7 +204,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_RGBa(self, mode): + def test_image_mask_RGBa(self, mode) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -226,7 +226,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_solid(self, mode): + def test_color_solid(self, mode) -> None: im = Image.new(mode, (200, 200), "black") rect = (12, 23, 128 + 12, 128 + 23) @@ -239,7 +239,7 @@ class TestImagingPaste: assert sum(head[:255]) == 0 @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_1(self, mode): + def test_color_mask_1(self, mode) -> None: im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)]) color = (10, 20, 30, 40)[: len(mode)] @@ -261,7 +261,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_L(self, mode): + def test_color_mask_L(self, mode) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" @@ -283,7 +283,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_RGBA(self, mode): + def test_color_mask_RGBA(self, mode) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" @@ -305,7 +305,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_RGBa(self, mode): + def test_color_mask_RGBa(self, mode) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" @@ -326,7 +326,7 @@ class TestImagingPaste: ], ) - def test_different_sizes(self): + def test_different_sizes(self) -> None: im = Image.new("RGB", (100, 100)) im2 = Image.new("RGB", (50, 50)) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 2648af8fa..103019916 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -10,7 +10,7 @@ from PIL import Image from .helper import assert_image_equal, hopper -def test_sanity(): +def test_sanity() -> None: im1 = hopper() data = list(im1.getdata()) @@ -29,7 +29,7 @@ def test_sanity(): assert_image_equal(im1, im2) -def test_long_integers(): +def test_long_integers() -> None: # see bug-200802-systemerror def put(value): im = Image.new("RGBA", (1, 1)) @@ -46,19 +46,19 @@ def test_long_integers(): assert put(sys.maxsize) == (255, 255, 255, 127) -def test_pypy_performance(): +def test_pypy_performance() -> None: im = Image.new("L", (256, 256)) im.putdata(list(range(256)) * 256) -def test_mode_with_L_with_float(): +def test_mode_with_L_with_float() -> None: im = Image.new("L", (1, 1), 0) im.putdata([2.0]) assert im.getpixel((0, 0)) == 2 @pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B")) -def test_mode_i(mode): +def test_mode_i(mode) -> None: src = hopper("L") data = list(src.getdata()) im = Image.new(mode, src.size, 0) @@ -68,7 +68,7 @@ def test_mode_i(mode): assert list(im.getdata()) == target -def test_mode_F(): +def test_mode_F() -> None: src = hopper("L") data = list(src.getdata()) im = Image.new("F", src.size, 0) @@ -79,7 +79,7 @@ def test_mode_F(): @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) -def test_mode_BGR(mode): +def test_mode_BGR(mode) -> None: data = [(16, 32, 49), (32, 32, 98)] im = Image.new(mode, (1, 2)) im.putdata(data) @@ -87,7 +87,7 @@ def test_mode_BGR(mode): assert list(im.getdata()) == data -def test_array_B(): +def test_array_B() -> None: # shouldn't segfault # see https://github.com/python-pillow/Pillow/issues/1008 @@ -98,7 +98,7 @@ def test_array_B(): assert len(im.getdata()) == len(arr) -def test_array_F(): +def test_array_F() -> None: # shouldn't segfault # see https://github.com/python-pillow/Pillow/issues/1008 @@ -109,7 +109,7 @@ def test_array_F(): assert len(im.getdata()) == len(arr) -def test_not_flattened(): +def test_not_flattened() -> None: im = Image.new("L", (1, 1)) with pytest.raises(TypeError): im.putdata([[0]]) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 43b65be2b..ffe2551d2 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -7,7 +7,7 @@ from PIL import Image, ImagePalette from .helper import assert_image_equal, assert_image_equal_tofile, hopper -def test_putpalette(): +def test_putpalette() -> None: def palette(mode): im = hopper(mode).copy() im.putpalette(list(range(256)) * 3) @@ -43,7 +43,7 @@ def test_putpalette(): im.putpalette(list(range(256)) * 3) -def test_imagepalette(): +def test_imagepalette() -> None: im = hopper("P") im.putpalette(ImagePalette.negative()) assert_image_equal_tofile(im.convert("RGB"), "Tests/images/palette_negative.png") @@ -57,7 +57,7 @@ def test_imagepalette(): assert_image_equal_tofile(im.convert("RGB"), "Tests/images/palette_wedge.png") -def test_putpalette_with_alpha_values(): +def test_putpalette_with_alpha_values() -> None: with Image.open("Tests/images/transparent.gif") as im: expected = im.convert("RGBA") @@ -81,19 +81,19 @@ def test_putpalette_with_alpha_values(): ("RGBAX", (1, 2, 3, 4, 0)), ), ) -def test_rgba_palette(mode, palette): +def test_rgba_palette(mode, palette) -> None: im = Image.new("P", (1, 1)) im.putpalette(palette, mode) assert im.getpalette() == [1, 2, 3] assert im.palette.colors == {(1, 2, 3, 4): 0} -def test_empty_palette(): +def test_empty_palette() -> None: im = Image.new("P", (1, 1)) assert im.getpalette() == [] -def test_undefined_palette_index(): +def test_undefined_palette_index() -> None: im = Image.new("P", (1, 1), 3) im.putpalette((1, 2, 3)) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 0) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index ba9100415..c29830a7e 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -48,7 +48,7 @@ gradients_image.load() ((1, 3), (10, 4)), ), ) -def test_args_factor(size, expected): +def test_args_factor(size, expected) -> None: im = Image.new("L", (10, 10)) assert expected == im.reduce(size).size @@ -56,7 +56,7 @@ def test_args_factor(size, expected): @pytest.mark.parametrize( "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) ) -def test_args_factor_error(size, expected_error): +def test_args_factor_error(size, expected_error) -> None: im = Image.new("L", (10, 10)) with pytest.raises(expected_error): im.reduce(size) @@ -69,7 +69,7 @@ def test_args_factor_error(size, expected_error): ((5, 5, 6, 6), (1, 1)), ), ) -def test_args_box(size, expected): +def test_args_box(size, expected) -> None: im = Image.new("L", (10, 10)) assert expected == im.reduce(2, size).size @@ -86,14 +86,14 @@ def test_args_box(size, expected): ((5, 0, 5, 10), ValueError), ), ) -def test_args_box_error(size, expected_error): +def test_args_box_error(size, expected_error) -> None: im = Image.new("L", (10, 10)) with pytest.raises(expected_error): im.reduce(2, size).size @pytest.mark.parametrize("mode", ("P", "1", "I;16")) -def test_unsupported_modes(mode): +def test_unsupported_modes(mode) -> None: im = Image.new("P", (10, 10)) with pytest.raises(ValueError): im.reduce(3) @@ -119,14 +119,16 @@ def get_image(mode): return im.crop((0, 0, im.width, im.height - 5)) -def compare_reduce_with_box(im, factor): +def compare_reduce_with_box(im, factor) -> None: box = (11, 13, 146, 164) reduced = im.reduce(factor, box=box) reference = im.crop(box).reduce(factor) assert reduced == reference -def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): +def compare_reduce_with_reference( + im, factor, average_diff: float = 0.4, max_diff: int = 1 +) -> None: """Image.reduce() should look very similar to Image.resize(BOX). A reference image is compiled from a large source area @@ -171,7 +173,7 @@ def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): assert_compare_images(reduced, reference, average_diff, max_diff) -def assert_compare_images(a, b, max_average_diff, max_diff=255): +def assert_compare_images(a, b, max_average_diff, max_diff: int = 255) -> None: assert a.mode == b.mode, f"got mode {repr(a.mode)}, expected {repr(b.mode)}" assert a.size == b.size, f"got size {repr(a.size)}, expected {repr(b.size)}" @@ -199,20 +201,20 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255): @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_L(factor): +def test_mode_L(factor) -> None: im = get_image("L") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_LA(factor): +def test_mode_LA(factor) -> None: im = get_image("LA") compare_reduce_with_reference(im, factor, 0.8, 5) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_LA_opaque(factor): +def test_mode_LA_opaque(factor) -> None: im = get_image("LA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) @@ -221,27 +223,27 @@ def test_mode_LA_opaque(factor): @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_La(factor): +def test_mode_La(factor) -> None: im = get_image("La") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGB(factor): +def test_mode_RGB(factor) -> None: im = get_image("RGB") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBA(factor): +def test_mode_RGBA(factor) -> None: im = get_image("RGBA") compare_reduce_with_reference(im, factor, 0.8, 5) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBA_opaque(factor): +def test_mode_RGBA_opaque(factor) -> None: im = get_image("RGBA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) @@ -250,27 +252,27 @@ def test_mode_RGBA_opaque(factor): @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBa(factor): +def test_mode_RGBa(factor) -> None: im = get_image("RGBa") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_I(factor): +def test_mode_I(factor) -> None: im = get_image("I") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_F(factor): +def test_mode_F(factor) -> None: im = get_image("F") compare_reduce_with_reference(im, factor, 0, 0) compare_reduce_with_box(im, factor) @skip_unless_feature("jpg_2000") -def test_jpeg2k(): +def test_jpeg2k() -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: assert im.reduce(2).size == (320, 240) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index af730dce1..f4c9eb0e6 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -16,7 +16,7 @@ from .helper import ( class TestImagingResampleVulnerability: # see https://github.com/python-pillow/Pillow/issues/1710 - def test_overflow(self): + def test_overflow(self) -> None: im = hopper("L") size_too_large = 0x100000008 // 4 size_normal = 1000 # unimportant @@ -28,7 +28,7 @@ class TestImagingResampleVulnerability: # any resampling filter will do here im.im.resize((xsize, ysize), Image.Resampling.BILINEAR) - def test_invalid_size(self): + def test_invalid_size(self) -> None: im = hopper() # Should not crash @@ -40,7 +40,7 @@ class TestImagingResampleVulnerability: with pytest.raises(ValueError): im.resize((100, -100)) - def test_modify_after_resizing(self): + def test_modify_after_resizing(self) -> None: im = hopper("RGB") # get copy with same size copy = im.resize(im.size) @@ -83,7 +83,7 @@ class TestImagingCoreResampleAccuracy: s_px[size[0] - x - 1, y] = 255 - val return sample - def check_case(self, case, sample): + def check_case(self, case, sample) -> None: s_px = sample.load() c_px = case.load() for y in range(case.size[1]): @@ -103,7 +103,7 @@ class TestImagingCoreResampleAccuracy: ) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_box(self, mode): + def test_reduce_box(self, mode) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off @@ -114,7 +114,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_bilinear(self, mode): + def test_reduce_bilinear(self, mode) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off @@ -125,7 +125,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_hamming(self, mode): + def test_reduce_hamming(self, mode) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off @@ -136,7 +136,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_bicubic(self, mode): + def test_reduce_bicubic(self, mode) -> None: case = self.make_case(mode, (12, 12), 0xE1) case = case.resize((6, 6), Image.Resampling.BICUBIC) # fmt: off @@ -148,7 +148,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (6, 6))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_lanczos(self, mode): + def test_reduce_lanczos(self, mode) -> None: case = self.make_case(mode, (16, 16), 0xE1) case = case.resize((8, 8), Image.Resampling.LANCZOS) # fmt: off @@ -161,7 +161,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (8, 8))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_box(self, mode): + def test_enlarge_box(self, mode) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off @@ -172,7 +172,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_bilinear(self, mode): + def test_enlarge_bilinear(self, mode) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off @@ -183,7 +183,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_hamming(self, mode): + def test_enlarge_hamming(self, mode) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off @@ -194,7 +194,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_bicubic(self, mode): + def test_enlarge_bicubic(self, mode) -> None: case = self.make_case(mode, (4, 4), 0xE1) case = case.resize((8, 8), Image.Resampling.BICUBIC) # fmt: off @@ -207,7 +207,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (8, 8))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_lanczos(self, mode): + def test_enlarge_lanczos(self, mode) -> None: case = self.make_case(mode, (6, 6), 0xE1) case = case.resize((12, 12), Image.Resampling.LANCZOS) data = ( @@ -221,7 +221,7 @@ class TestImagingCoreResampleAccuracy: for channel in case.split(): self.check_case(channel, self.make_sample(data, (12, 12))) - def test_box_filter_correct_range(self): + def test_box_filter_correct_range(self) -> None: im = Image.new("RGB", (8, 8), "#1688ff").resize( (100, 100), Image.Resampling.BOX ) @@ -234,7 +234,7 @@ class TestCoreResampleConsistency: im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] - def run_case(self, case): + def run_case(self, case) -> None: channel, color = case px = channel.load() for x in range(channel.size[0]): @@ -243,7 +243,7 @@ class TestCoreResampleConsistency: message = f"{px[x, y]} != {color} for pixel {(x, y)}" assert px[x, y] == color, message - def test_8u(self): + def test_8u(self) -> None: im, color = self.make_case("RGB", (0, 64, 255)) r, g, b = im.split() self.run_case((r, color[0])) @@ -251,13 +251,13 @@ class TestCoreResampleConsistency: self.run_case((b, color[2])) self.run_case(self.make_case("L", 12)) - def test_32i(self): + def test_32i(self) -> None: self.run_case(self.make_case("I", 12)) self.run_case(self.make_case("I", 0x7FFFFFFF)) self.run_case(self.make_case("I", -12)) self.run_case(self.make_case("I", -1 << 31)) - def test_32f(self): + def test_32f(self) -> None: self.run_case(self.make_case("F", 1)) self.run_case(self.make_case("F", 3.40282306074e38)) self.run_case(self.make_case("F", 1.175494e-38)) @@ -275,7 +275,7 @@ class TestCoreResampleAlphaCorrect: px[x, y] = tuple(pix) return i - def run_levels_case(self, i): + def run_levels_case(self, i) -> None: px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} @@ -285,7 +285,7 @@ class TestCoreResampleAlphaCorrect: ) @pytest.mark.xfail(reason="Current implementation isn't precise enough") - def test_levels_rgba(self): + def test_levels_rgba(self) -> None: case = self.make_levels_case("RGBA") self.run_levels_case(case.resize((512, 32), Image.Resampling.BOX)) self.run_levels_case(case.resize((512, 32), Image.Resampling.BILINEAR)) @@ -294,7 +294,7 @@ class TestCoreResampleAlphaCorrect: self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS)) @pytest.mark.xfail(reason="Current implementation isn't precise enough") - def test_levels_la(self): + def test_levels_la(self) -> None: case = self.make_levels_case("LA") self.run_levels_case(case.resize((512, 32), Image.Resampling.BOX)) self.run_levels_case(case.resize((512, 32), Image.Resampling.BILINEAR)) @@ -312,7 +312,7 @@ class TestCoreResampleAlphaCorrect: px[x + xdiv4, y + ydiv4] = clean_pixel return i - def run_dirty_case(self, i, clean_pixel): + def run_dirty_case(self, i, clean_pixel) -> None: px = i.load() for y in range(i.size[1]): for x in range(i.size[0]): @@ -323,7 +323,7 @@ class TestCoreResampleAlphaCorrect: ) assert px[x, y][:3] == clean_pixel, message - def test_dirty_pixels_rgba(self): + def test_dirty_pixels_rgba(self) -> None: case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.Resampling.BOX), (255, 255, 0)) self.run_dirty_case( @@ -339,7 +339,7 @@ class TestCoreResampleAlphaCorrect: case.resize((20, 20), Image.Resampling.LANCZOS), (255, 255, 0) ) - def test_dirty_pixels_la(self): + def test_dirty_pixels_la(self) -> None: case = self.make_dirty_case("LA", (255, 128), (0, 0)) self.run_dirty_case(case.resize((20, 20), Image.Resampling.BOX), (255,)) self.run_dirty_case(case.resize((20, 20), Image.Resampling.BILINEAR), (255,)) @@ -355,22 +355,22 @@ class TestCoreResamplePasses: yield assert Image.core.get_stats()["new_count"] - count == diff - def test_horizontal(self): + def test_horizontal(self) -> None: im = hopper("L") with self.count(1): im.resize((im.size[0] - 10, im.size[1]), Image.Resampling.BILINEAR) - def test_vertical(self): + def test_vertical(self) -> None: im = hopper("L") with self.count(1): im.resize((im.size[0], im.size[1] - 10), Image.Resampling.BILINEAR) - def test_both(self): + def test_both(self) -> None: im = hopper("L") with self.count(2): im.resize((im.size[0] - 10, im.size[1] - 10), Image.Resampling.BILINEAR) - def test_box_horizontal(self): + def test_box_horizontal(self) -> None: im = hopper("L") box = (20, 0, im.size[0] - 20, im.size[1]) with self.count(1): @@ -380,7 +380,7 @@ class TestCoreResamplePasses: cropped = im.crop(box).resize(im.size, Image.Resampling.BILINEAR) assert_image_similar(with_box, cropped, 0.1) - def test_box_vertical(self): + def test_box_vertical(self) -> None: im = hopper("L") box = (0, 20, im.size[0], im.size[1] - 20) with self.count(1): @@ -392,7 +392,7 @@ class TestCoreResamplePasses: class TestCoreResampleCoefficients: - def test_reduce(self): + def test_reduce(self) -> None: test_color = 254 for size in range(400000, 400010, 2): @@ -404,7 +404,7 @@ class TestCoreResampleCoefficients: if px[2, 0] != test_color // 2: assert test_color // 2 == px[2, 0] - def test_non_zero_coefficients(self): + def test_non_zero_coefficients(self) -> None: # regression test for the wrong coefficients calculation # due to bug https://github.com/python-pillow/Pillow/issues/2161 im = Image.new("RGBA", (1280, 1280), (0x20, 0x40, 0x60, 0xFF)) @@ -432,7 +432,7 @@ class TestCoreResampleBox: Image.Resampling.LANCZOS, ), ) - def test_wrong_arguments(self, resample): + def test_wrong_arguments(self, resample) -> None: im = hopper() im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height)) @@ -478,7 +478,7 @@ class TestCoreResampleBox: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_tiles(self): + def test_tiles(self) -> None: with Image.open("Tests/images/flower.jpg") as im: assert im.size == (480, 360) dst_size = (251, 188) @@ -491,7 +491,7 @@ class TestCoreResampleBox: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_subsample(self): + def test_subsample(self) -> None: # This test shows advantages of the subpixel resizing # after supersampling (e.g. during JPEG decoding). with Image.open("Tests/images/flower.jpg") as im: @@ -518,14 +518,14 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR) ) - def test_formats(self, mode, resample): + def test_formats(self, mode, resample) -> None: im = hopper(mode) box = (20, 20, im.size[0] - 20, im.size[1] - 20) with_box = im.resize((32, 32), resample, box) cropped = im.crop(box).resize((32, 32), resample) assert_image_similar(cropped, with_box, 0.4) - def test_passthrough(self): + def test_passthrough(self) -> None: # When no resize is required im = hopper() @@ -539,7 +539,7 @@ class TestCoreResampleBox: assert res.size == size assert_image_equal(res, im.crop(box), f">>> {size} {box}") - def test_no_passthrough(self): + def test_no_passthrough(self) -> None: # When resize is required im = hopper() @@ -558,7 +558,7 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) ) - def test_skip_horizontal(self, flt): + def test_skip_horizontal(self, flt) -> None: # Can skip resize for one dimension im = hopper() @@ -581,7 +581,7 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) ) - def test_skip_vertical(self, flt): + def test_skip_vertical(self, flt) -> None: # Can skip resize for one dimension im = hopper() diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index e63fef2c1..51e0f5854 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -12,7 +12,7 @@ from .helper import ( ) -def rotate(im, mode, angle, center=None, translate=None): +def rotate(im, mode, angle, center=None, translate=None) -> None: out = im.rotate(angle, center=center, translate=translate) assert out.mode == mode assert out.size == im.size # default rotate clips output @@ -27,13 +27,13 @@ def rotate(im, mode, angle, center=None, translate=None): @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) -def test_mode(mode): +def test_mode(mode) -> None: im = hopper(mode) rotate(im, mode, 45) @pytest.mark.parametrize("angle", (0, 90, 180, 270)) -def test_angle(angle): +def test_angle(angle) -> None: with Image.open("Tests/images/test-card.png") as im: rotate(im, im.mode, angle) @@ -42,12 +42,12 @@ def test_angle(angle): @pytest.mark.parametrize("angle", (0, 45, 90, 180, 270)) -def test_zero(angle): +def test_zero(angle) -> None: im = Image.new("RGB", (0, 0)) rotate(im, im.mode, angle) -def test_resample(): +def test_resample() -> None: # Target image creation, inspected by eye. # >>> im = Image.open('Tests/images/hopper.ppm') # >>> im = im.rotate(45, resample=Image.Resampling.BICUBIC, expand=True) @@ -64,7 +64,7 @@ def test_resample(): assert_image_similar(im, target, epsilon) -def test_center_0(): +def test_center_0() -> None: im = hopper() im = im.rotate(45, center=(0, 0), resample=Image.Resampling.BICUBIC) @@ -75,7 +75,7 @@ def test_center_0(): assert_image_similar(im, target, 15) -def test_center_14(): +def test_center_14() -> None: im = hopper() im = im.rotate(45, center=(14, 14), resample=Image.Resampling.BICUBIC) @@ -86,7 +86,7 @@ def test_center_14(): assert_image_similar(im, target, 10) -def test_translate(): +def test_translate() -> None: im = hopper() with Image.open("Tests/images/hopper_45.png") as target: target_origin = (target.size[1] / 2 - 64) - 5 @@ -99,7 +99,7 @@ def test_translate(): assert_image_similar(im, target, 1) -def test_fastpath_center(): +def test_fastpath_center() -> None: # if the center is -1,-1 and we rotate by 90<=x<=270 the # resulting image should be black for angle in (90, 180, 270): @@ -107,7 +107,7 @@ def test_fastpath_center(): assert_image_equal(im, Image.new("RGB", im.size, "black")) -def test_fastpath_translate(): +def test_fastpath_translate() -> None: # if we post-translate by -128 # resulting image should be black for angle in (0, 90, 180, 270): @@ -115,26 +115,26 @@ def test_fastpath_translate(): assert_image_equal(im, Image.new("RGB", im.size, "black")) -def test_center(): +def test_center() -> None: im = hopper() rotate(im, im.mode, 45, center=(0, 0)) rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0)) rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0)) -def test_rotate_no_fill(): +def test_rotate_no_fill() -> None: im = Image.new("RGB", (100, 100), "green") im = im.rotate(45) assert_image_equal_tofile(im, "Tests/images/rotate_45_no_fill.png") -def test_rotate_with_fill(): +def test_rotate_with_fill() -> None: im = Image.new("RGB", (100, 100), "green") im = im.rotate(45, fillcolor="white") assert_image_equal_tofile(im, "Tests/images/rotate_45_with_fill.png") -def test_alpha_rotate_no_fill(): +def test_alpha_rotate_no_fill() -> None: # Alpha images are handled differently internally im = Image.new("RGBA", (10, 10), "green") im = im.rotate(45, expand=1) @@ -142,7 +142,7 @@ def test_alpha_rotate_no_fill(): assert corner == (0, 0, 0, 0) -def test_alpha_rotate_with_fill(): +def test_alpha_rotate_with_fill() -> None: # Alpha images are handled differently internally im = Image.new("RGBA", (10, 10), "green") im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 7fa5692aa..6aeeea2ed 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -14,14 +14,14 @@ from .helper import ( ) -def test_sanity(): +def test_sanity() -> None: im = hopper() assert im.thumbnail((100, 100)) is None assert im.size == (100, 100) -def test_aspect(): +def test_aspect() -> None: im = Image.new("L", (128, 128)) im.thumbnail((100, 100)) assert im.size == (100, 100) @@ -67,19 +67,19 @@ def test_aspect(): assert im.size == (75, 23) # ratio is 3.260869565217 -def test_division_by_zero(): +def test_division_by_zero() -> None: im = Image.new("L", (200, 2)) im.thumbnail((75, 75)) assert im.size == (75, 1) -def test_float(): +def test_float() -> None: im = Image.new("L", (128, 128)) im.thumbnail((99.9, 99.9)) assert im.size == (99, 99) -def test_no_resize(): +def test_no_resize() -> None: # Check that draft() can resize the image to the destination size with Image.open("Tests/images/hopper.jpg") as im: im.draft(None, (64, 64)) @@ -92,7 +92,7 @@ def test_no_resize(): @skip_unless_feature("libtiff") -def test_load_first(): +def test_load_first() -> None: # load() may change the size of the image # Test that thumbnail() is calling it before performing size calculations with Image.open("Tests/images/g4_orientation_5.tif") as im: @@ -106,7 +106,7 @@ def test_load_first(): assert im.size == (590, 88) -def test_load_first_unless_jpeg(): +def test_load_first_unless_jpeg() -> None: # Test that thumbnail() still uses draft() for JPEG with Image.open("Tests/images/hopper.jpg") as im: draft = im.draft @@ -124,7 +124,7 @@ def test_load_first_unless_jpeg(): # valgrind test is failing with memory allocated in libjpeg @pytest.mark.valgrind_known_error(reason="Known Failing") -def test_DCT_scaling_edges(): +def test_DCT_scaling_edges() -> None: # Make an image with red borders and size (N * 8) + 1 to cross DCT grid im = Image.new("RGB", (257, 257), "red") im.paste(Image.new("RGB", (235, 235)), (11, 11)) @@ -138,7 +138,7 @@ def test_DCT_scaling_edges(): assert_image_similar(thumb, ref, 1.5) -def test_reducing_gap_values(): +def test_reducing_gap_values() -> None: im = hopper() im.thumbnail((18, 18), Image.Resampling.BICUBIC) @@ -155,7 +155,7 @@ def test_reducing_gap_values(): assert_image_similar(ref, im, 3.5) -def test_reducing_gap_for_DCT_scaling(): +def test_reducing_gap_for_DCT_scaling() -> None: with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale ref.draft(None, (18 * 3, 18 * 3)) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 0fe9fd1d5..1067dd563 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -10,7 +10,7 @@ from .helper import assert_image_equal, assert_image_similar, hopper class TestImageTransform: - def test_sanity(self): + def test_sanity(self) -> None: im = hopper() for transform in ( @@ -31,7 +31,7 @@ class TestImageTransform: ): assert_image_equal(im, im.transform(im.size, transform)) - def test_info(self): + def test_info(self) -> None: comment = b"File written by Adobe Photoshop\xa8 4.0" with Image.open("Tests/images/hopper.gif") as im: @@ -41,14 +41,14 @@ class TestImageTransform: new_im = im.transform((100, 100), transform) assert new_im.info["comment"] == comment - def test_palette(self): + def test_palette(self) -> None: with Image.open("Tests/images/hopper.gif") as im: transformed = im.transform( im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] ) assert im.palette.palette == transformed.palette.palette - def test_extent(self): + def test_extent(self) -> None: im = hopper("RGB") (w, h) = im.size transformed = im.transform( @@ -63,7 +63,7 @@ class TestImageTransform: # undone -- precision? assert_image_similar(transformed, scaled, 23) - def test_quad(self): + def test_quad(self) -> None: # one simple quad transform, equivalent to scale & crop upper left quad im = hopper("RGB") (w, h) = im.size @@ -91,7 +91,7 @@ class TestImageTransform: ("LA", (76, 0)), ), ) - def test_fill(self, mode, expected_pixel): + def test_fill(self, mode, expected_pixel) -> None: im = hopper(mode) (w, h) = im.size transformed = im.transform( @@ -103,7 +103,7 @@ class TestImageTransform: ) assert transformed.getpixel((w - 1, h - 1)) == expected_pixel - def test_mesh(self): + def test_mesh(self) -> None: # this should be a checkerboard of halfsized hoppers in ul, lr im = hopper("RGBA") (w, h) = im.size @@ -142,7 +142,7 @@ class TestImageTransform: assert_image_equal(blank, transformed.crop((w // 2, 0, w, h // 2))) assert_image_equal(blank, transformed.crop((0, h // 2, w // 2, h))) - def _test_alpha_premult(self, op): + def _test_alpha_premult(self, op) -> None: # create image with half white, half black, # with the black half transparent. # do op, @@ -158,13 +158,13 @@ class TestImageTransform: hist = im_background.histogram() assert 40 * 10 == hist[-1] - def test_alpha_premult_resize(self): + def test_alpha_premult_resize(self) -> None: def op(im, sz): return im.resize(sz, Image.Resampling.BILINEAR) self._test_alpha_premult(op) - def test_alpha_premult_transform(self): + def test_alpha_premult_transform(self) -> None: def op(im, sz): (w, h) = im.size return im.transform( @@ -173,7 +173,7 @@ class TestImageTransform: self._test_alpha_premult(op) - def _test_nearest(self, op, mode): + def _test_nearest(self, op, mode) -> None: # create white image with half transparent, # do op, # the image should remain white with half transparent @@ -196,14 +196,14 @@ class TestImageTransform: ) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_resize(self, mode): + def test_nearest_resize(self, mode) -> None: def op(im, sz): return im.resize(sz, Image.Resampling.NEAREST) self._test_nearest(op, mode) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_transform(self, mode): + def test_nearest_transform(self, mode) -> None: def op(im, sz): (w, h) = im.size return im.transform( @@ -212,7 +212,7 @@ class TestImageTransform: self._test_nearest(op, mode) - def test_blank_fill(self): + def test_blank_fill(self) -> None: # attempting to hit # https://github.com/python-pillow/Pillow/issues/254 reported # @@ -234,13 +234,13 @@ class TestImageTransform: self.test_mesh() - def test_missing_method_data(self): + def test_missing_method_data(self) -> None: with hopper() as im: with pytest.raises(ValueError): im.transform((100, 100), None) @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) - def test_unknown_resampling_filter(self, resample): + def test_unknown_resampling_filter(self, resample) -> None: with hopper() as im: (w, h) = im.size with pytest.raises(ValueError): @@ -263,7 +263,7 @@ class TestImageTransformAffine: (270, Image.Transpose.ROTATE_270), ), ) - def test_rotate(self, deg, transpose): + def test_rotate(self, deg, transpose) -> None: im = self._test_image() angle = -math.radians(deg) @@ -313,7 +313,7 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_resize(self, scale, epsilon_scale, resample, epsilon): + def test_resize(self, scale, epsilon_scale, resample, epsilon) -> None: im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) @@ -342,7 +342,7 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_translate(self, x, y, epsilon_scale, resample, epsilon): + def test_translate(self, x, y, epsilon_scale, resample, epsilon) -> None: im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 2f0614385..94f57e066 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -15,7 +15,7 @@ WHITE = (255, 255, 255) GRAY = 128 -def test_sanity(): +def test_sanity() -> None: im = hopper("L") ImageChops.constant(im, 128) @@ -48,7 +48,7 @@ def test_sanity(): ImageChops.offset(im, 10, 20) -def test_add(): +def test_add() -> None: # Arrange with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: @@ -60,7 +60,7 @@ def test_add(): assert new.getpixel((50, 50)) == ORANGE -def test_add_scale_offset(): +def test_add_scale_offset() -> None: # Arrange with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: @@ -72,7 +72,7 @@ def test_add_scale_offset(): assert new.getpixel((50, 50)) == (202, 151, 100) -def test_add_clip(): +def test_add_clip() -> None: # Arrange im = hopper() @@ -83,7 +83,7 @@ def test_add_clip(): assert new.getpixel((50, 50)) == (255, 255, 254) -def test_add_modulo(): +def test_add_modulo() -> None: # Arrange with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: @@ -95,7 +95,7 @@ def test_add_modulo(): assert new.getpixel((50, 50)) == ORANGE -def test_add_modulo_no_clip(): +def test_add_modulo_no_clip() -> None: # Arrange im = hopper() @@ -106,7 +106,7 @@ def test_add_modulo_no_clip(): assert new.getpixel((50, 50)) == (224, 76, 254) -def test_blend(): +def test_blend() -> None: # Arrange with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: @@ -118,7 +118,7 @@ def test_blend(): assert new.getpixel((50, 50)) == BROWN -def test_constant(): +def test_constant() -> None: # Arrange im = Image.new("RGB", (20, 10)) @@ -131,7 +131,7 @@ def test_constant(): assert new.getpixel((19, 9)) == GRAY -def test_darker_image(): +def test_darker_image() -> None: # Arrange with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: @@ -142,7 +142,7 @@ def test_darker_image(): assert_image_equal(new, im2) -def test_darker_pixel(): +def test_darker_pixel() -> None: # Arrange im1 = hopper() with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: @@ -153,7 +153,7 @@ def test_darker_pixel(): assert new.getpixel((50, 50)) == (240, 166, 0) -def test_difference(): +def test_difference() -> None: # Arrange with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1: with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2: @@ -164,7 +164,7 @@ def test_difference(): assert new.getbbox() == (25, 25, 76, 76) -def test_difference_pixel(): +def test_difference_pixel() -> None: # Arrange im1 = hopper() with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: @@ -175,7 +175,7 @@ def test_difference_pixel(): assert new.getpixel((50, 50)) == (240, 166, 128) -def test_duplicate(): +def test_duplicate() -> None: # Arrange im = hopper() @@ -186,7 +186,7 @@ def test_duplicate(): assert_image_equal(new, im) -def test_invert(): +def test_invert() -> None: # Arrange with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: # Act @@ -198,7 +198,7 @@ def test_invert(): assert new.getpixel((50, 50)) == CYAN -def test_lighter_image(): +def test_lighter_image() -> None: # Arrange with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: @@ -209,7 +209,7 @@ def test_lighter_image(): assert_image_equal(new, im1) -def test_lighter_pixel(): +def test_lighter_pixel() -> None: # Arrange im1 = hopper() with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: @@ -220,7 +220,7 @@ def test_lighter_pixel(): assert new.getpixel((50, 50)) == (255, 255, 127) -def test_multiply_black(): +def test_multiply_black() -> None: """If you multiply an image with a solid black image, the result is black.""" # Arrange @@ -234,7 +234,7 @@ def test_multiply_black(): assert_image_equal(new, black) -def test_multiply_green(): +def test_multiply_green() -> None: # Arrange with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: green = Image.new("RGB", im.size, "green") @@ -248,7 +248,7 @@ def test_multiply_green(): assert new.getpixel((50, 50)) == BLACK -def test_multiply_white(): +def test_multiply_white() -> None: """If you multiply with a solid white image, the image is unaffected.""" # Arrange im1 = hopper() @@ -261,7 +261,7 @@ def test_multiply_white(): assert_image_equal(new, im1) -def test_offset(): +def test_offset() -> None: # Arrange xoffset = 45 yoffset = 20 @@ -278,7 +278,7 @@ def test_offset(): assert ImageChops.offset(im, xoffset) == ImageChops.offset(im, xoffset, xoffset) -def test_screen(): +def test_screen() -> None: # Arrange with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: @@ -290,7 +290,7 @@ def test_screen(): assert new.getpixel((50, 50)) == ORANGE -def test_subtract(): +def test_subtract() -> None: # Arrange with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: @@ -303,7 +303,7 @@ def test_subtract(): assert new.getpixel((50, 52)) == BLACK -def test_subtract_scale_offset(): +def test_subtract_scale_offset() -> None: # Arrange with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: @@ -315,7 +315,7 @@ def test_subtract_scale_offset(): assert new.getpixel((50, 50)) == (100, 202, 100) -def test_subtract_clip(): +def test_subtract_clip() -> None: # Arrange im1 = hopper() with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: @@ -326,7 +326,7 @@ def test_subtract_clip(): assert new.getpixel((50, 50)) == (0, 0, 127) -def test_subtract_modulo(): +def test_subtract_modulo() -> None: # Arrange with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: @@ -339,7 +339,7 @@ def test_subtract_modulo(): assert new.getpixel((50, 52)) == BLACK -def test_subtract_modulo_no_clip(): +def test_subtract_modulo_no_clip() -> None: # Arrange im1 = hopper() with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: @@ -350,7 +350,7 @@ def test_subtract_modulo_no_clip(): assert new.getpixel((50, 50)) == (241, 167, 127) -def test_soft_light(): +def test_soft_light() -> None: # Arrange with Image.open("Tests/images/hopper.png") as im1: with Image.open("Tests/images/hopper-XYZ.png") as im2: @@ -362,7 +362,7 @@ def test_soft_light(): assert new.getpixel((15, 100)) == (1, 1, 3) -def test_hard_light(): +def test_hard_light() -> None: # Arrange with Image.open("Tests/images/hopper.png") as im1: with Image.open("Tests/images/hopper-XYZ.png") as im2: @@ -374,7 +374,7 @@ def test_hard_light(): assert new.getpixel((15, 100)) == (1, 1, 2) -def test_overlay(): +def test_overlay() -> None: # Arrange with Image.open("Tests/images/hopper.png") as im1: with Image.open("Tests/images/hopper-XYZ.png") as im2: @@ -386,7 +386,7 @@ def test_overlay(): assert new.getpixel((15, 100)) == (1, 1, 2) -def test_logical(): +def test_logical() -> None: def table(op, a, b): out = [] for x in (a, b): diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 03332699a..7f6527155 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -5,6 +5,7 @@ import os import re import shutil from io import BytesIO +from pathlib import Path import pytest @@ -32,7 +33,7 @@ SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc" HAVE_PROFILE = os.path.exists(SRGB) -def setup_module(): +def setup_module() -> None: try: from PIL import ImageCms @@ -42,12 +43,12 @@ def setup_module(): pytest.skip(str(v)) -def skip_missing(): +def skip_missing() -> None: if not HAVE_PROFILE: pytest.skip("SRGB profile not available") -def test_sanity(): +def test_sanity() -> None: # basic smoke test. # this mostly follows the cms_test outline. with pytest.warns(DeprecationWarning): @@ -91,7 +92,7 @@ def test_sanity(): hopper().point(t) -def test_flags(): +def test_flags() -> None: assert ImageCms.Flags.NONE == 0 assert ImageCms.Flags.GRIDPOINTS(0) == ImageCms.Flags.NONE assert ImageCms.Flags.GRIDPOINTS(256) == ImageCms.Flags.NONE @@ -101,7 +102,7 @@ def test_flags(): assert ImageCms.Flags.GRIDPOINTS(511) == ImageCms.Flags.GRIDPOINTS(255) -def test_name(): +def test_name() -> None: skip_missing() # get profile information for file assert ( @@ -110,7 +111,7 @@ def test_name(): ) -def test_info(): +def test_info() -> None: skip_missing() assert ImageCms.getProfileInfo(SRGB).splitlines() == [ "sRGB IEC61966-2-1 black scaled", @@ -120,7 +121,7 @@ def test_info(): ] -def test_copyright(): +def test_copyright() -> None: skip_missing() assert ( ImageCms.getProfileCopyright(SRGB).strip() @@ -128,12 +129,12 @@ def test_copyright(): ) -def test_manufacturer(): +def test_manufacturer() -> None: skip_missing() assert ImageCms.getProfileManufacturer(SRGB).strip() == "" -def test_model(): +def test_model() -> None: skip_missing() assert ( ImageCms.getProfileModel(SRGB).strip() @@ -141,14 +142,14 @@ def test_model(): ) -def test_description(): +def test_description() -> None: skip_missing() assert ( ImageCms.getProfileDescription(SRGB).strip() == "sRGB IEC61966-2-1 black scaled" ) -def test_intent(): +def test_intent() -> None: skip_missing() assert ImageCms.getDefaultIntent(SRGB) == 0 support = ImageCms.isIntentSupported( @@ -157,7 +158,7 @@ def test_intent(): assert support == 1 -def test_profile_object(): +def test_profile_object() -> None: # same, using profile object p = ImageCms.createProfile("sRGB") # assert ImageCms.getProfileName(p).strip() == "sRGB built-in - (lcms internal)" @@ -170,7 +171,7 @@ def test_profile_object(): assert support == 1 -def test_extensions(): +def test_extensions() -> None: # extensions with Image.open("Tests/images/rgb.jpg") as i: @@ -181,7 +182,7 @@ def test_extensions(): ) -def test_exceptions(): +def test_exceptions() -> None: # Test mode mismatch psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") @@ -207,17 +208,17 @@ def test_exceptions(): ImageCms.isIntentSupported(SRGB, None, None) -def test_display_profile(): +def test_display_profile() -> None: # try fetching the profile for the current display device ImageCms.get_display_profile() -def test_lab_color_profile(): +def test_lab_color_profile() -> None: ImageCms.createProfile("LAB", 5000) ImageCms.createProfile("LAB", 6500) -def test_unsupported_color_space(): +def test_unsupported_color_space() -> None: with pytest.raises( ImageCms.PyCMSError, match=re.escape( @@ -227,7 +228,7 @@ def test_unsupported_color_space(): ImageCms.createProfile("unsupported") -def test_invalid_color_temperature(): +def test_invalid_color_temperature() -> None: with pytest.raises( ImageCms.PyCMSError, match='Color temperature must be numeric, "invalid" not valid', @@ -236,7 +237,7 @@ def test_invalid_color_temperature(): @pytest.mark.parametrize("flag", ("my string", -1)) -def test_invalid_flag(flag): +def test_invalid_flag(flag) -> None: with hopper() as im: with pytest.raises( ImageCms.PyCMSError, match="flags must be an integer between 0 and " @@ -244,7 +245,7 @@ def test_invalid_flag(flag): ImageCms.profileToProfile(im, "foo", "bar", flags=flag) -def test_simple_lab(): +def test_simple_lab() -> None: i = Image.new("RGB", (10, 10), (128, 128, 128)) psRGB = ImageCms.createProfile("sRGB") @@ -268,7 +269,7 @@ def test_simple_lab(): assert list(b_data) == [128] * 100 -def test_lab_color(): +def test_lab_color() -> None: psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") @@ -283,7 +284,7 @@ def test_lab_color(): assert_image_similar_tofile(i, "Tests/images/hopper.Lab.tif", 3.5) -def test_lab_srgb(): +def test_lab_srgb() -> None: psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") @@ -300,7 +301,7 @@ def test_lab_srgb(): assert "sRGB" in ImageCms.getProfileDescription(profile) -def test_lab_roundtrip(): +def test_lab_roundtrip() -> None: # check to see if we're at least internally consistent. psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") @@ -317,7 +318,7 @@ def test_lab_roundtrip(): assert_image_similar(hopper(), out, 2) -def test_profile_tobytes(): +def test_profile_tobytes() -> None: with Image.open("Tests/images/rgb.jpg") as i: p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) @@ -329,12 +330,12 @@ def test_profile_tobytes(): assert ImageCms.getProfileDescription(p) == ImageCms.getProfileDescription(p2) -def test_extended_information(): +def test_extended_information() -> None: skip_missing() o = ImageCms.getOpenProfile(SRGB) p = o.profile - def assert_truncated_tuple_equal(tup1, tup2, digits=10): + def assert_truncated_tuple_equal(tup1, tup2, digits: int = 10) -> None: # Helper function to reduce precision of tuples of floats # recursively and then check equality. power = 10**digits @@ -476,7 +477,7 @@ def test_extended_information(): assert p.xcolor_space == "RGB " -def test_non_ascii_path(tmp_path): +def test_non_ascii_path(tmp_path: Path) -> None: skip_missing() tempfile = str(tmp_path / ("temp_" + chr(128) + ".icc")) try: @@ -489,7 +490,7 @@ def test_non_ascii_path(tmp_path): assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB" -def test_profile_typesafety(): +def test_profile_typesafety() -> None: """Profile init type safety prepatch, these would segfault, postpatch they should emit a typeerror @@ -501,7 +502,7 @@ def test_profile_typesafety(): ImageCms.ImageCmsProfile(1).tobytes() -def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel): +def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel) -> None: def create_test_image(): # set up test image with something interesting in the tested aux channel. # fmt: off @@ -556,31 +557,31 @@ def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel): assert_image_equal(source_image_aux, result_image_aux) -def test_preserve_auxiliary_channels_rgba(): +def test_preserve_auxiliary_channels_rgba() -> None: assert_aux_channel_preserved( mode="RGBA", transform_in_place=False, preserved_channel="A" ) -def test_preserve_auxiliary_channels_rgba_in_place(): +def test_preserve_auxiliary_channels_rgba_in_place() -> None: assert_aux_channel_preserved( mode="RGBA", transform_in_place=True, preserved_channel="A" ) -def test_preserve_auxiliary_channels_rgbx(): +def test_preserve_auxiliary_channels_rgbx() -> None: assert_aux_channel_preserved( mode="RGBX", transform_in_place=False, preserved_channel="X" ) -def test_preserve_auxiliary_channels_rgbx_in_place(): +def test_preserve_auxiliary_channels_rgbx_in_place() -> None: assert_aux_channel_preserved( mode="RGBX", transform_in_place=True, preserved_channel="X" ) -def test_auxiliary_channels_isolated(): +def test_auxiliary_channels_isolated() -> None: # test data in aux channels does not affect non-aux channels aux_channel_formats = [ # format, profile, color-only format, source test image @@ -630,7 +631,7 @@ def test_auxiliary_channels_isolated(): @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) -def test_rgb_lab(mode): +def test_rgb_lab(mode) -> None: im = Image.new(mode, (1, 1)) converted_im = im.convert("LAB") assert converted_im.getpixel((0, 0)) == (0, 128, 128) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index b602172b6..6eea7886d 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -5,7 +5,7 @@ import pytest from PIL import Image, ImageColor -def test_hash(): +def test_hash() -> None: # short 3 components assert (255, 0, 0) == ImageColor.getrgb("#f00") assert (0, 255, 0) == ImageColor.getrgb("#0f0") @@ -57,7 +57,7 @@ def test_hash(): ImageColor.getrgb("#f00000 ") -def test_colormap(): +def test_colormap() -> None: assert (0, 0, 0) == ImageColor.getrgb("black") assert (255, 255, 255) == ImageColor.getrgb("white") assert (255, 255, 255) == ImageColor.getrgb("WHITE") @@ -66,7 +66,7 @@ def test_colormap(): ImageColor.getrgb("black ") -def test_functions(): +def test_functions() -> None: # rgb numbers assert (255, 0, 0) == ImageColor.getrgb("rgb(255,0,0)") assert (0, 255, 0) == ImageColor.getrgb("rgb(0,255,0)") @@ -160,7 +160,7 @@ def test_functions(): # look for rounding errors (based on code by Tim Hatch) -def test_rounding_errors(): +def test_rounding_errors() -> None: for color in ImageColor.colormap: expected = Image.new("RGB", (1, 1), color).convert("L").getpixel((0, 0)) actual = ImageColor.getcolor(color, "L") @@ -195,11 +195,11 @@ def test_rounding_errors(): Image.new("LA", (1, 1), "white") -def test_color_hsv(): +def test_color_hsv() -> None: assert (170, 255, 255) == ImageColor.getcolor("hsv(240, 100%, 100%)", "HSV") -def test_color_too_long(): +def test_color_too_long() -> None: # Arrange color_too_long = "hsl(" + "1" * 40 + "," + "1" * 40 + "%," + "1" * 40 + "%)" diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 69aab4891..86d25b1eb 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -47,7 +47,7 @@ KITE_POINTS = ( ) -def test_sanity(): +def test_sanity() -> None: im = hopper("RGB").copy() draw = ImageDraw.ImageDraw(im) @@ -59,13 +59,13 @@ def test_sanity(): draw.rectangle(list(range(4))) -def test_valueerror(): +def test_valueerror() -> None: with Image.open("Tests/images/chi.gif") as im: draw = ImageDraw.Draw(im) draw.line((0, 0), fill=(0, 0, 0)) -def test_mode_mismatch(): +def test_mode_mismatch() -> None: im = hopper("RGB").copy() with pytest.raises(ValueError): @@ -74,7 +74,7 @@ def test_mode_mismatch(): @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) -def test_arc(bbox, start, end): +def test_arc(bbox, start, end) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -87,7 +87,7 @@ def test_arc(bbox, start, end): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_end_le_start(bbox): +def test_arc_end_le_start(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -102,7 +102,7 @@ def test_arc_end_le_start(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_no_loops(bbox): +def test_arc_no_loops(bbox) -> None: # No need to go in loops # Arrange im = Image.new("RGB", (W, H)) @@ -118,7 +118,7 @@ def test_arc_no_loops(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width(bbox): +def test_arc_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -131,7 +131,7 @@ def test_arc_width(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_pieslice_large(bbox): +def test_arc_width_pieslice_large(bbox) -> None: # Tests an arc with a large enough width that it is a pieslice # Arrange im = Image.new("RGB", (W, H)) @@ -145,7 +145,7 @@ def test_arc_width_pieslice_large(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_fill(bbox): +def test_arc_width_fill(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -158,7 +158,7 @@ def test_arc_width_fill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_non_whole_angle(bbox): +def test_arc_width_non_whole_angle(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -171,7 +171,7 @@ def test_arc_width_non_whole_angle(bbox): assert_image_similar_tofile(im, expected, 1) -def test_arc_high(): +def test_arc_high() -> None: # Arrange im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) @@ -184,7 +184,7 @@ def test_arc_high(): assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_high.png") -def test_bitmap(): +def test_bitmap() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -200,7 +200,7 @@ def test_bitmap(): @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_chord(mode, bbox): +def test_chord(mode, bbox) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -214,7 +214,7 @@ def test_chord(mode, bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width(bbox): +def test_chord_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -227,7 +227,7 @@ def test_chord_width(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width_fill(bbox): +def test_chord_width_fill(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -240,7 +240,7 @@ def test_chord_width_fill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_chord_zero_width(bbox): +def test_chord_zero_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -252,7 +252,7 @@ def test_chord_zero_width(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png") -def test_chord_too_fat(): +def test_chord_too_fat() -> None: # Arrange im = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(im) @@ -266,7 +266,7 @@ def test_chord_too_fat(): @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse(mode, bbox): +def test_ellipse(mode, bbox) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -280,7 +280,7 @@ def test_ellipse(mode, bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_translucent(bbox): +def test_ellipse_translucent(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -293,7 +293,7 @@ def test_ellipse_translucent(bbox): assert_image_similar_tofile(im, expected, 1) -def test_ellipse_edge(): +def test_ellipse_edge() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -305,7 +305,7 @@ def test_ellipse_edge(): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) -def test_ellipse_symmetric(): +def test_ellipse_symmetric() -> None: for width, bbox in ( (100, (24, 24, 75, 75)), (101, (25, 25, 75, 75)), @@ -317,7 +317,7 @@ def test_ellipse_symmetric(): @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width(bbox): +def test_ellipse_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -329,7 +329,7 @@ def test_ellipse_width(bbox): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1) -def test_ellipse_width_large(): +def test_ellipse_width_large() -> None: # Arrange im = Image.new("RGB", (500, 500)) draw = ImageDraw.Draw(im) @@ -342,7 +342,7 @@ def test_ellipse_width_large(): @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width_fill(bbox): +def test_ellipse_width_fill(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -355,7 +355,7 @@ def test_ellipse_width_fill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_zero_width(bbox): +def test_ellipse_zero_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -394,13 +394,13 @@ def ellipse_various_sizes_helper(filled): return im -def test_ellipse_various_sizes(): +def test_ellipse_various_sizes() -> None: im = ellipse_various_sizes_helper(False) assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_various_sizes.png") -def test_ellipse_various_sizes_filled(): +def test_ellipse_various_sizes_filled() -> None: im = ellipse_various_sizes_helper(True) assert_image_equal_tofile( @@ -409,7 +409,7 @@ def test_ellipse_various_sizes_filled(): @pytest.mark.parametrize("points", POINTS) -def test_line(points): +def test_line(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -421,7 +421,7 @@ def test_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_shape1(): +def test_shape1() -> None: # Arrange im = Image.new("RGB", (100, 100), "white") draw = ImageDraw.Draw(im) @@ -442,7 +442,7 @@ def test_shape1(): assert_image_equal_tofile(im, "Tests/images/imagedraw_shape1.png") -def test_shape2(): +def test_shape2() -> None: # Arrange im = Image.new("RGB", (100, 100), "white") draw = ImageDraw.Draw(im) @@ -463,7 +463,7 @@ def test_shape2(): assert_image_equal_tofile(im, "Tests/images/imagedraw_shape2.png") -def test_transform(): +def test_transform() -> None: # Arrange im = Image.new("RGB", (100, 100), "white") expected = im.copy() @@ -482,7 +482,7 @@ def test_transform(): @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) -def test_pieslice(bbox, start, end): +def test_pieslice(bbox, start, end) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -495,7 +495,7 @@ def test_pieslice(bbox, start, end): @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width(bbox): +def test_pieslice_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -508,7 +508,7 @@ def test_pieslice_width(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width_fill(bbox): +def test_pieslice_width_fill(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -522,7 +522,7 @@ def test_pieslice_width_fill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_zero_width(bbox): +def test_pieslice_zero_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -534,7 +534,7 @@ def test_pieslice_zero_width(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png") -def test_pieslice_wide(): +def test_pieslice_wide() -> None: # Arrange im = Image.new("RGB", (200, 100)) draw = ImageDraw.Draw(im) @@ -546,7 +546,7 @@ def test_pieslice_wide(): assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_wide.png") -def test_pieslice_no_spikes(): +def test_pieslice_no_spikes() -> None: im = Image.new("RGB", (161, 161), "white") draw = ImageDraw.Draw(im) cxs = ( @@ -577,7 +577,7 @@ def test_pieslice_no_spikes(): @pytest.mark.parametrize("points", POINTS) -def test_point(points): +def test_point(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -589,7 +589,7 @@ def test_point(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") -def test_point_I16(): +def test_point_I16() -> None: # Arrange im = Image.new("I;16", (1, 1)) draw = ImageDraw.Draw(im) @@ -602,7 +602,7 @@ def test_point_I16(): @pytest.mark.parametrize("points", POINTS) -def test_polygon(points): +def test_polygon(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -616,7 +616,7 @@ def test_polygon(points): @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("kite_points", KITE_POINTS) -def test_polygon_kite(mode, kite_points): +def test_polygon_kite(mode, kite_points) -> None: # Test drawing lines of different gradients (dx>dy, dy>dx) and # vertical (dx==0) and horizontal (dy==0) lines # Arrange @@ -631,7 +631,7 @@ def test_polygon_kite(mode, kite_points): assert_image_equal_tofile(im, expected) -def test_polygon_1px_high(): +def test_polygon_1px_high() -> None: # Test drawing a 1px high polygon # Arrange im = Image.new("RGB", (3, 3)) @@ -645,7 +645,7 @@ def test_polygon_1px_high(): assert_image_equal_tofile(im, expected) -def test_polygon_1px_high_translucent(): +def test_polygon_1px_high_translucent() -> None: # Test drawing a translucent 1px high polygon # Arrange im = Image.new("RGB", (4, 3)) @@ -659,7 +659,7 @@ def test_polygon_1px_high_translucent(): assert_image_equal_tofile(im, expected) -def test_polygon_translucent(): +def test_polygon_translucent() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -673,7 +673,7 @@ def test_polygon_translucent(): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle(bbox): +def test_rectangle(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -685,7 +685,7 @@ def test_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_big_rectangle(): +def test_big_rectangle() -> None: # Test drawing a rectangle bigger than the image # Arrange im = Image.new("RGB", (W, H)) @@ -700,7 +700,7 @@ def test_big_rectangle(): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width(bbox): +def test_rectangle_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -714,7 +714,7 @@ def test_rectangle_width(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width_fill(bbox): +def test_rectangle_width_fill(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -728,7 +728,7 @@ def test_rectangle_width_fill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_zero_width(bbox): +def test_rectangle_zero_width(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -741,7 +741,7 @@ def test_rectangle_zero_width(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_I16(bbox): +def test_rectangle_I16(bbox) -> None: # Arrange im = Image.new("I;16", (W, H)) draw = ImageDraw.Draw(im) @@ -754,7 +754,7 @@ def test_rectangle_I16(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_translucent_outline(bbox): +def test_rectangle_translucent_outline(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -772,7 +772,7 @@ def test_rectangle_translucent_outline(bbox): "xy", [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], ) -def test_rounded_rectangle(xy): +def test_rounded_rectangle(xy) -> None: # Arrange im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) @@ -788,7 +788,9 @@ def test_rounded_rectangle(xy): @pytest.mark.parametrize("top_right", (True, False)) @pytest.mark.parametrize("bottom_right", (True, False)) @pytest.mark.parametrize("bottom_left", (True, False)) -def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left): +def test_rounded_rectangle_corners( + top_left, top_right, bottom_right, bottom_left +) -> None: corners = (top_left, top_right, bottom_right, bottom_left) # Arrange @@ -822,7 +824,7 @@ def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_lef ((10, 20, 190, 181), 85, "height"), ], ) -def test_rounded_rectangle_non_integer_radius(xy, radius, type): +def test_rounded_rectangle_non_integer_radius(xy, radius, type) -> None: # Arrange im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) @@ -838,7 +840,7 @@ def test_rounded_rectangle_non_integer_radius(xy, radius, type): @pytest.mark.parametrize("bbox", BBOX) -def test_rounded_rectangle_zero_radius(bbox): +def test_rounded_rectangle_zero_radius(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -860,7 +862,7 @@ def test_rounded_rectangle_zero_radius(bbox): ((20, 20, 80, 80), "both"), ], ) -def test_rounded_rectangle_translucent(xy, suffix): +def test_rounded_rectangle_translucent(xy, suffix) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -877,7 +879,7 @@ def test_rounded_rectangle_translucent(xy, suffix): @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill(bbox): +def test_floodfill(bbox) -> None: red = ImageColor.getrgb("red") for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: @@ -910,7 +912,7 @@ def test_floodfill(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_border(bbox): +def test_floodfill_border(bbox) -> None: # floodfill() is experimental # Arrange @@ -932,7 +934,7 @@ def test_floodfill_border(bbox): @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_thresh(bbox): +def test_floodfill_thresh(bbox) -> None: # floodfill() is experimental # Arrange @@ -948,7 +950,7 @@ def test_floodfill_thresh(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") -def test_floodfill_not_negative(): +def test_floodfill_not_negative() -> None: # floodfill() is experimental # Test that floodfill does not extend into negative coordinates @@ -976,7 +978,7 @@ def create_base_image_draw( return img, ImageDraw.Draw(img) -def test_square(): +def test_square() -> None: expected = os.path.join(IMAGES_PATH, "square.png") img, draw = create_base_image_draw((10, 10)) draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) @@ -989,7 +991,7 @@ def test_square(): assert_image_equal_tofile(img, expected, "square as normal rectangle failed") -def test_triangle_right(): +def test_triangle_right() -> None: img, draw = create_base_image_draw((20, 20)) draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) assert_image_equal_tofile( @@ -1001,7 +1003,7 @@ def test_triangle_right(): "fill, suffix", ((BLACK, "width"), (None, "width_no_fill")), ) -def test_triangle_right_width(fill, suffix): +def test_triangle_right_width(fill, suffix) -> None: img, draw = create_base_image_draw((100, 100)) draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5) assert_image_equal_tofile( @@ -1009,7 +1011,7 @@ def test_triangle_right_width(fill, suffix): ) -def test_line_horizontal(): +def test_line_horizontal() -> None: img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 2) assert_image_equal_tofile( @@ -1047,7 +1049,7 @@ def test_line_horizontal(): ) -def test_line_h_s1_w2(): +def test_line_h_s1_w2() -> None: pytest.skip("failing") img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 6), BLACK, 2) @@ -1058,7 +1060,7 @@ def test_line_h_s1_w2(): ) -def test_line_vertical(): +def test_line_vertical() -> None: img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 5, 14), BLACK, 2) assert_image_equal_tofile( @@ -1104,7 +1106,7 @@ def test_line_vertical(): ) -def test_line_oblique_45(): +def test_line_oblique_45() -> None: expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 14), BLACK, 3) @@ -1126,7 +1128,7 @@ def test_line_oblique_45(): ) -def test_wide_line_dot(): +def test_wide_line_dot() -> None: # Test drawing a wide "line" from one point to another just draws a single point # Arrange im = Image.new("RGB", (W, H)) @@ -1139,7 +1141,7 @@ def test_wide_line_dot(): assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1) -def test_wide_line_larger_than_int(): +def test_wide_line_larger_than_int() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -1233,7 +1235,7 @@ def test_wide_line_larger_than_int(): ], ], ) -def test_line_joint(xy): +def test_line_joint(xy) -> None: im = Image.new("RGB", (500, 325)) draw = ImageDraw.Draw(im) @@ -1244,7 +1246,7 @@ def test_line_joint(xy): assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3) -def test_textsize_empty_string(): +def test_textsize_empty_string() -> None: # https://github.com/python-pillow/Pillow/issues/2783 # Arrange im = Image.new("RGB", (W, H)) @@ -1260,7 +1262,7 @@ def test_textsize_empty_string(): @skip_unless_feature("freetype2") -def test_textbbox_stroke(): +def test_textbbox_stroke() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -1274,7 +1276,7 @@ def test_textbbox_stroke(): @skip_unless_feature("freetype2") -def test_stroke(): +def test_stroke() -> None: for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): # Arrange im = Image.new("RGB", (120, 130)) @@ -1291,7 +1293,7 @@ def test_stroke(): @skip_unless_feature("freetype2") -def test_stroke_descender(): +def test_stroke_descender() -> None: # Arrange im = Image.new("RGB", (120, 130)) draw = ImageDraw.Draw(im) @@ -1305,7 +1307,7 @@ def test_stroke_descender(): @skip_unless_feature("freetype2") -def test_split_word(): +def test_split_word() -> None: # Arrange im = Image.new("RGB", (230, 55)) expected = im.copy() @@ -1326,7 +1328,7 @@ def test_split_word(): @skip_unless_feature("freetype2") -def test_stroke_multiline(): +def test_stroke_multiline() -> None: # Arrange im = Image.new("RGB", (100, 250)) draw = ImageDraw.Draw(im) @@ -1342,7 +1344,7 @@ def test_stroke_multiline(): @skip_unless_feature("freetype2") -def test_setting_default_font(): +def test_setting_default_font() -> None: # Arrange im = Image.new("RGB", (100, 250)) draw = ImageDraw.Draw(im) @@ -1359,7 +1361,7 @@ def test_setting_default_font(): assert isinstance(draw.getfont(), ImageFont.load_default().__class__) -def test_default_font_size(): +def test_default_font_size() -> None: freetype_support = features.check_module("freetype2") text = "Default font at a specific size." @@ -1386,7 +1388,7 @@ def test_default_font_size(): @pytest.mark.parametrize("bbox", BBOX) -def test_same_color_outline(bbox): +def test_same_color_outline(bbox) -> None: # Prepare shape x0, y0 = 5, 5 x1, y1 = 5, 50 @@ -1432,7 +1434,7 @@ def test_same_color_outline(bbox): (3, "triangle_width", {"width": 5, "outline": "yellow"}), ], ) -def test_draw_regular_polygon(n_sides, polygon_name, args): +def test_draw_regular_polygon(n_sides, polygon_name, args) -> None: im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) filename = f"Tests/images/imagedraw_{polygon_name}.png" draw = ImageDraw.Draw(im) @@ -1469,7 +1471,7 @@ def test_draw_regular_polygon(n_sides, polygon_name, args): ), ], ) -def test_compute_regular_polygon_vertices(n_sides, expected_vertices): +def test_compute_regular_polygon_vertices(n_sides, expected_vertices) -> None: bounding_circle = (W // 2, H // 2, 25) vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0) assert vertices == expected_vertices @@ -1521,13 +1523,13 @@ def test_compute_regular_polygon_vertices(n_sides, expected_vertices): ) def test_compute_regular_polygon_vertices_input_error_handling( n_sides, bounding_circle, rotation, expected_error, error_message -): +) -> None: with pytest.raises(expected_error) as e: ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) assert str(e.value) == error_message -def test_continuous_horizontal_edges_polygon(): +def test_continuous_horizontal_edges_polygon() -> None: xy = [ (2, 6), (6, 6), @@ -1546,7 +1548,7 @@ def test_continuous_horizontal_edges_polygon(): ) -def test_discontiguous_corners_polygon(): +def test_discontiguous_corners_polygon() -> None: img, draw = create_base_image_draw((84, 68)) draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK) draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK) @@ -1558,7 +1560,7 @@ def test_discontiguous_corners_polygon(): assert_image_similar_tofile(img, expected, 1) -def test_polygon2(): +def test_polygon2() -> None: im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") @@ -1567,7 +1569,7 @@ def test_polygon2(): @pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0))) -def test_incorrectly_ordered_coordinates(xy): +def test_incorrectly_ordered_coordinates(xy) -> None: im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) with pytest.raises(ValueError): diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 004c2d768..07a25b84b 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -43,7 +43,7 @@ POINTS = ( FONT_PATH = "Tests/fonts/FreeMono.ttf" -def test_sanity(): +def test_sanity() -> None: im = hopper("RGB").copy() draw = ImageDraw2.Draw(im) @@ -56,7 +56,7 @@ def test_sanity(): @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse(bbox): +def test_ellipse(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -70,7 +70,7 @@ def test_ellipse(bbox): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_RGB.png", 1) -def test_ellipse_edge(): +def test_ellipse_edge() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -84,7 +84,7 @@ def test_ellipse_edge(): @pytest.mark.parametrize("points", POINTS) -def test_line(points): +def test_line(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -98,7 +98,7 @@ def test_line(points): @pytest.mark.parametrize("points", POINTS) -def test_line_pen_as_brush(points): +def test_line_pen_as_brush(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -114,7 +114,7 @@ def test_line_pen_as_brush(points): @pytest.mark.parametrize("points", POINTS) -def test_polygon(points): +def test_polygon(points) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -129,7 +129,7 @@ def test_polygon(points): @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle(bbox): +def test_rectangle(bbox) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -143,7 +143,7 @@ def test_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_big_rectangle(): +def test_big_rectangle() -> None: # Test drawing a rectangle bigger than the image # Arrange im = Image.new("RGB", (W, H)) @@ -160,7 +160,7 @@ def test_big_rectangle(): @skip_unless_feature("freetype2") -def test_text(): +def test_text() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -175,7 +175,7 @@ def test_text(): @skip_unless_feature("freetype2") -def test_textbbox(): +def test_textbbox() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -190,7 +190,7 @@ def test_textbbox(): @skip_unless_feature("freetype2") -def test_textsize_empty_string(): +def test_textsize_empty_string() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -206,7 +206,7 @@ def test_textsize_empty_string(): @skip_unless_feature("freetype2") -def test_flush(): +def test_flush() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index e3d8a7ab2..9ce9cda82 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -7,7 +7,7 @@ from PIL import Image, ImageEnhance from .helper import assert_image_equal, hopper -def test_sanity(): +def test_sanity() -> None: # FIXME: assert_image # Implicit asserts no exception: ImageEnhance.Color(hopper()).enhance(0.5) @@ -16,7 +16,7 @@ def test_sanity(): ImageEnhance.Sharpness(hopper()).enhance(0.5) -def test_crash(): +def test_crash() -> None: # crashes on small images im = Image.new("RGB", (1, 1)) ImageEnhance.Sharpness(im).enhance(0.5) @@ -34,7 +34,7 @@ def _half_transparent_image(): return im -def _check_alpha(im, original, op, amount): +def _check_alpha(im, original, op, amount) -> None: assert im.getbands() == original.getbands() assert_image_equal( im.getchannel("A"), @@ -44,7 +44,7 @@ def _check_alpha(im, original, op, amount): @pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness")) -def test_alpha(op): +def test_alpha(op) -> None: # Issue https://github.com/python-pillow/Pillow/issues/899 # Is alpha preserved through image enhancement? diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 99731f352..491409781 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -30,7 +30,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK class TestImageFile: - def test_parser(self): + def test_parser(self) -> None: def roundtrip(format): im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST) if format in ("MSP", "XBM"): @@ -84,7 +84,7 @@ class TestImageFile: with pytest.raises(OSError): roundtrip("PDF") - def test_ico(self): + def test_ico(self) -> None: with open("Tests/images/python.ico", "rb") as f: data = f.read() with ImageFile.Parser() as p: @@ -93,7 +93,7 @@ class TestImageFile: @skip_unless_feature("webp") @skip_unless_feature("webp_anim") - def test_incremental_webp(self): + def test_incremental_webp(self) -> None: with ImageFile.Parser() as p: with open("Tests/images/hopper.webp", "rb") as f: p.feed(f.read(1024)) @@ -105,7 +105,7 @@ class TestImageFile: assert (128, 128) == p.image.size @skip_unless_feature("zlib") - def test_safeblock(self): + def test_safeblock(self) -> None: im1 = hopper() try: @@ -116,17 +116,17 @@ class TestImageFile: assert_image_equal(im1, im2) - def test_raise_oserror(self): + def test_raise_oserror(self) -> None: with pytest.warns(DeprecationWarning): with pytest.raises(OSError): ImageFile.raise_oserror(1) - def test_raise_typeerror(self): + def test_raise_typeerror(self) -> None: with pytest.raises(TypeError): parser = ImageFile.Parser() parser.feed(1) - def test_negative_stride(self): + def test_negative_stride(self) -> None: with open("Tests/images/raw_negative_stride.bin", "rb") as f: input = f.read() p = ImageFile.Parser() @@ -134,11 +134,11 @@ class TestImageFile: with pytest.raises(OSError): p.close() - def test_no_format(self): + def test_no_format(self) -> None: buf = BytesIO(b"\x00" * 255) class DummyImageFile(ImageFile.ImageFile): - def _open(self): + def _open(self) -> None: self._mode = "RGB" self._size = (1, 1) @@ -146,12 +146,12 @@ class TestImageFile: assert im.format is None assert im.get_format_mimetype() is None - def test_oserror(self): + def test_oserror(self) -> None: im = Image.new("RGB", (1, 1)) with pytest.raises(OSError): im.save(BytesIO(), "JPEG2000", num_resolutions=2) - def test_truncated(self): + def test_truncated(self) -> None: b = BytesIO( b"BM000000000000" # head_data + _binary.o32le( @@ -166,7 +166,7 @@ class TestImageFile: assert str(e.value) == "Truncated File Read" @skip_unless_feature("zlib") - def test_truncated_with_errors(self): + def test_truncated_with_errors(self) -> None: with Image.open("Tests/images/truncated_image.png") as im: with pytest.raises(OSError): im.load() @@ -176,7 +176,7 @@ class TestImageFile: im.load() @skip_unless_feature("zlib") - def test_truncated_without_errors(self): + def test_truncated_without_errors(self) -> None: with Image.open("Tests/images/truncated_image.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -185,13 +185,13 @@ class TestImageFile: ImageFile.LOAD_TRUNCATED_IMAGES = False @skip_unless_feature("zlib") - def test_broken_datastream_with_errors(self): + def test_broken_datastream_with_errors(self) -> None: with Image.open("Tests/images/broken_data_stream.png") as im: with pytest.raises(OSError): im.load() @skip_unless_feature("zlib") - def test_broken_datastream_without_errors(self): + def test_broken_datastream_without_errors(self) -> None: with Image.open("Tests/images/broken_data_stream.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -210,7 +210,7 @@ class MockPyEncoder(ImageFile.PyEncoder): def encode(self, buffer): return 1, 1, b"" - def cleanup(self): + def cleanup(self) -> None: self.cleanup_called = True @@ -218,7 +218,7 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100 class MockImageFile(ImageFile.ImageFile): - def _open(self): + def _open(self) -> None: self.rawmode = "RGBA" self._mode = "RGBA" self._size = (200, 200) @@ -227,7 +227,7 @@ class MockImageFile(ImageFile.ImageFile): class CodecsTest: @classmethod - def setup_class(cls): + def setup_class(cls) -> None: cls.decoder = MockPyDecoder(None) cls.encoder = MockPyEncoder(None) @@ -244,7 +244,7 @@ class CodecsTest: class TestPyDecoder(CodecsTest): - def test_setimage(self): + def test_setimage(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -259,7 +259,7 @@ class TestPyDecoder(CodecsTest): with pytest.raises(ValueError): self.decoder.set_as_raw(b"\x00") - def test_extents_none(self): + def test_extents_none(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -272,7 +272,7 @@ class TestPyDecoder(CodecsTest): assert self.decoder.state.xsize == 200 assert self.decoder.state.ysize == 200 - def test_negsize(self): + def test_negsize(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -285,7 +285,7 @@ class TestPyDecoder(CodecsTest): with pytest.raises(ValueError): im.load() - def test_oversize(self): + def test_oversize(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -298,14 +298,14 @@ class TestPyDecoder(CodecsTest): with pytest.raises(ValueError): im.load() - def test_decode(self): + def test_decode(self) -> None: decoder = ImageFile.PyDecoder(None) with pytest.raises(NotImplementedError): decoder.decode(None) class TestPyEncoder(CodecsTest): - def test_setimage(self): + def test_setimage(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -320,7 +320,7 @@ class TestPyEncoder(CodecsTest): assert self.encoder.state.xsize == xsize assert self.encoder.state.ysize == ysize - def test_extents_none(self): + def test_extents_none(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -334,7 +334,7 @@ class TestPyEncoder(CodecsTest): assert self.encoder.state.xsize == 200 assert self.encoder.state.ysize == 200 - def test_negsize(self): + def test_negsize(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -352,7 +352,7 @@ class TestPyEncoder(CodecsTest): im, fp, [("MOCK", (xoff, yoff, xoff + xsize, -10), 0, "RGB")] ) - def test_oversize(self): + def test_oversize(self) -> None: buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) @@ -372,7 +372,7 @@ class TestPyEncoder(CodecsTest): [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 0, "RGB")], ) - def test_encode(self): + def test_encode(self) -> None: encoder = ImageFile.PyEncoder(None) with pytest.raises(NotImplementedError): encoder.encode(None) @@ -388,6 +388,6 @@ class TestPyEncoder(CodecsTest): with pytest.raises(NotImplementedError): encoder.encode_to_file(None, None) - def test_zero_height(self): + def test_zero_height(self) -> None: with pytest.raises(UnidentifiedImageError): Image.open("Tests/images/zero_height.j2k") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index d2c87d42a..909026dc8 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -31,7 +31,7 @@ TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" pytestmark = skip_unless_feature("freetype2") -def test_sanity(): +def test_sanity() -> None: assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) @@ -51,7 +51,7 @@ def font(layout_engine): return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine) -def test_font_properties(font): +def test_font_properties(font) -> None: assert font.path == FONT_PATH assert font.size == FONT_SIZE @@ -80,11 +80,11 @@ def _render(font, layout_engine): @pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH))) -def test_font_with_name(layout_engine, font): +def test_font_with_name(layout_engine, font) -> None: _render(font, layout_engine) -def test_font_with_filelike(layout_engine): +def test_font_with_filelike(layout_engine) -> None: def _font_as_bytes(): with open(FONT_PATH, "rb") as f: font_bytes = BytesIO(f.read()) @@ -102,12 +102,12 @@ def test_font_with_filelike(layout_engine): # _render(shared_bytes) -def test_font_with_open_file(layout_engine): +def test_font_with_open_file(layout_engine) -> None: with open(FONT_PATH, "rb") as f: _render(f, layout_engine) -def test_render_equal(layout_engine): +def test_render_equal(layout_engine) -> None: img_path = _render(FONT_PATH, layout_engine) with open(FONT_PATH, "rb") as f: font_filelike = BytesIO(f.read()) @@ -116,7 +116,7 @@ def test_render_equal(layout_engine): assert_image_equal(img_path, img_filelike) -def test_non_ascii_path(tmp_path, layout_engine): +def test_non_ascii_path(tmp_path: Path, layout_engine) -> None: tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) try: shutil.copy(FONT_PATH, tempfile) @@ -126,7 +126,7 @@ def test_non_ascii_path(tmp_path, layout_engine): ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine) -def test_transparent_background(font): +def test_transparent_background(font) -> None: im = Image.new(mode="RGBA", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -140,7 +140,7 @@ def test_transparent_background(font): assert_image_similar_tofile(im.convert("L"), target, 0.01) -def test_I16(font): +def test_I16(font) -> None: im = Image.new(mode="I;16", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -153,7 +153,7 @@ def test_I16(font): assert_image_similar_tofile(im.convert("L"), target, 0.01) -def test_textbbox_equal(font): +def test_textbbox_equal(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -182,7 +182,7 @@ def test_textbbox_equal(font): ) def test_getlength( text, mode, fontname, size, layout_engine, length_basic, length_raqm -): +) -> None: f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine) im = Image.new(mode, (1, 1), 0) @@ -197,7 +197,7 @@ def test_getlength( assert length == length_raqm -def test_float_size(): +def test_float_size() -> None: lengths = [] for size in (48, 48.5, 49): f = ImageFont.truetype( @@ -207,7 +207,7 @@ def test_float_size(): assert lengths[0] != lengths[1] != lengths[2] -def test_render_multiline(font): +def test_render_multiline(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) line_spacing = font.getbbox("A")[3] + 4 @@ -223,7 +223,7 @@ def test_render_multiline(font): assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) -def test_render_multiline_text(font): +def test_render_multiline_text(font) -> None: # Test that text() correctly connects to multiline_text() # and that align defaults to left im = Image.new(mode="RGB", size=(300, 100)) @@ -243,7 +243,7 @@ def test_render_multiline_text(font): @pytest.mark.parametrize( "align, ext", (("left", ""), ("center", "_center"), ("right", "_right")) ) -def test_render_multiline_text_align(font, align, ext): +def test_render_multiline_text_align(font, align, ext) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align) @@ -251,7 +251,7 @@ def test_render_multiline_text_align(font, align, ext): assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01) -def test_unknown_align(font): +def test_unknown_align(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -260,14 +260,14 @@ def test_unknown_align(font): draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown") -def test_draw_align(font): +def test_draw_align(font) -> None: im = Image.new("RGB", (300, 100), "white") draw = ImageDraw.Draw(im) line = "some text" draw.text((100, 40), line, (0, 0, 0), font=font, align="left") -def test_multiline_bbox(font): +def test_multiline_bbox(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -285,7 +285,7 @@ def test_multiline_bbox(font): draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4) -def test_multiline_width(font): +def test_multiline_width(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -295,7 +295,7 @@ def test_multiline_width(font): ) -def test_multiline_spacing(font): +def test_multiline_spacing(font) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10) @@ -306,7 +306,7 @@ def test_multiline_spacing(font): @pytest.mark.parametrize( "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) ) -def test_rotated_transposed_font(font, orientation): +def test_rotated_transposed_font(font, orientation) -> None: img_gray = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_gray) word = "testing" @@ -347,7 +347,7 @@ def test_rotated_transposed_font(font, orientation): Image.Transpose.FLIP_TOP_BOTTOM, ), ) -def test_unrotated_transposed_font(font, orientation): +def test_unrotated_transposed_font(font, orientation) -> None: img_gray = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_gray) word = "testing" @@ -382,7 +382,7 @@ def test_unrotated_transposed_font(font, orientation): @pytest.mark.parametrize( "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) ) -def test_rotated_transposed_font_get_mask(font, orientation): +def test_rotated_transposed_font_get_mask(font, orientation) -> None: # Arrange text = "mask this" transposed_font = ImageFont.TransposedFont(font, orientation=orientation) @@ -403,7 +403,7 @@ def test_rotated_transposed_font_get_mask(font, orientation): Image.Transpose.FLIP_TOP_BOTTOM, ), ) -def test_unrotated_transposed_font_get_mask(font, orientation): +def test_unrotated_transposed_font_get_mask(font, orientation) -> None: # Arrange text = "mask this" transposed_font = ImageFont.TransposedFont(font, orientation=orientation) @@ -415,11 +415,11 @@ def test_unrotated_transposed_font_get_mask(font, orientation): assert mask.size == (108, 13) -def test_free_type_font_get_name(font): +def test_free_type_font_get_name(font) -> None: assert ("FreeMono", "Regular") == font.getname() -def test_free_type_font_get_metrics(font): +def test_free_type_font_get_metrics(font) -> None: ascent, descent = font.getmetrics() assert isinstance(ascent, int) @@ -427,7 +427,7 @@ def test_free_type_font_get_metrics(font): assert (ascent, descent) == (16, 4) -def test_free_type_font_get_mask(font): +def test_free_type_font_get_mask(font) -> None: # Arrange text = "mask this" @@ -438,7 +438,7 @@ def test_free_type_font_get_mask(font): assert mask.size == (108, 13) -def test_load_path_not_found(): +def test_load_path_not_found() -> None: # Arrange filename = "somefilenamethatdoesntexist.ttf" @@ -449,13 +449,13 @@ def test_load_path_not_found(): ImageFont.truetype(filename) -def test_load_non_font_bytes(): +def test_load_non_font_bytes() -> None: with open("Tests/images/hopper.jpg", "rb") as f: with pytest.raises(OSError): ImageFont.truetype(f) -def test_default_font(): +def test_default_font() -> None: # Arrange txt = "This is a default font using FreeType support." im = Image.new(mode="RGB", size=(300, 100)) @@ -473,16 +473,16 @@ def test_default_font(): @pytest.mark.parametrize("mode", (None, "1", "RGBA")) -def test_getbbox(font, mode): +def test_getbbox(font, mode) -> None: assert (0, 4, 12, 16) == font.getbbox("A", mode) -def test_getbbox_empty(font): +def test_getbbox_empty(font) -> None: # issue #2614, should not crash. assert (0, 0, 0, 0) == font.getbbox("") -def test_render_empty(font): +def test_render_empty(font) -> None: # issue 2666 im = Image.new(mode="RGB", size=(300, 100)) target = im.copy() @@ -492,7 +492,7 @@ def test_render_empty(font): assert_image_equal(im, target) -def test_unicode_extended(layout_engine): +def test_unicode_extended(layout_engine) -> None: # issue #3777 text = "A\u278A\U0001F12B" target = "Tests/images/unicode_extended.png" @@ -515,8 +515,8 @@ def test_unicode_extended(layout_engine): (("linux", "/usr/local/share/fonts"), ("darwin", "/System/Library/Fonts")), ) @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") -def test_find_font(monkeypatch, platform, font_directory): - def _test_fake_loading_font(path_to_fake, fontname): +def test_find_font(monkeypatch, platform, font_directory) -> None: + def _test_fake_loading_font(path_to_fake, fontname) -> None: # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) with monkeypatch.context() as m: @@ -567,7 +567,7 @@ def test_find_font(monkeypatch, platform, font_directory): _test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate") -def test_imagefont_getters(font): +def test_imagefont_getters(font) -> None: assert font.getmetrics() == (16, 4) assert font.font.ascent == 16 assert font.font.descent == 4 @@ -588,7 +588,7 @@ def test_imagefont_getters(font): @pytest.mark.parametrize("stroke_width", (0, 2)) -def test_getsize_stroke(font, stroke_width): +def test_getsize_stroke(font, stroke_width) -> None: assert font.getbbox("A", stroke_width=stroke_width) == ( 0 - stroke_width, 4 - stroke_width, @@ -597,7 +597,7 @@ def test_getsize_stroke(font, stroke_width): ) -def test_complex_font_settings(): +def test_complex_font_settings() -> None: t = ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.BASIC) with pytest.raises(KeyError): t.getmask("абвг", direction="rtl") @@ -607,7 +607,7 @@ def test_complex_font_settings(): t.getmask("абвг", language="sr") -def test_variation_get(font): +def test_variation_get(font) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -677,7 +677,7 @@ def _check_text(font, path, epsilon): raise -def test_variation_set_by_name(font): +def test_variation_set_by_name(font) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -702,7 +702,7 @@ def test_variation_set_by_name(font): _check_text(font, "Tests/images/variation_tiny_name.png", 40) -def test_variation_set_by_axes(font): +def test_variation_set_by_axes(font) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -737,7 +737,7 @@ def test_variation_set_by_axes(font): ), ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), ) -def test_anchor(layout_engine, anchor, left, top): +def test_anchor(layout_engine, anchor, left, top) -> None: name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" @@ -782,7 +782,7 @@ def test_anchor(layout_engine, anchor, left, top): ("md", "center"), ), ) -def test_anchor_multiline(layout_engine, anchor, align): +def test_anchor_multiline(layout_engine, anchor, align) -> None: target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" text = "a\nlong\ntext sample" @@ -800,7 +800,7 @@ def test_anchor_multiline(layout_engine, anchor, align): assert_image_similar_tofile(im, target, 4) -def test_anchor_invalid(font): +def test_anchor_invalid(font) -> None: im = Image.new("RGB", (100, 100), "white") d = ImageDraw.Draw(im) d.font = font @@ -826,7 +826,7 @@ def test_anchor_invalid(font): @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) -def test_bitmap_font(layout_engine, bpp): +def test_bitmap_font(layout_engine, bpp) -> None: text = "Bitmap Font" layout_name = ["basic", "raqm"][layout_engine] target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" @@ -843,7 +843,7 @@ def test_bitmap_font(layout_engine, bpp): assert_image_equal_tofile(im, target) -def test_bitmap_font_stroke(layout_engine): +def test_bitmap_font_stroke(layout_engine) -> None: text = "Bitmap Font" layout_name = ["basic", "raqm"][layout_engine] target = f"Tests/images/bitmap_font_stroke_{layout_name}.png" @@ -861,7 +861,7 @@ def test_bitmap_font_stroke(layout_engine): @pytest.mark.parametrize("embedded_color", (False, True)) -def test_bitmap_blend(layout_engine, embedded_color): +def test_bitmap_blend(layout_engine, embedded_color) -> None: font = ImageFont.truetype( "Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine ) @@ -873,7 +873,7 @@ def test_bitmap_blend(layout_engine, embedded_color): assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png") -def test_standard_embedded_color(layout_engine): +def test_standard_embedded_color(layout_engine) -> None: txt = "Hello World!" ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) ttf.getbbox(txt) @@ -908,7 +908,7 @@ def test_float_coord(layout_engine, fontmode): raise -def test_cbdt(layout_engine): +def test_cbdt(layout_engine) -> None: try: font = ImageFont.truetype( "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine @@ -925,7 +925,7 @@ def test_cbdt(layout_engine): pytest.skip("freetype compiled without libpng or CBDT support") -def test_cbdt_mask(layout_engine): +def test_cbdt_mask(layout_engine) -> None: try: font = ImageFont.truetype( "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine @@ -942,7 +942,7 @@ def test_cbdt_mask(layout_engine): pytest.skip("freetype compiled without libpng or CBDT support") -def test_sbix(layout_engine): +def test_sbix(layout_engine) -> None: try: font = ImageFont.truetype( "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine @@ -959,7 +959,7 @@ def test_sbix(layout_engine): pytest.skip("freetype compiled without libpng or SBIX support") -def test_sbix_mask(layout_engine): +def test_sbix_mask(layout_engine) -> None: try: font = ImageFont.truetype( "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine @@ -977,7 +977,7 @@ def test_sbix_mask(layout_engine): @skip_unless_feature_version("freetype2", "2.10.0") -def test_colr(layout_engine): +def test_colr(layout_engine) -> None: font = ImageFont.truetype( "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", size=64, @@ -993,7 +993,7 @@ def test_colr(layout_engine): @skip_unless_feature_version("freetype2", "2.10.0") -def test_colr_mask(layout_engine): +def test_colr_mask(layout_engine) -> None: font = ImageFont.truetype( "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", size=64, @@ -1008,7 +1008,7 @@ def test_colr_mask(layout_engine): assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) -def test_woff2(layout_engine): +def test_woff2(layout_engine) -> None: try: font = ImageFont.truetype( "Tests/fonts/OpenSans.woff2", @@ -1027,7 +1027,7 @@ def test_woff2(layout_engine): assert_image_similar_tofile(im, "Tests/images/test_woff2.png", 5) -def test_render_mono_size(): +def test_render_mono_size() -> None: # issue 4177 im = Image.new("P", (100, 30), "white") @@ -1042,7 +1042,7 @@ def test_render_mono_size(): assert_image_equal_tofile(im, "Tests/images/text_mono.gif") -def test_too_many_characters(font): +def test_too_many_characters(font) -> None: with pytest.raises(ValueError): font.getlength("A" * 1_000_001) with pytest.raises(ValueError): @@ -1070,14 +1070,14 @@ def test_too_many_characters(font): "Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf", ], ) -def test_oom(test_file): +def test_oom(test_file) -> None: with open(test_file, "rb") as f: font = ImageFont.truetype(BytesIO(f.read())) with pytest.raises(Image.DecompressionBombError): font.getmask("Test Text") -def test_raqm_missing_warning(monkeypatch): +def test_raqm_missing_warning(monkeypatch) -> None: monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False) with pytest.warns(UserWarning) as record: font = ImageFont.truetype( @@ -1091,6 +1091,6 @@ def test_raqm_missing_warning(monkeypatch): @pytest.mark.parametrize("size", [-1, 0]) -def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size): +def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size) -> None: with pytest.raises(ValueError): ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 09e68ea48..325e7ef21 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -12,7 +12,7 @@ FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" pytestmark = skip_unless_feature("raqm") -def test_english(): +def test_english() -> None: # smoke test, this should not fail ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -20,7 +20,7 @@ def test_english(): draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr") -def test_complex_text(): +def test_complex_text() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -31,7 +31,7 @@ def test_complex_text(): assert_image_similar_tofile(im, target, 0.5) -def test_y_offset(): +def test_y_offset() -> None: ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -42,7 +42,7 @@ def test_y_offset(): assert_image_similar_tofile(im, target, 1.7) -def test_complex_unicode_text(): +def test_complex_unicode_text() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -62,7 +62,7 @@ def test_complex_unicode_text(): assert_image_similar_tofile(im, target, 2.33) -def test_text_direction_rtl(): +def test_text_direction_rtl() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -73,7 +73,7 @@ def test_text_direction_rtl(): assert_image_similar_tofile(im, target, 0.5) -def test_text_direction_ltr(): +def test_text_direction_ltr() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -84,7 +84,7 @@ def test_text_direction_ltr(): assert_image_similar_tofile(im, target, 0.5) -def test_text_direction_rtl2(): +def test_text_direction_rtl2() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -95,7 +95,7 @@ def test_text_direction_rtl2(): assert_image_similar_tofile(im, target, 0.5) -def test_text_direction_ttb(): +def test_text_direction_ttb() -> None: ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) im = Image.new(mode="RGB", size=(100, 300)) @@ -110,7 +110,7 @@ def test_text_direction_ttb(): assert_image_similar_tofile(im, target, 2.8) -def test_text_direction_ttb_stroke(): +def test_text_direction_ttb_stroke() -> None: ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50) im = Image.new(mode="RGB", size=(100, 300)) @@ -133,7 +133,7 @@ def test_text_direction_ttb_stroke(): assert_image_similar_tofile(im, target, 19.4) -def test_ligature_features(): +def test_ligature_features() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -146,7 +146,7 @@ def test_ligature_features(): assert liga_bbox == (0, 4, 13, 19) -def test_kerning_features(): +def test_kerning_features() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -157,7 +157,7 @@ def test_kerning_features(): assert_image_similar_tofile(im, target, 0.5) -def test_arabictext_features(): +def test_arabictext_features() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -174,7 +174,7 @@ def test_arabictext_features(): assert_image_similar_tofile(im, target, 0.5) -def test_x_max_and_y_offset(): +def test_x_max_and_y_offset() -> None: ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40) im = Image.new(mode="RGB", size=(50, 100)) @@ -185,7 +185,7 @@ def test_x_max_and_y_offset(): assert_image_similar_tofile(im, target, 0.5) -def test_language(): +def test_language() -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode="RGB", size=(300, 100)) @@ -208,7 +208,7 @@ def test_language(): ), ids=("None", "ltr", "rtl2", "rtl", "ttb"), ) -def test_getlength(mode, text, direction, expected): +def test_getlength(mode, text, direction, expected) -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode, (1, 1), 0) d = ImageDraw.Draw(im) @@ -230,7 +230,7 @@ def test_getlength(mode, text, direction, expected): ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), ids=("caron-above", "caron-below", "double-breve", "overline"), ) -def test_getlength_combine(mode, direction, text): +def test_getlength_combine(mode, direction, text) -> None: if text == "i\u0305i" and direction == "ttb": pytest.skip("fails with this font") @@ -250,7 +250,7 @@ def test_getlength_combine(mode, direction, text): @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) -def test_anchor_ttb(anchor): +def test_anchor_ttb(anchor) -> None: text = "f" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) @@ -306,7 +306,7 @@ combine_tests = ( @pytest.mark.parametrize( "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] ) -def test_combine(name, text, dir, anchor, epsilon): +def test_combine(name, text, dir, anchor, epsilon) -> None: path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) @@ -337,7 +337,7 @@ def test_combine(name, text, dir, anchor, epsilon): ("rm", "right"), # pass with getsize ), ) -def test_combine_multiline(anchor, align): +def test_combine_multiline(anchor, align) -> None: # test that multiline text uses getlength, not getsize or getbbox path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" @@ -355,7 +355,7 @@ def test_combine_multiline(anchor, align): assert_image_similar_tofile(im, path, 0.015) -def test_anchor_invalid_ttb(): +def test_anchor_invalid_ttb() -> None: font = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new("RGB", (100, 100), "white") d = ImageDraw.Draw(im) diff --git a/Tests/test_imagefontpil.py b/Tests/test_imagefontpil.py index be4be1c54..3b1c14b4e 100644 --- a/Tests/test_imagefontpil.py +++ b/Tests/test_imagefontpil.py @@ -12,16 +12,16 @@ from .helper import assert_image_equal_tofile original_core = ImageFont.core -def setup_module(): +def setup_module() -> None: if features.check_module("freetype2"): ImageFont.core = _util.DeferredError(ImportError) -def teardown_module(): +def teardown_module() -> None: ImageFont.core = original_core -def test_default_font(): +def test_default_font() -> None: # Arrange txt = 'This is a "better than nothing" default font.' im = Image.new(mode="RGB", size=(300, 100)) @@ -35,12 +35,12 @@ def test_default_font(): assert_image_equal_tofile(im, "Tests/images/default_font.png") -def test_size_without_freetype(): +def test_size_without_freetype() -> None: with pytest.raises(ImportError): ImageFont.load_default(size=14) -def test_unicode(): +def test_unicode() -> None: # should not segfault, should return UnicodeDecodeError # issue #2826 font = ImageFont.load_default() @@ -48,7 +48,7 @@ def test_unicode(): font.getbbox("’") -def test_textbbox(): +def test_textbbox() -> None: im = Image.new("RGB", (200, 200)) d = ImageDraw.Draw(im) default_font = ImageFont.load_default() @@ -56,7 +56,7 @@ def test_textbbox(): assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11) -def test_decompression_bomb(): +def test_decompression_bomb() -> None: glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 256, 256, 0, 0, 256, 256) fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256) @@ -67,7 +67,7 @@ def test_decompression_bomb(): @pytest.mark.timeout(4) -def test_oom(): +def test_oom() -> None: glyph = struct.pack( ">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767 ) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 9d3d40398..40c1d323e 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -20,7 +20,7 @@ class TestImageGrab: @pytest.mark.skipif( sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) - def test_grab(self): + def test_grab(self) -> None: ImageGrab.grab() ImageGrab.grab(include_layered_windows=True) ImageGrab.grab(all_screens=True) @@ -29,7 +29,7 @@ class TestImageGrab: assert im.size == (40, 60) @skip_unless_feature("xcb") - def test_grab_x11(self): + def test_grab_x11(self) -> None: try: if sys.platform not in ("win32", "darwin"): ImageGrab.grab() @@ -39,7 +39,7 @@ class TestImageGrab: pytest.skip(str(e)) @pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB") - def test_grab_no_xcb(self): + def test_grab_no_xcb(self) -> None: if sys.platform not in ("win32", "darwin") and not shutil.which( "gnome-screenshot" ): @@ -52,12 +52,12 @@ class TestImageGrab: assert str(e.value).startswith("Pillow was built without XCB support") @skip_unless_feature("xcb") - def test_grab_invalid_xdisplay(self): + def test_grab_invalid_xdisplay(self) -> None: with pytest.raises(OSError) as e: ImageGrab.grab(xdisplay="error.test:0.0") assert str(e.value).startswith("X connection failed") - def test_grabclipboard(self): + def test_grabclipboard(self) -> None: if sys.platform == "darwin": subprocess.call(["screencapture", "-cx"]) elif sys.platform == "win32": @@ -82,7 +82,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200 ImageGrab.grabclipboard() @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") - def test_grabclipboard_file(self): + def test_grabclipboard_file(self) -> None: p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"') p.communicate() @@ -92,7 +92,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200 assert os.path.samefile(im[0], "Tests/images/hopper.gif") @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") - def test_grabclipboard_png(self): + def test_grabclipboard_png(self) -> None: p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) p.stdin.write( rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png") @@ -113,7 +113,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes) reason="Linux with wl-clipboard only", ) @pytest.mark.parametrize("ext", ("gif", "png", "ico")) - def test_grabclipboard_wl_clipboard(self, ext): + def test_grabclipboard_wl_clipboard(self, ext) -> None: image_path = "Tests/images/hopper." + ext with open(image_path, "rb") as fp: subprocess.call(["wl-copy"], stdin=fp) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 622ad27ea..ea6e80f1e 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -24,7 +24,7 @@ B2 = B.resize((2, 2)) images = {"A": A, "B": B, "F": F, "I": I} -def test_sanity(): +def test_sanity() -> None: assert ImageMath.eval("1") == 1 assert ImageMath.eval("1+A", A=2) == 3 assert pixel(ImageMath.eval("A+B", A=A, B=B)) == "I 3" @@ -33,7 +33,7 @@ def test_sanity(): assert pixel(ImageMath.eval("int(float(A)+B)", images)) == "I 3" -def test_ops(): +def test_ops() -> None: assert pixel(ImageMath.eval("-A", images)) == "I -1" assert pixel(ImageMath.eval("+B", images)) == "L 2" @@ -60,51 +60,51 @@ def test_ops(): "(lambda: (lambda: exec('pass'))())()", ), ) -def test_prevent_exec(expression): +def test_prevent_exec(expression) -> None: with pytest.raises(ValueError): ImageMath.eval(expression) -def test_prevent_double_underscores(): +def test_prevent_double_underscores() -> None: with pytest.raises(ValueError): ImageMath.eval("1", {"__": None}) -def test_prevent_builtins(): +def test_prevent_builtins() -> None: with pytest.raises(ValueError): ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None}) -def test_logical(): +def test_logical() -> None: assert pixel(ImageMath.eval("not A", images)) == 0 assert pixel(ImageMath.eval("A and B", images)) == "L 2" assert pixel(ImageMath.eval("A or B", images)) == "L 1" -def test_convert(): +def test_convert() -> None: assert pixel(ImageMath.eval("convert(A+B, 'L')", images)) == "L 3" assert pixel(ImageMath.eval("convert(A+B, '1')", images)) == "1 0" assert pixel(ImageMath.eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)" -def test_compare(): +def test_compare() -> None: assert pixel(ImageMath.eval("min(A, B)", images)) == "I 1" assert pixel(ImageMath.eval("max(A, B)", images)) == "I 2" assert pixel(ImageMath.eval("A == 1", images)) == "I 1" assert pixel(ImageMath.eval("A == 2", images)) == "I 0" -def test_one_image_larger(): +def test_one_image_larger() -> None: assert pixel(ImageMath.eval("A+B", A=A2, B=B)) == "I 3" assert pixel(ImageMath.eval("A+B", A=A, B=B2)) == "I 3" -def test_abs(): +def test_abs() -> None: assert pixel(ImageMath.eval("abs(A)", A=A)) == "I 1" assert pixel(ImageMath.eval("abs(B)", B=B)) == "I 2" -def test_binary_mod(): +def test_binary_mod() -> None: assert pixel(ImageMath.eval("A%A", A=A)) == "I 0" assert pixel(ImageMath.eval("B%B", B=B)) == "I 0" assert pixel(ImageMath.eval("A%B", A=A, B=B)) == "I 1" @@ -113,90 +113,90 @@ def test_binary_mod(): assert pixel(ImageMath.eval("Z%B", B=B, Z=Z)) == "I 0" -def test_bitwise_invert(): +def test_bitwise_invert() -> None: assert pixel(ImageMath.eval("~Z", Z=Z)) == "I -1" assert pixel(ImageMath.eval("~A", A=A)) == "I -2" assert pixel(ImageMath.eval("~B", B=B)) == "I -3" -def test_bitwise_and(): +def test_bitwise_and() -> None: assert pixel(ImageMath.eval("Z&Z", A=A, Z=Z)) == "I 0" assert pixel(ImageMath.eval("Z&A", A=A, Z=Z)) == "I 0" assert pixel(ImageMath.eval("A&Z", A=A, Z=Z)) == "I 0" assert pixel(ImageMath.eval("A&A", A=A, Z=Z)) == "I 1" -def test_bitwise_or(): +def test_bitwise_or() -> None: assert pixel(ImageMath.eval("Z|Z", A=A, Z=Z)) == "I 0" assert pixel(ImageMath.eval("Z|A", A=A, Z=Z)) == "I 1" assert pixel(ImageMath.eval("A|Z", A=A, Z=Z)) == "I 1" assert pixel(ImageMath.eval("A|A", A=A, Z=Z)) == "I 1" -def test_bitwise_xor(): +def test_bitwise_xor() -> None: assert pixel(ImageMath.eval("Z^Z", A=A, Z=Z)) == "I 0" assert pixel(ImageMath.eval("Z^A", A=A, Z=Z)) == "I 1" assert pixel(ImageMath.eval("A^Z", A=A, Z=Z)) == "I 1" assert pixel(ImageMath.eval("A^A", A=A, Z=Z)) == "I 0" -def test_bitwise_leftshift(): +def test_bitwise_leftshift() -> None: assert pixel(ImageMath.eval("Z<<0", Z=Z)) == "I 0" assert pixel(ImageMath.eval("Z<<1", Z=Z)) == "I 0" assert pixel(ImageMath.eval("A<<0", A=A)) == "I 1" assert pixel(ImageMath.eval("A<<1", A=A)) == "I 2" -def test_bitwise_rightshift(): +def test_bitwise_rightshift() -> None: assert pixel(ImageMath.eval("Z>>0", Z=Z)) == "I 0" assert pixel(ImageMath.eval("Z>>1", Z=Z)) == "I 0" assert pixel(ImageMath.eval("A>>0", A=A)) == "I 1" assert pixel(ImageMath.eval("A>>1", A=A)) == "I 0" -def test_logical_eq(): +def test_logical_eq() -> None: assert pixel(ImageMath.eval("A==A", A=A)) == "I 1" assert pixel(ImageMath.eval("B==B", B=B)) == "I 1" assert pixel(ImageMath.eval("A==B", A=A, B=B)) == "I 0" assert pixel(ImageMath.eval("B==A", A=A, B=B)) == "I 0" -def test_logical_ne(): +def test_logical_ne() -> None: assert pixel(ImageMath.eval("A!=A", A=A)) == "I 0" assert pixel(ImageMath.eval("B!=B", B=B)) == "I 0" assert pixel(ImageMath.eval("A!=B", A=A, B=B)) == "I 1" assert pixel(ImageMath.eval("B!=A", A=A, B=B)) == "I 1" -def test_logical_lt(): +def test_logical_lt() -> None: assert pixel(ImageMath.eval("A None: assert pixel(ImageMath.eval("A<=A", A=A)) == "I 1" assert pixel(ImageMath.eval("B<=B", B=B)) == "I 1" assert pixel(ImageMath.eval("A<=B", A=A, B=B)) == "I 1" assert pixel(ImageMath.eval("B<=A", A=A, B=B)) == "I 0" -def test_logical_gt(): +def test_logical_gt() -> None: assert pixel(ImageMath.eval("A>A", A=A)) == "I 0" assert pixel(ImageMath.eval("B>B", B=B)) == "I 0" assert pixel(ImageMath.eval("A>B", A=A, B=B)) == "I 0" assert pixel(ImageMath.eval("B>A", A=A, B=B)) == "I 1" -def test_logical_ge(): +def test_logical_ge() -> None: assert pixel(ImageMath.eval("A>=A", A=A)) == "I 1" assert pixel(ImageMath.eval("B>=B", B=B)) == "I 1" assert pixel(ImageMath.eval("A>=B", A=A, B=B)) == "I 0" assert pixel(ImageMath.eval("B>=A", A=A, B=B)) == "I 1" -def test_logical_equal(): +def test_logical_equal() -> None: assert pixel(ImageMath.eval("equal(A, A)", A=A)) == "I 1" assert pixel(ImageMath.eval("equal(B, B)", B=B)) == "I 1" assert pixel(ImageMath.eval("equal(Z, Z)", Z=Z)) == "I 1" @@ -205,7 +205,7 @@ def test_logical_equal(): assert pixel(ImageMath.eval("equal(A, Z)", A=A, Z=Z)) == "I 0" -def test_logical_not_equal(): +def test_logical_not_equal() -> None: assert pixel(ImageMath.eval("notequal(A, A)", A=A)) == "I 0" assert pixel(ImageMath.eval("notequal(B, B)", B=B)) == "I 0" assert pixel(ImageMath.eval("notequal(Z, Z)", Z=Z)) == "I 0" diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 0708ee639..0b0c6d2d3 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,6 +1,8 @@ # Test the ImageMorphology functionality from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, ImageMorph, _imagingmorph @@ -50,18 +52,18 @@ def img_string_normalize(im): return img_to_string(string_to_img(im)) -def assert_img_equal_img_string(a, b_string): +def assert_img_equal_img_string(a, b_string) -> None: assert img_to_string(a) == img_string_normalize(b_string) -def test_str_to_img(): +def test_str_to_img() -> None: assert_image_equal_tofile(A, "Tests/images/morph_a.png") @pytest.mark.parametrize( "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") ) -def test_lut(op): +def test_lut(op) -> None: lb = ImageMorph.LutBuilder(op_name=op) assert lb.get_lut() is None @@ -70,7 +72,7 @@ def test_lut(op): assert lut == bytearray(f.read()) -def test_no_operator_loaded(): +def test_no_operator_loaded() -> None: mop = ImageMorph.MorphOp() with pytest.raises(Exception) as e: mop.apply(None) @@ -84,7 +86,7 @@ def test_no_operator_loaded(): # Test the named patterns -def test_erosion8(): +def test_erosion8() -> None: # erosion8 mop = ImageMorph.MorphOp(op_name="erosion8") count, Aout = mop.apply(A) @@ -103,7 +105,7 @@ def test_erosion8(): ) -def test_dialation8(): +def test_dialation8() -> None: # dialation8 mop = ImageMorph.MorphOp(op_name="dilation8") count, Aout = mop.apply(A) @@ -122,7 +124,7 @@ def test_dialation8(): ) -def test_erosion4(): +def test_erosion4() -> None: # erosion4 mop = ImageMorph.MorphOp(op_name="dilation4") count, Aout = mop.apply(A) @@ -141,7 +143,7 @@ def test_erosion4(): ) -def test_edge(): +def test_edge() -> None: # edge mop = ImageMorph.MorphOp(op_name="edge") count, Aout = mop.apply(A) @@ -160,7 +162,7 @@ def test_edge(): ) -def test_corner(): +def test_corner() -> None: # Create a corner detector pattern mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) count, Aout = mop.apply(A) @@ -188,7 +190,7 @@ def test_corner(): assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4)) -def test_mirroring(): +def test_mirroring() -> None: # Test 'M' for mirroring mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"]) count, Aout = mop.apply(A) @@ -207,7 +209,7 @@ def test_mirroring(): ) -def test_negate(): +def test_negate() -> None: # Test 'N' for negate mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"]) count, Aout = mop.apply(A) @@ -226,7 +228,7 @@ def test_negate(): ) -def test_incorrect_mode(): +def test_incorrect_mode() -> None: im = hopper("RGB") mop = ImageMorph.MorphOp(op_name="erosion8") @@ -241,7 +243,7 @@ def test_incorrect_mode(): assert str(e.value) == "Image mode must be L" -def test_add_patterns(): +def test_add_patterns() -> None: # Arrange lb = ImageMorph.LutBuilder(op_name="corner") assert lb.patterns == ["1:(... ... ...)->0", "4:(00. 01. ...)->1"] @@ -259,12 +261,12 @@ def test_add_patterns(): ] -def test_unknown_pattern(): +def test_unknown_pattern() -> None: with pytest.raises(Exception): ImageMorph.LutBuilder(op_name="unknown") -def test_pattern_syntax_error(): +def test_pattern_syntax_error() -> None: # Arrange lb = ImageMorph.LutBuilder(op_name="corner") new_patterns = ["a pattern with a syntax error"] @@ -276,7 +278,7 @@ def test_pattern_syntax_error(): assert str(e.value) == 'Syntax error in pattern "a pattern with a syntax error"' -def test_load_invalid_mrl(): +def test_load_invalid_mrl() -> None: # Arrange invalid_mrl = "Tests/images/hopper.png" mop = ImageMorph.MorphOp() @@ -287,7 +289,7 @@ def test_load_invalid_mrl(): assert str(e.value) == "Wrong size operator file!" -def test_roundtrip_mrl(tmp_path): +def test_roundtrip_mrl(tmp_path: Path) -> None: # Arrange tempfile = str(tmp_path / "temp.mrl") mop = ImageMorph.MorphOp(op_name="corner") @@ -301,7 +303,7 @@ def test_roundtrip_mrl(tmp_path): assert mop.lut == initial_lut -def test_set_lut(): +def test_set_lut() -> None: # Arrange lb = ImageMorph.LutBuilder(op_name="corner") lut = lb.build_lut() @@ -314,7 +316,7 @@ def test_set_lut(): assert mop.lut == lut -def test_wrong_mode(): +def test_wrong_mode() -> None: lut = ImageMorph.LutBuilder(op_name="corner").build_lut() imrgb = Image.new("RGB", (10, 10)) iml = Image.new("L", (10, 10)) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 636b99dbe..50bf404ae 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -22,7 +22,7 @@ class Deformer: deformer = Deformer() -def test_sanity(): +def test_sanity() -> None: ImageOps.autocontrast(hopper("L")) ImageOps.autocontrast(hopper("RGB")) @@ -84,7 +84,7 @@ def test_sanity(): ImageOps.exif_transpose(hopper("RGB")) -def test_1pxfit(): +def test_1pxfit() -> None: # Division by zero in equalize if image is 1 pixel high newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) assert newimg.size == (35, 35) @@ -96,7 +96,7 @@ def test_1pxfit(): assert newimg.size == (35, 35) -def test_fit_same_ratio(): +def test_fit_same_ratio() -> None: # The ratio for this image is 1000.0 / 755 = 1.3245033112582782 # If the ratios are not acknowledged to be the same, # and Pillow attempts to adjust the width to @@ -108,13 +108,13 @@ def test_fit_same_ratio(): @pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512))) -def test_contain(new_size): +def test_contain(new_size) -> None: im = hopper() new_im = ImageOps.contain(im, new_size) assert new_im.size == (256, 256) -def test_contain_round(): +def test_contain_round() -> None: im = Image.new("1", (43, 63), 1) new_im = ImageOps.contain(im, (5, 7)) assert new_im.width == 5 @@ -132,13 +132,13 @@ def test_contain_round(): ("hopper.png", (256, 256)), # square ), ) -def test_cover(image_name, expected_size): +def test_cover(image_name, expected_size) -> None: with Image.open("Tests/images/" + image_name) as im: new_im = ImageOps.cover(im, (256, 256)) assert new_im.size == expected_size -def test_pad(): +def test_pad() -> None: # Same ratio im = hopper() new_size = (im.width * 2, im.height * 2) @@ -158,7 +158,7 @@ def test_pad(): ) -def test_pad_round(): +def test_pad_round() -> None: im = Image.new("1", (1, 1), 1) new_im = ImageOps.pad(im, (4, 1)) assert new_im.load()[2, 0] == 1 @@ -168,7 +168,7 @@ def test_pad_round(): @pytest.mark.parametrize("mode", ("P", "PA")) -def test_palette(mode): +def test_palette(mode) -> None: im = hopper(mode) # Expand @@ -182,7 +182,7 @@ def test_palette(mode): ) -def test_pil163(): +def test_pil163() -> None: # Division by zero in equalize if < 255 pixels in image (@PIL163) i = hopper("RGB").resize((15, 16)) @@ -192,7 +192,7 @@ def test_pil163(): ImageOps.equalize(i.convert("RGB")) -def test_scale(): +def test_scale() -> None: # Test the scaling function i = hopper("L").resize((50, 50)) @@ -210,7 +210,7 @@ def test_scale(): @pytest.mark.parametrize("border", (10, (1, 2, 3, 4))) -def test_expand_palette(border): +def test_expand_palette(border) -> None: with Image.open("Tests/images/p_16.tga") as im: im_expanded = ImageOps.expand(im, border, (255, 0, 0)) @@ -236,7 +236,7 @@ def test_expand_palette(border): assert_image_equal(im_cropped, im) -def test_colorize_2color(): +def test_colorize_2color() -> None: # Test the colorizing function with 2-color functionality # Open test image (256px by 10px, black to white) @@ -270,7 +270,7 @@ def test_colorize_2color(): ) -def test_colorize_2color_offset(): +def test_colorize_2color_offset() -> None: # Test the colorizing function with 2-color functionality and offset # Open test image (256px by 10px, black to white) @@ -306,7 +306,7 @@ def test_colorize_2color_offset(): ) -def test_colorize_3color_offset(): +def test_colorize_3color_offset() -> None: # Test the colorizing function with 3-color functionality and offset # Open test image (256px by 10px, black to white) @@ -359,14 +359,14 @@ def test_colorize_3color_offset(): ) -def test_exif_transpose(): +def test_exif_transpose() -> None: exts = [".jpg"] if features.check("webp") and features.check("webp_anim"): exts.append(".webp") for ext in exts: with Image.open("Tests/images/hopper" + ext) as base_im: - def check(orientation_im): + def check(orientation_im) -> None: for im in [ orientation_im, orientation_im.copy(), @@ -423,7 +423,7 @@ def test_exif_transpose(): assert 0x0112 not in transposed_im.getexif() -def test_exif_transpose_in_place(): +def test_exif_transpose_in_place() -> None: with Image.open("Tests/images/orientation_rectangle.jpg") as im: assert im.size == (2, 1) assert im.getexif()[0x0112] == 8 @@ -435,13 +435,13 @@ def test_exif_transpose_in_place(): assert_image_equal(im, expected) -def test_autocontrast_unsupported_mode(): +def test_autocontrast_unsupported_mode() -> None: im = Image.new("RGBA", (1, 1)) with pytest.raises(OSError): ImageOps.autocontrast(im) -def test_autocontrast_cutoff(): +def test_autocontrast_cutoff() -> None: # Test the cutoff argument of autocontrast with Image.open("Tests/images/bw_gradient.png") as img: @@ -452,7 +452,7 @@ def test_autocontrast_cutoff(): assert autocontrast(10) != autocontrast((1, 10)) -def test_autocontrast_mask_toy_input(): +def test_autocontrast_mask_toy_input() -> None: # Test the mask argument of autocontrast with Image.open("Tests/images/bw_gradient.png") as img: rect_mask = Image.new("L", img.size, 0) @@ -471,7 +471,7 @@ def test_autocontrast_mask_toy_input(): assert ImageStat.Stat(result_nomask).median == [128] -def test_autocontrast_mask_real_input(): +def test_autocontrast_mask_real_input() -> None: # Test the autocontrast with a rectangular mask with Image.open("Tests/images/iptc.jpg") as img: rect_mask = Image.new("L", img.size, 0) @@ -498,7 +498,7 @@ def test_autocontrast_mask_real_input(): ) -def test_autocontrast_preserve_tone(): +def test_autocontrast_preserve_tone() -> None: def autocontrast(mode, preserve_tone): im = hopper(mode) return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram() @@ -507,7 +507,7 @@ def test_autocontrast_preserve_tone(): assert autocontrast("L", True) == autocontrast("L", False) -def test_autocontrast_preserve_gradient(): +def test_autocontrast_preserve_gradient() -> None: gradient = Image.linear_gradient("L") # test with a grayscale gradient that extends to 0,255. @@ -533,7 +533,7 @@ def test_autocontrast_preserve_gradient(): @pytest.mark.parametrize( "color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0)) ) -def test_autocontrast_preserve_one_color(color): +def test_autocontrast_preserve_one_color(color) -> None: img = Image.new("RGB", (10, 10), color) # single color images shouldn't change diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 8ffb9bff7..03302e20f 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -18,7 +18,7 @@ def test_images(): im.close() -def test_filter_api(test_images): +def test_filter_api(test_images) -> None: im = test_images["im"] test_filter = ImageFilter.GaussianBlur(2.0) @@ -32,7 +32,7 @@ def test_filter_api(test_images): assert i.size == (128, 128) -def test_usm_formats(test_images): +def test_usm_formats(test_images) -> None: im = test_images["im"] usm = ImageFilter.UnsharpMask @@ -50,7 +50,7 @@ def test_usm_formats(test_images): im.convert("YCbCr").filter(usm) -def test_blur_formats(test_images): +def test_blur_formats(test_images) -> None: im = test_images["im"] blur = ImageFilter.GaussianBlur @@ -68,7 +68,7 @@ def test_blur_formats(test_images): im.convert("YCbCr").filter(blur) -def test_usm_accuracy(test_images): +def test_usm_accuracy(test_images) -> None: snakes = test_images["snakes"] src = snakes.convert("RGB") @@ -77,7 +77,7 @@ def test_usm_accuracy(test_images): assert i.tobytes() == src.tobytes() -def test_blur_accuracy(test_images): +def test_blur_accuracy(test_images) -> None: snakes = test_images["snakes"] i = snakes.filter(ImageFilter.GaussianBlur(0.4)) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index be21464b4..545229500 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, ImagePalette @@ -7,19 +9,19 @@ from PIL import Image, ImagePalette from .helper import assert_image_equal, assert_image_equal_tofile -def test_sanity(): +def test_sanity() -> None: palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) assert len(palette.colors) == 256 -def test_reload(): +def test_reload() -> None: with Image.open("Tests/images/hopper.gif") as im: original = im.copy() im.palette.dirty = 1 assert_image_equal(im.convert("RGB"), original.convert("RGB")) -def test_getcolor(): +def test_getcolor() -> None: palette = ImagePalette.ImagePalette() assert len(palette.palette) == 0 assert len(palette.colors) == 0 @@ -46,7 +48,7 @@ def test_getcolor(): palette.getcolor("unknown") -def test_getcolor_rgba_color_rgb_palette(): +def test_getcolor_rgba_color_rgb_palette() -> None: palette = ImagePalette.ImagePalette("RGB") # Opaque RGBA colors are converted @@ -65,7 +67,7 @@ def test_getcolor_rgba_color_rgb_palette(): (255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)), ], ) -def test_getcolor_not_special(index, palette): +def test_getcolor_not_special(index, palette) -> None: im = Image.new("P", (1, 1)) # Do not use transparency index as a new color @@ -79,7 +81,7 @@ def test_getcolor_not_special(index, palette): assert index2 not in (index, index1) -def test_file(tmp_path): +def test_file(tmp_path: Path) -> None: palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) f = str(tmp_path / "temp.lut") @@ -97,7 +99,7 @@ def test_file(tmp_path): assert p.palette == palette.tobytes() -def test_make_linear_lut(): +def test_make_linear_lut() -> None: # Arrange black = 0 white = 255 @@ -113,7 +115,7 @@ def test_make_linear_lut(): assert lut[i] == i -def test_make_linear_lut_not_yet_implemented(): +def test_make_linear_lut_not_yet_implemented() -> None: # Update after FIXME # Arrange black = 1 @@ -124,7 +126,7 @@ def test_make_linear_lut_not_yet_implemented(): ImagePalette.make_linear_lut(black, white) -def test_make_gamma_lut(): +def test_make_gamma_lut() -> None: # Arrange exp = 5 @@ -142,7 +144,7 @@ def test_make_gamma_lut(): assert lut[255] == 255 -def test_rawmode_valueerrors(tmp_path): +def test_rawmode_valueerrors(tmp_path: Path) -> None: # Arrange palette = ImagePalette.raw("RGB", list(range(256)) * 3) @@ -156,7 +158,7 @@ def test_rawmode_valueerrors(tmp_path): palette.save(f) -def test_getdata(): +def test_getdata() -> None: # Arrange data_in = list(range(256)) * 3 palette = ImagePalette.ImagePalette("RGB", data_in) @@ -168,7 +170,7 @@ def test_getdata(): assert mode == "RGB" -def test_rawmode_getdata(): +def test_rawmode_getdata() -> None: # Arrange data_in = list(range(256)) * 3 palette = ImagePalette.raw("RGB", data_in) @@ -181,7 +183,7 @@ def test_rawmode_getdata(): assert data_in == data_out -def test_2bit_palette(tmp_path): +def test_2bit_palette(tmp_path: Path) -> None: # issue #2258, 2 bit palettes are corrupted. outfile = str(tmp_path / "temp.png") @@ -193,6 +195,6 @@ def test_2bit_palette(tmp_path): assert_image_equal_tofile(img, outfile) -def test_invalid_palette(): +def test_invalid_palette() -> None: with pytest.raises(OSError): ImagePalette.load("Tests/images/hopper.jpg") diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 5c6393e23..8ba745f21 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -9,7 +9,7 @@ import pytest from PIL import Image, ImagePath -def test_path(): +def test_path() -> None: p = ImagePath.Path(list(range(10))) # sequence interface @@ -57,7 +57,7 @@ def test_path(): ImagePath.Path((0, 1)), ), ) -def test_path_constructors(coords): +def test_path_constructors(coords) -> None: # Arrange / Act p = ImagePath.Path(coords) @@ -75,7 +75,7 @@ def test_path_constructors(coords): [[0.0, 1.0]], ), ) -def test_invalid_path_constructors(coords): +def test_invalid_path_constructors(coords) -> None: # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) @@ -93,7 +93,7 @@ def test_invalid_path_constructors(coords): [0, 1, 2], ), ) -def test_path_odd_number_of_coordinates(coords): +def test_path_odd_number_of_coordinates(coords) -> None: # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) @@ -111,7 +111,7 @@ def test_path_odd_number_of_coordinates(coords): (1, (0.0, 0.0, 0.0, 0.0)), ], ) -def test_getbbox(coords, expected): +def test_getbbox(coords, expected) -> None: # Arrange p = ImagePath.Path(coords) @@ -119,7 +119,7 @@ def test_getbbox(coords, expected): assert p.getbbox() == expected -def test_getbbox_no_args(): +def test_getbbox_no_args() -> None: # Arrange p = ImagePath.Path([0, 1, 2, 3]) @@ -135,7 +135,7 @@ def test_getbbox_no_args(): (list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]), ], ) -def test_map(coords, expected): +def test_map(coords, expected) -> None: # Arrange p = ImagePath.Path(coords) @@ -147,7 +147,7 @@ def test_map(coords, expected): assert list(p) == expected -def test_transform(): +def test_transform() -> None: # Arrange p = ImagePath.Path([0, 1, 2, 3]) theta = math.pi / 15 @@ -165,7 +165,7 @@ def test_transform(): ] -def test_transform_with_wrap(): +def test_transform_with_wrap() -> None: # Arrange p = ImagePath.Path([0, 1, 2, 3]) theta = math.pi / 15 @@ -184,7 +184,7 @@ def test_transform_with_wrap(): ] -def test_overflow_segfault(): +def test_overflow_segfault() -> None: # Some Pythons fail getting the argument as an integer, and it falls # through to the sequence. Seeing this on 32-bit Windows. with pytest.raises((TypeError, MemoryError)): @@ -198,12 +198,12 @@ def test_overflow_segfault(): class Evil: - def __init__(self): + def __init__(self) -> None: self.corrupt = Image.core.path(0x4000000000000000) def __getitem__(self, i): x = self.corrupt[i] return struct.pack("dd", x[0], x[1]) - def __setitem__(self, i, x): + def __setitem__(self, i, x) -> None: self.corrupt[i] = struct.unpack("dd", x) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index d55d980d9..909f97167 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -16,7 +16,7 @@ if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba -def test_rgb(): +def test_rgb() -> None: # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, @@ -28,7 +28,7 @@ def test_rgb(): assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) - def checkrgb(r, g, b): + def checkrgb(r, g, b) -> None: val = ImageQt.rgb(r, g, b) val = val % 2**24 # drop the alpha assert val >> 16 == r @@ -41,7 +41,7 @@ def test_rgb(): checkrgb(0, 0, 255) -def test_image(): +def test_image() -> None: modes = ["1", "RGB", "RGBA", "L", "P"] qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ @@ -55,6 +55,6 @@ def test_image(): assert_image_similar(roundtripped_im, im, 1) -def test_closed_file(): +def test_closed_file() -> None: with warnings.catch_warnings(): ImageQt.ImageQt("Tests/images/hopper.gif") diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 66d553bcb..7280dded0 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image, ImageSequence, TiffImagePlugin @@ -7,7 +9,7 @@ from PIL import Image, ImageSequence, TiffImagePlugin from .helper import assert_image_equal, hopper, skip_unless_feature -def test_sanity(tmp_path): +def test_sanity(tmp_path: Path) -> None: test_file = str(tmp_path / "temp.im") im = hopper("RGB") @@ -27,7 +29,7 @@ def test_sanity(tmp_path): ImageSequence.Iterator(0) -def test_iterator(): +def test_iterator() -> None: with Image.open("Tests/images/multipage.tiff") as im: i = ImageSequence.Iterator(im) for index in range(0, im.n_frames): @@ -38,14 +40,14 @@ def test_iterator(): next(i) -def test_iterator_min_frame(): +def test_iterator_min_frame() -> None: with Image.open("Tests/images/hopper.psd") as im: i = ImageSequence.Iterator(im) for index in range(1, im.n_frames): assert i[index] == next(i) -def _test_multipage_tiff(): +def _test_multipage_tiff() -> None: with Image.open("Tests/images/multipage.tiff") as im: for index, frame in enumerate(ImageSequence.Iterator(im)): frame.load() @@ -53,18 +55,18 @@ def _test_multipage_tiff(): frame.convert("RGB") -def test_tiff(): +def test_tiff() -> None: _test_multipage_tiff() @skip_unless_feature("libtiff") -def test_libtiff(): +def test_libtiff() -> None: TiffImagePlugin.READ_LIBTIFF = True _test_multipage_tiff() TiffImagePlugin.READ_LIBTIFF = False -def test_consecutive(): +def test_consecutive() -> None: with Image.open("Tests/images/multipage.tiff") as im: first_frame = None for frame in ImageSequence.Iterator(im): @@ -75,7 +77,7 @@ def test_consecutive(): break -def test_palette_mmap(): +def test_palette_mmap() -> None: # Using mmap in ImageFile can require to reload the palette. with Image.open("Tests/images/multipage-mmap.tiff") as im: color1 = im.getpalette()[:3] @@ -84,7 +86,7 @@ def test_palette_mmap(): assert color1 == color2 -def test_all_frames(): +def test_all_frames() -> None: # Test a single image with Image.open("Tests/images/iss634.gif") as im: ims = ImageSequence.all_frames(im) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 0996ad41d..f7269d45b 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -7,12 +7,12 @@ from PIL import Image, ImageShow from .helper import hopper, is_win32, on_ci -def test_sanity(): +def test_sanity() -> None: dir(Image) dir(ImageShow) -def test_register(): +def test_register() -> None: # Test registering a viewer that is not a class ImageShow.register("not a class") @@ -24,9 +24,9 @@ def test_register(): "order", [-1, 0], ) -def test_viewer_show(order): +def test_viewer_show(order) -> None: class TestViewer(ImageShow.Viewer): - def show_image(self, image, **options): + def show_image(self, image, **options) -> bool: self.methodCalled = True return True @@ -48,12 +48,12 @@ def test_viewer_show(order): reason="Only run on CIs; hangs on Windows CIs", ) @pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA")) -def test_show(mode): +def test_show(mode) -> None: im = hopper(mode) assert ImageShow.show(im) -def test_show_without_viewers(): +def test_show_without_viewers() -> None: viewers = ImageShow._viewers ImageShow._viewers = [] @@ -63,7 +63,7 @@ def test_show_without_viewers(): ImageShow._viewers = viewers -def test_viewer(): +def test_viewer() -> None: viewer = ImageShow.Viewer() assert viewer.get_format(None) is None @@ -73,14 +73,14 @@ def test_viewer(): @pytest.mark.parametrize("viewer", ImageShow._viewers) -def test_viewers(viewer): +def test_viewers(viewer) -> None: try: viewer.get_command("test.jpg") except NotImplementedError: pass -def test_ipythonviewer(): +def test_ipythonviewer() -> None: pytest.importorskip("IPython", reason="IPython not installed") for viewer in ImageShow._viewers: if isinstance(viewer, ImageShow.IPythonViewer): diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 01687db35..b1c1306c1 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -7,7 +7,7 @@ from PIL import Image, ImageStat from .helper import hopper -def test_sanity(): +def test_sanity() -> None: im = hopper() st = ImageStat.Stat(im) @@ -31,7 +31,7 @@ def test_sanity(): ImageStat.Stat(1) -def test_hopper(): +def test_hopper() -> None: im = hopper() st = ImageStat.Stat(im) @@ -44,7 +44,7 @@ def test_hopper(): assert st.sum[2] == 1563008 -def test_constant(): +def test_constant() -> None: im = Image.new("L", (128, 128), 128) st = ImageStat.Stat(im) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index c06fc5823..a216bd21d 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -23,7 +23,7 @@ TK_MODES = ("1", "L", "P", "RGB", "RGBA") pytestmark = pytest.mark.skipif(not HAS_TK, reason="Tk not installed") -def setup_module(): +def setup_module() -> None: try: # setup tk tk.Frame() @@ -34,7 +34,7 @@ def setup_module(): pytest.skip(f"TCL Error: {v}") -def test_kw(): +def test_kw() -> None: TEST_JPG = "Tests/images/hopper.jpg" TEST_PNG = "Tests/images/hopper.png" with Image.open(TEST_JPG) as im1: @@ -57,7 +57,7 @@ def test_kw(): @pytest.mark.parametrize("mode", TK_MODES) -def test_photoimage(mode): +def test_photoimage(mode) -> None: # test as image: im = hopper(mode) @@ -71,7 +71,7 @@ def test_photoimage(mode): assert_image_equal(reloaded, im.convert("RGBA")) -def test_photoimage_apply_transparency(): +def test_photoimage_apply_transparency() -> None: with Image.open("Tests/images/pil123p.png") as im: im_tk = ImageTk.PhotoImage(im) reloaded = ImageTk.getimage(im_tk) @@ -79,7 +79,7 @@ def test_photoimage_apply_transparency(): @pytest.mark.parametrize("mode", TK_MODES) -def test_photoimage_blank(mode): +def test_photoimage_blank(mode) -> None: # test a image using mode/size: im_tk = ImageTk.PhotoImage(mode, (100, 100)) @@ -91,7 +91,7 @@ def test_photoimage_blank(mode): assert_image_equal(reloaded.convert(mode), im) -def test_bitmapimage(): +def test_bitmapimage() -> None: im = hopper("1") # this should not crash diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index f93eabcb4..b43c31b52 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -8,10 +8,10 @@ from .helper import hopper, is_win32 class TestImageWin: - def test_sanity(self): + def test_sanity(self) -> None: dir(ImageWin) - def test_hdc(self): + def test_hdc(self) -> None: # Arrange dc = 50 @@ -22,7 +22,7 @@ class TestImageWin: # Assert assert dc2 == 50 - def test_hwnd(self): + def test_hwnd(self) -> None: # Arrange wnd = 50 @@ -36,7 +36,7 @@ class TestImageWin: @pytest.mark.skipif(not is_win32(), reason="Windows only") class TestImageWinDib: - def test_dib_image(self): + def test_dib_image(self) -> None: # Arrange im = hopper() @@ -46,7 +46,7 @@ class TestImageWinDib: # Assert assert dib.size == im.size - def test_dib_mode_string(self): + def test_dib_mode_string(self) -> None: # Arrange mode = "RGBA" size = (128, 128) @@ -57,7 +57,7 @@ class TestImageWinDib: # Assert assert dib.size == (128, 128) - def test_dib_paste(self): + def test_dib_paste(self) -> None: # Arrange im = hopper() @@ -71,7 +71,7 @@ class TestImageWinDib: # Assert assert dib.size == (128, 128) - def test_dib_paste_bbox(self): + def test_dib_paste_bbox(self) -> None: # Arrange im = hopper() bbox = (0, 0, 10, 10) @@ -86,7 +86,7 @@ class TestImageWinDib: # Assert assert dib.size == (128, 128) - def test_dib_frombytes_tobytes_roundtrip(self): + def test_dib_frombytes_tobytes_roundtrip(self) -> None: # Arrange # Make two different DIB images im = hopper() diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index 63d6b903c..c7f633e62 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -1,6 +1,7 @@ from __future__ import annotations from io import BytesIO +from pathlib import Path from PIL import Image, ImageWin @@ -83,7 +84,7 @@ if is_win32(): memcpy(bp + bf.bfOffBits, pixels, bi.biSizeImage) return bytearray(buf) - def test_pointer(tmp_path): + def test_pointer(tmp_path: Path) -> None: im = hopper() (width, height) = im.size opath = str(tmp_path / "temp.png") diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index e2024abbf..c8d6d33d2 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -10,7 +10,7 @@ X = 255 class TestLibPack: - def assert_pack(self, mode, rawmode, data, *pixels): + def assert_pack(self, mode, rawmode, data, *pixels) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. """ @@ -24,7 +24,7 @@ class TestLibPack: assert data == im.tobytes("raw", rawmode) - def test_1(self): + def test_1(self) -> None: self.assert_pack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) self.assert_pack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) self.assert_pack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) @@ -37,29 +37,29 @@ class TestLibPack: self.assert_pack("1", "L", b"\xff\x00\x00\xff\x00\x00", X, 0, 0, X, 0, 0) - def test_L(self): + def test_L(self) -> None: self.assert_pack("L", "L", 1, 1, 2, 3, 4) self.assert_pack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) self.assert_pack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) - def test_LA(self): + def test_LA(self) -> None: self.assert_pack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_pack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) - def test_La(self): + def test_La(self) -> None: self.assert_pack("La", "La", 2, (1, 2), (3, 4), (5, 6)) - def test_P(self): + def test_P(self) -> None: self.assert_pack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 255, 0, 0) self.assert_pack("P", "P;2", b"\xe4", 3, 2, 1, 0) self.assert_pack("P", "P;4", b"\x02\xef", 0, 2, 14, 15) self.assert_pack("P", "P", 1, 1, 2, 3, 4) - def test_PA(self): + def test_PA(self) -> None: self.assert_pack("PA", "PA", 2, (1, 2), (3, 4), (5, 6)) self.assert_pack("PA", "PA;L", 2, (1, 4), (2, 5), (3, 6)) - def test_RGB(self): + def test_RGB(self) -> None: self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack( "RGB", "RGBX", b"\x01\x02\x03\xff\x05\x06\x07\xff", (1, 2, 3), (5, 6, 7) @@ -79,7 +79,7 @@ class TestLibPack: self.assert_pack("RGB", "G", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - def test_RGBA(self): + def test_RGBA(self) -> None: self.assert_pack("RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) @@ -101,12 +101,12 @@ class TestLibPack: self.assert_pack("RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - def test_RGBa(self): + def test_RGBa(self) -> None: self.assert_pack("RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack("RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) self.assert_pack("RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - def test_RGBX(self): + def test_RGBX(self) -> None: self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) @@ -134,7 +134,7 @@ class TestLibPack: self.assert_pack("RGBX", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) self.assert_pack("RGBX", "X", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - def test_CMYK(self): + def test_CMYK(self) -> None: self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( "CMYK", @@ -149,7 +149,7 @@ class TestLibPack: ) self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) - def test_YCbCr(self): + def test_YCbCr(self) -> None: self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack( @@ -172,19 +172,19 @@ class TestLibPack: self.assert_pack("YCbCr", "Cb", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - def test_LAB(self): + def test_LAB(self) -> None: self.assert_pack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - def test_HSV(self): + def test_HSV(self) -> None: self.assert_pack("HSV", "HSV", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack("HSV", "H", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("HSV", "S", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("HSV", "V", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) - def test_I(self): + def test_I(self) -> None: self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304) self.assert_pack( "I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 0x01000083, -2097151999 @@ -209,10 +209,10 @@ class TestLibPack: 0x01000083, ) - def test_I16(self): + def test_I16(self) -> None: self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) - def test_F_float(self): + def test_F_float(self) -> None: self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) if sys.byteorder == "little": @@ -228,7 +228,7 @@ class TestLibPack: class TestLibUnpack: - def assert_unpack(self, mode, rawmode, data, *pixels): + def assert_unpack(self, mode, rawmode, data, *pixels) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. """ @@ -241,7 +241,7 @@ class TestLibUnpack: for x, pixel in enumerate(pixels): assert pixel == im.getpixel((x, 0)) - def test_1(self): + def test_1(self) -> None: self.assert_unpack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) self.assert_unpack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) self.assert_unpack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) @@ -254,7 +254,7 @@ class TestLibUnpack: self.assert_unpack("1", "1;8", b"\x00\x01\x02\xff", 0, X, X, X) - def test_L(self): + def test_L(self) -> None: self.assert_unpack("L", "L;2", b"\xe4", 255, 170, 85, 0) self.assert_unpack("L", "L;2I", b"\xe4", 0, 85, 170, 255) self.assert_unpack("L", "L;2R", b"\xe4", 0, 170, 85, 255) @@ -273,14 +273,14 @@ class TestLibUnpack: self.assert_unpack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) self.assert_unpack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) - def test_LA(self): + def test_LA(self) -> None: self.assert_unpack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_unpack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) - def test_La(self): + def test_La(self) -> None: self.assert_unpack("La", "La", 2, (1, 2), (3, 4), (5, 6)) - def test_P(self): + def test_P(self) -> None: self.assert_unpack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 1, 0, 0) self.assert_unpack("P", "P;2", b"\xe4", 3, 2, 1, 0) # erroneous? @@ -291,11 +291,11 @@ class TestLibUnpack: self.assert_unpack("P", "P", 1, 1, 2, 3, 4) self.assert_unpack("P", "P;R", 1, 128, 64, 192, 32) - def test_PA(self): + def test_PA(self) -> None: self.assert_unpack("PA", "PA", 2, (1, 2), (3, 4), (5, 6)) self.assert_unpack("PA", "PA;L", 2, (1, 4), (2, 5), (3, 6)) - def test_RGB(self): + def test_RGB(self) -> None: self.assert_unpack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_unpack("RGB", "RGB;R", 3, (128, 64, 192), (32, 160, 96)) @@ -346,14 +346,14 @@ class TestLibUnpack: "RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233) ) - def test_BGR(self): + def test_BGR(self) -> None: self.assert_unpack("BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)) self.assert_unpack( "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) ) self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - def test_RGBA(self): + def test_RGBA(self) -> None: self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11) @@ -522,7 +522,7 @@ class TestLibUnpack: "RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5) ) - def test_RGBa(self): + def test_RGBa(self) -> None: self.assert_unpack( "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) ) @@ -536,7 +536,7 @@ class TestLibUnpack: "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9) ) - def test_RGBX(self): + def test_RGBX(self) -> None: self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) self.assert_unpack("RGBX", "RGB;L", 3, (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X)) @@ -581,7 +581,7 @@ class TestLibUnpack: self.assert_unpack("RGBX", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) - def test_CMYK(self): + def test_CMYK(self) -> None: self.assert_unpack( "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) ) @@ -619,25 +619,25 @@ class TestLibUnpack: "CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252) ) - def test_YCbCr(self): + def test_YCbCr(self) -> None: self.assert_unpack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_unpack("YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack("YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - def test_LAB(self): + def test_LAB(self) -> None: self.assert_unpack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) - def test_HSV(self): + def test_HSV(self) -> None: self.assert_unpack("HSV", "HSV", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack("HSV", "H", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("HSV", "S", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("HSV", "V", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) - def test_I(self): + def test_I(self) -> None: self.assert_unpack("I", "I;8", 1, 0x01, 0x02, 0x03, 0x04) self.assert_unpack("I", "I;8S", b"\x01\x83", 1, -125) self.assert_unpack("I", "I;16", 2, 0x0201, 0x0403) @@ -678,7 +678,7 @@ class TestLibUnpack: 0x01000083, ) - def test_F_int(self): + def test_F_int(self) -> None: self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04) self.assert_unpack("F", "F;8S", b"\x01\x83", 1, -125) self.assert_unpack("F", "F;16", 2, 0x0201, 0x0403) @@ -717,7 +717,7 @@ class TestLibUnpack: 16777348, ) - def test_F_float(self): + def test_F_float(self) -> None: self.assert_unpack( "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34 ) @@ -768,7 +768,7 @@ class TestLibUnpack: -1234.5, ) - def test_I16(self): + def test_I16(self) -> None: self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16", "I;16B", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506) @@ -785,7 +785,7 @@ class TestLibUnpack: self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506) - def test_CMYK16(self): + def test_CMYK16(self) -> None: self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("CMYK", "CMYK;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) if sys.byteorder == "little": @@ -793,7 +793,7 @@ class TestLibUnpack: else: self.assert_unpack("CMYK", "CMYK;16N", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - def test_value_error(self): + def test_value_error(self) -> None: with pytest.raises(ValueError): self.assert_unpack("L", "L", 0, 0) with pytest.raises(ValueError): diff --git a/Tests/test_map.py b/Tests/test_map.py index 9c79fe359..93140f6e5 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -7,7 +7,7 @@ import pytest from PIL import Image -def test_overflow(): +def test_overflow() -> None: # There is the potential to overflow comparisons in map.c # if there are > SIZE_MAX bytes in the image or if # the file encodes an offset that makes @@ -25,7 +25,7 @@ def test_overflow(): Image.MAX_IMAGE_PIXELS = max_pixels -def test_tobytes(): +def test_tobytes() -> None: # Note that this image triggers the decompression bomb warning: max_pixels = Image.MAX_IMAGE_PIXELS Image.MAX_IMAGE_PIXELS = None @@ -39,7 +39,7 @@ def test_tobytes(): @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") -def test_ysize(): +def test_ysize() -> None: numpy = pytest.importorskip("numpy", reason="NumPy not installed") # Should not raise 'Integer overflow in ysize' diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index d3ee511b7..f2540bb46 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import Image @@ -9,7 +11,7 @@ from .helper import hopper original = hopper().resize((32, 32)).convert("I") -def verify(im1): +def verify(im1) -> None: im2 = original.copy() assert im1.size == im2.size pix1 = im1.load() @@ -25,7 +27,7 @@ def verify(im1): @pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I")) -def test_basic(tmp_path, mode): +def test_basic(tmp_path: Path, mode) -> None: # PIL 1.1 has limited support for 16-bit image data. Check that # create/copy/transform and save works as expected. @@ -75,7 +77,7 @@ def test_basic(tmp_path, mode): assert im_in.getpixel((0, 0)) == min(512, maximum) -def test_tobytes(): +def test_tobytes() -> None: def tobytes(mode): return Image.new(mode, (1, 1), 1).tobytes() @@ -87,7 +89,7 @@ def test_tobytes(): assert tobytes("I") == b"\x01\x00\x00\x00"[::order] -def test_convert(): +def test_convert() -> None: im = original.copy() for mode in ("I;16", "I;16B", "I;16N"): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 24dff36a6..6ba95c2d7 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -13,8 +13,8 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed") TEST_IMAGE_SIZE = (10, 10) -def test_numpy_to_image(): - def to_image(dtype, bands=1, boolean=0): +def test_numpy_to_image() -> None: + def to_image(dtype, bands: int = 1, boolean: int = 0): if bands == 1: if boolean: data = [0, 255] * 50 @@ -82,7 +82,7 @@ def test_numpy_to_image(): # Based on an erring example at # https://stackoverflow.com/questions/10854903/what-is-causing-dimension-dependent-attributeerror-in-pil-fromarray-function -def test_3d_array(): +def test_3d_array() -> None: size = (5, TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1]) a = numpy.ones(size, dtype=numpy.uint8) assert_image(Image.fromarray(a[1, :, :]), "L", TEST_IMAGE_SIZE) @@ -94,12 +94,12 @@ def test_3d_array(): assert_image(Image.fromarray(a[:, :, 1]), "L", TEST_IMAGE_SIZE) -def test_1d_array(): +def test_1d_array() -> None: a = numpy.ones(5, dtype=numpy.uint8) assert_image(Image.fromarray(a), "L", (1, 5)) -def _test_img_equals_nparray(img, np): +def _test_img_equals_nparray(img, np) -> None: assert len(np.shape) >= 2 np_size = np.shape[1], np.shape[0] assert img.size == np_size @@ -109,14 +109,14 @@ def _test_img_equals_nparray(img, np): assert_deep_equal(px[x, y], np[y, x]) -def test_16bit(): +def test_16bit() -> None: with Image.open("Tests/images/16bit.cropped.tif") as img: np_img = numpy.array(img) _test_img_equals_nparray(img, np_img) assert np_img.dtype == numpy.dtype(" None: # Test that 1-bit arrays convert to numpy and back # See: https://github.com/python-pillow/Pillow/issues/350 arr = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], "u1") @@ -126,7 +126,7 @@ def test_1bit(): numpy.testing.assert_array_equal(arr, arr_back) -def test_save_tiff_uint16(): +def test_save_tiff_uint16() -> None: # Tests that we're getting the pixel value in the right byte order. pixel_value = 0x1234 a = numpy.array( @@ -157,7 +157,7 @@ def test_save_tiff_uint16(): ("HSV", numpy.uint8), ), ) -def test_to_array(mode, dtype): +def test_to_array(mode, dtype) -> None: img = hopper(mode) # Resize to non-square @@ -169,7 +169,7 @@ def test_to_array(mode, dtype): assert np_img.dtype == dtype -def test_point_lut(): +def test_point_lut() -> None: # See https://github.com/python-pillow/Pillow/issues/439 data = list(range(256)) * 3 @@ -180,7 +180,7 @@ def test_point_lut(): im.point(lut) -def test_putdata(): +def test_putdata() -> None: # Shouldn't segfault # See https://github.com/python-pillow/Pillow/issues/1008 @@ -207,12 +207,12 @@ def test_putdata(): numpy.float64, ), ) -def test_roundtrip_eye(dtype): +def test_roundtrip_eye(dtype) -> None: arr = numpy.eye(10, dtype=dtype) numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr))) -def test_zero_size(): +def test_zero_size() -> None: # Shouldn't cause floating point exception # See https://github.com/python-pillow/Pillow/issues/2259 @@ -222,13 +222,13 @@ def test_zero_size(): @skip_unless_feature("libtiff") -def test_load_first(): +def test_load_first() -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: a = numpy.array(im) assert a.shape == (88, 590) -def test_bool(): +def test_bool() -> None: # https://github.com/python-pillow/Pillow/issues/2044 a = numpy.zeros((10, 2), dtype=bool) a[0][0] = True @@ -237,7 +237,7 @@ def test_bool(): assert im2.getdata()[0] == 255 -def test_no_resource_warning_for_numpy_array(): +def test_no_resource_warning_for_numpy_array() -> None: # https://github.com/python-pillow/Pillow/issues/835 # Arrange from numpy import array diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index a89d75b59..f6b12cb20 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -19,14 +19,14 @@ from PIL.PdfParser import ( ) -def test_text_encode_decode(): +def test_text_encode_decode() -> None: assert encode_text("abc") == b"\xFE\xFF\x00a\x00b\x00c" assert decode_text(b"\xFE\xFF\x00a\x00b\x00c") == "abc" assert decode_text(b"abc") == "abc" assert decode_text(b"\x1B a \x1C") == "\u02D9 a \u02DD" -def test_indirect_refs(): +def test_indirect_refs() -> None: assert IndirectReference(1, 2) == IndirectReference(1, 2) assert IndirectReference(1, 2) != IndirectReference(1, 3) assert IndirectReference(1, 2) != IndirectObjectDef(1, 2) @@ -37,7 +37,7 @@ def test_indirect_refs(): assert IndirectObjectDef(1, 2) != (1, 2) -def test_parsing(): +def test_parsing() -> None: assert PdfParser.interpret_name(b"Name#23Hash") == b"Name#Hash" assert PdfParser.interpret_name(b"Name#23Hash", as_text=True) == "Name#Hash" assert PdfParser.get_value(b"1 2 R ", 0) == (IndirectReference(1, 2), 5) @@ -95,7 +95,7 @@ def test_parsing(): assert time.strftime("%Y%m%d%H%M%S", getattr(d, name)) == value -def test_pdf_repr(): +def test_pdf_repr() -> None: assert bytes(IndirectReference(1, 2)) == b"1 2 R" assert bytes(IndirectObjectDef(*IndirectReference(1, 2))) == b"1 2 obj" assert bytes(PdfName(b"Name#Hash")) == b"/Name#23Hash" @@ -121,7 +121,7 @@ def test_pdf_repr(): assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>" -def test_duplicate_xref_entry(): +def test_duplicate_xref_entry() -> None: pdf = PdfParser("Tests/images/duplicate_xref_entry.pdf") assert pdf.xref_table.existing_entries[6][0] == 1197 pdf.close() diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index c445e3494..560cdbd35 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,6 +1,7 @@ from __future__ import annotations import pickle +from pathlib import Path import pytest @@ -12,7 +13,7 @@ FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" -def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode): +def helper_pickle_file(tmp_path: Path, pickle, protocol, test_file, mode) -> None: # Arrange with Image.open(test_file) as im: filename = str(tmp_path / "temp.pkl") @@ -29,7 +30,7 @@ def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode): assert im == loaded_im -def helper_pickle_string(pickle, protocol, test_file, mode): +def helper_pickle_string(pickle, protocol, test_file, mode) -> None: with Image.open(test_file) as im: if mode: im = im.convert(mode) @@ -63,13 +64,13 @@ def helper_pickle_string(pickle, protocol, test_file, mode): ], ) @pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1)) -def test_pickle_image(tmp_path, test_file, test_mode, protocol): +def test_pickle_image(tmp_path: Path, test_file, test_mode, protocol) -> None: # Act / Assert helper_pickle_string(pickle, protocol, test_file, test_mode) helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode) -def test_pickle_la_mode_with_palette(tmp_path): +def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: # Arrange filename = str(tmp_path / "temp.pkl") with Image.open("Tests/images/hopper.jpg") as im: @@ -88,7 +89,7 @@ def test_pickle_la_mode_with_palette(tmp_path): @skip_unless_feature("webp") -def test_pickle_tell(): +def test_pickle_tell() -> None: # Arrange with Image.open("Tests/images/hopper.webp") as image: # Act: roundtrip @@ -98,7 +99,7 @@ def test_pickle_tell(): assert unpickled_image.tell() == 0 -def helper_assert_pickled_font_images(font1, font2): +def helper_assert_pickled_font_images(font1, font2) -> None: # Arrange im1 = Image.new(mode="RGBA", size=(300, 100)) im2 = Image.new(mode="RGBA", size=(300, 100)) @@ -116,7 +117,7 @@ def helper_assert_pickled_font_images(font1, font2): @skip_unless_feature("freetype2") @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) -def test_pickle_font_string(protocol): +def test_pickle_font_string(protocol) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -130,7 +131,7 @@ def test_pickle_font_string(protocol): @skip_unless_feature("freetype2") @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) -def test_pickle_font_file(tmp_path, protocol): +def test_pickle_font_file(tmp_path: Path, protocol) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) filename = str(tmp_path / "temp.pkl") diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 7f618d0f5..797539f35 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -3,13 +3,14 @@ from __future__ import annotations import os import sys from io import BytesIO +from pathlib import Path import pytest from PIL import Image, PSDraw -def _create_document(ps): +def _create_document(ps) -> None: title = "hopper" box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points @@ -31,7 +32,7 @@ def _create_document(ps): ps.end_document() -def test_draw_postscript(tmp_path): +def test_draw_postscript(tmp_path: Path) -> None: # Based on Pillow tutorial, but there is no textsize: # https://pillow.readthedocs.io/en/latest/handbook/tutorial.html#drawing-postscript @@ -49,7 +50,7 @@ def test_draw_postscript(tmp_path): @pytest.mark.parametrize("buffer", (True, False)) -def test_stdout(buffer): +def test_stdout(buffer) -> None: # Temporarily redirect stdout old_stdout = sys.stdout diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index ad2b5ad9b..7d6c0a8cb 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import ImageQt @@ -19,7 +21,7 @@ if ImageQt.qt_is_installed: from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget class Example(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() img = hopper().resize((1000, 1000)) @@ -35,14 +37,14 @@ if ImageQt.qt_is_installed: lbl.setPixmap(pixmap1.copy()) -def roundtrip(expected): +def roundtrip(expected) -> None: result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) # Qt saves all pixmaps as rgb assert_image_similar(result, expected.convert("RGB"), 1) @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") -def test_sanity(tmp_path): +def test_sanity(tmp_path: Path) -> None: # Segfault test app = QApplication([]) ex = Example() diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index b26787ce6..a222a7d71 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + import pytest from PIL import ImageQt @@ -15,7 +17,7 @@ if ImageQt.qt_is_installed: @pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1")) -def test_sanity(mode, tmp_path): +def test_sanity(mode, tmp_path: Path) -> None: src = hopper(mode) data = ImageQt.toqimage(src) diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index dee6258ec..9442801d0 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -21,7 +21,7 @@ from PIL import Image "Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi", ], ) -def test_crashes(test_file): +def test_crashes(test_file) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 9f3e86a32..3db0660ea 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,6 +1,7 @@ from __future__ import annotations import shutil +from pathlib import Path import pytest @@ -16,7 +17,7 @@ test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") class TestShellInjection: - def assert_save_filename_check(self, tmp_path, src_img, save_func): + def assert_save_filename_check(self, tmp_path: Path, src_img, save_func) -> None: for filename in test_filenames: dest_file = str(tmp_path / filename) save_func(src_img, 0, dest_file) @@ -25,7 +26,7 @@ class TestShellInjection: im.load() @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") - def test_load_djpeg_filename(self, tmp_path): + def test_load_djpeg_filename(self, tmp_path: Path) -> None: for filename in test_filenames: src_file = str(tmp_path / filename) shutil.copy(TEST_JPG, src_file) @@ -34,18 +35,18 @@ class TestShellInjection: im.load_djpeg() @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") - def test_save_cjpeg_filename(self, tmp_path): + def test_save_cjpeg_filename(self, tmp_path: Path) -> None: with Image.open(TEST_JPG) as im: self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg) @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") - def test_save_netpbm_filename_bmp_mode(self, tmp_path): + def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: im = im.convert("RGB") self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") - def test_save_netpbm_filename_l_mode(self, tmp_path): + def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: im = im.convert("L") self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index c07e7f7d3..536854523 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,6 +1,7 @@ from __future__ import annotations from fractions import Fraction +from pathlib import Path from PIL import Image, TiffImagePlugin, features from PIL.TiffImagePlugin import IFDRational @@ -8,14 +9,14 @@ from PIL.TiffImagePlugin import IFDRational from .helper import hopper -def _test_equal(num, denom, target): +def _test_equal(num, denom, target) -> None: t = IFDRational(num, denom) assert target == t assert t == target -def test_sanity(): +def test_sanity() -> None: _test_equal(1, 1, 1) _test_equal(1, 1, Fraction(1, 1)) @@ -31,13 +32,13 @@ def test_sanity(): _test_equal(7, 5, 1.4) -def test_ranges(): +def test_ranges() -> None: for num in range(1, 10): for denom in range(1, 10): assert IFDRational(num, denom) == IFDRational(num, denom) -def test_nonetype(): +def test_nonetype() -> None: # Fails if the _delegate function doesn't return a valid function xres = IFDRational(72) @@ -51,7 +52,7 @@ def test_nonetype(): assert xres and yres -def test_ifd_rational_save(tmp_path): +def test_ifd_rational_save(tmp_path: Path) -> None: methods = (True, False) if not features.check("libtiff"): methods = (False,) From bb1fece57a2c894773597c9a6fb10bd81e36123d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 31 Jan 2024 21:55:32 +1100 Subject: [PATCH 044/157] Added type hints --- Tests/test_bmp_reference.py | 6 +- Tests/test_box_blur.py | 12 +- Tests/test_file_apng.py | 6 +- Tests/test_file_container.py | 10 +- Tests/test_file_gif.py | 30 ++-- Tests/test_file_mpo.py | 19 +-- Tests/test_file_ppm.py | 22 +-- Tests/test_file_sgi.py | 2 +- Tests/test_image_frombytes.py | 2 +- Tests/test_image_load.py | 2 +- Tests/test_imagedraw.py | 291 ++++++++++++++++++++++++++++------ Tests/test_imagetk.py | 4 +- Tests/test_lib_pack.py | 16 +- Tests/test_mode_i16.py | 6 +- 14 files changed, 327 insertions(+), 101 deletions(-) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 22ac9443e..0ad496135 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -10,7 +10,7 @@ from .helper import assert_image_similar base = os.path.join("Tests", "images", "bmp") -def get_files(d, ext: str = ".bmp"): +def get_files(d: str, ext: str = ".bmp") -> list[str]: return [ os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f ] @@ -29,7 +29,7 @@ def test_bad() -> None: pass -def test_questionable(): +def test_questionable() -> None: """These shouldn't crash/dos, but it's not well defined that these are in spec""" supported = [ @@ -80,7 +80,7 @@ def test_good() -> None: "rgb32bf.bmp": "rgb24.png", } - def get_compare(f): + def get_compare(f: str) -> str: name = os.path.split(f)[1] if name in file_map: return os.path.join(base, "html", file_map[name]) diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index dfedb48d9..1f6ed6127 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -23,11 +23,11 @@ def test_imageops_box_blur() -> None: assert isinstance(i, Image.Image) -def box_blur(image, radius: int = 1, n: int = 1): +def box_blur(image: Image.Image, radius: float = 1, n: int = 1) -> Image.Image: return image._new(image.im.box_blur((radius, radius), n)) -def assert_image(im, data, delta: int = 0) -> None: +def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None: it = iter(im.getdata()) for data_row in data: im_row = [next(it) for _ in range(im.size[0])] @@ -37,7 +37,13 @@ def assert_image(im, data, delta: int = 0) -> None: next(it) -def assert_blur(im, radius, data, passes: int = 1, delta: int = 0) -> None: +def assert_blur( + im: Image.Image, + radius: float, + data: list[list[int]], + passes: int = 1, + delta: int = 0, +) -> None: # check grayscale image assert_image(box_blur(im, radius, passes), data, delta) rgba = Image.merge("RGBA", (im, im, im, im)) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index f9edf6e98..395165b36 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -47,7 +47,7 @@ def test_apng_basic() -> None: "filename", ("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"), ) -def test_apng_fdat(filename) -> None: +def test_apng_fdat(filename: str) -> None: with Image.open(filename) as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) @@ -338,7 +338,7 @@ def test_apng_syntax_errors() -> None: "sequence_fdat_fctl.png", ), ) -def test_apng_sequence_errors(test_file) -> None: +def test_apng_sequence_errors(test_file: str) -> None: with pytest.raises(SyntaxError): with Image.open(f"Tests/images/apng/{test_file}") as im: im.seek(im.n_frames - 1) @@ -681,7 +681,7 @@ def test_seek_after_close() -> None: @pytest.mark.parametrize("default_image", (True, False)) @pytest.mark.parametrize("duplicate", (True, False)) def test_different_modes_in_later_frames( - mode, default_image, duplicate, tmp_path: Path + mode: str, default_image: bool, duplicate: bool, tmp_path: Path ) -> None: test_file = str(tmp_path / "temp.png") diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 4dba4be5d..813b444db 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -64,7 +64,7 @@ def test_seek_mode_2() -> None: @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_n0(bytesmode) -> None: +def test_read_n0(bytesmode: bool) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -80,7 +80,7 @@ def test_read_n0(bytesmode) -> None: @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_n(bytesmode) -> None: +def test_read_n(bytesmode: bool) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -96,7 +96,7 @@ def test_read_n(bytesmode) -> None: @pytest.mark.parametrize("bytesmode", (True, False)) -def test_read_eof(bytesmode) -> None: +def test_read_eof(bytesmode: bool) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -112,7 +112,7 @@ def test_read_eof(bytesmode) -> None: @pytest.mark.parametrize("bytesmode", (True, False)) -def test_readline(bytesmode) -> None: +def test_readline(bytesmode: bool) -> None: # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 0, 120) @@ -127,7 +127,7 @@ def test_readline(bytesmode) -> None: @pytest.mark.parametrize("bytesmode", (True, False)) -def test_readlines(bytesmode) -> None: +def test_readlines(bytesmode: bool) -> None: # Arrange expected = [ "This is line 1\n", diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3f550fd11..db9d3586c 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -3,6 +3,7 @@ from __future__ import annotations import warnings from io import BytesIO from pathlib import Path +from typing import Generator import pytest @@ -144,13 +145,13 @@ def test_strategy() -> None: def test_optimize() -> None: - def test_grayscale(optimize): + def test_grayscale(optimize: int) -> int: im = Image.new("L", (1, 1), 0) filename = BytesIO() im.save(filename, "GIF", optimize=optimize) return len(filename.getvalue()) - def test_bilevel(optimize): + def test_bilevel(optimize: int) -> int: im = Image.new("1", (1, 1), 0) test_file = BytesIO() im.save(test_file, "GIF", optimize=optimize) @@ -178,7 +179,9 @@ def test_optimize() -> None: (4, 513, 256), ), ) -def test_optimize_correctness(colors, size, expected_palette_length) -> None: +def test_optimize_correctness( + colors: int, size: int, expected_palette_length: int +) -> None: # 256 color Palette image, posterize to > 128 and < 128 levels. # Size bigger and smaller than 512x512. # Check the palette for number of colors allocated. @@ -297,7 +300,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None: ("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), ), ) -def test_loading_multiple_palettes(path, mode) -> None: +def test_loading_multiple_palettes(path: str, mode: str) -> None: with Image.open(path) as im: assert im.mode == "P" first_frame_colors = im.palette.colors.keys() @@ -347,9 +350,9 @@ def test_palette_handling(tmp_path: Path) -> None: def test_palette_434(tmp_path: Path) -> None: # see https://github.com/python-pillow/Pillow/issues/434 - def roundtrip(im, *args, **kwargs): + def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image: out = str(tmp_path / "temp.gif") - im.copy().save(out, *args, **kwargs) + im.copy().save(out, **kwargs) reloaded = Image.open(out) return reloaded @@ -429,7 +432,7 @@ def test_seek_rewind() -> None: ("Tests/images/iss634.gif", 42), ), ) -def test_n_frames(path, n_frames) -> None: +def test_n_frames(path: str, n_frames: int) -> None: # Test is_animated before n_frames with Image.open(path) as im: assert im.is_animated == (n_frames != 1) @@ -541,7 +544,10 @@ def test_dispose_background_transparency() -> None: ), ), ) -def test_transparent_dispose(loading_strategy, expected_colors) -> None: +def test_transparent_dispose( + loading_strategy: GifImagePlugin.LoadingStrategy, + expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]], +) -> None: GifImagePlugin.LOADING_STRATEGY = loading_strategy try: with Image.open("Tests/images/transparent_dispose.gif") as img: @@ -889,7 +895,9 @@ def test_identical_frames(tmp_path: Path) -> None: 1500, ), ) -def test_identical_frames_to_single_frame(duration, tmp_path: Path) -> None: +def test_identical_frames_to_single_frame( + duration: int | list[int], tmp_path: Path +) -> None: out = str(tmp_path / "temp.gif") im_list = [ Image.new("L", (100, 100), "#000"), @@ -1049,7 +1057,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None: def test_version(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") - def assert_version_after_save(im, version) -> None: + def assert_version_after_save(im: Image.Image, version: bytes) -> None: im.save(out) with Image.open(out) as reread: assert reread.info["version"] == version @@ -1088,7 +1096,7 @@ def test_append_images(tmp_path: Path) -> None: assert reread.n_frames == 3 # Tests appending using a generator - def im_generator(ims): + def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]: yield from ims im.save(out, save_all=True, append_images=im_generator(ims)) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 55b04a1e0..4fb00d699 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -2,6 +2,7 @@ from __future__ import annotations import warnings from io import BytesIO +from typing import Any import pytest @@ -19,7 +20,7 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] pytestmark = skip_unless_feature("jpg") -def roundtrip(im, **options): +def roundtrip(im: Image.Image, **options: Any) -> Image.Image: out = BytesIO() im.save(out, "MPO", **options) test_bytes = out.tell() @@ -30,7 +31,7 @@ def roundtrip(im, **options): @pytest.mark.parametrize("test_file", test_files) -def test_sanity(test_file) -> None: +def test_sanity(test_file: str) -> None: with Image.open(test_file) as im: im.load() assert im.mode == "RGB" @@ -70,7 +71,7 @@ def test_context_manager() -> None: @pytest.mark.parametrize("test_file", test_files) -def test_app(test_file) -> None: +def test_app(test_file: str) -> None: # Test APP/COM reader (@PIL135) with Image.open(test_file) as im: assert im.applist[0][0] == "APP1" @@ -82,7 +83,7 @@ def test_app(test_file) -> None: @pytest.mark.parametrize("test_file", test_files) -def test_exif(test_file) -> None: +def test_exif(test_file: str) -> None: with Image.open(test_file) as im_original: im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif()) @@ -143,7 +144,7 @@ def test_reload_exif_after_seek() -> None: @pytest.mark.parametrize("test_file", test_files) -def test_mp(test_file) -> None: +def test_mp(test_file: str) -> None: with Image.open(test_file) as im: mpinfo = im._getmp() assert mpinfo[45056] == b"0100" @@ -168,7 +169,7 @@ def test_mp_no_data() -> None: @pytest.mark.parametrize("test_file", test_files) -def test_mp_attribute(test_file) -> None: +def test_mp_attribute(test_file: str) -> None: with Image.open(test_file) as im: mpinfo = im._getmp() for frame_number, mpentry in enumerate(mpinfo[0xB002]): @@ -185,7 +186,7 @@ def test_mp_attribute(test_file) -> None: @pytest.mark.parametrize("test_file", test_files) -def test_seek(test_file) -> None: +def test_seek(test_file: str) -> None: with Image.open(test_file) as im: assert im.tell() == 0 # prior to first image raises an error, both blatant and borderline @@ -229,7 +230,7 @@ def test_eoferror() -> None: @pytest.mark.parametrize("test_file", test_files) -def test_image_grab(test_file) -> None: +def test_image_grab(test_file: str) -> None: with Image.open(test_file) as im: assert im.tell() == 0 im0 = im.tobytes() @@ -244,7 +245,7 @@ def test_image_grab(test_file) -> None: @pytest.mark.parametrize("test_file", test_files) -def test_save(test_file) -> None: +def test_save(test_file: str) -> None: with Image.open(test_file) as im: assert im.tell() == 0 jpg0 = roundtrip(im) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 94f66ee7d..6e0fa32e4 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -70,7 +70,9 @@ def test_sanity() -> None: ), ), ) -def test_arbitrary_maxval(data, mode, pixels) -> None: +def test_arbitrary_maxval( + data: bytes, mode: str, pixels: tuple[int | tuple[int, int, int], ...] +) -> None: fp = BytesIO(data) with Image.open(fp) as im: assert im.size == (3, 1) @@ -139,7 +141,7 @@ def test_pfm_big_endian(tmp_path: Path) -> None: b"Pf 1 1 -0.0 \0\0\0\0", ], ) -def test_pfm_invalid(data) -> None: +def test_pfm_invalid(data: bytes) -> None: with pytest.raises(ValueError): with Image.open(BytesIO(data)): pass @@ -162,7 +164,7 @@ def test_pfm_invalid(data) -> None: ), ), ) -def test_plain(plain_path, raw_path) -> None: +def test_plain(plain_path: str, raw_path: str) -> None: with Image.open(plain_path) as im: assert_image_equal_tofile(im, raw_path) @@ -186,7 +188,9 @@ def test_16bit_plain_pgm() -> None: (b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6), ), ) -def test_plain_data_with_comment(tmp_path: Path, header, data, comment_count) -> None: +def test_plain_data_with_comment( + tmp_path: Path, header: bytes, data: bytes, comment_count: int +) -> None: path1 = str(tmp_path / "temp1.ppm") path2 = str(tmp_path / "temp2.ppm") comment = b"# comment" * comment_count @@ -199,7 +203,7 @@ def test_plain_data_with_comment(tmp_path: Path, header, data, comment_count) -> @pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n")) -def test_plain_truncated_data(tmp_path: Path, data) -> None: +def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -210,7 +214,7 @@ def test_plain_truncated_data(tmp_path: Path, data) -> None: @pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A")) -def test_plain_invalid_data(tmp_path: Path, data) -> None: +def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -227,7 +231,7 @@ def test_plain_invalid_data(tmp_path: Path, data) -> None: b"P3\n128 128\n255\n012345678910 0", # token too long ), ) -def test_plain_ppm_token_too_long(tmp_path: Path, data) -> None: +def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(data) @@ -313,7 +317,7 @@ def test_not_enough_image_data(tmp_path: Path) -> None: @pytest.mark.parametrize("maxval", (b"0", b"65536")) -def test_invalid_maxval(maxval, tmp_path: Path) -> None: +def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n3 1 " + maxval) @@ -351,7 +355,7 @@ def test_mimetypes(tmp_path: Path) -> None: @pytest.mark.parametrize("buffer", (True, False)) -def test_save_stdout(buffer) -> None: +def test_save_stdout(buffer: bool) -> None: old_stdout = sys.stdout if buffer: diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 92aea0735..e13a8019e 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -72,7 +72,7 @@ def test_invalid_file() -> None: def test_write(tmp_path: Path) -> None: - def roundtrip(img) -> None: + def roundtrip(img: Image.Image) -> None: out = str(tmp_path / "temp.sgi") img.save(out, format="sgi") assert_image_equal_tofile(img, out) diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index 6474daba1..98c0ea0b4 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -8,7 +8,7 @@ from .helper import assert_image_equal, hopper @pytest.mark.parametrize("data_type", ("bytes", "memoryview")) -def test_sanity(data_type) -> None: +def test_sanity(data_type: str) -> None: im1 = hopper() data = im1.tobytes() diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index 5b1a9ee2d..0605821e0 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -26,7 +26,7 @@ def test_close() -> None: im.getpixel((0, 0)) -def test_close_after_load(caplog) -> None: +def test_close_after_load(caplog: pytest.LogCaptureFixture) -> None: im = Image.open("Tests/images/hopper.gif") im.load() with caplog.at_level(logging.DEBUG): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 86d25b1eb..c02ac49dd 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -74,7 +74,14 @@ def test_mode_mismatch() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) -def test_arc(bbox, start, end) -> None: +def test_arc( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int], + start: float, + end: float, +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -87,7 +94,12 @@ def test_arc(bbox, start, end) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_end_le_start(bbox) -> None: +def test_arc_end_le_start( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -102,7 +114,12 @@ def test_arc_end_le_start(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_no_loops(bbox) -> None: +def test_arc_no_loops( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # No need to go in loops # Arrange im = Image.new("RGB", (W, H)) @@ -118,7 +135,12 @@ def test_arc_no_loops(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width(bbox) -> None: +def test_arc_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -131,7 +153,12 @@ def test_arc_width(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_pieslice_large(bbox) -> None: +def test_arc_width_pieslice_large( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Tests an arc with a large enough width that it is a pieslice # Arrange im = Image.new("RGB", (W, H)) @@ -145,7 +172,12 @@ def test_arc_width_pieslice_large(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_fill(bbox) -> None: +def test_arc_width_fill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -158,7 +190,12 @@ def test_arc_width_fill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_non_whole_angle(bbox) -> None: +def test_arc_width_non_whole_angle( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -200,7 +237,13 @@ def test_bitmap() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_chord(mode, bbox) -> None: +def test_chord( + mode: str, + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int], +) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -214,7 +257,12 @@ def test_chord(mode, bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width(bbox) -> None: +def test_chord_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -227,7 +275,12 @@ def test_chord_width(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width_fill(bbox) -> None: +def test_chord_width_fill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -240,7 +293,12 @@ def test_chord_width_fill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_zero_width(bbox) -> None: +def test_chord_zero_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -266,7 +324,13 @@ def test_chord_too_fat() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse(mode, bbox) -> None: +def test_ellipse( + mode: str, + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int], +) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -280,7 +344,12 @@ def test_ellipse(mode, bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_translucent(bbox) -> None: +def test_ellipse_translucent( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -317,7 +386,12 @@ def test_ellipse_symmetric() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width(bbox) -> None: +def test_ellipse_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -342,7 +416,12 @@ def test_ellipse_width_large() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width_fill(bbox) -> None: +def test_ellipse_width_fill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -355,7 +434,12 @@ def test_ellipse_width_fill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_zero_width(bbox) -> None: +def test_ellipse_zero_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -367,7 +451,7 @@ def test_ellipse_zero_width(bbox) -> None: assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png") -def ellipse_various_sizes_helper(filled): +def ellipse_various_sizes_helper(filled: bool) -> Image.Image: ellipse_sizes = range(32) image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1 im = Image.new("RGB", (image_size, image_size)) @@ -409,7 +493,12 @@ def test_ellipse_various_sizes_filled() -> None: @pytest.mark.parametrize("points", POINTS) -def test_line(points) -> None: +def test_line( + points: tuple[tuple[int, int], ...] + | list[tuple[int, int]] + | tuple[int, ...] + | list[int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -482,7 +571,14 @@ def test_transform() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) -def test_pieslice(bbox, start, end) -> None: +def test_pieslice( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int], + start: float, + end: float, +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -495,7 +591,12 @@ def test_pieslice(bbox, start, end) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width(bbox) -> None: +def test_pieslice_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -508,7 +609,12 @@ def test_pieslice_width(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width_fill(bbox) -> None: +def test_pieslice_width_fill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -522,7 +628,12 @@ def test_pieslice_width_fill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_zero_width(bbox) -> None: +def test_pieslice_zero_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -577,7 +688,12 @@ def test_pieslice_no_spikes() -> None: @pytest.mark.parametrize("points", POINTS) -def test_point(points) -> None: +def test_point( + points: tuple[tuple[int, int], ...] + | list[tuple[int, int]] + | tuple[int, ...] + | list[int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -602,7 +718,12 @@ def test_point_I16() -> None: @pytest.mark.parametrize("points", POINTS) -def test_polygon(points) -> None: +def test_polygon( + points: tuple[tuple[int, int], ...] + | list[tuple[int, int]] + | tuple[int, ...] + | list[int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -616,7 +737,9 @@ def test_polygon(points) -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("kite_points", KITE_POINTS) -def test_polygon_kite(mode, kite_points) -> None: +def test_polygon_kite( + mode: str, kite_points: tuple[tuple[int, int], ...] | list[tuple[int, int]] +) -> None: # Test drawing lines of different gradients (dx>dy, dy>dx) and # vertical (dx==0) and horizontal (dy==0) lines # Arrange @@ -673,7 +796,12 @@ def test_polygon_translucent() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle(bbox) -> None: +def test_rectangle( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -700,7 +828,12 @@ def test_big_rectangle() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width(bbox) -> None: +def test_rectangle_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -714,7 +847,12 @@ def test_rectangle_width(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width_fill(bbox) -> None: +def test_rectangle_width_fill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -728,7 +866,12 @@ def test_rectangle_width_fill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_zero_width(bbox) -> None: +def test_rectangle_zero_width( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -741,7 +884,12 @@ def test_rectangle_zero_width(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_I16(bbox) -> None: +def test_rectangle_I16( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("I;16", (W, H)) draw = ImageDraw.Draw(im) @@ -754,7 +902,12 @@ def test_rectangle_I16(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_translucent_outline(bbox) -> None: +def test_rectangle_translucent_outline( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -772,7 +925,11 @@ def test_rectangle_translucent_outline(bbox) -> None: "xy", [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], ) -def test_rounded_rectangle(xy) -> None: +def test_rounded_rectangle( + xy: tuple[int, int, int, int] + | tuple[list[int]] + | tuple[tuple[int, int], tuple[int, int]] +) -> None: # Arrange im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) @@ -789,7 +946,7 @@ def test_rounded_rectangle(xy) -> None: @pytest.mark.parametrize("bottom_right", (True, False)) @pytest.mark.parametrize("bottom_left", (True, False)) def test_rounded_rectangle_corners( - top_left, top_right, bottom_right, bottom_left + top_left: bool, top_right: bool, bottom_right: bool, bottom_left: bool ) -> None: corners = (top_left, top_right, bottom_right, bottom_left) @@ -824,7 +981,9 @@ def test_rounded_rectangle_corners( ((10, 20, 190, 181), 85, "height"), ], ) -def test_rounded_rectangle_non_integer_radius(xy, radius, type) -> None: +def test_rounded_rectangle_non_integer_radius( + xy: tuple[int, int, int, int], radius: float, type: str +) -> None: # Arrange im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) @@ -840,7 +999,12 @@ def test_rounded_rectangle_non_integer_radius(xy, radius, type) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rounded_rectangle_zero_radius(bbox) -> None: +def test_rounded_rectangle_zero_radius( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -862,7 +1026,9 @@ def test_rounded_rectangle_zero_radius(bbox) -> None: ((20, 20, 80, 80), "both"), ], ) -def test_rounded_rectangle_translucent(xy, suffix) -> None: +def test_rounded_rectangle_translucent( + xy: tuple[int, int, int, int], suffix: str +) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -879,7 +1045,12 @@ def test_rounded_rectangle_translucent(xy, suffix) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill(bbox) -> None: +def test_floodfill( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: red = ImageColor.getrgb("red") for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: @@ -912,7 +1083,12 @@ def test_floodfill(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_border(bbox) -> None: +def test_floodfill_border( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # floodfill() is experimental # Arrange @@ -934,7 +1110,12 @@ def test_floodfill_border(bbox) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_thresh(bbox) -> None: +def test_floodfill_thresh( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # floodfill() is experimental # Arrange @@ -968,8 +1149,11 @@ def test_floodfill_not_negative() -> None: def create_base_image_draw( - size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY -): + size: tuple[int, int], + mode: str = DEFAULT_MODE, + background1: tuple[int, int, int] = WHITE, + background2: tuple[int, int, int] = GRAY, +) -> tuple[Image.Image, ImageDraw.ImageDraw]: img = Image.new(mode, size, background1) for x in range(0, size[0]): for y in range(0, size[1]): @@ -1003,7 +1187,7 @@ def test_triangle_right() -> None: "fill, suffix", ((BLACK, "width"), (None, "width_no_fill")), ) -def test_triangle_right_width(fill, suffix) -> None: +def test_triangle_right_width(fill: tuple[int, int, int] | None, suffix: str) -> None: img, draw = create_base_image_draw((100, 100)) draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5) assert_image_equal_tofile( @@ -1235,7 +1419,7 @@ def test_wide_line_larger_than_int() -> None: ], ], ) -def test_line_joint(xy) -> None: +def test_line_joint(xy: list[tuple[int, int]] | tuple[int, ...] | list[int]) -> None: im = Image.new("RGB", (500, 325)) draw = ImageDraw.Draw(im) @@ -1388,7 +1572,12 @@ def test_default_font_size() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_same_color_outline(bbox) -> None: +def test_same_color_outline( + bbox: tuple[tuple[int, int], tuple[int, int]] + | list[tuple[int, int]] + | list[int] + | tuple[int, int, int, int] +) -> None: # Prepare shape x0, y0 = 5, 5 x1, y1 = 5, 50 @@ -1402,7 +1591,8 @@ def test_same_color_outline(bbox) -> None: # Begin for mode in ["RGB", "L"]: - for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: + fill = "red" + for outline in [None, "red", "#f00"]: for operation, args in { "chord": [bbox, 0, 180], "ellipse": [bbox], @@ -1417,6 +1607,7 @@ def test_same_color_outline(bbox) -> None: # Act draw_method = getattr(draw, operation) + assert isinstance(args, list) args += [fill, outline] draw_method(*args) @@ -1434,7 +1625,9 @@ def test_same_color_outline(bbox) -> None: (3, "triangle_width", {"width": 5, "outline": "yellow"}), ], ) -def test_draw_regular_polygon(n_sides, polygon_name, args) -> None: +def test_draw_regular_polygon( + n_sides: int, polygon_name: str, args: dict[str, int | str] +) -> None: im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) filename = f"Tests/images/imagedraw_{polygon_name}.png" draw = ImageDraw.Draw(im) @@ -1471,7 +1664,9 @@ def test_draw_regular_polygon(n_sides, polygon_name, args) -> None: ), ], ) -def test_compute_regular_polygon_vertices(n_sides, expected_vertices) -> None: +def test_compute_regular_polygon_vertices( + n_sides: int, expected_vertices: list[tuple[float, float]] +) -> None: bounding_circle = (W // 2, H // 2, 25) vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0) assert vertices == expected_vertices @@ -1569,7 +1764,7 @@ def test_polygon2() -> None: @pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0))) -def test_incorrectly_ordered_coordinates(xy) -> None: +def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None: im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) with pytest.raises(ValueError): diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a216bd21d..b607b8c43 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -57,7 +57,7 @@ def test_kw() -> None: @pytest.mark.parametrize("mode", TK_MODES) -def test_photoimage(mode) -> None: +def test_photoimage(mode: str) -> None: # test as image: im = hopper(mode) @@ -79,7 +79,7 @@ def test_photoimage_apply_transparency() -> None: @pytest.mark.parametrize("mode", TK_MODES) -def test_photoimage_blank(mode) -> None: +def test_photoimage_blank(mode: str) -> None: # test a image using mode/size: im_tk = ImageTk.PhotoImage(mode, (100, 100)) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index c8d6d33d2..629a6dc7a 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -10,7 +10,13 @@ X = 255 class TestLibPack: - def assert_pack(self, mode, rawmode, data, *pixels) -> None: + def assert_pack( + self, + mode: str, + rawmode: str, + data: int | bytes, + *pixels: int | float | tuple[int, ...], + ) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. """ @@ -228,7 +234,13 @@ class TestLibPack: class TestLibUnpack: - def assert_unpack(self, mode, rawmode, data, *pixels) -> None: + def assert_unpack( + self, + mode: str, + rawmode: str, + data: int | bytes, + *pixels: int | float | tuple[int, ...], + ) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. """ diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index f2540bb46..903f7e0c6 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -11,7 +11,7 @@ from .helper import hopper original = hopper().resize((32, 32)).convert("I") -def verify(im1) -> None: +def verify(im1: Image.Image) -> None: im2 = original.copy() assert im1.size == im2.size pix1 = im1.load() @@ -27,7 +27,7 @@ def verify(im1) -> None: @pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I")) -def test_basic(tmp_path: Path, mode) -> None: +def test_basic(tmp_path: Path, mode: str) -> None: # PIL 1.1 has limited support for 16-bit image data. Check that # create/copy/transform and save works as expected. @@ -78,7 +78,7 @@ def test_basic(tmp_path: Path, mode) -> None: def test_tobytes() -> None: - def tobytes(mode): + def tobytes(mode: str) -> Image.Image: return Image.new(mode, (1, 1), 1).tobytes() order = 1 if Image._ENDIAN == "<" else -1 From b8769d1cf5782f3db934b861ad764dc9b1466fb4 Mon Sep 17 00:00:00 2001 From: FangFuxin <38530078+lajiyuan@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:02:50 +0800 Subject: [PATCH 045/157] Update Tests/test_file_png.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_file_png.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0884ddcc3..ec8794b30 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -778,7 +778,7 @@ class TestFilePng: with Image.open(mystdout) as reloaded: assert_image_equal_tofile(reloaded, TEST_PNG_FILE) - def test_truncated_end_chunk(self): + def test_truncated_end_chunk(self) -> None: with Image.open("Tests/images/truncated_end_chunk.png") as im: with pytest.raises(OSError): im.load() From f2228e0a7c19d74c83b99f92edc113ba0cac7625 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:37:53 -0700 Subject: [PATCH 046/157] Replace bytes | str | Path with StrOrBytesPath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- src/PIL/_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index b649500ab..f7a69fae1 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -11,7 +11,7 @@ def is_path(f: Any) -> TypeGuard[StrOrBytesPath]: return isinstance(f, (bytes, str, os.PathLike)) -def is_directory(f: Any) -> TypeGuard[bytes | str | Path]: +def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]: """Checks if an object is a string, and that it points to a directory.""" return is_path(f) and os.path.isdir(f) From 256f3f1966d6b56f178d0a9bb2bc4b0b334c77b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 19:38:49 +0000 Subject: [PATCH 047/157] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index f7a69fae1..6bc762816 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,7 +1,6 @@ from __future__ import annotations import os -from pathlib import Path from typing import Any, NoReturn from ._typing import StrOrBytesPath, TypeGuard From 6dba9c988765084c104fd93c9fcc9ba3d18f6873 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:14:01 +0000 Subject: [PATCH 048/157] Update github-actions to v4 --- .github/workflows/test-cygwin.yml | 4 ++-- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index b5c8c39aa..7bbe5a37f 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -81,7 +81,7 @@ jobs: zlib-devel - name: Add Lapack to PATH - uses: egor-tensin/cleanup-path@v3 + uses: egor-tensin/cleanup-path@v4 with: dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' @@ -142,7 +142,7 @@ jobs: bash.exe .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: GHA_Cygwin diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 3bb6856f6..75aab9bd4 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -101,7 +101,7 @@ jobs: MATRIX_DOCKER: ${{ matrix.docker }} - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: GHA_Docker name: ${{ matrix.docker }} diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index cdd51e2bb..acea78c37 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -82,7 +82,7 @@ jobs: python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 8cad7a8b2..b737615ca 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -202,7 +202,7 @@ jobs: shell: pwsh - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae84a4d8f..038bcfbc3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,7 +149,7 @@ jobs: .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From 2515938cdd321a5940a070f808c01ed48ad4e10e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 2 Feb 2024 19:04:22 +1100 Subject: [PATCH 049/157] Simplified type hints --- Tests/test_imagedraw.py | 222 ++++++---------------------------------- 1 file changed, 32 insertions(+), 190 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index c02ac49dd..6e7dce420 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -2,6 +2,7 @@ from __future__ import annotations import contextlib import os.path +from typing import Sequence import pytest @@ -74,14 +75,7 @@ def test_mode_mismatch() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) -def test_arc( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int], - start: float, - end: float, -) -> None: +def test_arc(bbox: Sequence[int | Sequence[int]], start: float, end: float) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -94,12 +88,7 @@ def test_arc( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_end_le_start( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_end_le_start(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -114,12 +103,7 @@ def test_arc_end_le_start( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_no_loops( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_no_loops(bbox: Sequence[int | Sequence[int]]) -> None: # No need to go in loops # Arrange im = Image.new("RGB", (W, H)) @@ -135,12 +119,7 @@ def test_arc_no_loops( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -153,12 +132,7 @@ def test_arc_width( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_pieslice_large( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_width_pieslice_large(bbox: Sequence[int | Sequence[int]]) -> None: # Tests an arc with a large enough width that it is a pieslice # Arrange im = Image.new("RGB", (W, H)) @@ -172,12 +146,7 @@ def test_arc_width_pieslice_large( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_fill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -190,12 +159,7 @@ def test_arc_width_fill( @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_non_whole_angle( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_arc_width_non_whole_angle(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -237,13 +201,7 @@ def test_bitmap() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_chord( - mode: str, - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int], -) -> None: +def test_chord(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -257,12 +215,7 @@ def test_chord( @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_chord_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -275,12 +228,7 @@ def test_chord_width( @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width_fill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_chord_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -293,12 +241,7 @@ def test_chord_width_fill( @pytest.mark.parametrize("bbox", BBOX) -def test_chord_zero_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_chord_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -324,13 +267,7 @@ def test_chord_too_fat() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse( - mode: str, - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int], -) -> None: +def test_ellipse(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -344,12 +281,7 @@ def test_ellipse( @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_translucent( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_ellipse_translucent(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -386,12 +318,7 @@ def test_ellipse_symmetric() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_ellipse_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -416,12 +343,7 @@ def test_ellipse_width_large() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width_fill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_ellipse_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -434,12 +356,7 @@ def test_ellipse_width_fill( @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_zero_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_ellipse_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -572,12 +489,7 @@ def test_transform() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) def test_pieslice( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int], - start: float, - end: float, + bbox: Sequence[int | Sequence[int]], start: float, end: float ) -> None: # Arrange im = Image.new("RGB", (W, H)) @@ -591,12 +503,7 @@ def test_pieslice( @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_pieslice_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -609,12 +516,7 @@ def test_pieslice_width( @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width_fill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_pieslice_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -628,12 +530,7 @@ def test_pieslice_width_fill( @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_zero_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_pieslice_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -796,12 +693,7 @@ def test_polygon_translucent() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -828,12 +720,7 @@ def test_big_rectangle() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -847,12 +734,7 @@ def test_rectangle_width( @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width_fill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -866,12 +748,7 @@ def test_rectangle_width_fill( @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_zero_width( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -884,12 +761,7 @@ def test_rectangle_zero_width( @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_I16( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle_I16(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("I;16", (W, H)) draw = ImageDraw.Draw(im) @@ -902,12 +774,7 @@ def test_rectangle_I16( @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_translucent_outline( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rectangle_translucent_outline(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -999,12 +866,7 @@ def test_rounded_rectangle_non_integer_radius( @pytest.mark.parametrize("bbox", BBOX) -def test_rounded_rectangle_zero_radius( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_rounded_rectangle_zero_radius(bbox: Sequence[int | Sequence[int]]) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -1045,12 +907,7 @@ def test_rounded_rectangle_translucent( @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_floodfill(bbox: Sequence[int | Sequence[int]]) -> None: red = ImageColor.getrgb("red") for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: @@ -1083,12 +940,7 @@ def test_floodfill( @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_border( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_floodfill_border(bbox: Sequence[int | Sequence[int]]) -> None: # floodfill() is experimental # Arrange @@ -1110,12 +962,7 @@ def test_floodfill_border( @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_thresh( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_floodfill_thresh(bbox: Sequence[int | Sequence[int]]) -> None: # floodfill() is experimental # Arrange @@ -1572,12 +1419,7 @@ def test_default_font_size() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_same_color_outline( - bbox: tuple[tuple[int, int], tuple[int, int]] - | list[tuple[int, int]] - | list[int] - | tuple[int, int, int, int] -) -> None: +def test_same_color_outline(bbox: Sequence[int | Sequence[int]]) -> None: # Prepare shape x0, y0 = 5, 5 x1, y1 = 5, 50 From 8d96e3bc590ec9c003efc47ad35295d7de4ed95c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 2 Feb 2024 23:54:31 +1100 Subject: [PATCH 050/157] Changed name of first _Tile parameter --- src/PIL/ImageFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 5ba5a6f82..487f53efe 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -91,7 +91,7 @@ def _tilesort(t): class _Tile(NamedTuple): - encoder_name: str + codec_name: str extents: tuple[int, int, int, int] offset: int args: tuple[Any, ...] | str | None From 6207ad419640475440de2f57c710e1a6235dfe90 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 02:24:17 +0000 Subject: [PATCH 051/157] Update release-drafter/release-drafter action to v6 --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 8fc7bd379..a8ddef22c 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -23,6 +23,6 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next release notes as pull requests are merged into "main" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From dba7dea3263dfa3252f7381307323477531646c8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:43:04 +0200 Subject: [PATCH 052/157] Pin codecov/codecov-action to v3.1.5 --- .github/workflows/test-cygwin.yml | 2 +- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 7bbe5a37f..a6b2935a9 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -142,7 +142,7 @@ jobs: bash.exe .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3.1.5 with: file: ./coverage.xml flags: GHA_Cygwin diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 75aab9bd4..f40286fe4 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -101,7 +101,7 @@ jobs: MATRIX_DOCKER: ${{ matrix.docker }} - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3.1.5 with: flags: GHA_Docker name: ${{ matrix.docker }} diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index acea78c37..1c6d15b77 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -82,7 +82,7 @@ jobs: python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3.1.5 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index b737615ca..75fccf795 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -202,7 +202,7 @@ jobs: shell: pwsh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3.1.5 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 038bcfbc3..19f4a6dae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,7 +149,7 @@ jobs: .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3.1.5 with: flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From 435c884ebbee326daf55599c6248028684206cc4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Feb 2024 23:44:33 +1100 Subject: [PATCH 053/157] Removed platform argument from setup-cygwin action --- .github/workflows/test-cygwin.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 7bbe5a37f..4b958e889 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -49,7 +49,6 @@ jobs: - name: Install Cygwin uses: egor-tensin/setup-cygwin@v4 with: - platform: x86_64 packages: > gcc-g++ ghostscript From 1b6723967440cf8474a9bd1e1c394c90c5c2f986 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Feb 2024 11:56:55 +1100 Subject: [PATCH 054/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7d80eec03..a8404260f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Update wl-paste handling and return None for some errors in grabclipboard() on Linux #7745 + [nik012003, radarhere] + +- Remove execute bit from ``setup.py`` #7760 + [hugovk] + - Do not support using test-image-results to upload images after test failures #7739 [radarhere] From dfb48ff297aa2b227a98f20ff0ae5a0009644ad3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Feb 2024 19:16:15 +1100 Subject: [PATCH 055/157] Match mask size to pasted image size --- Tests/test_file_gif.py | 15 +++++++++++++++ src/PIL/GifImagePlugin.py | 4 +--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3f550fd11..263c897ef 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1105,6 +1105,21 @@ def test_append_images(tmp_path: Path) -> None: assert reread.n_frames == 10 +def test_append_different_size_image(tmp_path: Path) -> None: + out = str(tmp_path / "temp.gif") + + im = Image.new("RGB", (100, 100)) + bigger_im = Image.new("RGB", (200, 200), "#f00") + + im.save(out, save_all=True, append_images=[bigger_im]) + + with Image.open(out) as reread: + assert reread.size == (100, 100) + + reread.seek(1) + assert reread.size == (100, 100) + + def test_transparent_optimize(tmp_path: Path) -> None: # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses # transparency. diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 57d87078b..935b95ca8 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -649,9 +649,7 @@ def _write_multiple_frames(im, fp, palette): if "transparency" in encoderinfo: # When the delta is zero, fill the image with transparency diff_frame = im_frame.copy() - fill = Image.new( - "P", diff_frame.size, encoderinfo["transparency"] - ) + fill = Image.new("P", delta.size, encoderinfo["transparency"]) if delta.mode == "RGBA": r, g, b, a = delta.split() mask = ImageMath.eval( From 5a8e7dda79e5ee4d0f8436179f61881a5d8bd286 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Feb 2024 20:36:34 +1100 Subject: [PATCH 056/157] Added type hints --- Tests/test_imagedraw.py | 2 +- src/PIL/Image.py | 2 +- src/PIL/ImageDraw.py | 73 +++++++++++++++++++++-------------------- src/PIL/ImageFont.py | 2 +- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 6e7dce420..4503a9292 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1519,7 +1519,7 @@ def test_compute_regular_polygon_vertices( [ (None, (50, 50, 25), 0, TypeError, "n_sides should be an int"), (1, (50, 50, 25), 0, ValueError, "n_sides should be an int > 2"), - (3, 50, 0, TypeError, "bounding_circle should be a tuple"), + (3, 50, 0, TypeError, "bounding_circle should be a sequence"), ( 3, (50, 50, 100, 100), diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 553f36703..111d06012 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -571,7 +571,7 @@ class Image: # object is gone. self.im = DeferredError(ValueError("Operation on closed image")) - def _copy(self): + def _copy(self) -> None: self.load() self.im = self.im.copy() self.pyaccess = None diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 84665f54f..650e30857 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -48,7 +48,7 @@ directly. class ImageDraw: font = None - def __init__(self, im, mode=None): + def __init__(self, im: Image.Image, mode: str | None = None) -> None: """ Create a drawing instance. @@ -115,7 +115,7 @@ class ImageDraw: self.font = ImageFont.load_default() return self.font - def _getfont(self, font_size): + def _getfont(self, font_size: float | None): if font_size is not None: from . import ImageFont @@ -124,7 +124,7 @@ class ImageDraw: font = self.getfont() return font - def _getink(self, ink, fill=None): + def _getink(self, ink, fill=None) -> tuple[int | None, int | None]: if ink is None and fill is None: if self.fill: fill = self.ink @@ -145,13 +145,13 @@ class ImageDraw: fill = self.draw.draw_ink(fill) return ink, fill - def arc(self, xy, start, end, fill=None, width=1): + def arc(self, xy, start, end, fill=None, width=1) -> None: """Draw an arc.""" ink, fill = self._getink(fill) if ink is not None: self.draw.draw_arc(xy, start, end, ink, width) - def bitmap(self, xy, bitmap, fill=None): + def bitmap(self, xy, bitmap, fill=None) -> None: """Draw a bitmap.""" bitmap.load() ink, fill = self._getink(fill) @@ -160,7 +160,7 @@ class ImageDraw: if ink is not None: self.draw.draw_bitmap(xy, bitmap.im, ink) - def chord(self, xy, start, end, fill=None, outline=None, width=1): + def chord(self, xy, start, end, fill=None, outline=None, width=1) -> None: """Draw a chord.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -168,7 +168,7 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_chord(xy, start, end, ink, 0, width) - def ellipse(self, xy, fill=None, outline=None, width=1): + def ellipse(self, xy, fill=None, outline=None, width=1) -> None: """Draw an ellipse.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -176,7 +176,7 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_ellipse(xy, ink, 0, width) - def line(self, xy, fill=None, width=0, joint=None): + def line(self, xy, fill=None, width=0, joint=None) -> None: """Draw a line, or a connected sequence of line segments.""" ink = self._getink(fill)[0] if ink is not None: @@ -236,7 +236,7 @@ class ImageDraw: ] self.line(gap_coords, fill, width=3) - def shape(self, shape, fill=None, outline=None): + def shape(self, shape, fill=None, outline=None) -> None: """(Experimental) Draw a shape.""" shape.close() ink, fill = self._getink(outline, fill) @@ -245,7 +245,7 @@ class ImageDraw: if ink is not None and ink != fill: self.draw.draw_outline(shape, ink, 0) - def pieslice(self, xy, start, end, fill=None, outline=None, width=1): + def pieslice(self, xy, start, end, fill=None, outline=None, width=1) -> None: """Draw a pieslice.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -253,13 +253,13 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_pieslice(xy, start, end, ink, 0, width) - def point(self, xy, fill=None): + def point(self, xy, fill=None) -> None: """Draw one or more individual pixels.""" ink, fill = self._getink(fill) if ink is not None: self.draw.draw_points(xy, ink) - def polygon(self, xy, fill=None, outline=None, width=1): + def polygon(self, xy, fill=None, outline=None, width=1) -> None: """Draw a polygon.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -267,7 +267,7 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: if width == 1: self.draw.draw_polygon(xy, ink, 0, width) - else: + elif self.im is not None: # To avoid expanding the polygon outwards, # use the fill as a mask mask = Image.new("1", self.im.size) @@ -291,12 +291,12 @@ class ImageDraw: def regular_polygon( self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1 - ): + ) -> None: """Draw a regular polygon.""" xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) self.polygon(xy, fill, outline, width) - def rectangle(self, xy, fill=None, outline=None, width=1): + def rectangle(self, xy, fill=None, outline=None, width=1) -> None: """Draw a rectangle.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -306,7 +306,7 @@ class ImageDraw: def rounded_rectangle( self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None - ): + ) -> None: """Draw a rounded rectangle.""" if isinstance(xy[0], (list, tuple)): (x0, y0), (x1, y1) = xy @@ -346,7 +346,7 @@ class ImageDraw: r = d // 2 ink, fill = self._getink(outline, fill) - def draw_corners(pieslice): + def draw_corners(pieslice) -> None: if full_x: # Draw top and bottom halves parts = ( @@ -431,12 +431,12 @@ class ImageDraw: right[3] -= r + 1 self.draw.draw_rectangle(right, ink, 1) - def _multiline_check(self, text): + def _multiline_check(self, text) -> bool: split_character = "\n" if isinstance(text, str) else b"\n" return split_character in text - def _multiline_split(self, text): + def _multiline_split(self, text) -> list[str | bytes]: split_character = "\n" if isinstance(text, str) else b"\n" return text.split(split_character) @@ -465,7 +465,7 @@ class ImageDraw: embedded_color=False, *args, **kwargs, - ): + ) -> None: """Draw text.""" if embedded_color and self.mode not in ("RGB", "RGBA"): msg = "Embedded color supported only in RGB and RGBA modes" @@ -497,7 +497,7 @@ class ImageDraw: return fill return ink - def draw_text(ink, stroke_width=0, stroke_offset=None): + def draw_text(ink, stroke_width=0, stroke_offset=None) -> None: mode = self.fontmode if stroke_width == 0 and embedded_color: mode = "RGBA" @@ -547,7 +547,8 @@ class ImageDraw: ink_alpha = struct.pack("i", ink)[3] color.fillband(3, ink_alpha) x, y = coord - self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) + if self.im is not None: + self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) else: self.draw.draw_bitmap(coord, mask, ink) @@ -584,7 +585,7 @@ class ImageDraw: embedded_color=False, *, font_size=None, - ): + ) -> None: if direction == "ttb": msg = "ttb direction is unsupported for multiline text" raise ValueError(msg) @@ -693,7 +694,7 @@ class ImageDraw: embedded_color=False, *, font_size=None, - ): + ) -> tuple[int, int, int, int]: """Get the bounding box of a given string, in pixels.""" if embedded_color and self.mode not in ("RGB", "RGBA"): msg = "Embedded color supported only in RGB and RGBA modes" @@ -738,7 +739,7 @@ class ImageDraw: embedded_color=False, *, font_size=None, - ): + ) -> tuple[int, int, int, int]: if direction == "ttb": msg = "ttb direction is unsupported for multiline text" raise ValueError(msg) @@ -777,7 +778,7 @@ class ImageDraw: elif anchor[1] == "d": top -= (len(lines) - 1) * line_spacing - bbox = None + bbox: tuple[int, int, int, int] | None = None for idx, line in enumerate(lines): left = xy[0] @@ -828,7 +829,7 @@ class ImageDraw: return bbox -def Draw(im, mode=None): +def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: """ A simple 2D drawing interface for PIL images. @@ -876,7 +877,7 @@ def getdraw(im=None, hints=None): return im, handler -def floodfill(image, xy, value, border=None, thresh=0): +def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None: """ (experimental) Fills a bounded region with a given color. @@ -932,7 +933,7 @@ def floodfill(image, xy, value, border=None, thresh=0): edge = new_edge -def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): +def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) -> list[tuple[float, float]]: """ Generate a list of vertices for a 2D regular polygon. @@ -982,7 +983,7 @@ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): # 1.2 Check `bounding_circle` has an appropriate value if not isinstance(bounding_circle, (list, tuple)): - msg = "bounding_circle should be a tuple" + msg = "bounding_circle should be a sequence" raise TypeError(msg) if len(bounding_circle) == 3: @@ -1014,7 +1015,7 @@ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): raise ValueError(msg) # 2. Define Helper Functions - def _apply_rotation(point, degrees, centroid): + def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]: return ( round( point[0] * math.cos(math.radians(360 - degrees)) @@ -1030,11 +1031,11 @@ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): ), ) - def _compute_polygon_vertex(centroid, polygon_radius, angle): + def _compute_polygon_vertex(angle: float) -> tuple[int, int]: start_point = [polygon_radius, 0] - return _apply_rotation(start_point, angle, centroid) + return _apply_rotation(start_point, angle) - def _get_angles(n_sides, rotation): + def _get_angles(n_sides: int, rotation: float) -> list[float]: angles = [] degrees = 360 / n_sides # Start with the bottom left polygon vertex @@ -1051,11 +1052,11 @@ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): # 4. Compute Vertices return [ - _compute_polygon_vertex(centroid, polygon_radius, angle) for angle in angles + _compute_polygon_vertex(angle) for angle in angles ] -def _color_diff(color1, color2): +def _color_diff(color1, color2: float | tuple[int, ...]) -> float: """ Uses 1-norm distance to calculate difference between two values. """ diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index a63b73b33..1ec8a9f4d 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -872,7 +872,7 @@ def load_path(filename): raise OSError(msg) -def load_default(size=None): +def load_default(size: float | None = None) -> FreeTypeFont | ImageFont: """If FreeType support is available, load a version of Aileron Regular, https://dotcolon.net/font/aileron, with a more limited character set. From e0da2b71206c8c06ff4a9f67d6dade6973a7ae96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:18:25 +0000 Subject: [PATCH 057/157] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.2.0) - [github.com/psf/black-pre-commit-mirror: 23.12.1 → 24.1.1](https://github.com/psf/black-pre-commit-mirror/compare/23.12.1...24.1.1) - [github.com/PyCQA/bandit: 1.7.6 → 1.7.7](https://github.com/PyCQA/bandit/compare/1.7.6...1.7.7) - [github.com/tox-dev/pyproject-fmt: 1.5.3 → 1.7.0](https://github.com/tox-dev/pyproject-fmt/compare/1.5.3...1.7.0) - [github.com/abravalheri/validate-pyproject: v0.15 → v0.16](https://github.com/abravalheri/validate-pyproject/compare/v0.15...v0.16) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ce0c9a17..c52fdcb55 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,17 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.2.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.12.1 + rev: 24.1.1 hooks: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.7.6 + rev: 1.7.7 hooks: - id: bandit args: [--severity-level=high] @@ -48,12 +48,12 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.5.3 + rev: 1.7.0 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.15 + rev: v0.16 hooks: - id: validate-pyproject From 27b0cf67e784c0c9e58e60afd8ffa1ba274681c6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:18:49 +0000 Subject: [PATCH 058/157] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/workflows/system-info.py | 1 + Tests/helper.py | 1 + Tests/test_file_dds.py | 1 + Tests/test_file_libtiff_small.py | 1 - Tests/test_image_access.py | 4 +--- Tests/test_image_resize.py | 1 + Tests/test_imagecms.py | 8 +++++--- docs/example/DdsImagePlugin.py | 1 + src/PIL/BlpImagePlugin.py | 1 + src/PIL/DdsImagePlugin.py | 1 + src/PIL/FontFile.py | 4 +--- src/PIL/FtexImagePlugin.py | 1 + src/PIL/GifImagePlugin.py | 6 +++--- src/PIL/ImageCms.py | 2 -- src/PIL/JpegPresets.py | 1 + src/PIL/PdfImagePlugin.py | 6 +++--- src/PIL/__init__.py | 1 + src/PIL/_tkinter_finder.py | 1 + 18 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.github/workflows/system-info.py b/.github/workflows/system-info.py index 57f28c620..9e97b8971 100644 --- a/.github/workflows/system-info.py +++ b/.github/workflows/system-info.py @@ -6,6 +6,7 @@ This sort of info is missing from GitHub Actions. Requested here: https://github.com/actions/virtual-environments/issues/79 """ + from __future__ import annotations import os diff --git a/Tests/helper.py b/Tests/helper.py index b2e7d43dd..3e2a40e02 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -1,6 +1,7 @@ """ Helper functions. """ + from __future__ import annotations import logging diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 09ee8986a..b78a0dd81 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -1,4 +1,5 @@ """Test DdsImagePlugin""" + from __future__ import annotations from io import BytesIO diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index ac5270eac..617e1e89c 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -9,7 +9,6 @@ from .test_file_libtiff import LibTiffTestCase class TestFileLibTiffSmall(LibTiffTestCase): - """The small lena image was failing on open in the libtiff decoder because the file pointer was set to the wrong place by a spurious seek. It wasn't failing with the byteio method. diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 00cd4e7a9..e4cb2dad1 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -230,9 +230,7 @@ class TestImageGetPixel(AccessTest): assert im.getpixel([0, 0]) == (20, 20, 70) @pytest.mark.parametrize("mode", ("I;16", "I;16B")) - @pytest.mark.parametrize( - "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1) - ) + @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)) def test_signedness(self, mode, expected_color) -> None: # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index bd45ee893..a64e4a846 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -1,6 +1,7 @@ """ Tests for resize functionality. """ + from __future__ import annotations from itertools import permutations diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 7f6527155..83fc38ed3 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -342,9 +342,11 @@ def test_extended_information() -> None: def truncate_tuple(tuple_or_float): return tuple( - truncate_tuple(val) - if isinstance(val, tuple) - else int(val * power) / power + ( + truncate_tuple(val) + if isinstance(val, tuple) + else int(val * power) / power + ) for val in tuple_or_float ) diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index e98bb8680..2a2a0ba29 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -9,6 +9,7 @@ The contents of this file are hereby released in the public domain (CC0) Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ + from __future__ import annotations import struct diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index b8f38b78a..f0fbc8cc2 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -28,6 +28,7 @@ BLP files come in many different flavours: - DXT3 compression is used if alpha_encoding == 1. - DXT5 compression is used if alpha_encoding == 7. """ + from __future__ import annotations import os diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index eb4c8f557..3785174ef 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -9,6 +9,7 @@ The contents of this file are hereby released in the public domain (CC0) Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ + from __future__ import annotations import io diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 3ec1ae819..1e0c1c166 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -50,9 +50,7 @@ class FontFile: | None ] = [None] * 256 - def __getitem__( - self, ix: int - ) -> ( + def __getitem__(self, ix: int) -> ( tuple[ tuple[int, int], tuple[int, int, int, int], diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index d5513a56a..b4488e6ee 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -50,6 +50,7 @@ bytes for that mipmap level. Note: All data is stored in little-Endian (Intel) byte order. """ + from __future__ import annotations import struct diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 57d87078b..dc842d7a3 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -641,9 +641,9 @@ def _write_multiple_frames(im, fp, palette): if encoderinfo.get("optimize") and im_frame.mode != "1": if "transparency" not in encoderinfo: try: - encoderinfo[ - "transparency" - ] = im_frame.palette._new_color_index(im_frame) + encoderinfo["transparency"] = ( + im_frame.palette._new_color_index(im_frame) + ) except ValueError: pass if "transparency" in encoderinfo: diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 3e40105e4..2b0ed6c9d 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -281,7 +281,6 @@ class ImageCmsProfile: class ImageCmsTransform(Image.ImagePointHandler): - """ Transform. This can be used with the procedural API, or with the standard :py:func:`~PIL.Image.Image.point` method. @@ -369,7 +368,6 @@ def get_display_profile(handle=None): class PyCMSError(Exception): - """(pyCMS) Exception class. This is used for all errors in the pyCMS API.""" diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 9ecfdb259..d0e64a35e 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -62,6 +62,7 @@ Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html """ + from __future__ import annotations # fmt: off diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 3506aadce..1777f1f20 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -188,9 +188,9 @@ def _save(im, fp, filename, save_all=False): x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0) info = { - "title": None - if is_appending - else os.path.splitext(os.path.basename(filename))[0], + "title": ( + None if is_appending else os.path.splitext(os.path.basename(filename))[0] + ), "author": None, "subject": None, "keywords": None, diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 3fcac8643..63a45769b 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -12,6 +12,7 @@ Use PIL.__version__ for this Pillow version. ;-) """ + from __future__ import annotations from . import _version diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 03a6eba44..71c0ad465 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -1,5 +1,6 @@ """ Find compiled module linking to Tcl / Tk libraries """ + from __future__ import annotations import sys From 1acaf20f7215c567f0ea04bf20c396a237a2a542 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:37:45 +0200 Subject: [PATCH 059/157] Enable LOG rules for Ruff linter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b1ce9cf1d..48257b750 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,12 +104,12 @@ select = [ "F", # pyflakes errors "I", # isort "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging "PGH", # pygrep-hooks "RUF100", # unused noqa (yesqa) "UP", # pyupgrade "W", # pycodestyle warnings "YTT", # flake8-2020 - # "LOG", # TODO: enable flake8-logging when it's not in preview anymore ] extend-ignore = [ "E203", # Whitespace before ':' From 3bcc7072d68d3d534f06938879cd6b2fa31e61b9 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:39:08 +0200 Subject: [PATCH 060/157] Move linter config from deprecated top-level to own section --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 48257b750..d7b60ef17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" -[tool.ruff] +[tool.ruff.lint] select = [ "C4", # flake8-comprehensions "E", # pycodestyle errors @@ -118,11 +118,11 @@ extend-ignore = [ "E241", # Multiple spaces after ',' ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "Tests/oss-fuzz/fuzz_font.py" = ["I002"] "Tests/oss-fuzz/fuzz_pillow.py" = ["I002"] -[tool.ruff.isort] +[tool.ruff.lint.isort] known-first-party = ["PIL"] required-imports = ["from __future__ import annotations"] From 65cb0b0487c29c91f0145226e1cd173511bc3586 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 6 Feb 2024 07:49:43 +1100 Subject: [PATCH 061/157] Added _typing.Coords --- Tests/test_imagedraw.py | 87 +++++++++++++++++------------------------ src/PIL/ImageDraw.py | 87 ++++++++++++++++++++++++----------------- src/PIL/_typing.py | 4 ++ 3 files changed, 91 insertions(+), 87 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 4503a9292..4e6cedcd1 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -2,11 +2,11 @@ from __future__ import annotations import contextlib import os.path -from typing import Sequence import pytest from PIL import Image, ImageColor, ImageDraw, ImageFont, features +from PIL._typing import Coords from .helper import ( assert_image_equal, @@ -75,7 +75,7 @@ def test_mode_mismatch() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) -def test_arc(bbox: Sequence[int | Sequence[int]], start: float, end: float) -> None: +def test_arc(bbox: Coords, start: float, end: float) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -88,7 +88,7 @@ def test_arc(bbox: Sequence[int | Sequence[int]], start: float, end: float) -> N @pytest.mark.parametrize("bbox", BBOX) -def test_arc_end_le_start(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_end_le_start(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -103,7 +103,7 @@ def test_arc_end_le_start(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_no_loops(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_no_loops(bbox: Coords) -> None: # No need to go in loops # Arrange im = Image.new("RGB", (W, H)) @@ -119,7 +119,7 @@ def test_arc_no_loops(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -132,7 +132,7 @@ def test_arc_width(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_pieslice_large(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_width_pieslice_large(bbox: Coords) -> None: # Tests an arc with a large enough width that it is a pieslice # Arrange im = Image.new("RGB", (W, H)) @@ -146,7 +146,7 @@ def test_arc_width_pieslice_large(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_width_fill(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -159,7 +159,7 @@ def test_arc_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_arc_width_non_whole_angle(bbox: Sequence[int | Sequence[int]]) -> None: +def test_arc_width_non_whole_angle(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -201,7 +201,7 @@ def test_bitmap() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_chord(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: +def test_chord(mode: str, bbox: Coords) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -215,7 +215,7 @@ def test_chord(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_chord_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -228,7 +228,7 @@ def test_chord_width(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_chord_width_fill(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -241,7 +241,7 @@ def test_chord_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_chord_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_chord_zero_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -267,7 +267,7 @@ def test_chord_too_fat() -> None: @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: +def test_ellipse(mode: str, bbox: Coords) -> None: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -281,7 +281,7 @@ def test_ellipse(mode: str, bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_translucent(bbox: Sequence[int | Sequence[int]]) -> None: +def test_ellipse_translucent(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -318,7 +318,7 @@ def test_ellipse_symmetric() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_ellipse_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -343,7 +343,7 @@ def test_ellipse_width_large() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_ellipse_width_fill(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -356,7 +356,7 @@ def test_ellipse_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_ellipse_zero_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -410,12 +410,7 @@ def test_ellipse_various_sizes_filled() -> None: @pytest.mark.parametrize("points", POINTS) -def test_line( - points: tuple[tuple[int, int], ...] - | list[tuple[int, int]] - | tuple[int, ...] - | list[int] -) -> None: +def test_line(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -488,9 +483,7 @@ def test_transform() -> None: @pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) -def test_pieslice( - bbox: Sequence[int | Sequence[int]], start: float, end: float -) -> None: +def test_pieslice(bbox: Coords, start: float, end: float) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -503,7 +496,7 @@ def test_pieslice( @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_pieslice_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -516,7 +509,7 @@ def test_pieslice_width(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_pieslice_width_fill(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -530,7 +523,7 @@ def test_pieslice_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_pieslice_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_pieslice_zero_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -585,12 +578,7 @@ def test_pieslice_no_spikes() -> None: @pytest.mark.parametrize("points", POINTS) -def test_point( - points: tuple[tuple[int, int], ...] - | list[tuple[int, int]] - | tuple[int, ...] - | list[int] -) -> None: +def test_point(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -615,12 +603,7 @@ def test_point_I16() -> None: @pytest.mark.parametrize("points", POINTS) -def test_polygon( - points: tuple[tuple[int, int], ...] - | list[tuple[int, int]] - | tuple[int, ...] - | list[int] -) -> None: +def test_polygon(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -693,7 +676,7 @@ def test_polygon_translucent() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -720,7 +703,7 @@ def test_big_rectangle() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -734,7 +717,7 @@ def test_rectangle_width(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle_width_fill(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -748,7 +731,7 @@ def test_rectangle_width_fill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle_zero_width(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -761,7 +744,7 @@ def test_rectangle_zero_width(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_I16(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle_I16(bbox: Coords) -> None: # Arrange im = Image.new("I;16", (W, H)) draw = ImageDraw.Draw(im) @@ -774,7 +757,7 @@ def test_rectangle_I16(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_translucent_outline(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rectangle_translucent_outline(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im, "RGBA") @@ -866,7 +849,7 @@ def test_rounded_rectangle_non_integer_radius( @pytest.mark.parametrize("bbox", BBOX) -def test_rounded_rectangle_zero_radius(bbox: Sequence[int | Sequence[int]]) -> None: +def test_rounded_rectangle_zero_radius(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -907,7 +890,7 @@ def test_rounded_rectangle_translucent( @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill(bbox: Sequence[int | Sequence[int]]) -> None: +def test_floodfill(bbox: Coords) -> None: red = ImageColor.getrgb("red") for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: @@ -940,7 +923,7 @@ def test_floodfill(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_border(bbox: Sequence[int | Sequence[int]]) -> None: +def test_floodfill_border(bbox: Coords) -> None: # floodfill() is experimental # Arrange @@ -962,7 +945,7 @@ def test_floodfill_border(bbox: Sequence[int | Sequence[int]]) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_floodfill_thresh(bbox: Sequence[int | Sequence[int]]) -> None: +def test_floodfill_thresh(bbox: Coords) -> None: # floodfill() is experimental # Arrange @@ -1419,7 +1402,7 @@ def test_default_font_size() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_same_color_outline(bbox: Sequence[int | Sequence[int]]) -> None: +def test_same_color_outline(bbox: Coords) -> None: # Prepare shape x0, y0 = 5, 5 x1, y1 = 5, 50 diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 650e30857..d4e000087 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -34,8 +34,10 @@ from __future__ import annotations import math import numbers import struct +from typing import Sequence, cast from . import Image, ImageColor +from ._typing import Coords """ A simple 2D drawing interface for PIL images. @@ -145,13 +147,13 @@ class ImageDraw: fill = self.draw.draw_ink(fill) return ink, fill - def arc(self, xy, start, end, fill=None, width=1) -> None: + def arc(self, xy: Coords, start, end, fill=None, width=1) -> None: """Draw an arc.""" ink, fill = self._getink(fill) if ink is not None: self.draw.draw_arc(xy, start, end, ink, width) - def bitmap(self, xy, bitmap, fill=None) -> None: + def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None: """Draw a bitmap.""" bitmap.load() ink, fill = self._getink(fill) @@ -160,7 +162,7 @@ class ImageDraw: if ink is not None: self.draw.draw_bitmap(xy, bitmap.im, ink) - def chord(self, xy, start, end, fill=None, outline=None, width=1) -> None: + def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None: """Draw a chord.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -168,7 +170,7 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_chord(xy, start, end, ink, 0, width) - def ellipse(self, xy, fill=None, outline=None, width=1) -> None: + def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None: """Draw an ellipse.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -176,20 +178,29 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_ellipse(xy, ink, 0, width) - def line(self, xy, fill=None, width=0, joint=None) -> None: + def line(self, xy: Coords, fill=None, width=0, joint=None) -> None: """Draw a line, or a connected sequence of line segments.""" ink = self._getink(fill)[0] if ink is not None: self.draw.draw_lines(xy, ink, width) if joint == "curve" and width > 4: - if not isinstance(xy[0], (list, tuple)): - xy = [tuple(xy[i : i + 2]) for i in range(0, len(xy), 2)] - for i in range(1, len(xy) - 1): - point = xy[i] + points: Sequence[Sequence[float]] + if isinstance(xy[0], (list, tuple)): + points = cast(Sequence[Sequence[float]], xy) + else: + points = [ + cast(Sequence[float], tuple(xy[i : i + 2])) + for i in range(0, len(xy), 2) + ] + for i in range(1, len(points) - 1): + point = points[i] angles = [ math.degrees(math.atan2(end[0] - start[0], start[1] - end[1])) % 360 - for start, end in ((xy[i - 1], point), (point, xy[i + 1])) + for start, end in ( + (points[i - 1], point), + (point, points[i + 1]), + ) ] if angles[0] == angles[1]: # This is a straight line, so no joint is required @@ -245,7 +256,9 @@ class ImageDraw: if ink is not None and ink != fill: self.draw.draw_outline(shape, ink, 0) - def pieslice(self, xy, start, end, fill=None, outline=None, width=1) -> None: + def pieslice( + self, xy: Coords, start, end, fill=None, outline=None, width=1 + ) -> None: """Draw a pieslice.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -253,13 +266,13 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_pieslice(xy, start, end, ink, 0, width) - def point(self, xy, fill=None) -> None: + def point(self, xy: Coords, fill=None) -> None: """Draw one or more individual pixels.""" ink, fill = self._getink(fill) if ink is not None: self.draw.draw_points(xy, ink) - def polygon(self, xy, fill=None, outline=None, width=1) -> None: + def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None: """Draw a polygon.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -296,7 +309,7 @@ class ImageDraw: xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) self.polygon(xy, fill, outline, width) - def rectangle(self, xy, fill=None, outline=None, width=1) -> None: + def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None: """Draw a rectangle.""" ink, fill = self._getink(outline, fill) if fill is not None: @@ -305,13 +318,13 @@ class ImageDraw: self.draw.draw_rectangle(xy, ink, 0, width) def rounded_rectangle( - self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None + self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None ) -> None: """Draw a rounded rectangle.""" if isinstance(xy[0], (list, tuple)): - (x0, y0), (x1, y1) = xy + (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy) else: - x0, y0, x1, y1 = xy + x0, y0, x1, y1 = cast(Sequence[float], xy) if x1 < x0: msg = "x1 must be greater than or equal to x0" raise ValueError(msg) @@ -347,6 +360,7 @@ class ImageDraw: ink, fill = self._getink(outline, fill) def draw_corners(pieslice) -> None: + parts: tuple[tuple[tuple[float, float, float, float], int, int], ...] if full_x: # Draw top and bottom halves parts = ( @@ -361,17 +375,18 @@ class ImageDraw: ) else: # Draw four separate corners - parts = [] - for i, part in enumerate( - ( - ((x0, y0, x0 + d, y0 + d), 180, 270), - ((x1 - d, y0, x1, y0 + d), 270, 360), - ((x1 - d, y1 - d, x1, y1), 0, 90), - ((x0, y1 - d, x0 + d, y1), 90, 180), + parts = tuple( + part + for i, part in enumerate( + ( + ((x0, y0, x0 + d, y0 + d), 180, 270), + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ) ) - ): - if corners[i]: - parts.append(part) + if corners[i] + ) for part in parts: if pieslice: self.draw.draw_pieslice(*(part + (fill, 1))) @@ -520,7 +535,7 @@ class ImageDraw: *args, **kwargs, ) - coord = coord[0] + offset[0], coord[1] + offset[1] + coord = [coord[0] + offset[0], coord[1] + offset[1]] except AttributeError: try: mask = font.getmask( @@ -539,7 +554,7 @@ class ImageDraw: except TypeError: mask = font.getmask(text) if stroke_offset: - coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1] + coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]] if mode == "RGBA": # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # extract mask and set text alpha @@ -548,7 +563,9 @@ class ImageDraw: color.fillband(3, ink_alpha) x, y = coord if self.im is not None: - self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) + self.im.paste( + color, (x, y, x + mask.size[0], y + mask.size[1]), mask + ) else: self.draw.draw_bitmap(coord, mask, ink) @@ -829,7 +846,7 @@ class ImageDraw: return bbox -def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: +def Draw(im, mode: str | None = None) -> ImageDraw: """ A simple 2D drawing interface for PIL images. @@ -933,7 +950,9 @@ def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None: edge = new_edge -def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) -> list[tuple[float, float]]: +def _compute_regular_polygon_vertices( + bounding_circle, n_sides, rotation +) -> list[tuple[float, float]]: """ Generate a list of vertices for a 2D regular polygon. @@ -1051,9 +1070,7 @@ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) -> lis angles = _get_angles(n_sides, rotation) # 4. Compute Vertices - return [ - _compute_polygon_vertex(angle) for angle in angles - ] + return [_compute_polygon_vertex(angle) for angle in angles] def _color_diff(color1, color2: float | tuple[int, ...]) -> float: diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 608b2b41f..ddea0b414 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys +from typing import Sequence, Union if sys.version_info >= (3, 10): from typing import TypeGuard @@ -15,4 +16,7 @@ else: return bool +Coords = Union[Sequence[float], Sequence[Sequence[float]]] + + __all__ = ["TypeGuard"] From 5f115df74f7aa26ec94b79ecad720a707be029e8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 6 Feb 2024 08:05:30 +1100 Subject: [PATCH 062/157] Replace deprecated "extend-ignore" with "ignore" --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d7b60ef17..652ae3633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ select = [ "W", # pycodestyle warnings "YTT", # flake8-2020 ] -extend-ignore = [ +ignore = [ "E203", # Whitespace before ':' "E221", # Multiple spaces before operator "E226", # Missing whitespace around arithmetic operator From 469db5114cf317ea128bd8c4b508eed537b7ce9e Mon Sep 17 00:00:00 2001 From: Evan Miller Date: Tue, 6 Feb 2024 15:41:08 -0500 Subject: [PATCH 063/157] Release GIL while calling WebPAnimDecoderGetNext --- src/_webp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/_webp.c b/src/_webp.c index a1b4dbc1a..4e7d41f11 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -450,12 +450,16 @@ _anim_decoder_get_next(PyObject *self) { int timestamp; PyObject *bytes; PyObject *ret; + ImagingSectionCookie cookie; WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; + ImagingSectionEnter(&cookie); if (!WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp)) { + ImagingSectionLeave(&cookie); PyErr_SetString(PyExc_OSError, "failed to read next frame"); return NULL; } + ImagingSectionLeave(&cookie); bytes = PyBytes_FromStringAndSize( (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height); From 91645f9efffc623cb83221a8d9c9a0b98d3ce548 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 7 Feb 2024 10:19:00 +1100 Subject: [PATCH 064/157] Lint fix --- Tests/test_imagedraw.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 4e6cedcd1..f7aea3034 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -776,9 +776,11 @@ def test_rectangle_translucent_outline(bbox: Coords) -> None: [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], ) def test_rounded_rectangle( - xy: tuple[int, int, int, int] - | tuple[list[int]] - | tuple[tuple[int, int], tuple[int, int]] + xy: ( + tuple[int, int, int, int] + | tuple[list[int]] + | tuple[tuple[int, int], tuple[int, int]] + ) ) -> None: # Arrange im = Image.new("RGB", (200, 200)) From cdc498e6f3b2060906ca14fe9b9187e0a93a1613 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 7 Feb 2024 19:16:28 +1100 Subject: [PATCH 065/157] Added type hints --- Tests/test_color_lut.py | 4 ++- Tests/test_file_eps.py | 32 ++++++++++---------- Tests/test_file_jpeg.py | 35 +++++++++++----------- Tests/test_file_jpeg2k.py | 21 ++++++------- Tests/test_file_libtiff.py | 38 +++++++++++++----------- Tests/test_file_png.py | 15 +++++----- Tests/test_image_paste.py | 41 +++++++++++++++----------- Tests/test_image_reduce.py | 43 +++++++++++++++------------ Tests/test_image_resample.py | 57 ++++++++++++++++++++---------------- Tests/test_imagemath.py | 8 ++--- 10 files changed, 160 insertions(+), 134 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index e6c8d7819..2bb1b57d4 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -15,7 +15,9 @@ except ImportError: class TestColorLut3DCoreAPI: - def generate_identity_table(self, channels, size): + def generate_identity_table( + self, channels: int, size: int | tuple[int, int, int] + ) -> tuple[int, int, int, int, list[float]]: if isinstance(size, tuple): size_1d, size_2d, size_3d = size else: diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 06f927c7b..00f5f39e8 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -84,7 +84,7 @@ simple_eps_file_with_long_binary_data = ( ("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252))) ) @pytest.mark.parametrize("scale", (1, 2)) -def test_sanity(filename, size, scale) -> None: +def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None: expected_size = tuple(s * scale for s in size) with Image.open(filename) as image: image.load(scale=scale) @@ -129,28 +129,28 @@ def test_binary_header_only() -> None: @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_missing_version_comment(prefix) -> None: +def test_missing_version_comment(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version)) with pytest.raises(SyntaxError): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_missing_boundingbox_comment(prefix) -> None: +def test_missing_boundingbox_comment(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox)) with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_invalid_boundingbox_comment(prefix) -> None: +def test_invalid_boundingbox_comment(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox)) with pytest.raises(OSError, match="cannot determine EPS bounding box"): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix) -> None: +def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix: bytes) -> None: data = io.BytesIO( prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata) ) @@ -161,21 +161,21 @@ def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix) -> None: @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_ascii_comment_too_long(prefix) -> None: +def test_ascii_comment_too_long(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment)) with pytest.raises(SyntaxError, match="not an EPS file"): EpsImagePlugin.EpsImageFile(data) @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_long_binary_data(prefix) -> None: +def test_long_binary_data(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data)) EpsImagePlugin.EpsImageFile(data) @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("prefix", (b"", simple_binary_header)) -def test_load_long_binary_data(prefix) -> None: +def test_load_long_binary_data(prefix: bytes) -> None: data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data)) with Image.open(data) as img: img.load() @@ -305,7 +305,7 @@ def test_render_scale2() -> None: @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps")) -def test_resize(filename) -> None: +def test_resize(filename: str) -> None: with Image.open(filename) as im: new_size = (100, 100) im = im.resize(new_size) @@ -314,7 +314,7 @@ def test_resize(filename) -> None: @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.parametrize("filename", (FILE1, FILE2)) -def test_thumbnail(filename) -> None: +def test_thumbnail(filename: str) -> None: # Issue #619 with Image.open(filename) as im: new_size = (100, 100) @@ -335,7 +335,7 @@ def test_readline_psfile(tmp_path: Path) -> None: line_endings = ["\r\n", "\n", "\n\r", "\r"] strings = ["something", "else", "baz", "bif"] - def _test_readline(t, ending) -> None: + def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None: ending = "Failure with line ending: %s" % ( "".join("%s" % ord(s) for s in ending) ) @@ -344,13 +344,13 @@ def test_readline_psfile(tmp_path: Path) -> None: assert t.readline().strip("\r\n") == "baz", ending assert t.readline().strip("\r\n") == "bif", ending - def _test_readline_io_psfile(test_string, ending) -> None: + def _test_readline_io_psfile(test_string: str, ending: str) -> None: f = io.BytesIO(test_string.encode("latin-1")) with pytest.warns(DeprecationWarning): t = EpsImagePlugin.PSFile(f) _test_readline(t, ending) - def _test_readline_file_psfile(test_string, ending) -> None: + def _test_readline_file_psfile(test_string: str, ending: str) -> None: f = str(tmp_path / "temp.txt") with open(f, "wb") as w: w.write(test_string.encode("latin-1")) @@ -376,7 +376,7 @@ def test_psfile_deprecation() -> None: "line_ending", (b"\r\n", b"\n", b"\n\r", b"\r"), ) -def test_readline(prefix, line_ending) -> None: +def test_readline(prefix: bytes, line_ending: bytes) -> None: simple_file = prefix + line_ending.join(simple_eps_file_with_comments) data = io.BytesIO(simple_file) test_file = EpsImagePlugin.EpsImageFile(data) @@ -394,7 +394,7 @@ def test_readline(prefix, line_ending) -> None: "Tests/images/illuCS6_preview.eps", ), ) -def test_open_eps(filename) -> None: +def test_open_eps(filename: str) -> None: # https://github.com/python-pillow/Pillow/issues/1104 with Image.open(filename) as img: assert img.mode == "RGB" @@ -417,7 +417,7 @@ def test_emptyline() -> None: "test_file", ["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], ) -def test_timeout(test_file) -> None: +def test_timeout(test_file: str) -> None: with open(test_file, "rb") as f: with pytest.raises(Image.UnidentifiedImageError): with Image.open(f): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ff278d4c1..6b0662e0b 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -5,6 +5,7 @@ import re import warnings from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -42,7 +43,7 @@ TEST_FILE = "Tests/images/hopper.jpg" @skip_unless_feature("jpg") class TestFileJpeg: - def roundtrip(self, im, **options): + def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image: out = BytesIO() im.save(out, "JPEG", **options) test_bytes = out.tell() @@ -51,7 +52,7 @@ class TestFileJpeg: im.bytes = test_bytes # for testing only return im - def gen_random_image(self, size, mode: str = "RGB"): + def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image: """Generates a very hard to compress file :param size: tuple :param mode: optional image mode @@ -71,7 +72,7 @@ class TestFileJpeg: assert im.get_format_mimetype() == "image/jpeg" @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero(self, size, tmp_path: Path) -> None: + def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None: f = str(tmp_path / "temp.jpg") im = Image.new("RGB", size) with pytest.raises(ValueError): @@ -108,13 +109,11 @@ class TestFileJpeg: assert "comment" not in reloaded.info # Test that a comment argument overrides the default comment - for comment in ("Test comment text", b"Text comment text"): + for comment in ("Test comment text", b"Test comment text"): out = BytesIO() im.save(out, format="JPEG", comment=comment) with Image.open(out) as reloaded: - if not isinstance(comment, bytes): - comment = comment.encode() - assert reloaded.info["comment"] == comment + assert reloaded.info["comment"] == b"Test comment text" def test_cmyk(self) -> None: # Test CMYK handling. Thanks to Tim and Charlie for test data, @@ -145,7 +144,7 @@ class TestFileJpeg: assert k > 0.9 def test_rgb(self) -> None: - def getchannels(im): + def getchannels(im: Image.Image) -> tuple[int, int, int]: return tuple(v[0] for v in im.layer) im = hopper() @@ -161,8 +160,8 @@ class TestFileJpeg: "test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], ) - def test_dpi(self, test_image_path) -> None: - def test(xdpi, ydpi=None): + def test_dpi(self, test_image_path: str) -> None: + def test(xdpi: int, ydpi: int | None = None): with Image.open(test_image_path) as im: im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) return im.info.get("dpi") @@ -207,7 +206,7 @@ class TestFileJpeg: ImageFile.MAXBLOCK * 4 + 3, # large block ), ) - def test_icc_big(self, n) -> None: + def test_icc_big(self, n: int) -> None: # Make sure that the "extra" support handles large blocks # The ICC APP marker can store 65519 bytes per marker, so # using a 4-byte test code should allow us to detect out of @@ -433,7 +432,7 @@ class TestFileJpeg: assert_image(im1, im2.mode, im2.size) def test_subsampling(self) -> None: - def getsampling(im): + def getsampling(im: Image.Image): layer = im.layer return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] @@ -530,7 +529,7 @@ class TestFileJpeg: pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) def test_qtables(self, tmp_path: Path) -> None: - def _n_qtables_helper(n, test_file) -> None: + def _n_qtables_helper(n: int, test_file: str) -> None: with Image.open(test_file) as im: f = str(tmp_path / "temp.jpg") im.save(f, qtables=[[n] * 64] * n) @@ -666,7 +665,7 @@ class TestFileJpeg: "blocks, rows, markers", ((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)), ) - def test_restart_markers(self, blocks, rows, markers) -> None: + def test_restart_markers(self, blocks: int, rows: int, markers: int) -> None: im = Image.new("RGB", (32, 32)) # 16 MCUs out = BytesIO() im.save( @@ -724,13 +723,13 @@ class TestFileJpeg: assert im.format == "JPEG" @pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) - def test_save_correct_modes(self, mode) -> None: + def test_save_correct_modes(self, mode: str) -> None: out = BytesIO() img = Image.new(mode, (20, 20)) img.save(out, "JPEG") @pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P")) - def test_save_wrong_modes(self, mode) -> None: + def test_save_wrong_modes(self, mode: str) -> None: # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() img = Image.new(mode, (20, 20)) @@ -982,12 +981,12 @@ class TestFileJpeg: # Even though this decoder never says that it is finished # the image should still end when there is no new data class InfiniteMockPyDecoder(ImageFile.PyDecoder): - def decode(self, buffer): + def decode(self, buffer: bytes) -> tuple[int, int]: return 0, 0 decoder = InfiniteMockPyDecoder(None) - def closure(mode, *args): + def closure(mode: str, *args) -> InfiniteMockPyDecoder: decoder.__init__(mode, *args) return decoder diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index e3f1fa8fd..fab19e2ea 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -4,6 +4,7 @@ import os import re from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -36,7 +37,7 @@ test_card.load() # 'Not enough memory to handle tile data' -def roundtrip(im, **options): +def roundtrip(im: Image.Image, **options: Any) -> Image.Image: out = BytesIO() im.save(out, "JPEG2000", **options) test_bytes = out.tell() @@ -138,7 +139,7 @@ def test_prog_res_rt() -> None: @pytest.mark.parametrize("num_resolutions", range(2, 6)) -def test_default_num_resolutions(num_resolutions) -> None: +def test_default_num_resolutions(num_resolutions: int) -> None: d = 1 << (num_resolutions - 1) im = test_card.resize((d - 1, d - 1)) with pytest.raises(OSError): @@ -198,9 +199,9 @@ def test_layers_type(tmp_path: Path) -> None: for quality_layers in [[100, 50, 10], (100, 50, 10), None]: test_card.save(outfile, quality_layers=quality_layers) - for quality_layers in ["quality_layers", ("100", "50", "10")]: + for quality_layers_str in ["quality_layers", ("100", "50", "10")]: with pytest.raises(ValueError): - test_card.save(outfile, quality_layers=quality_layers) + test_card.save(outfile, quality_layers=quality_layers_str) def test_layers() -> None: @@ -233,7 +234,7 @@ def test_layers() -> None: ("foo.jp2", {"no_jp2": False}, 4, b"jP"), ), ) -def test_no_jp2(name, args, offset, data) -> None: +def test_no_jp2(name: str, args: dict[str, bool], offset: int, data: bytes) -> None: out = BytesIO() if name: out.name = name @@ -278,7 +279,7 @@ def test_sgnd(tmp_path: Path) -> None: @pytest.mark.parametrize("ext", (".j2k", ".jp2")) -def test_rgba(ext) -> None: +def test_rgba(ext: str) -> None: # Arrange with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im: # Act @@ -289,7 +290,7 @@ def test_rgba(ext) -> None: @pytest.mark.parametrize("ext", (".j2k", ".jp2")) -def test_16bit_monochrome_has_correct_mode(ext) -> None: +def test_16bit_monochrome_has_correct_mode(ext: str) -> None: with Image.open("Tests/images/16bit.cropped" + ext) as im: im.load() assert im.mode == "I;16" @@ -346,12 +347,12 @@ def test_parser_feed() -> None: not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) @pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2")) -def test_subsampling_decode(name) -> None: +def test_subsampling_decode(name: str) -> None: test = f"{EXTRA_DIR}/{name}.jp2" reference = f"{EXTRA_DIR}/{name}.ppm" with Image.open(test) as im: - epsilon = 3 # for YCbCr images + epsilon = 3.0 # for YCbCr images with Image.open(reference) as im2: width, height = im2.size if name[-1] == "2": @@ -400,7 +401,7 @@ def test_save_comment() -> None: "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k", ], ) -def test_crashes(test_file) -> None: +def test_crashes(test_file: str) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: # Valgrind should not complain here diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 1386034e5..0994d9904 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -27,7 +27,7 @@ from .helper import ( @skip_unless_feature("libtiff") class LibTiffTestCase: - def _assert_noerr(self, tmp_path: Path, im) -> None: + def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None: """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit assert im.mode == "1" @@ -140,7 +140,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") @pytest.mark.parametrize("legacy_api", (False, True)) - def test_write_metadata(self, legacy_api, tmp_path: Path) -> None: + def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None: """Test metadata writing through libtiff""" f = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper_g4.tif") as img: @@ -243,7 +243,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False def test_custom_metadata(self, tmp_path: Path) -> None: - tc = namedtuple("test_case", "value,type,supported_by_default") + tc = namedtuple("tc", "value,type,supported_by_default") custom = { 37000 + k: v for k, v in enumerate( @@ -284,7 +284,9 @@ class TestFileLibTiff(LibTiffTestCase): for libtiff in libtiffs: TiffImagePlugin.WRITE_LIBTIFF = libtiff - def check_tags(tiffinfo) -> None: + def check_tags( + tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str] + ) -> None: im = hopper() out = str(tmp_path / "temp.tif") @@ -502,7 +504,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, out) @pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000"))) - def test_palette_save(self, im, tmp_path: Path) -> None: + def test_palette_save(self, im: Image.Image, tmp_path: Path) -> None: out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True @@ -514,7 +516,7 @@ class TestFileLibTiff(LibTiffTestCase): assert len(reloaded.tag_v2[320]) == 768 @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) - def test_bw_compression_w_rgb(self, compression, tmp_path: Path) -> None: + def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None: im = hopper("RGB") out = str(tmp_path / "temp.tif") @@ -647,7 +649,7 @@ class TestFileLibTiff(LibTiffTestCase): # Generate test image pilim = hopper() - def save_bytesio(compression=None) -> None: + def save_bytesio(compression: str | None = None) -> None: buffer_io = io.BytesIO() pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) @@ -731,7 +733,7 @@ class TestFileLibTiff(LibTiffTestCase): assert icc == icc_libtiff def test_write_icc(self, tmp_path: Path) -> None: - def check_write(libtiff) -> None: + def check_write(libtiff: bool) -> None: TiffImagePlugin.WRITE_LIBTIFF = libtiff with Image.open("Tests/images/hopper.iccprofile.tif") as img: @@ -837,7 +839,7 @@ class TestFileLibTiff(LibTiffTestCase): assert reloaded.mode == "F" assert reloaded.getexif()[SAMPLEFORMAT] == 3 - def test_lzma(self, capfd): + def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None: try: with Image.open("Tests/images/hopper_lzma.tif") as im: assert im.mode == "RGB" @@ -853,7 +855,7 @@ class TestFileLibTiff(LibTiffTestCase): sys.stderr.write(captured.err) raise - def test_webp(self, capfd): + def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None: try: with Image.open("Tests/images/hopper_webp.tif") as im: assert im.mode == "RGB" @@ -971,7 +973,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") @pytest.mark.parametrize("compression", (None, "jpeg")) - def test_block_tile_tags(self, compression, tmp_path: Path) -> None: + def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None: im = hopper() out = str(tmp_path / "temp.tif") @@ -1020,7 +1022,9 @@ class TestFileLibTiff(LibTiffTestCase): ), ], ) - def test_wrong_bits_per_sample(self, file_name, mode, size, tile) -> None: + def test_wrong_bits_per_sample( + self, file_name: str, mode: str, size: tuple[int, int], tile + ) -> None: with Image.open("Tests/images/" + file_name) as im: assert im.mode == mode assert im.size == size @@ -1086,7 +1090,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) - def test_save_multistrip(self, compression, tmp_path: Path) -> None: + def test_save_multistrip(self, compression: str, tmp_path: Path) -> None: im = hopper("RGB").resize((256, 256)) out = str(tmp_path / "temp.tif") im.save(out, compression=compression) @@ -1096,14 +1100,14 @@ class TestFileLibTiff(LibTiffTestCase): assert len(im.tag_v2[STRIPOFFSETS]) > 1 @pytest.mark.parametrize("argument", (True, False)) - def test_save_single_strip(self, argument, tmp_path: Path) -> None: + def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None: im = hopper("RGB").resize((256, 256)) out = str(tmp_path / "temp.tif") if not argument: TiffImagePlugin.STRIP_SIZE = 2**18 try: - arguments = {"compression": "tiff_adobe_deflate"} + arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"} if argument: arguments["strip_size"] = 2**18 im.save(out, **arguments) @@ -1114,7 +1118,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.STRIP_SIZE = 65536 @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) - def test_save_zero(self, compression, tmp_path: Path) -> None: + def test_save_zero(self, compression: str | None, tmp_path: Path) -> None: im = Image.new("RGB", (0, 0)) out = str(tmp_path / "temp.tif") with pytest.raises(SystemError): @@ -1134,7 +1138,7 @@ class TestFileLibTiff(LibTiffTestCase): ("Tests/images/child_ifd_jpeg.tiff", (20,)), ), ) - def test_get_child_images(self, path, sizes) -> None: + def test_get_child_images(self, path: str, sizes: tuple[int, ...]) -> None: with Image.open(path) as im: ims = im.get_child_images() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0f1d96365..d4a634316 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -6,6 +6,7 @@ import warnings import zlib from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -36,7 +37,7 @@ TEST_PNG_FILE = "Tests/images/hopper.png" MAGIC = PngImagePlugin._MAGIC -def chunk(cid, *data): +def chunk(cid: bytes, *data: bytes) -> bytes: test_file = BytesIO() PngImagePlugin.putchunk(*(test_file, cid) + data) return test_file.getvalue() @@ -52,11 +53,11 @@ HEAD = MAGIC + IHDR TAIL = IDAT + IEND -def load(data): +def load(data: bytes) -> Image.Image: return Image.open(BytesIO(data)) -def roundtrip(im, **options): +def roundtrip(im: Image.Image, **options: Any) -> Image.Image: out = BytesIO() im.save(out, "PNG", **options) out.seek(0) @@ -65,7 +66,7 @@ def roundtrip(im, **options): @skip_unless_feature("zlib") class TestFilePng: - def get_chunks(self, filename): + def get_chunks(self, filename: str) -> list[bytes]: chunks = [] with open(filename, "rb") as fp: fp.read(8) @@ -436,7 +437,7 @@ class TestFilePng: def test_unicode_text(self) -> None: # Check preservation of non-ASCII characters - def rt_text(value) -> None: + def rt_text(value: str) -> None: im = Image.new("RGB", (32, 32)) info = PngImagePlugin.PngInfo() info.add_text("Text", value) @@ -636,7 +637,7 @@ class TestFilePng: @pytest.mark.parametrize( "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT") ) - def test_truncated_chunks(self, cid) -> None: + def test_truncated_chunks(self, cid: bytes) -> None: fp = BytesIO() with PngImagePlugin.PngStream(fp) as png: with pytest.raises(ValueError): @@ -755,7 +756,7 @@ class TestFilePng: im.seek(1) @pytest.mark.parametrize("buffer", (True, False)) - def test_save_stdout(self, buffer) -> None: + def test_save_stdout(self, buffer: bool) -> None: old_stdout = sys.stdout if buffer: diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 34a2f8f3d..c4d7a5dd2 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -11,10 +11,9 @@ class TestImagingPaste: masks = {} size = 128 - def assert_9points_image(self, im, expected) -> None: - expected = [ - point[0] if im.mode == "L" else point[: len(im.mode)] for point in expected - ] + def assert_9points_image( + self, im: Image.Image, expected: list[tuple[int, int, int, int]] + ) -> None: px = im.load() actual = [ px[0, 0], @@ -27,9 +26,17 @@ class TestImagingPaste: px[self.size // 2, self.size - 1], px[self.size - 1, self.size - 1], ] - assert actual == expected + assert actual == [ + point[0] if im.mode == "L" else point[: len(im.mode)] for point in expected + ] - def assert_9points_paste(self, im, im2, mask, expected) -> None: + def assert_9points_paste( + self, + im: Image.Image, + im2: Image.Image, + mask: Image.Image, + expected: list[tuple[int, int, int, int]], + ) -> None: im3 = im.copy() im3.paste(im2, (0, 0), mask) self.assert_9points_image(im3, expected) @@ -106,7 +113,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_solid(self, mode) -> None: + def test_image_solid(self, mode: str) -> None: im = Image.new(mode, (200, 200), "red") im2 = getattr(self, "gradient_" + mode) @@ -116,7 +123,7 @@ class TestImagingPaste: assert_image_equal(im, im2) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_1(self, mode) -> None: + def test_image_mask_1(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -138,7 +145,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_L(self, mode) -> None: + def test_image_mask_L(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -160,7 +167,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_LA(self, mode) -> None: + def test_image_mask_LA(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -182,7 +189,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_RGBA(self, mode) -> None: + def test_image_mask_RGBA(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -204,7 +211,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_image_mask_RGBa(self, mode) -> None: + def test_image_mask_RGBa(self, mode: str) -> None: im = Image.new(mode, (200, 200), "white") im2 = getattr(self, "gradient_" + mode) @@ -226,7 +233,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_solid(self, mode) -> None: + def test_color_solid(self, mode: str) -> None: im = Image.new(mode, (200, 200), "black") rect = (12, 23, 128 + 12, 128 + 23) @@ -239,7 +246,7 @@ class TestImagingPaste: assert sum(head[:255]) == 0 @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_1(self, mode) -> None: + def test_color_mask_1(self, mode: str) -> None: im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)]) color = (10, 20, 30, 40)[: len(mode)] @@ -261,7 +268,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_L(self, mode) -> None: + def test_color_mask_L(self, mode: str) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" @@ -283,7 +290,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_RGBA(self, mode) -> None: + def test_color_mask_RGBA(self, mode: str) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" @@ -305,7 +312,7 @@ class TestImagingPaste: ) @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"]) - def test_color_mask_RGBa(self, mode) -> None: + def test_color_mask_RGBa(self, mode: str) -> None: im = getattr(self, "gradient_" + mode).copy() color = "white" diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index c29830a7e..33b33d6b7 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -48,7 +48,7 @@ gradients_image.load() ((1, 3), (10, 4)), ), ) -def test_args_factor(size, expected) -> None: +def test_args_factor(size: int | tuple[int, int], expected: tuple[int, int]) -> None: im = Image.new("L", (10, 10)) assert expected == im.reduce(size).size @@ -56,7 +56,7 @@ def test_args_factor(size, expected) -> None: @pytest.mark.parametrize( "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) ) -def test_args_factor_error(size, expected_error) -> None: +def test_args_factor_error(size: float | tuple[int, int], expected_error) -> None: im = Image.new("L", (10, 10)) with pytest.raises(expected_error): im.reduce(size) @@ -69,7 +69,7 @@ def test_args_factor_error(size, expected_error) -> None: ((5, 5, 6, 6), (1, 1)), ), ) -def test_args_box(size, expected) -> None: +def test_args_box(size: tuple[int, int, int, int], expected: tuple[int, int]) -> None: im = Image.new("L", (10, 10)) assert expected == im.reduce(2, size).size @@ -86,20 +86,20 @@ def test_args_box(size, expected) -> None: ((5, 0, 5, 10), ValueError), ), ) -def test_args_box_error(size, expected_error) -> None: +def test_args_box_error(size: str | tuple[int, int, int, int], expected_error) -> None: im = Image.new("L", (10, 10)) with pytest.raises(expected_error): im.reduce(2, size).size @pytest.mark.parametrize("mode", ("P", "1", "I;16")) -def test_unsupported_modes(mode) -> None: +def test_unsupported_modes(mode: str) -> None: im = Image.new("P", (10, 10)) with pytest.raises(ValueError): im.reduce(3) -def get_image(mode): +def get_image(mode: str) -> Image.Image: mode_info = ImageMode.getmode(mode) if mode_info.basetype == "L": bands = [gradients_image] @@ -119,7 +119,7 @@ def get_image(mode): return im.crop((0, 0, im.width, im.height - 5)) -def compare_reduce_with_box(im, factor) -> None: +def compare_reduce_with_box(im: Image.Image, factor: int | tuple[int, int]) -> None: box = (11, 13, 146, 164) reduced = im.reduce(factor, box=box) reference = im.crop(box).reduce(factor) @@ -127,7 +127,10 @@ def compare_reduce_with_box(im, factor) -> None: def compare_reduce_with_reference( - im, factor, average_diff: float = 0.4, max_diff: int = 1 + im: Image.Image, + factor: int | tuple[int, int], + average_diff: float = 0.4, + max_diff: int = 1, ) -> None: """Image.reduce() should look very similar to Image.resize(BOX). @@ -173,7 +176,9 @@ def compare_reduce_with_reference( assert_compare_images(reduced, reference, average_diff, max_diff) -def assert_compare_images(a, b, max_average_diff, max_diff: int = 255) -> None: +def assert_compare_images( + a: Image.Image, b: Image.Image, max_average_diff: float, max_diff: int = 255 +) -> None: assert a.mode == b.mode, f"got mode {repr(a.mode)}, expected {repr(b.mode)}" assert a.size == b.size, f"got size {repr(a.size)}, expected {repr(b.size)}" @@ -201,20 +206,20 @@ def assert_compare_images(a, b, max_average_diff, max_diff: int = 255) -> None: @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_L(factor) -> None: +def test_mode_L(factor: int | tuple[int, int]) -> None: im = get_image("L") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_LA(factor) -> None: +def test_mode_LA(factor: int | tuple[int, int]) -> None: im = get_image("LA") compare_reduce_with_reference(im, factor, 0.8, 5) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_LA_opaque(factor) -> None: +def test_mode_LA_opaque(factor: int | tuple[int, int]) -> None: im = get_image("LA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) @@ -223,27 +228,27 @@ def test_mode_LA_opaque(factor) -> None: @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_La(factor) -> None: +def test_mode_La(factor: int | tuple[int, int]) -> None: im = get_image("La") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGB(factor) -> None: +def test_mode_RGB(factor: int | tuple[int, int]) -> None: im = get_image("RGB") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBA(factor) -> None: +def test_mode_RGBA(factor: int | tuple[int, int]) -> None: im = get_image("RGBA") compare_reduce_with_reference(im, factor, 0.8, 5) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBA_opaque(factor) -> None: +def test_mode_RGBA_opaque(factor: int | tuple[int, int]) -> None: im = get_image("RGBA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) @@ -252,21 +257,21 @@ def test_mode_RGBA_opaque(factor) -> None: @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_RGBa(factor) -> None: +def test_mode_RGBa(factor: int | tuple[int, int]) -> None: im = get_image("RGBa") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_I(factor) -> None: +def test_mode_I(factor: int | tuple[int, int]) -> None: im = get_image("I") compare_reduce_with_reference(im, factor) compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) -def test_mode_F(factor) -> None: +def test_mode_F(factor: int | tuple[int, int]) -> None: im = get_image("F") compare_reduce_with_reference(im, factor, 0, 0) compare_reduce_with_box(im, factor) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index f4c9eb0e6..f3ec12c05 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,6 +1,7 @@ from __future__ import annotations from contextlib import contextmanager +from typing import Generator import pytest @@ -51,7 +52,7 @@ class TestImagingResampleVulnerability: class TestImagingCoreResampleAccuracy: - def make_case(self, mode, size, color): + def make_case(self, mode: str, size: tuple[int, int], color: int) -> Image.Image: """Makes a sample image with two dark and two bright squares. For example: e0 e0 1f 1f @@ -66,7 +67,7 @@ class TestImagingCoreResampleAccuracy: return Image.merge(mode, [case] * len(mode)) - def make_sample(self, data, size): + def make_sample(self, data: str, size: tuple[int, int]) -> Image.Image: """Restores a sample image from given data string which contains hex-encoded pixels from the top left fourth of a sample. """ @@ -83,7 +84,7 @@ class TestImagingCoreResampleAccuracy: s_px[size[0] - x - 1, y] = 255 - val return sample - def check_case(self, case, sample) -> None: + def check_case(self, case: Image.Image, sample: Image.Image) -> None: s_px = sample.load() c_px = case.load() for y in range(case.size[1]): @@ -95,7 +96,7 @@ class TestImagingCoreResampleAccuracy: ) assert s_px[x, y] == c_px[x, y], message - def serialize_image(self, image): + def serialize_image(self, image: Image.Image) -> str: s_px = image.load() return "\n".join( " ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0])) @@ -103,7 +104,7 @@ class TestImagingCoreResampleAccuracy: ) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_box(self, mode) -> None: + def test_reduce_box(self, mode: str) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off @@ -114,7 +115,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_bilinear(self, mode) -> None: + def test_reduce_bilinear(self, mode: str) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off @@ -125,7 +126,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_hamming(self, mode) -> None: + def test_reduce_hamming(self, mode: str) -> None: case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off @@ -136,7 +137,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_bicubic(self, mode) -> None: + def test_reduce_bicubic(self, mode: str) -> None: case = self.make_case(mode, (12, 12), 0xE1) case = case.resize((6, 6), Image.Resampling.BICUBIC) # fmt: off @@ -148,7 +149,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (6, 6))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_reduce_lanczos(self, mode) -> None: + def test_reduce_lanczos(self, mode: str) -> None: case = self.make_case(mode, (16, 16), 0xE1) case = case.resize((8, 8), Image.Resampling.LANCZOS) # fmt: off @@ -161,7 +162,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (8, 8))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_box(self, mode) -> None: + def test_enlarge_box(self, mode: str) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off @@ -172,7 +173,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_bilinear(self, mode) -> None: + def test_enlarge_bilinear(self, mode: str) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off @@ -183,7 +184,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_hamming(self, mode) -> None: + def test_enlarge_hamming(self, mode: str) -> None: case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off @@ -194,7 +195,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (4, 4))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_bicubic(self, mode) -> None: + def test_enlarge_bicubic(self, mode: str) -> None: case = self.make_case(mode, (4, 4), 0xE1) case = case.resize((8, 8), Image.Resampling.BICUBIC) # fmt: off @@ -207,7 +208,7 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (8, 8))) @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L")) - def test_enlarge_lanczos(self, mode) -> None: + def test_enlarge_lanczos(self, mode: str) -> None: case = self.make_case(mode, (6, 6), 0xE1) case = case.resize((12, 12), Image.Resampling.LANCZOS) data = ( @@ -230,7 +231,7 @@ class TestImagingCoreResampleAccuracy: class TestCoreResampleConsistency: - def make_case(self, mode, fill): + def make_case(self, mode: str, fill: tuple[int, int, int] | float): im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] @@ -265,7 +266,7 @@ class TestCoreResampleConsistency: class TestCoreResampleAlphaCorrect: - def make_levels_case(self, mode): + def make_levels_case(self, mode: str) -> Image.Image: i = Image.new(mode, (256, 16)) px = i.load() for y in range(i.size[1]): @@ -275,7 +276,7 @@ class TestCoreResampleAlphaCorrect: px[x, y] = tuple(pix) return i - def run_levels_case(self, i) -> None: + def run_levels_case(self, i: Image.Image) -> None: px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} @@ -302,7 +303,9 @@ class TestCoreResampleAlphaCorrect: self.run_levels_case(case.resize((512, 32), Image.Resampling.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS)) - def make_dirty_case(self, mode, clean_pixel, dirty_pixel): + def make_dirty_case( + self, mode: str, clean_pixel: tuple[int, ...], dirty_pixel: tuple[int, ...] + ) -> Image.Image: i = Image.new(mode, (64, 64), dirty_pixel) px = i.load() xdiv4 = i.size[0] // 4 @@ -312,7 +315,7 @@ class TestCoreResampleAlphaCorrect: px[x + xdiv4, y + ydiv4] = clean_pixel return i - def run_dirty_case(self, i, clean_pixel) -> None: + def run_dirty_case(self, i: Image.Image, clean_pixel: tuple[int, ...]) -> None: px = i.load() for y in range(i.size[1]): for x in range(i.size[0]): @@ -432,7 +435,7 @@ class TestCoreResampleBox: Image.Resampling.LANCZOS, ), ) - def test_wrong_arguments(self, resample) -> None: + def test_wrong_arguments(self, resample: Image.Resampling) -> None: im = hopper() im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height)) @@ -459,8 +462,12 @@ class TestCoreResampleBox: with pytest.raises(ValueError, match="can't exceed"): im.resize((32, 32), resample, (0, 0, im.width, im.height + 1)) - def resize_tiled(self, im, dst_size, xtiles, ytiles): - def split_range(size, tiles): + def resize_tiled( + self, im: Image.Image, dst_size: tuple[int, int], xtiles: int, ytiles: int + ) -> Image.Image: + def split_range( + size: int, tiles: int + ) -> Generator[tuple[int, int], None, None]: scale = size / tiles for i in range(tiles): yield int(round(scale * i)), int(round(scale * (i + 1))) @@ -518,7 +525,7 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR) ) - def test_formats(self, mode, resample) -> None: + def test_formats(self, mode: str, resample: Image.Resampling) -> None: im = hopper(mode) box = (20, 20, im.size[0] - 20, im.size[1] - 20) with_box = im.resize((32, 32), resample, box) @@ -558,7 +565,7 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) ) - def test_skip_horizontal(self, flt) -> None: + def test_skip_horizontal(self, flt: Image.Resampling) -> None: # Can skip resize for one dimension im = hopper() @@ -581,7 +588,7 @@ class TestCoreResampleBox: @pytest.mark.parametrize( "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) ) - def test_skip_vertical(self, flt) -> None: + def test_skip_vertical(self, flt: Image.Resampling) -> None: # Can skip resize for one dimension im = hopper() diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index ea6e80f1e..b65ea8740 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -5,11 +5,11 @@ import pytest from PIL import Image, ImageMath -def pixel(im): - if hasattr(im, "im"): - return f"{im.mode} {repr(im.getpixel((0, 0)))}" +def pixel(im: Image.Image | int) -> str | int: if isinstance(im, int): return int(im) # hack to deal with booleans + else: + return f"{im.mode} {repr(im.getpixel((0, 0)))}" A = Image.new("L", (1, 1), 1) @@ -60,7 +60,7 @@ def test_ops() -> None: "(lambda: (lambda: exec('pass'))())()", ), ) -def test_prevent_exec(expression) -> None: +def test_prevent_exec(expression: str) -> None: with pytest.raises(ValueError): ImageMath.eval(expression) From 463c36821136652a05517a4db810a265d25c9b0c Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:02:34 +1100 Subject: [PATCH 066/157] Simplified code Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_imagemath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index b65ea8740..a21e2307d 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -8,8 +8,8 @@ from PIL import Image, ImageMath def pixel(im: Image.Image | int) -> str | int: if isinstance(im, int): return int(im) # hack to deal with booleans - else: - return f"{im.mode} {repr(im.getpixel((0, 0)))}" + + return f"{im.mode} {repr(im.getpixel((0, 0)))}" A = Image.new("L", (1, 1), 1) From c93b23239d4cbe8b8c6d4d6c04db35763a25db62 Mon Sep 17 00:00:00 2001 From: Evan Miller Date: Wed, 7 Feb 2024 20:20:27 -0500 Subject: [PATCH 067/157] Update src/_webp.c Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/_webp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_webp.c b/src/_webp.c index 4e7d41f11..927d8dc3f 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -448,18 +448,19 @@ PyObject * _anim_decoder_get_next(PyObject *self) { uint8_t *buf; int timestamp; + int ok; PyObject *bytes; PyObject *ret; ImagingSectionCookie cookie; WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; ImagingSectionEnter(&cookie); - if (!WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp)) { - ImagingSectionLeave(&cookie); + ok = WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp) + ImagingSectionLeave(&cookie); + if (!ok) { PyErr_SetString(PyExc_OSError, "failed to read next frame"); return NULL; } - ImagingSectionLeave(&cookie); bytes = PyBytes_FromStringAndSize( (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height); From cb39b1c89e71f67ce4dacd41cebf723ff86306dd Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 8 Feb 2024 12:29:06 +1100 Subject: [PATCH 068/157] Corrected syntax --- src/_webp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_webp.c b/src/_webp.c index 927d8dc3f..47592547c 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -455,7 +455,7 @@ _anim_decoder_get_next(PyObject *self) { WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; ImagingSectionEnter(&cookie); - ok = WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp) + ok = WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp); ImagingSectionLeave(&cookie); if (!ok) { PyErr_SetString(PyExc_OSError, "failed to read next frame"); From a276cf2c9fadf39cc5e663e44bc160c566d7c050 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 8 Feb 2024 18:48:38 +1100 Subject: [PATCH 069/157] Use _typing alias --- src/PIL/ImageFont.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9f8394d63..7be2fdf04 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -33,10 +33,10 @@ import sys import warnings from enum import IntEnum from io import BytesIO -from pathlib import Path from typing import BinaryIO from . import Image +from ._typing import StrOrBytesPath from ._util import is_directory, is_path @@ -193,7 +193,7 @@ class FreeTypeFont: def __init__( self, - font: bytes | str | Path | BinaryIO | None = None, + font: StrOrBytesPath | BinaryIO | None = None, size: float = 10, index: int = 0, encoding: str = "", From a118a82c30acf6427653b129fab263fde3bdbbac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 8 Feb 2024 18:35:37 +1100 Subject: [PATCH 070/157] Use os.path.realpath consistently when os.fspath is used --- src/PIL/Image.py | 2 +- src/PIL/ImageFont.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d7d0a1ae7..adb63b07f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2385,7 +2385,7 @@ class Image: filename = "" open_fp = False if is_path(fp): - filename = os.fspath(fp) + filename = os.path.realpath(os.fspath(fp)) open_fp = True elif fp == sys.stdout: try: diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 7be2fdf04..256c581df 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -230,7 +230,7 @@ class FreeTypeFont: ) if is_path(font): - font = os.fspath(font) + font = os.path.realpath(os.fspath(font)) if sys.platform == "win32": font_bytes_path = font if isinstance(font, bytes) else font.encode() try: From 152a24e13abfe099d4cf75dc7982290feb200ad2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 16:48:02 +1100 Subject: [PATCH 071/157] Simplified code --- src/PIL/Image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index adb63b07f..231674f54 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3240,10 +3240,8 @@ def open(fp, mode="r", formats=None) -> Image: exclusive_fp = False filename = "" - if isinstance(fp, os.PathLike): + if is_path(fp): filename = os.path.realpath(os.fspath(fp)) - elif is_path(fp): - filename = fp if filename: fp = builtins.open(filename, "rb") From 19a6edeecce2f3605fcdb074c00ac0152c6bdf05 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 19:50:45 +1100 Subject: [PATCH 072/157] Added type hints --- pyproject.toml | 10 ---- src/PIL/DdsImagePlugin.py | 16 ++++-- src/PIL/ImImagePlugin.py | 4 +- src/PIL/Image.py | 86 +++++++++++++++++++---------- src/PIL/ImageQt.py | 12 +++- src/PIL/PdfParser.py | 11 +++- src/PIL/PyAccess.py | 1 + src/PIL/TiffImagePlugin.py | 109 +++++++++++++++++++++---------------- src/PIL/TiffTags.py | 4 +- src/PIL/_imaging.pyi | 5 ++ src/PIL/_tkinter_finder.py | 3 +- src/PIL/_webp.pyi | 5 ++ tox.ini | 5 ++ 13 files changed, 171 insertions(+), 100 deletions(-) create mode 100644 src/PIL/_imaging.pyi create mode 100644 src/PIL/_webp.pyi diff --git a/pyproject.toml b/pyproject.toml index 652ae3633..48c59f2a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,16 +141,6 @@ warn_redundant_casts = true warn_unreachable = true warn_unused_ignores = true exclude = [ - '^src/PIL/_tkinter_finder.py$', - '^src/PIL/DdsImagePlugin.py$', '^src/PIL/FpxImagePlugin.py$', - '^src/PIL/Image.py$', - '^src/PIL/ImageQt.py$', - '^src/PIL/ImImagePlugin.py$', '^src/PIL/MicImagePlugin.py$', - '^src/PIL/PdfParser.py$', - '^src/PIL/PyAccess.py$', - '^src/PIL/TiffImagePlugin.py$', - '^src/PIL/TiffTags.py$', - '^src/PIL/WebPImagePlugin.py$', ] diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 3785174ef..be17f4223 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -270,13 +270,17 @@ class D3DFMT(IntEnum): # Backward compatibility layer module = sys.modules[__name__] for item in DDSD: + assert item.name is not None setattr(module, "DDSD_" + item.name, item.value) -for item in DDSCAPS: - setattr(module, "DDSCAPS_" + item.name, item.value) -for item in DDSCAPS2: - setattr(module, "DDSCAPS2_" + item.name, item.value) -for item in DDPF: - setattr(module, "DDPF_" + item.name, item.value) +for item1 in DDSCAPS: + assert item1.name is not None + setattr(module, "DDSCAPS_" + item1.name, item1.value) +for item2 in DDSCAPS2: + assert item2.name is not None + setattr(module, "DDSCAPS2_" + item2.name, item2.value) +for item3 in DDPF: + assert item3.name is not None + setattr(module, "DDPF_" + item3.name, item3.value) DDS_FOURCC = DDPF.FOURCC DDS_RGB = DDPF.RGB diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 97d726a8a..4613e40b6 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -93,8 +93,8 @@ for i in ["16", "16L", "16B"]: for i in ["32S"]: OPEN[f"L {i} image"] = ("I", f"I;{i}") OPEN[f"L*{i} image"] = ("I", f"I;{i}") -for i in range(2, 33): - OPEN[f"L*{i} image"] = ("F", f"F;{i}") +for j in range(2, 33): + OPEN[f"L*{j} image"] = ("F", f"F;{j}") # -------------------------------------------------------------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 111d06012..d32a0fc19 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -26,6 +26,7 @@ from __future__ import annotations +import abc import atexit import builtins import io @@ -40,11 +41,8 @@ import warnings from collections.abc import Callable, MutableMapping from enum import IntEnum from pathlib import Path - -try: - from defusedxml import ElementTree -except ImportError: - ElementTree = None +from types import ModuleType +from typing import IO, TYPE_CHECKING, Any # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -60,6 +58,12 @@ from . import ( from ._binary import i32le, o32be, o32le from ._util import DeferredError, is_path +ElementTree: ModuleType | None +try: + from defusedxml import ElementTree +except ImportError: + ElementTree = None + logger = logging.getLogger(__name__) @@ -110,6 +114,7 @@ except ImportError as v: USE_CFFI_ACCESS = False +cffi: ModuleType | None try: import cffi except ImportError: @@ -211,14 +216,22 @@ if hasattr(core, "DEFAULT_STRATEGY"): # -------------------------------------------------------------------- # Registries -ID = [] -OPEN = {} -MIME = {} -SAVE = {} -SAVE_ALL = {} -EXTENSION = {} -DECODERS = {} -ENCODERS = {} +if TYPE_CHECKING: + from . import ImageFile # pragma: no cover +ID: list[str] = [] +OPEN: dict[ + str, + tuple[ + Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], + Callable[[bytes], bool] | None, + ], +] = {} +MIME: dict[str, str] = {} +SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +EXTENSION: dict[str, str] = {} +DECODERS: dict[str, object] = {} +ENCODERS: dict[str, object] = {} # -------------------------------------------------------------------- # Modes @@ -2383,12 +2396,12 @@ class Image: may have been created, and may contain partial data. """ - filename = "" + filename: str | bytes = "" open_fp = False if isinstance(fp, Path): filename = str(fp) open_fp = True - elif is_path(fp): + elif isinstance(fp, (str, bytes)): filename = fp open_fp = True elif fp == sys.stdout: @@ -2398,7 +2411,7 @@ class Image: pass if not filename and hasattr(fp, "name") and is_path(fp.name): # only set the name for metadata purposes - filename = fp.name + filename = os.path.realpath(os.fspath(fp.name)) # may mutate self! self._ensure_mutable() @@ -2409,7 +2422,8 @@ class Image: preinit() - ext = os.path.splitext(filename)[1].lower() + filename_ext = os.path.splitext(filename)[1].lower() + ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext if not format: if ext not in EXTENSION: @@ -2451,7 +2465,7 @@ class Image: if open_fp: fp.close() - def seek(self, frame) -> Image: + def seek(self, frame) -> None: """ Seeks to the given frame in this sequence file. If you seek beyond the end of the sequence, the method raises an @@ -2511,10 +2525,9 @@ class Image: self.load() if self.im.bands == 1: - ims = [self.copy()] + return (self.copy(),) else: - ims = map(self._new, self.im.split()) - return tuple(ims) + return tuple(map(self._new, self.im.split())) def getchannel(self, channel): """ @@ -2871,7 +2884,14 @@ class ImageTransformHandler: (for use with :py:meth:`~PIL.Image.Image.transform`) """ - pass + @abc.abstractmethod + def transform( + self, + size: tuple[int, int], + image: Image, + **options: dict[str, str | int | tuple[int, ...] | list[int]], + ) -> Image: + pass # pragma: no cover # -------------------------------------------------------------------- @@ -3243,11 +3263,9 @@ def open(fp, mode="r", formats=None) -> Image: raise TypeError(msg) exclusive_fp = False - filename = "" - if isinstance(fp, Path): - filename = str(fp.resolve()) - elif is_path(fp): - filename = fp + filename: str | bytes = "" + if is_path(fp): + filename = os.path.realpath(os.fspath(fp)) if filename: fp = builtins.open(filename, "rb") @@ -3421,7 +3439,11 @@ def merge(mode, bands): # Plugin registry -def register_open(id, factory, accept=None) -> None: +def register_open( + id, + factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], + accept: Callable[[bytes], bool] | None = None, +) -> None: """ Register an image file plugin. This function should not be used in application code. @@ -3631,7 +3653,13 @@ _apply_env_variables() atexit.register(core.clear_cache) -class Exif(MutableMapping): +if TYPE_CHECKING: + _ExifBase = MutableMapping[int, Any] # pragma: no cover +else: + _ExifBase = MutableMapping + + +class Exif(_ExifBase): """ This class provides read and write access to EXIF image data:: diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 6377c7501..293ba4941 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -19,19 +19,26 @@ from __future__ import annotations import sys from io import BytesIO +from typing import Callable from . import Image from ._util import is_path +qt_version: str | None qt_versions = [ ["6", "PyQt6"], ["side6", "PySide6"], ] # If a version has already been imported, attempt it first -qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) -for qt_version, qt_module in qt_versions: +qt_versions.sort(key=lambda version: version[1] in sys.modules, reverse=True) +for version, qt_module in qt_versions: try: + QBuffer: type + QIODevice: type + QImage: type + QPixmap: type + qRgba: Callable[[int, int, int, int], int] if qt_module == "PyQt6": from PyQt6.QtCore import QBuffer, QIODevice from PyQt6.QtGui import QImage, QPixmap, qRgba @@ -41,6 +48,7 @@ for qt_version, qt_module in qt_versions: except (ImportError, RuntimeError): continue qt_is_installed = True + qt_version = version break else: qt_is_installed = False diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 014460006..9aa8dde83 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -8,6 +8,7 @@ import os import re import time import zlib +from typing import TYPE_CHECKING, Any, List, Union # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set @@ -239,12 +240,18 @@ class PdfName: return bytes(result) -class PdfArray(list): +class PdfArray(List[Any]): def __bytes__(self): return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]" -class PdfDict(collections.UserDict): +if TYPE_CHECKING: + _DictBase = collections.UserDict[Union[str, bytes], Any] # pragma: no cover +else: + _DictBase = collections.UserDict + + +class PdfDict(_DictBase): def __setattr__(self, key, value): if key == "data": collections.UserDict.__setattr__(self, key, value) diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 07bb712d8..2c831913d 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -25,6 +25,7 @@ import sys from ._deprecate import deprecate +FFI: type try: from cffi import FFI diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index e20d4d5ea..af22d76cb 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -50,6 +50,7 @@ import warnings from collections.abc import MutableMapping from fractions import Fraction from numbers import Number, Rational +from typing import TYPE_CHECKING, Any, Callable from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags from ._binary import i16be as i16 @@ -306,6 +307,13 @@ _load_dispatch = {} _write_dispatch = {} +def _delegate(op): + def delegate(self, *args): + return getattr(self._val, op)(*args) + + return delegate + + class IFDRational(Rational): """Implements a rational class where 0/0 is a legal value to match the in the wild use of exif rationals. @@ -391,12 +399,6 @@ class IFDRational(Rational): self._numerator = _numerator self._denominator = _denominator - def _delegate(op): - def delegate(self, *args): - return getattr(self._val, op)(*args) - - return delegate - """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul', 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', 'mod','rmod', 'pow','rpow', 'pos', 'neg', @@ -436,7 +438,50 @@ class IFDRational(Rational): __int__ = _delegate("__int__") -class ImageFileDirectory_v2(MutableMapping): +def _register_loader(idx, size): + def decorator(func): + from .TiffTags import TYPES + + if func.__name__.startswith("load_"): + TYPES[idx] = func.__name__[5:].replace("_", " ") + _load_dispatch[idx] = size, func # noqa: F821 + return func + + return decorator + + +def _register_writer(idx): + def decorator(func): + _write_dispatch[idx] = func # noqa: F821 + return func + + return decorator + + +def _register_basic(idx_fmt_name): + from .TiffTags import TYPES + + idx, fmt, name = idx_fmt_name + TYPES[idx] = name + size = struct.calcsize("=" + fmt) + _load_dispatch[idx] = ( # noqa: F821 + size, + lambda self, data, legacy_api=True: ( + self._unpack(f"{len(data) // size}{fmt}", data) + ), + ) + _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 + b"".join(self._pack(fmt, value) for value in values) + ) + + +if TYPE_CHECKING: + _IFDv2Base = MutableMapping[int, Any] # pragma: no cover +else: + _IFDv2Base = MutableMapping + + +class ImageFileDirectory_v2(_IFDv2Base): """This class represents a TIFF tag directory. To speed things up, we don't decode tags unless they're asked for. @@ -497,6 +542,9 @@ class ImageFileDirectory_v2(MutableMapping): """ + _load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {} + _write_dispatch: dict[int, Callable[..., Any]] = {} + def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None): """Initialize an ImageFileDirectory. @@ -531,7 +579,10 @@ class ImageFileDirectory_v2(MutableMapping): prefix = property(lambda self: self._prefix) offset = property(lambda self: self._offset) - legacy_api = property(lambda self: self._legacy_api) + + @property + def legacy_api(self): + return self._legacy_api @legacy_api.setter def legacy_api(self, value): @@ -674,40 +725,6 @@ class ImageFileDirectory_v2(MutableMapping): def _pack(self, fmt, *values): return struct.pack(self._endian + fmt, *values) - def _register_loader(idx, size): - def decorator(func): - from .TiffTags import TYPES - - if func.__name__.startswith("load_"): - TYPES[idx] = func.__name__[5:].replace("_", " ") - _load_dispatch[idx] = size, func # noqa: F821 - return func - - return decorator - - def _register_writer(idx): - def decorator(func): - _write_dispatch[idx] = func # noqa: F821 - return func - - return decorator - - def _register_basic(idx_fmt_name): - from .TiffTags import TYPES - - idx, fmt, name = idx_fmt_name - TYPES[idx] = name - size = struct.calcsize("=" + fmt) - _load_dispatch[idx] = ( # noqa: F821 - size, - lambda self, data, legacy_api=True: ( - self._unpack(f"{len(data) // size}{fmt}", data) - ), - ) - _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 - b"".join(self._pack(fmt, value) for value in values) - ) - list( map( _register_basic, @@ -995,7 +1012,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): tagdata = property(lambda self: self._tagdata) # defined in ImageFileDirectory_v2 - tagtype: dict + tagtype: dict[int, int] """Dictionary of tag types""" @classmethod @@ -1835,11 +1852,11 @@ def _save(im, fp, filename): tags = list(atts.items()) tags.sort() a = (rawmode, compression, _fp, filename, tags, types) - e = Image._getencoder(im.mode, "libtiff", a, encoderconfig) - e.setimage(im.im, (0, 0) + im.size) + encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig) + encoder.setimage(im.im, (0, 0) + im.size) while True: # undone, change to self.decodermaxblock: - errcode, data = e.encode(16 * 1024)[1:] + errcode, data = encoder.encode(16 * 1024)[1:] if not _fp: fp.write(data) if errcode: diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 88ff2f4fc..b94193931 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -22,7 +22,7 @@ from collections import namedtuple class TagInfo(namedtuple("_TagInfo", "value name type length enum")): - __slots__ = [] + __slots__: list[str] = [] def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): return super().__new__(cls, value, name, type, length, enum or {}) @@ -437,7 +437,7 @@ _populate() ## # Map type numbers to type names -- defined in ImageFileDirectory. -TYPES = {} +TYPES: dict[int, str] = {} # # These tags are handled by default in libtiff, without diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi new file mode 100644 index 000000000..b0235555d --- /dev/null +++ b/src/PIL/_imaging.pyi @@ -0,0 +1,5 @@ +from __future__ import annotations + +from typing import Any + +def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 71c0ad465..beddfb062 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -5,7 +5,8 @@ from __future__ import annotations import sys import tkinter -from tkinter import _tkinter as tk + +tk = getattr(tkinter, "_tkinter") try: if hasattr(sys, "pypy_find_executable"): diff --git a/src/PIL/_webp.pyi b/src/PIL/_webp.pyi new file mode 100644 index 000000000..b0235555d --- /dev/null +++ b/src/PIL/_webp.pyi @@ -0,0 +1,5 @@ +from __future__ import annotations + +from typing import Any + +def __getattr__(name: str) -> Any: ... diff --git a/tox.ini b/tox.ini index fb6746ce7..8c818df7a 100644 --- a/tox.ini +++ b/tox.ini @@ -33,9 +33,14 @@ commands = [testenv:mypy] skip_install = true deps = + IceSpringPySideStubs-PyQt6 + IceSpringPySideStubs-PySide6 ipython mypy==1.7.1 numpy + packaging + types-cffi + types-defusedxml extras = typing commands = From 517b797132a65a8a873d0c22008d760d2a706ff6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 20:47:32 +1100 Subject: [PATCH 073/157] Removed FileDescriptor --- src/PIL/GdImageFile.py | 6 ++---- src/PIL/_typing.py | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 315ac6d6c..88b87a22c 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -32,7 +32,7 @@ from typing import IO from . import ImageFile, ImagePalette, UnidentifiedImageError from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._typing import FileDescriptor, StrOrBytesPath +from ._typing import StrOrBytesPath class GdImageFile(ImageFile.ImageFile): @@ -81,9 +81,7 @@ class GdImageFile(ImageFile.ImageFile): ] -def open( - fp: StrOrBytesPath | FileDescriptor | IO[bytes], mode: str = "r" -) -> GdImageFile: +def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile: """ Load texture from a GD image file. diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 346702037..7075e8672 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -27,8 +27,7 @@ class SupportsRead(Protocol[_T_co]): def read(self, __length: int = ...) -> _T_co: ... -FileDescriptor = int StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] -__all__ = ["FileDescriptor", "TypeGuard", "StrOrBytesPath", "SupportsRead"] +__all__ = ["TypeGuard", "StrOrBytesPath", "SupportsRead"] From 68db96981c0819efc51bea995915eaa389a292e7 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:50:48 +1100 Subject: [PATCH 074/157] Removed else Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- src/PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d32a0fc19..c3ab62174 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2526,8 +2526,7 @@ class Image: self.load() if self.im.bands == 1: return (self.copy(),) - else: - return tuple(map(self._new, self.im.split())) + return tuple(map(self._new, self.im.split())) def getchannel(self, channel): """ From d02a778efd443db9f69233763f187e14eebde6db Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:57:59 +1100 Subject: [PATCH 075/157] Removed no cover pragmas Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- src/PIL/Image.py | 6 +++--- src/PIL/PdfParser.py | 2 +- src/PIL/TiffImagePlugin.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c3ab62174..d9d708d5d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -217,7 +217,7 @@ if hasattr(core, "DEFAULT_STRATEGY"): # Registries if TYPE_CHECKING: - from . import ImageFile # pragma: no cover + from . import ImageFile ID: list[str] = [] OPEN: dict[ str, @@ -2890,7 +2890,7 @@ class ImageTransformHandler: image: Image, **options: dict[str, str | int | tuple[int, ...] | list[int]], ) -> Image: - pass # pragma: no cover + pass # -------------------------------------------------------------------- @@ -3653,7 +3653,7 @@ atexit.register(core.clear_cache) if TYPE_CHECKING: - _ExifBase = MutableMapping[int, Any] # pragma: no cover + _ExifBase = MutableMapping[int, Any] else: _ExifBase = MutableMapping diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 9aa8dde83..4c5101738 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -246,7 +246,7 @@ class PdfArray(List[Any]): if TYPE_CHECKING: - _DictBase = collections.UserDict[Union[str, bytes], Any] # pragma: no cover + _DictBase = collections.UserDict[Union[str, bytes], Any] else: _DictBase = collections.UserDict diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index af22d76cb..3ba4de9d1 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -476,7 +476,7 @@ def _register_basic(idx_fmt_name): if TYPE_CHECKING: - _IFDv2Base = MutableMapping[int, Any] # pragma: no cover + _IFDv2Base = MutableMapping[int, Any] else: _IFDv2Base = MutableMapping From 8ef0ffc2b849245bde6f96b58b4af48bf498bda7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 22:37:42 +1100 Subject: [PATCH 076/157] Removed no cover pragma --- src/PIL/ImageShow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index c03122c11..d90545e92 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -184,7 +184,7 @@ class UnixViewer(Viewer): @abc.abstractmethod def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]: - pass # pragma: no cover + pass def get_command(self, file: str, **options: Any) -> str: command = self.get_command_ex(file, **options)[0] From e614bbfe501811bcb4a080ba9f07d745ba3ad231 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 22:39:18 +1100 Subject: [PATCH 077/157] Exclude code only for type checking --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 46df3f90d..115286b74 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,6 +10,7 @@ exclude_also = if DEBUG: # Don't complain about compatibility code for missing optional dependencies except ImportError + if TYPE_CHECKING: [run] omit = From 112a5a4813f34235530c2ac382108a5cf722789a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Feb 2024 22:40:24 +1100 Subject: [PATCH 078/157] Exclude abstract methods --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 115286b74..ca5f114c6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,6 +11,7 @@ exclude_also = # Don't complain about compatibility code for missing optional dependencies except ImportError if TYPE_CHECKING: + @abc.abstractmethod [run] omit = From 3977124908b934a9b037d1e8ba5549393bed9dda Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 10 Feb 2024 14:54:20 +0200 Subject: [PATCH 079/157] Update docs/reference/internal_modules.rst Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/reference/internal_modules.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index c3cc70060..899e4966f 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -33,10 +33,6 @@ Internal Modules Provides a convenient way to import type hints that are not available on some Python versions. -.. py:class:: FileDescriptor - - Typing alias. - .. py:class:: StrOrBytesPath Typing alias. From 3f6422b512ff39cffaa5a37915c970d7683b6d62 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 12 Feb 2024 09:28:53 +1100 Subject: [PATCH 080/157] Added type hints --- Tests/helper.py | 2 +- Tests/test_image_access.py | 47 ++++++++++++++++++-------------- Tests/test_image_array.py | 10 ++++--- Tests/test_image_draft.py | 7 ++++- Tests/test_image_entropy.py | 2 +- Tests/test_image_filter.py | 27 ++++++++++++------ Tests/test_image_getextrema.py | 2 +- Tests/test_image_getpalette.py | 2 +- Tests/test_image_paste.py | 14 +++++----- Tests/test_image_putdata.py | 6 ++-- Tests/test_image_putpalette.py | 4 +-- Tests/test_image_resample.py | 8 ++++-- Tests/test_image_rotate.py | 14 +++++++--- Tests/test_image_thumbnail.py | 2 +- Tests/test_image_transform.py | 50 ++++++++++++++++++++++++---------- 15 files changed, 124 insertions(+), 73 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 3e2a40e02..b98883946 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -244,7 +244,7 @@ def fromstring(data: bytes) -> Image.Image: return Image.open(BytesIO(data)) -def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes: +def tostring(im: Image.Image, string_format: str, **options: Any) -> bytes: out = BytesIO() im.save(out, string_format, **options) return out.getvalue() diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index e4cb2dad1..3bdaea750 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -4,6 +4,7 @@ import os import subprocess import sys import sysconfig +from types import ModuleType import pytest @@ -23,6 +24,7 @@ else: except ImportError: cffi = None +numpy: ModuleType | None try: import numpy except ImportError: @@ -71,9 +73,10 @@ class TestImagePutPixel(AccessTest): pix1 = im1.load() pix2 = im2.load() - for x, y in ((0, "0"), ("0", 0)): - with pytest.raises(TypeError): - pix1[x, y] + with pytest.raises(TypeError): + pix1[0, "0"] + with pytest.raises(TypeError): + pix1["0", 0] for y in range(im1.size[1]): for x in range(im1.size[0]): @@ -123,12 +126,13 @@ class TestImagePutPixel(AccessTest): im = hopper() pix = im.load() + assert numpy is not None assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59) class TestImageGetPixel(AccessTest): @staticmethod - def color(mode): + def color(mode: str) -> int | tuple[int, ...]: bands = Image.getmodebands(mode) if bands == 1: return 1 @@ -138,12 +142,13 @@ class TestImageGetPixel(AccessTest): return (16, 32, 49) return tuple(range(1, bands + 1)) - def check(self, mode, expected_color=None) -> None: + def check(self, mode: str, expected_color_int: int | None = None) -> None: if self._need_cffi_access and mode.startswith("BGR;"): pytest.skip("Support not added to deprecated module for BGR;* modes") - if not expected_color: - expected_color = self.color(mode) + expected_color = ( + expected_color_int if expected_color_int is not None else self.color(mode) + ) # check putpixel im = Image.new(mode, (1, 1), None) @@ -222,7 +227,7 @@ class TestImageGetPixel(AccessTest): "YCbCr", ), ) - def test_basic(self, mode) -> None: + def test_basic(self, mode: str) -> None: self.check(mode) def test_list(self) -> None: @@ -231,14 +236,14 @@ class TestImageGetPixel(AccessTest): @pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)) - def test_signedness(self, mode, expected_color) -> None: + def test_signedness(self, mode: str, expected_color: int) -> None: # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* self.check(mode, expected_color) @pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) - def test_p_putpixel_rgb_rgba(self, mode, color) -> None: + def test_p_putpixel_rgb_rgba(self, mode: str, color: tuple[int, ...]) -> None: im = Image.new(mode, (1, 1)) im.putpixel((0, 0), color) @@ -262,7 +267,7 @@ class TestCffiGetPixel(TestImageGetPixel): class TestCffi(AccessTest): _need_cffi_access = True - def _test_get_access(self, im) -> None: + def _test_get_access(self, im: Image.Image) -> None: """Do we get the same thing as the old pixel access Using private interfaces, forcing a capi access and @@ -299,7 +304,7 @@ class TestCffi(AccessTest): # im = Image.new('I;32B', (10, 10), 2**10) # self._test_get_access(im) - def _test_set_access(self, im, color) -> None: + def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None: """Are we writing the correct bits into the image? Using private interfaces, forcing a capi access and @@ -359,7 +364,7 @@ class TestCffi(AccessTest): assert px[i, 0] == 0 @pytest.mark.parametrize("mode", ("P", "PA")) - def test_p_putpixel_rgb_rgba(self, mode) -> None: + def test_p_putpixel_rgb_rgba(self, mode: str) -> None: for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)): im = Image.new(mode, (1, 1)) with pytest.warns(DeprecationWarning): @@ -377,7 +382,7 @@ class TestImagePutPixelError(AccessTest): INVALID_TYPES = ["foo", 1.0, None] @pytest.mark.parametrize("mode", IMAGE_MODES1) - def test_putpixel_type_error1(self, mode) -> None: + def test_putpixel_type_error1(self, mode: str) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises(TypeError, match="color must be int or tuple"): @@ -400,14 +405,16 @@ class TestImagePutPixelError(AccessTest): ), ), ) - def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match) -> None: + def test_putpixel_invalid_number_of_bands( + self, mode: str, band_numbers: tuple[int, ...], match: str + ) -> None: im = hopper(mode) for band_number in band_numbers: with pytest.raises(TypeError, match=match): im.putpixel((0, 0), (0,) * band_number) @pytest.mark.parametrize("mode", IMAGE_MODES2) - def test_putpixel_type_error2(self, mode) -> None: + def test_putpixel_type_error2(self, mode: str) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises( @@ -416,7 +423,7 @@ class TestImagePutPixelError(AccessTest): im.putpixel((0, 0), v) @pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2) - def test_putpixel_overflow_error(self, mode) -> None: + def test_putpixel_overflow_error(self, mode: str) -> None: im = hopper(mode) with pytest.raises(OverflowError): im.putpixel((0, 0), 2**80) @@ -428,7 +435,7 @@ class TestEmbeddable: def test_embeddable(self) -> None: import ctypes - from setuptools.command.build_ext import new_compiler + from setuptools.command import build_ext with open("embed_pil.c", "w", encoding="utf-8") as fh: fh.write( @@ -457,7 +464,7 @@ int main(int argc, char* argv[]) % sys.prefix.replace("\\", "\\\\") ) - compiler = new_compiler() + compiler = getattr(build_ext, "new_compiler")() compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( @@ -471,7 +478,7 @@ int main(int argc, char* argv[]) env["PATH"] = sys.prefix + ";" + env["PATH"] # do not display the Windows Error Reporting dialog - ctypes.windll.kernel32.SetErrorMode(0x0002) + getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) process = subprocess.Popen(["embed_pil.exe"], env=env) process.communicate() diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 0125ab56a..cf85ee4fa 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from packaging.version import parse as parse_version @@ -13,7 +15,7 @@ im = hopper().resize((128, 100)) def test_toarray() -> None: - def test(mode): + def test(mode: str) -> tuple[tuple[int, ...], str, int]: ai = numpy.array(im.convert(mode)) return ai.shape, ai.dtype.str, ai.nbytes @@ -50,14 +52,14 @@ def test_fromarray() -> None: class Wrapper: """Class with API matching Image.fromarray""" - def __init__(self, img, arr_params) -> None: + def __init__(self, img: Image.Image, arr_params: dict[str, Any]) -> None: self.img = img self.__array_interface__ = arr_params - def tobytes(self): + def tobytes(self) -> bytes: return self.img.tobytes() - def test(mode): + def test(mode: str) -> tuple[str, tuple[int, int], bool]: i = im.convert(mode) a = numpy.array(i) # Make wrapper instance for image, new array interface diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 54474311a..1ce1a7cd8 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -7,7 +7,12 @@ from .helper import fromstring, skip_unless_feature, tostring pytestmark = skip_unless_feature("jpg") -def draft_roundtrip(in_mode, in_size, req_mode, req_size): +def draft_roundtrip( + in_mode: str, + in_size: tuple[int, int], + req_mode: str | None, + req_size: tuple[int, int] | None, +) -> Image.Image: im = Image.new(in_mode, in_size) data = tostring(im, "JPEG") im = fromstring(data) diff --git a/Tests/test_image_entropy.py b/Tests/test_image_entropy.py index 01107ae6b..c1dbb879b 100644 --- a/Tests/test_image_entropy.py +++ b/Tests/test_image_entropy.py @@ -4,7 +4,7 @@ from .helper import hopper def test_entropy() -> None: - def entropy(mode): + def entropy(mode: str) -> float: return hopper(mode).entropy() assert round(abs(entropy("1") - 0.9138803254693582), 7) == 0 diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 2b6787933..6a10ae453 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -36,7 +36,7 @@ from .helper import assert_image_equal, hopper ), ) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity(filter_to_apply, mode) -> None: +def test_sanity(filter_to_apply: ImageFilter.Filter, mode: str) -> None: im = hopper(mode) if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter): out = im.filter(filter_to_apply) @@ -45,7 +45,7 @@ def test_sanity(filter_to_apply, mode) -> None: @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity_error(mode) -> None: +def test_sanity_error(mode: str) -> None: with pytest.raises(TypeError): im = hopper(mode) im.filter("hello") @@ -53,7 +53,7 @@ def test_sanity_error(mode) -> None: # crashes on small images @pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3))) -def test_crash(size) -> None: +def test_crash(size: tuple[int, int]) -> None: im = Image.new("RGB", size) im.filter(ImageFilter.SMOOTH) @@ -67,7 +67,10 @@ def test_crash(size) -> None: ("RGB", ((4, 0, 0), (0, 0, 0))), ), ) -def test_modefilter(mode, expected) -> None: +def test_modefilter( + mode: str, + expected: tuple[int, int] | tuple[tuple[int, int, int], tuple[int, int, int]], +) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -90,7 +93,13 @@ def test_modefilter(mode, expected) -> None: ("F", (0.0, 4.0, 8.0)), ), ) -def test_rankfilter(mode, expected) -> None: +def test_rankfilter( + mode: str, + expected: ( + tuple[float, float, float] + | tuple[tuple[int, int, int], tuple[int, int, int], tuple[int, int, int]] + ), +) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -106,7 +115,7 @@ def test_rankfilter(mode, expected) -> None: @pytest.mark.parametrize( "filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter) ) -def test_rankfilter_error(filter) -> None: +def test_rankfilter_error(filter: ImageFilter.RankFilter) -> None: with pytest.raises(ValueError): im = Image.new("P", (3, 3), None) im.putdata(list(range(9))) @@ -137,7 +146,7 @@ def test_kernel_not_enough_coefficients() -> None: @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_3x3(mode) -> None: +def test_consistency_3x3(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -163,7 +172,7 @@ def test_consistency_3x3(mode) -> None: @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_5x5(mode) -> None: +def test_consistency_5x5(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss_more" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -199,7 +208,7 @@ def test_consistency_5x5(mode) -> None: (2, -2), ), ) -def test_invalid_box_blur_filter(radius) -> None: +def test_invalid_box_blur_filter(radius: int | tuple[int, int]) -> None: with pytest.raises(ValueError): ImageFilter.BoxBlur(radius) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 0107fdcc4..a5b974459 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -6,7 +6,7 @@ from .helper import hopper def test_extrema() -> None: - def extrema(mode): + def extrema(mode: str) -> tuple[int, int] | tuple[tuple[int, int], ...]: return hopper(mode).getextrema() assert extrema("1") == (0, 255) diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index e7304c98f..6a8f157fc 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -6,7 +6,7 @@ from .helper import hopper def test_palette() -> None: - def palette(mode): + def palette(mode: str) -> list[int] | None: p = hopper(mode).getpalette() if p: return p[:10] diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index c4d7a5dd2..ce7345572 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -46,7 +46,7 @@ class TestImagingPaste: self.assert_9points_image(im, expected) @CachedProperty - def mask_1(self): + def mask_1(self) -> Image.Image: mask = Image.new("1", (self.size, self.size)) px = mask.load() for y in range(mask.height): @@ -55,11 +55,11 @@ class TestImagingPaste: return mask @CachedProperty - def mask_L(self): + def mask_L(self) -> Image.Image: return self.gradient_L.transpose(Image.Transpose.ROTATE_270) @CachedProperty - def gradient_L(self): + def gradient_L(self) -> Image.Image: gradient = Image.new("L", (self.size, self.size)) px = gradient.load() for y in range(gradient.height): @@ -68,7 +68,7 @@ class TestImagingPaste: return gradient @CachedProperty - def gradient_RGB(self): + def gradient_RGB(self) -> Image.Image: return Image.merge( "RGB", [ @@ -79,7 +79,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_LA(self): + def gradient_LA(self) -> Image.Image: return Image.merge( "LA", [ @@ -89,7 +89,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_RGBA(self): + def gradient_RGBA(self) -> Image.Image: return Image.merge( "RGBA", [ @@ -101,7 +101,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_RGBa(self): + def gradient_RGBa(self) -> Image.Image: return Image.merge( "RGBa", [ diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 103019916..73145faac 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -31,7 +31,7 @@ def test_sanity() -> None: def test_long_integers() -> None: # see bug-200802-systemerror - def put(value): + def put(value: int) -> tuple[int, int, int, int]: im = Image.new("RGBA", (1, 1)) im.putdata([value]) return im.getpixel((0, 0)) @@ -58,7 +58,7 @@ def test_mode_with_L_with_float() -> None: @pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B")) -def test_mode_i(mode) -> None: +def test_mode_i(mode: str) -> None: src = hopper("L") data = list(src.getdata()) im = Image.new(mode, src.size, 0) @@ -79,7 +79,7 @@ def test_mode_F() -> None: @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) -def test_mode_BGR(mode) -> None: +def test_mode_BGR(mode: str) -> None: data = [(16, 32, 49), (32, 32, 98)] im = Image.new(mode, (1, 2)) im.putdata(data) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index ffe2551d2..cc7cf58f0 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -8,7 +8,7 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper def test_putpalette() -> None: - def palette(mode): + def palette(mode: str) -> str | tuple[str, list[int]]: im = hopper(mode).copy() im.putpalette(list(range(256)) * 3) p = im.getpalette() @@ -81,7 +81,7 @@ def test_putpalette_with_alpha_values() -> None: ("RGBAX", (1, 2, 3, 4, 0)), ), ) -def test_rgba_palette(mode, palette) -> None: +def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None: im = Image.new("P", (1, 1)) im.putpalette(palette, mode) assert im.getpalette() == [1, 2, 3] diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index f3ec12c05..7090ff9cd 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -231,11 +231,13 @@ class TestImagingCoreResampleAccuracy: class TestCoreResampleConsistency: - def make_case(self, mode: str, fill: tuple[int, int, int] | float): + def make_case( + self, mode: str, fill: tuple[int, int, int] | float + ) -> tuple[Image.Image, tuple[int, ...]]: im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] - def run_case(self, case) -> None: + def run_case(self, case: tuple[Image.Image, Image.Image]) -> None: channel, color = case px = channel.load() for x in range(channel.size[0]): @@ -353,7 +355,7 @@ class TestCoreResampleAlphaCorrect: class TestCoreResamplePasses: @contextmanager - def count(self, diff): + def count(self, diff: int) -> Generator[None, None, None]: count = Image.core.get_stats()["new_count"] yield assert Image.core.get_stats()["new_count"] - count == diff diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 51e0f5854..c10c96da6 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -12,7 +12,13 @@ from .helper import ( ) -def rotate(im, mode, angle, center=None, translate=None) -> None: +def rotate( + im: Image.Image, + mode: str, + angle: int, + center: tuple[int, int] | None = None, + translate: tuple[int, int] | None = None, +) -> None: out = im.rotate(angle, center=center, translate=translate) assert out.mode == mode assert out.size == im.size # default rotate clips output @@ -27,13 +33,13 @@ def rotate(im, mode, angle, center=None, translate=None) -> None: @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) -def test_mode(mode) -> None: +def test_mode(mode: str) -> None: im = hopper(mode) rotate(im, mode, 45) @pytest.mark.parametrize("angle", (0, 90, 180, 270)) -def test_angle(angle) -> None: +def test_angle(angle: int) -> None: with Image.open("Tests/images/test-card.png") as im: rotate(im, im.mode, angle) @@ -42,7 +48,7 @@ def test_angle(angle) -> None: @pytest.mark.parametrize("angle", (0, 45, 90, 180, 270)) -def test_zero(angle) -> None: +def test_zero(angle: int) -> None: im = Image.new("RGB", (0, 0)) rotate(im, im.mode, angle) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 6aeeea2ed..2ca1d2cfc 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -111,7 +111,7 @@ def test_load_first_unless_jpeg() -> None: with Image.open("Tests/images/hopper.jpg") as im: draft = im.draft - def im_draft(mode, size): + def im_draft(mode: str, size: tuple[int, int]): result = draft(mode, size) assert result is not None diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 1067dd563..638d12247 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +from typing import Callable import pytest @@ -91,7 +92,7 @@ class TestImageTransform: ("LA", (76, 0)), ), ) - def test_fill(self, mode, expected_pixel) -> None: + def test_fill(self, mode: str, expected_pixel: tuple[int, ...]) -> None: im = hopper(mode) (w, h) = im.size transformed = im.transform( @@ -142,7 +143,9 @@ class TestImageTransform: assert_image_equal(blank, transformed.crop((w // 2, 0, w, h // 2))) assert_image_equal(blank, transformed.crop((0, h // 2, w // 2, h))) - def _test_alpha_premult(self, op) -> None: + def _test_alpha_premult( + self, op: Callable[[Image.Image, tuple[int, int]], Image.Image] + ) -> None: # create image with half white, half black, # with the black half transparent. # do op, @@ -159,13 +162,13 @@ class TestImageTransform: assert 40 * 10 == hist[-1] def test_alpha_premult_resize(self) -> None: - def op(im, sz): + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: return im.resize(sz, Image.Resampling.BILINEAR) self._test_alpha_premult(op) def test_alpha_premult_transform(self) -> None: - def op(im, sz): + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: (w, h) = im.size return im.transform( sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.BILINEAR @@ -173,7 +176,9 @@ class TestImageTransform: self._test_alpha_premult(op) - def _test_nearest(self, op, mode) -> None: + def _test_nearest( + self, op: Callable[[Image.Image, tuple[int, int]], Image.Image], mode: str + ) -> None: # create white image with half transparent, # do op, # the image should remain white with half transparent @@ -196,15 +201,15 @@ class TestImageTransform: ) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_resize(self, mode) -> None: - def op(im, sz): + def test_nearest_resize(self, mode: str) -> None: + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: return im.resize(sz, Image.Resampling.NEAREST) self._test_nearest(op, mode) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_transform(self, mode) -> None: - def op(im, sz): + def test_nearest_transform(self, mode: str) -> None: + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: (w, h) = im.size return im.transform( sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.NEAREST @@ -227,7 +232,9 @@ class TestImageTransform: # Running by default, but I'd totally understand not doing it in # the future - pattern = [Image.new("RGBA", (1024, 1024), (a, a, a, a)) for a in range(1, 65)] + pattern: list[Image.Image] | None = [ + Image.new("RGBA", (1024, 1024), (a, a, a, a)) for a in range(1, 65) + ] # Yeah. Watch some JIT optimize this out. pattern = None # noqa: F841 @@ -240,7 +247,7 @@ class TestImageTransform: im.transform((100, 100), None) @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) - def test_unknown_resampling_filter(self, resample) -> None: + def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None: with hopper() as im: (w, h) = im.size with pytest.raises(ValueError): @@ -250,7 +257,7 @@ class TestImageTransform: class TestImageTransformAffine: transform = Image.Transform.AFFINE - def _test_image(self): + def _test_image(self) -> Image.Image: im = hopper("RGB") return im.crop((10, 20, im.width - 10, im.height - 20)) @@ -263,7 +270,7 @@ class TestImageTransformAffine: (270, Image.Transpose.ROTATE_270), ), ) - def test_rotate(self, deg, transpose) -> None: + def test_rotate(self, deg: int, transpose: Image.Transpose | None) -> None: im = self._test_image() angle = -math.radians(deg) @@ -313,7 +320,13 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_resize(self, scale, epsilon_scale, resample, epsilon) -> None: + def test_resize( + self, + scale: float, + epsilon_scale: float, + resample: Image.Resampling, + epsilon: int, + ) -> None: im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) @@ -342,7 +355,14 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_translate(self, x, y, epsilon_scale, resample, epsilon) -> None: + def test_translate( + self, + x: float, + y: float, + epsilon_scale: float, + resample: Image.Resampling, + epsilon: float, + ) -> None: im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) From ea0240bf2d414ff297c7959f904c252bae696ff3 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:12:08 +1100 Subject: [PATCH 081/157] Use is None Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_image_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 3bdaea750..380b89de8 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -147,7 +147,7 @@ class TestImageGetPixel(AccessTest): pytest.skip("Support not added to deprecated module for BGR;* modes") expected_color = ( - expected_color_int if expected_color_int is not None else self.color(mode) + self.color(mode) if expected_color_int is None else expected_color_int ) # check putpixel From 4ce06aac3bc6aeb0425f78fc691210a839d6d875 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 12 Feb 2024 21:06:17 +1100 Subject: [PATCH 082/157] Added type hints --- Tests/test_color_lut.py | 4 ++++ Tests/test_core_resources.py | 2 +- Tests/test_file_dds.py | 18 ++++++++-------- Tests/test_file_fli.py | 4 ++-- Tests/test_file_gribstub.py | 7 +++--- Tests/test_file_hdf5stub.py | 7 +++--- Tests/test_file_jpeg.py | 22 ++++++++++--------- Tests/test_file_palm.py | 8 +++---- Tests/test_file_tar.py | 2 +- Tests/test_file_webp_metadata.py | 2 ++ Tests/test_image.py | 37 +++++++++++++++++++------------- Tests/test_imagefontctl.py | 14 +++++++----- Tests/test_imagemorph.py | 10 ++++----- Tests/test_imagepalette.py | 2 +- Tests/test_pickle.py | 22 ++++++++++++------- Tests/test_psdraw.py | 4 ++-- Tests/test_sgi_crash.py | 2 +- Tests/test_shell_injection.py | 8 ++++++- Tests/test_tiff_crashes.py | 2 +- Tests/test_tiff_ifdrational.py | 6 +++--- 20 files changed, 108 insertions(+), 75 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 2bb1b57d4..c8886a779 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,6 +1,7 @@ from __future__ import annotations from array import array +from types import ModuleType import pytest @@ -8,6 +9,7 @@ from PIL import Image, ImageFilter from .helper import assert_image_equal +numpy: ModuleType | None try: import numpy except ImportError: @@ -397,6 +399,7 @@ class TestColorLut3DFilter: @pytest.mark.skipif(numpy is None, reason="NumPy not installed") def test_numpy_sources(self) -> None: + assert numpy is not None table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) with pytest.raises(ValueError, match="should have either channels"): lut = ImageFilter.Color3DLUT((5, 6, 7), table) @@ -430,6 +433,7 @@ class TestColorLut3DFilter: @pytest.mark.skipif(numpy is None, reason="NumPy not installed") def test_numpy_formats(self) -> None: + assert numpy is not None g = Image.linear_gradient("L") im = Image.merge( "RGB", diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 5eabe8f11..2c1de8bc3 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -187,6 +187,6 @@ class TestEnvVars: {"PILLOW_BLOCKS_MAX": "wat"}, ), ) - def test_warnings(self, var) -> None: + def test_warnings(self, var: dict[str, str]) -> None: with pytest.warns(UserWarning): Image._apply_env_variables(var) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index b78a0dd81..ebc0e89a1 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -48,7 +48,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds" TEST_FILE_DX10_BC1_TYPELESS, ), ) -def test_sanity_dxt1_bc1(image_path) -> None: +def test_sanity_dxt1_bc1(image_path: str) -> None: """Check DXT1 and BC1 images can be opened""" with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") @@ -96,7 +96,7 @@ def test_sanity_dxt5() -> None: TEST_FILE_BC4U, ), ) -def test_sanity_ati1_bc4u(image_path) -> None: +def test_sanity_ati1_bc4u(image_path: str) -> None: """Check ATI1 and BC4U images can be opened""" with Image.open(image_path) as im: @@ -117,7 +117,7 @@ def test_sanity_ati1_bc4u(image_path) -> None: TEST_FILE_DX10_BC4_TYPELESS, ), ) -def test_dx10_bc4(image_path) -> None: +def test_dx10_bc4(image_path: str) -> None: """Check DX10 BC4 images can be opened""" with Image.open(image_path) as im: @@ -138,7 +138,7 @@ def test_dx10_bc4(image_path) -> None: TEST_FILE_BC5U, ), ) -def test_sanity_ati2_bc5u(image_path) -> None: +def test_sanity_ati2_bc5u(image_path: str) -> None: """Check ATI2 and BC5U images can be opened""" with Image.open(image_path) as im: @@ -162,7 +162,7 @@ def test_sanity_ati2_bc5u(image_path) -> None: (TEST_FILE_BC5S, TEST_FILE_BC5S), ), ) -def test_dx10_bc5(image_path, expected_path) -> None: +def test_dx10_bc5(image_path: str, expected_path: str) -> None: """Check DX10 BC5 images can be opened""" with Image.open(image_path) as im: @@ -176,7 +176,7 @@ def test_dx10_bc5(image_path, expected_path) -> None: @pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS)) -def test_dx10_bc6h(image_path) -> None: +def test_dx10_bc6h(image_path: str) -> None: """Check DX10 BC6H/BC6HS images can be opened""" with Image.open(image_path) as im: @@ -257,7 +257,7 @@ def test_dx10_r8g8b8a8_unorm_srgb() -> None: ("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA), ], ) -def test_uncompressed(mode, size, test_file) -> None: +def test_uncompressed(mode: str, size: tuple[int, int], test_file: str) -> None: """Check uncompressed images can be opened""" with Image.open(test_file) as im: @@ -359,7 +359,7 @@ def test_unsupported_bitcount() -> None: "Tests/images/unimplemented_pfflags.dds", ), ) -def test_not_implemented(test_file) -> None: +def test_not_implemented(test_file: str) -> None: with pytest.raises(NotImplementedError): with Image.open(test_file): pass @@ -381,7 +381,7 @@ def test_save_unsupported_mode(tmp_path: Path) -> None: ("RGBA", "Tests/images/pil123rgba.png"), ], ) -def test_save(mode, test_file, tmp_path: Path) -> None: +def test_save(mode: str, test_file: str, tmp_path: Path) -> None: out = str(tmp_path / "temp.dds") with Image.open(test_file) as im: assert im.mode == mode diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index a673d4af8..fc524721c 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -147,7 +147,7 @@ def test_seek() -> None: ], ) @pytest.mark.timeout(timeout=3) -def test_timeouts(test_file) -> None: +def test_timeouts(test_file: str) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): @@ -160,7 +160,7 @@ def test_timeouts(test_file) -> None: "Tests/images/crash-5762152299364352.fli", ], ) -def test_crash(test_file) -> None: +def test_crash(test_file: str) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 4945468be..096a5b88b 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from typing import IO import pytest @@ -55,15 +56,15 @@ def test_handler(tmp_path: Path) -> None: loaded = False saved = False - def open(self, im) -> None: + def open(self, im: Image.Image) -> None: self.opened = True - def load(self, im): + def load(self, im: Image.Image) -> Image.Image: self.loaded = True im.fp.close() return Image.new("RGB", (1, 1)) - def save(self, im, fp, filename) -> None: + def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None: self.saved = True handler = TestHandler() diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index ac3d40bf2..f871e2eff 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from typing import IO import pytest @@ -56,15 +57,15 @@ def test_handler(tmp_path: Path) -> None: loaded = False saved = False - def open(self, im) -> None: + def open(self, im: Image.Image) -> None: self.opened = True - def load(self, im): + def load(self, im: Image.Image) -> Image.Image: self.loaded = True im.fp.close() return Image.new("RGB", (1, 1)) - def save(self, im, fp, filename) -> None: + def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None: self.saved = True handler = TestHandler() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 6b0662e0b..654242148 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -5,6 +5,7 @@ import re import warnings from io import BytesIO from pathlib import Path +from types import ModuleType from typing import Any import pytest @@ -33,6 +34,7 @@ from .helper import ( skip_unless_feature, ) +ElementTree: ModuleType | None try: from defusedxml import ElementTree except ImportError: @@ -440,25 +442,25 @@ class TestFileJpeg: for subsampling in (-1, 3): # (default, invalid) im = self.roundtrip(hopper(), subsampling=subsampling) assert getsampling(im) == (2, 2, 1, 1, 1, 1) - for subsampling in (0, "4:4:4"): - im = self.roundtrip(hopper(), subsampling=subsampling) + for subsampling1 in (0, "4:4:4"): + im = self.roundtrip(hopper(), subsampling=subsampling1) assert getsampling(im) == (1, 1, 1, 1, 1, 1) - for subsampling in (1, "4:2:2"): - im = self.roundtrip(hopper(), subsampling=subsampling) + for subsampling1 in (1, "4:2:2"): + im = self.roundtrip(hopper(), subsampling=subsampling1) assert getsampling(im) == (2, 1, 1, 1, 1, 1) - for subsampling in (2, "4:2:0", "4:1:1"): - im = self.roundtrip(hopper(), subsampling=subsampling) + for subsampling1 in (2, "4:2:0", "4:1:1"): + im = self.roundtrip(hopper(), subsampling=subsampling1) assert getsampling(im) == (2, 2, 1, 1, 1, 1) # RGB colorspace - for subsampling in (-1, 0, "4:4:4"): + for subsampling1 in (-1, 0, "4:4:4"): # "4:4:4" doesn't really make sense for RGB, but the conversion # to an integer happens at a higher level - im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling) + im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1) assert getsampling(im) == (1, 1, 1, 1, 1, 1) - for subsampling in (1, "4:2:2", 2, "4:2:0", 3): + for subsampling1 in (1, "4:2:2", 2, "4:2:0", 3): with pytest.raises(OSError): - self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling) + self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1) with pytest.raises(TypeError): self.roundtrip(hopper(), subsampling="1:1:1") diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 55041a4b2..194f39b30 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -11,7 +11,7 @@ from PIL import Image from .helper import assert_image_equal, hopper, magick_command -def helper_save_as_palm(tmp_path: Path, mode) -> None: +def helper_save_as_palm(tmp_path: Path, mode: str) -> None: # Arrange im = hopper(mode) outfile = str(tmp_path / ("temp_" + mode + ".palm")) @@ -24,7 +24,7 @@ def helper_save_as_palm(tmp_path: Path, mode) -> None: assert os.path.getsize(outfile) > 0 -def open_with_magick(magick, tmp_path: Path, f): +def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image: outfile = str(tmp_path / "temp.png") rc = subprocess.call( magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT @@ -33,7 +33,7 @@ def open_with_magick(magick, tmp_path: Path, f): return Image.open(outfile) -def roundtrip(tmp_path: Path, mode) -> None: +def roundtrip(tmp_path: Path, mode: str) -> None: magick = magick_command() if not magick: return @@ -66,6 +66,6 @@ def test_p_mode(tmp_path: Path) -> None: @pytest.mark.parametrize("mode", ("L", "RGB")) -def test_oserror(tmp_path: Path, mode) -> None: +def test_oserror(tmp_path: Path, mode: str) -> None: with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 44e78e972..6217ebedd 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -19,7 +19,7 @@ TEST_TAR_FILE = "Tests/images/hopper.tar" ("jpg", "hopper.jpg", "JPEG"), ), ) -def test_sanity(codec, test_path, format) -> None: +def test_sanity(codec: str, test_path: str, format: str) -> None: if features.check(codec): with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: with Image.open(tar) as im: diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index fea196941..875941240 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -2,6 +2,7 @@ from __future__ import annotations from io import BytesIO from pathlib import Path +from types import ModuleType import pytest @@ -14,6 +15,7 @@ pytestmark = [ skip_unless_feature("webp_mux"), ] +ElementTree: ModuleType | None try: from defusedxml import ElementTree except ImportError: diff --git a/Tests/test_image.py b/Tests/test_image.py index 67a7d7eca..75b28c2dc 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -8,6 +8,7 @@ import sys import tempfile import warnings from pathlib import Path +from typing import IO import pytest @@ -61,11 +62,11 @@ class TestImage: "HSV", ), ) - def test_image_modes_success(self, mode) -> None: + def test_image_modes_success(self, mode: str) -> None: Image.new(mode, (1, 1)) @pytest.mark.parametrize("mode", ("", "bad", "very very long")) - def test_image_modes_fail(self, mode) -> None: + def test_image_modes_fail(self, mode: str) -> None: with pytest.raises(ValueError) as e: Image.new(mode, (1, 1)) assert str(e.value) == "unrecognized image mode" @@ -100,7 +101,7 @@ class TestImage: def test_repr_pretty(self) -> None: class Pretty: - def text(self, text) -> None: + def text(self, text: str) -> None: self.pretty_output = text im = Image.new("L", (100, 100)) @@ -184,7 +185,9 @@ class TestImage: temp_file = str(tmp_path / "temp.jpg") class FP: - def write(self, b) -> None: + name: str + + def write(self, b: bytes) -> None: pass fp = FP() @@ -538,7 +541,7 @@ class TestImage: "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower" ) @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) - def test_empty_image(self, size) -> None: + def test_empty_image(self, size: tuple[int, int]) -> None: Image.new("RGB", size) def test_storage_neg(self) -> None: @@ -565,7 +568,7 @@ class TestImage: Image.linear_gradient(wrong_mode) @pytest.mark.parametrize("mode", ("L", "P", "I", "F")) - def test_linear_gradient(self, mode) -> None: + def test_linear_gradient(self, mode: str) -> None: # Arrange target_file = "Tests/images/linear_gradient.png" @@ -590,7 +593,7 @@ class TestImage: Image.radial_gradient(wrong_mode) @pytest.mark.parametrize("mode", ("L", "P", "I", "F")) - def test_radial_gradient(self, mode) -> None: + def test_radial_gradient(self, mode: str) -> None: # Arrange target_file = "Tests/images/radial_gradient.png" @@ -665,7 +668,11 @@ class TestImage: blank_p.palette = None blank_pa.palette = None - def _make_new(base_image, image, palette_result=None) -> None: + def _make_new( + base_image: Image.Image, + image: Image.Image, + palette_result: ImagePalette.ImagePalette | None = None, + ) -> None: new_image = base_image._new(image.im) assert new_image.mode == image.mode assert new_image.size == image.size @@ -713,7 +720,7 @@ class TestImage: def test_load_on_nonexclusive_multiframe(self) -> None: with open("Tests/images/frozenpond.mpo", "rb") as fp: - def act(fp) -> None: + def act(fp: IO[bytes]) -> None: im = Image.open(fp) im.load() @@ -906,12 +913,12 @@ class TestImage: assert exif.get_ifd(0xA005) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero_tobytes(self, size) -> None: + def test_zero_tobytes(self, size: tuple[int, int]) -> None: im = Image.new("RGB", size) assert im.tobytes() == b"" @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) - def test_zero_frombytes(self, size) -> None: + def test_zero_frombytes(self, size: tuple[int, int]) -> None: Image.frombytes("RGB", size, b"") im = Image.new("RGB", size) @@ -996,7 +1003,7 @@ class TestImage: "01r_00.pcx", ], ) - def test_overrun(self, path) -> None: + def test_overrun(self, path: str) -> None: """For overrun completeness, test as: valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c """ @@ -1023,7 +1030,7 @@ class TestImage: pass assert not hasattr(im, "fp") - def test_close_graceful(self, caplog) -> None: + def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None: with Image.open("Tests/images/hopper.jpg") as im: copy = im.copy() with caplog.at_level(logging.DEBUG): @@ -1034,10 +1041,10 @@ class TestImage: class MockEncoder: - pass + args: tuple[str, ...] -def mock_encode(*args): +def mock_encode(*args: str) -> MockEncoder: encoder = MockEncoder() encoder.args = args return encoder diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 325e7ef21..24c7b871a 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -208,7 +208,9 @@ def test_language() -> None: ), ids=("None", "ltr", "rtl2", "rtl", "ttb"), ) -def test_getlength(mode, text, direction, expected) -> None: +def test_getlength( + mode: str, text: str, direction: str | None, expected: float +) -> None: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode, (1, 1), 0) d = ImageDraw.Draw(im) @@ -230,7 +232,7 @@ def test_getlength(mode, text, direction, expected) -> None: ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), ids=("caron-above", "caron-below", "double-breve", "overline"), ) -def test_getlength_combine(mode, direction, text) -> None: +def test_getlength_combine(mode: str, direction: str, text: str) -> None: if text == "i\u0305i" and direction == "ttb": pytest.skip("fails with this font") @@ -250,7 +252,7 @@ def test_getlength_combine(mode, direction, text) -> None: @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) -def test_anchor_ttb(anchor) -> None: +def test_anchor_ttb(anchor: str) -> None: text = "f" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) @@ -306,7 +308,9 @@ combine_tests = ( @pytest.mark.parametrize( "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] ) -def test_combine(name, text, dir, anchor, epsilon) -> None: +def test_combine( + name: str, text: str, dir: str | None, anchor: str | None, epsilon: float +) -> None: path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) @@ -337,7 +341,7 @@ def test_combine(name, text, dir, anchor, epsilon) -> None: ("rm", "right"), # pass with getsize ), ) -def test_combine_multiline(anchor, align) -> None: +def test_combine_multiline(anchor: str, align: str) -> None: # test that multiline text uses getlength, not getsize or getbbox path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 0b0c6d2d3..46b473d7a 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -10,7 +10,7 @@ from PIL import Image, ImageMorph, _imagingmorph from .helper import assert_image_equal_tofile, hopper -def string_to_img(image_string): +def string_to_img(image_string: str) -> Image.Image: """Turn a string image representation into a binary image""" rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)] height = len(rows) @@ -38,7 +38,7 @@ A = string_to_img( ) -def img_to_string(im): +def img_to_string(im: Image.Image) -> str: """Turn a (small) binary image into a string representation""" chars = ".1" width, height = im.size @@ -48,11 +48,11 @@ def img_to_string(im): ) -def img_string_normalize(im): +def img_string_normalize(im: str) -> str: return img_to_string(string_to_img(im)) -def assert_img_equal_img_string(a, b_string) -> None: +def assert_img_equal_img_string(a: Image.Image, b_string: str) -> None: assert img_to_string(a) == img_string_normalize(b_string) @@ -63,7 +63,7 @@ def test_str_to_img() -> None: @pytest.mark.parametrize( "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") ) -def test_lut(op) -> None: +def test_lut(op: str) -> None: lb = ImageMorph.LutBuilder(op_name=op) assert lb.get_lut() is None diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 545229500..8e2db15aa 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -67,7 +67,7 @@ def test_getcolor_rgba_color_rgb_palette() -> None: (255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)), ], ) -def test_getcolor_not_special(index, palette) -> None: +def test_getcolor_not_special(index: int, palette: ImagePalette.ImagePalette) -> None: im = Image.new("P", (1, 1)) # Do not use transparency index as a new color diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 560cdbd35..ed415953f 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -13,7 +13,9 @@ FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" -def helper_pickle_file(tmp_path: Path, pickle, protocol, test_file, mode) -> None: +def helper_pickle_file( + tmp_path: Path, protocol: int, test_file: str, mode: str | None +) -> None: # Arrange with Image.open(test_file) as im: filename = str(tmp_path / "temp.pkl") @@ -30,7 +32,7 @@ def helper_pickle_file(tmp_path: Path, pickle, protocol, test_file, mode) -> Non assert im == loaded_im -def helper_pickle_string(pickle, protocol, test_file, mode) -> None: +def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None: with Image.open(test_file) as im: if mode: im = im.convert(mode) @@ -64,10 +66,12 @@ def helper_pickle_string(pickle, protocol, test_file, mode) -> None: ], ) @pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1)) -def test_pickle_image(tmp_path: Path, test_file, test_mode, protocol) -> None: +def test_pickle_image( + tmp_path: Path, test_file: str, test_mode: str | None, protocol: int +) -> None: # Act / Assert - helper_pickle_string(pickle, protocol, test_file, test_mode) - helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode) + helper_pickle_string(protocol, test_file, test_mode) + helper_pickle_file(tmp_path, protocol, test_file, test_mode) def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: @@ -99,7 +103,9 @@ def test_pickle_tell() -> None: assert unpickled_image.tell() == 0 -def helper_assert_pickled_font_images(font1, font2) -> None: +def helper_assert_pickled_font_images( + font1: ImageFont.FreeTypeFont, font2: ImageFont.FreeTypeFont +) -> None: # Arrange im1 = Image.new(mode="RGBA", size=(300, 100)) im2 = Image.new(mode="RGBA", size=(300, 100)) @@ -117,7 +123,7 @@ def helper_assert_pickled_font_images(font1, font2) -> None: @skip_unless_feature("freetype2") @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) -def test_pickle_font_string(protocol) -> None: +def test_pickle_font_string(protocol: int) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -131,7 +137,7 @@ def test_pickle_font_string(protocol) -> None: @skip_unless_feature("freetype2") @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) -def test_pickle_font_file(tmp_path: Path, protocol) -> None: +def test_pickle_font_file(tmp_path: Path, protocol: int) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) filename = str(tmp_path / "temp.pkl") diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 797539f35..64dfb2c95 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -10,7 +10,7 @@ import pytest from PIL import Image, PSDraw -def _create_document(ps) -> None: +def _create_document(ps: PSDraw.PSDraw) -> None: title = "hopper" box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points @@ -50,7 +50,7 @@ def test_draw_postscript(tmp_path: Path) -> None: @pytest.mark.parametrize("buffer", (True, False)) -def test_stdout(buffer) -> None: +def test_stdout(buffer: bool) -> None: # Temporarily redirect stdout old_stdout = sys.stdout diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index 9442801d0..3ce31cd2d 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -21,7 +21,7 @@ from PIL import Image "Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi", ], ) -def test_crashes(test_file) -> None: +def test_crashes(test_file: str) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 3db0660ea..2a072fd44 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -2,6 +2,7 @@ from __future__ import annotations import shutil from pathlib import Path +from typing import Callable import pytest @@ -17,7 +18,12 @@ test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") class TestShellInjection: - def assert_save_filename_check(self, tmp_path: Path, src_img, save_func) -> None: + def assert_save_filename_check( + self, + tmp_path: Path, + src_img: Image.Image, + save_func: Callable[[Image.Image, int, str], None], + ) -> None: for filename in test_filenames: dest_file = str(tmp_path / filename) save_func(src_img, 0, dest_file) diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py index 64e781cba..f51e8b3a8 100644 --- a/Tests/test_tiff_crashes.py +++ b/Tests/test_tiff_crashes.py @@ -42,7 +42,7 @@ from .helper import on_ci @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") @pytest.mark.filterwarnings("ignore:Metadata warning") @pytest.mark.filterwarnings("ignore:Truncated File Read") -def test_tiff_crashes(test_file): +def test_tiff_crashes(test_file: str) -> None: try: with Image.open(test_file) as im: im.load() diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 536854523..f6adae3e6 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -53,9 +53,9 @@ def test_nonetype() -> None: def test_ifd_rational_save(tmp_path: Path) -> None: - methods = (True, False) - if not features.check("libtiff"): - methods = (False,) + methods = [True] + if features.check("libtiff"): + methods.append(False) for libtiff in methods: TiffImagePlugin.WRITE_LIBTIFF = libtiff From 47eaf0937f8e4f10fce8473007aba449c0c280f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 13 Feb 2024 22:26:23 +1100 Subject: [PATCH 083/157] Use IO[bytes] in type hints --- src/PIL/ImageFile.py | 4 ++-- src/PIL/MspImagePlugin.py | 3 ++- src/PIL/PcxImagePlugin.py | 3 ++- src/PIL/PpmImagePlugin.py | 4 ++-- src/PIL/SgiImagePlugin.py | 4 ++-- src/PIL/TgaImagePlugin.py | 4 ++-- src/PIL/XbmImagePlugin.py | 4 ++-- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 487f53efe..e929b665e 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -32,7 +32,7 @@ import io import itertools import struct import sys -from typing import Any, NamedTuple +from typing import IO, Any, NamedTuple from . import Image from ._deprecate import deprecate @@ -616,7 +616,7 @@ class PyCodecState: class PyCodec: - fd: io.BytesIO | None + fd: IO[bytes] | None def __init__(self, mode, *args): self.im = None diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index bb7e466a7..65cc70624 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -26,6 +26,7 @@ from __future__ import annotations import io import struct +from typing import IO from . import Image, ImageFile from ._binary import i16le as i16 @@ -163,7 +164,7 @@ Image.register_decoder("MSP", MspDecoder) # write MSP files (uncompressed only) -def _save(im: Image.Image, fp: io.BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode != "1": msg = f"cannot write mode {im.mode} as MSP" raise OSError(msg) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 3e0968a83..026bfd9a0 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -28,6 +28,7 @@ from __future__ import annotations import io import logging +from typing import IO from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -143,7 +144,7 @@ SAVE = { } -def _save(im: Image.Image, fp: io.BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: try: version, bits, planes, rawmode = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 3e45ba95c..6ac7a9bbc 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -16,7 +16,7 @@ from __future__ import annotations import math -from io import BytesIO +from typing import IO from . import Image, ImageFile from ._binary import i16be as i16 @@ -324,7 +324,7 @@ class PpmDecoder(ImageFile.PyDecoder): # -------------------------------------------------------------------- -def _save(im: Image.Image, fp: BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ccf661ff1..7bd84ebd4 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -24,7 +24,7 @@ from __future__ import annotations import os import struct -from io import BytesIO +from typing import IO from . import Image, ImageFile from ._binary import i16be as i16 @@ -125,7 +125,7 @@ class SgiImageFile(ImageFile.ImageFile): ] -def _save(im: Image.Image, fp: BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode not in {"RGB", "RGBA", "L"}: msg = "Unsupported SGI image mode" raise ValueError(msg) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 584932d2c..828701342 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -18,7 +18,7 @@ from __future__ import annotations import warnings -from io import BytesIO +from typing import IO from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -175,7 +175,7 @@ SAVE = { } -def _save(im: Image.Image, fp: BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index 0291e2858..eee727436 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -21,7 +21,7 @@ from __future__ import annotations import re -from io import BytesIO +from typing import IO from . import Image, ImageFile @@ -70,7 +70,7 @@ class XbmImageFile(ImageFile.ImageFile): self.tile = [("xbm", (0, 0) + self.size, m.end(), None)] -def _save(im: Image.Image, fp: BytesIO, filename: str) -> None: +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode != "1": msg = f"cannot write mode {im.mode} as XBM" raise OSError(msg) From 26e0f6df56c1289d52b156642c6ee1197d2bf69b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Feb 2024 10:18:54 +1100 Subject: [PATCH 084/157] Pin Python 3.13 on Windows to a3 --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 75fccf795..79a2e60b2 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-alpha.3"] timeout-minutes: 30 From 5c858d75e4a58e895bed56c2ff6c0bae245c88cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Feb 2024 10:45:52 +1100 Subject: [PATCH 085/157] Added type hints --- src/PIL/Image.py | 33 ++++---- src/PIL/ImageColor.py | 2 +- src/PIL/ImageOps.py | 171 +++++++++++++++++++++++++++------------- src/PIL/ImagePalette.py | 7 +- 4 files changed, 139 insertions(+), 74 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a770488b7..ba81a22c7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1430,7 +1430,7 @@ class Image: root = ElementTree.fromstring(xmp_tags) return {get_name(root.tag): get_value(root)} - def getexif(self): + def getexif(self) -> Exif: """ Gets EXIF data from the image. @@ -1438,7 +1438,6 @@ class Image: """ if self._exif is None: self._exif = Exif() - self._exif._loaded = False elif self._exif._loaded: return self._exif self._exif._loaded = True @@ -1525,7 +1524,7 @@ class Image: self.load() return self.im.ptr - def getpalette(self, rawmode="RGB"): + def getpalette(self, rawmode: str | None = "RGB") -> list[int] | None: """ Returns the image palette as a list. @@ -1615,7 +1614,7 @@ class Image: x, y = self.im.getprojection() return list(x), list(y) - def histogram(self, mask=None, extrema=None): + def histogram(self, mask=None, extrema=None) -> list[int]: """ Returns a histogram for the image. The histogram is returned as a list of pixel counts, one for each pixel value in the source @@ -1804,7 +1803,7 @@ class Image: result = alpha_composite(background, overlay) self.paste(result, box) - def point(self, lut, mode=None): + def point(self, lut, mode: str | None = None) -> Image: """ Maps this image through a lookup table or function. @@ -1928,7 +1927,7 @@ class Image: self.im.putdata(data, scale, offset) - def putpalette(self, data, rawmode="RGB"): + def putpalette(self, data, rawmode="RGB") -> None: """ Attaches a palette to this image. The image must be a "P", "PA", "L" or "LA" image. @@ -2108,7 +2107,7 @@ class Image: min(self.size[1], math.ceil(box[3] + support_y)), ) - def resize(self, size, resample=None, box=None, reducing_gap=None): + def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image: """ Returns a resized copy of this image. @@ -2200,10 +2199,11 @@ class Image: if factor_x > 1 or factor_y > 1: reduce_box = self._get_safe_box(size, resample, box) factor = (factor_x, factor_y) - if callable(self.reduce): - self = self.reduce(factor, box=reduce_box) - else: - self = Image.reduce(self, factor, box=reduce_box) + self = ( + self.reduce(factor, box=reduce_box) + if callable(self.reduce) + else Image.reduce(self, factor, box=reduce_box) + ) box = ( (box[0] - reduce_box[0]) / factor_x, (box[1] - reduce_box[1]) / factor_y, @@ -2818,7 +2818,7 @@ class Image: self.im.transform2(box, image.im, method, data, resample, fill) - def transpose(self, method): + def transpose(self, method: Transpose) -> Image: """ Transpose image (flip or rotate in 90 degree steps) @@ -2870,7 +2870,9 @@ class ImagePointHandler: (for use with :py:meth:`~PIL.Image.Image.point`) """ - pass + @abc.abstractmethod + def point(self, im: Image) -> Image: + pass class ImageTransformHandler: @@ -3690,6 +3692,7 @@ class Exif(_ExifBase): endian = None bigtiff = False + _loaded = False def __init__(self): self._data = {} @@ -3805,7 +3808,7 @@ class Exif(_ExifBase): return merged_dict - def tobytes(self, offset=8): + def tobytes(self, offset: int = 8) -> bytes: from . import TiffImagePlugin head = self._get_head() @@ -3960,7 +3963,7 @@ class Exif(_ExifBase): del self._info[tag] self._data[tag] = value - def __delitem__(self, tag): + def __delitem__(self, tag: int) -> None: if self._info is not None and tag in self._info: del self._info[tag] else: diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index ad59b0667..5fb80b753 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -124,7 +124,7 @@ def getrgb(color): @lru_cache -def getcolor(color, mode): +def getcolor(color, mode: str) -> tuple[int, ...]: """ Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index a9e626b2b..6218c723f 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -21,6 +21,7 @@ from __future__ import annotations import functools import operator import re +from typing import Protocol, Sequence, cast from . import ExifTags, Image, ImagePalette @@ -28,7 +29,7 @@ from . import ExifTags, Image, ImagePalette # helpers -def _border(border): +def _border(border: int | tuple[int, ...]) -> tuple[int, int, int, int]: if isinstance(border, tuple): if len(border) == 2: left, top = right, bottom = border @@ -39,7 +40,7 @@ def _border(border): return left, top, right, bottom -def _color(color, mode): +def _color(color: str | int | tuple[int, ...], mode: str) -> int | tuple[int, ...]: if isinstance(color, str): from . import ImageColor @@ -47,7 +48,7 @@ def _color(color, mode): return color -def _lut(image, lut): +def _lut(image: Image.Image, lut: list[int]) -> Image.Image: if image.mode == "P": # FIXME: apply to lookup table, not image data msg = "mode P support coming soon" @@ -65,7 +66,13 @@ def _lut(image, lut): # actions -def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): +def autocontrast( + image: Image.Image, + cutoff: float | tuple[float, float] = 0, + ignore: int | Sequence[int] | None = None, + mask: Image.Image | None = None, + preserve_tone: bool = False, +) -> Image.Image: """ Maximize (normalize) image contrast. This function calculates a histogram of the input image (or mask region), removes ``cutoff`` percent of the @@ -97,10 +104,9 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): h = histogram[layer : layer + 256] if ignore is not None: # get rid of outliers - try: + if isinstance(ignore, int): h[ignore] = 0 - except TypeError: - # assume sequence + else: for ix in ignore: h[ix] = 0 if cutoff: @@ -112,7 +118,7 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): for ix in range(256): n = n + h[ix] # remove cutoff% pixels from the low end - cut = n * cutoff[0] // 100 + cut = int(n * cutoff[0] // 100) for lo in range(256): if cut > h[lo]: cut = cut - h[lo] @@ -123,7 +129,7 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): if cut <= 0: break # remove cutoff% samples from the high end - cut = n * cutoff[1] // 100 + cut = int(n * cutoff[1] // 100) for hi in range(255, -1, -1): if cut > h[hi]: cut = cut - h[hi] @@ -156,7 +162,15 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): return _lut(image, lut) -def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127): +def colorize( + image: Image.Image, + black: str | tuple[int, ...], + white: str | tuple[int, ...], + mid: str | int | tuple[int, ...] | None = None, + blackpoint: int = 0, + whitepoint: int = 255, + midpoint: int = 127, +) -> Image.Image: """ Colorize grayscale image. This function calculates a color wedge which maps all black pixels in @@ -188,10 +202,9 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 # Define colors from arguments - black = _color(black, "RGB") - white = _color(white, "RGB") - if mid is not None: - mid = _color(mid, "RGB") + rgb_black = cast(Sequence[int], _color(black, "RGB")) + rgb_white = cast(Sequence[int], _color(white, "RGB")) + rgb_mid = cast(Sequence[int], _color(mid, "RGB")) if mid is not None else None # Empty lists for the mapping red = [] @@ -200,18 +213,24 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi # Create the low-end values for i in range(0, blackpoint): - red.append(black[0]) - green.append(black[1]) - blue.append(black[2]) + red.append(rgb_black[0]) + green.append(rgb_black[1]) + blue.append(rgb_black[2]) # Create the mapping (2-color) - if mid is None: + if rgb_mid is None: range_map = range(0, whitepoint - blackpoint) for i in range_map: - red.append(black[0] + i * (white[0] - black[0]) // len(range_map)) - green.append(black[1] + i * (white[1] - black[1]) // len(range_map)) - blue.append(black[2] + i * (white[2] - black[2]) // len(range_map)) + red.append( + rgb_black[0] + i * (rgb_white[0] - rgb_black[0]) // len(range_map) + ) + green.append( + rgb_black[1] + i * (rgb_white[1] - rgb_black[1]) // len(range_map) + ) + blue.append( + rgb_black[2] + i * (rgb_white[2] - rgb_black[2]) // len(range_map) + ) # Create the mapping (3-color) else: @@ -219,26 +238,36 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi range_map2 = range(0, whitepoint - midpoint) for i in range_map1: - red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1)) - green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1)) - blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1)) + red.append( + rgb_black[0] + i * (rgb_mid[0] - rgb_black[0]) // len(range_map1) + ) + green.append( + rgb_black[1] + i * (rgb_mid[1] - rgb_black[1]) // len(range_map1) + ) + blue.append( + rgb_black[2] + i * (rgb_mid[2] - rgb_black[2]) // len(range_map1) + ) for i in range_map2: - red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2)) - green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2)) - blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2)) + red.append(rgb_mid[0] + i * (rgb_white[0] - rgb_mid[0]) // len(range_map2)) + green.append( + rgb_mid[1] + i * (rgb_white[1] - rgb_mid[1]) // len(range_map2) + ) + blue.append(rgb_mid[2] + i * (rgb_white[2] - rgb_mid[2]) // len(range_map2)) # Create the high-end values for i in range(0, 256 - whitepoint): - red.append(white[0]) - green.append(white[1]) - blue.append(white[2]) + red.append(rgb_white[0]) + green.append(rgb_white[1]) + blue.append(rgb_white[2]) # Return converted image image = image.convert("RGB") return _lut(image, red + green + blue) -def contain(image, size, method=Image.Resampling.BICUBIC): +def contain( + image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC +) -> Image.Image: """ Returns a resized version of the image, set to the maximum width and height within the requested size, while maintaining the original aspect ratio. @@ -267,7 +296,9 @@ def contain(image, size, method=Image.Resampling.BICUBIC): return image.resize(size, resample=method) -def cover(image, size, method=Image.Resampling.BICUBIC): +def cover( + image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC +) -> Image.Image: """ Returns a resized version of the image, so that the requested size is covered, while maintaining the original aspect ratio. @@ -296,7 +327,13 @@ def cover(image, size, method=Image.Resampling.BICUBIC): return image.resize(size, resample=method) -def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)): +def pad( + image: Image.Image, + size: tuple[int, int], + method: int = Image.Resampling.BICUBIC, + color: str | int | tuple[int, ...] | None = None, + centering: tuple[float, float] = (0.5, 0.5), +) -> Image.Image: """ Returns a resized and padded version of the image, expanded to fill the requested aspect ratio and size. @@ -334,7 +371,7 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 return out -def crop(image, border=0): +def crop(image: Image.Image, border: int = 0) -> Image.Image: """ Remove border from image. The same amount of pixels are removed from all four sides. This function works on all image modes. @@ -349,7 +386,9 @@ def crop(image, border=0): return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) -def scale(image, factor, resample=Image.Resampling.BICUBIC): +def scale( + image: Image.Image, factor: float, resample: int = Image.Resampling.BICUBIC +) -> Image.Image: """ Returns a rescaled image by a specific factor given in parameter. A factor greater than 1 expands the image, between 0 and 1 contracts the @@ -372,7 +411,19 @@ def scale(image, factor, resample=Image.Resampling.BICUBIC): return image.resize(size, resample) -def deform(image, deformer, resample=Image.Resampling.BILINEAR): +class _SupportsGetMesh(Protocol): + def getmesh( + self, image: Image.Image + ) -> list[ + tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] + ]: ... + + +def deform( + image: Image.Image, + deformer: _SupportsGetMesh, + resample: int = Image.Resampling.BILINEAR, +) -> Image.Image: """ Deform the image. @@ -388,7 +439,7 @@ def deform(image, deformer, resample=Image.Resampling.BILINEAR): ) -def equalize(image, mask=None): +def equalize(image: Image.Image, mask: Image.Image | None = None) -> Image.Image: """ Equalize the image histogram. This function applies a non-linear mapping to the input image, in order to create a uniform @@ -419,7 +470,11 @@ def equalize(image, mask=None): return _lut(image, lut) -def expand(image, border=0, fill=0): +def expand( + image: Image.Image, + border: int | tuple[int, ...] = 0, + fill: str | int | tuple[int, ...] = 0, +) -> Image.Image: """ Add border to the image @@ -445,7 +500,13 @@ def expand(image, border=0, fill=0): return out -def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): +def fit( + image: Image.Image, + size: tuple[int, int], + method: int = Image.Resampling.BICUBIC, + bleed: float = 0.0, + centering: tuple[float, float] = (0.5, 0.5), +) -> Image.Image: """ Returns a resized and cropped version of the image, cropped to the requested aspect ratio and size. @@ -479,13 +540,12 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, # kevin@cazabon.com # https://www.cazabon.com - # ensure centering is mutable - centering = list(centering) + centering_x, centering_y = centering - if not 0.0 <= centering[0] <= 1.0: - centering[0] = 0.5 - if not 0.0 <= centering[1] <= 1.0: - centering[1] = 0.5 + if not 0.0 <= centering_x <= 1.0: + centering_x = 0.5 + if not 0.0 <= centering_y <= 1.0: + centering_y = 0.5 if not 0.0 <= bleed < 0.5: bleed = 0.0 @@ -522,8 +582,8 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, crop_height = live_size[0] / output_ratio # make the crop - crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering[0] - crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1] + crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering_x + crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering_y crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) @@ -531,7 +591,7 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, return image.resize(size, method, box=crop) -def flip(image): +def flip(image: Image.Image) -> Image.Image: """ Flip the image vertically (top to bottom). @@ -541,7 +601,7 @@ def flip(image): return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) -def grayscale(image): +def grayscale(image: Image.Image) -> Image.Image: """ Convert the image to grayscale. @@ -551,7 +611,7 @@ def grayscale(image): return image.convert("L") -def invert(image): +def invert(image: Image.Image) -> Image.Image: """ Invert (negate) the image. @@ -562,7 +622,7 @@ def invert(image): return image.point(lut) if image.mode == "1" else _lut(image, lut) -def mirror(image): +def mirror(image: Image.Image) -> Image.Image: """ Flip image horizontally (left to right). @@ -572,7 +632,7 @@ def mirror(image): return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) -def posterize(image, bits): +def posterize(image: Image.Image, bits: int) -> Image.Image: """ Reduce the number of bits for each color channel. @@ -585,7 +645,7 @@ def posterize(image, bits): return _lut(image, lut) -def solarize(image, threshold=128): +def solarize(image: Image.Image, threshold: int = 128) -> Image.Image: """ Invert all pixel values above a threshold. @@ -602,7 +662,7 @@ def solarize(image, threshold=128): return _lut(image, lut) -def exif_transpose(image, *, in_place=False): +def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None: """ If an image has an EXIF Orientation tag, other than 1, transpose the image accordingly, and remove the orientation data. @@ -616,7 +676,7 @@ def exif_transpose(image, *, in_place=False): """ image.load() image_exif = image.getexif() - orientation = image_exif.get(ExifTags.Base.Orientation) + orientation = image_exif.get(ExifTags.Base.Orientation, 1) method = { 2: Image.Transpose.FLIP_LEFT_RIGHT, 3: Image.Transpose.ROTATE_180, @@ -653,3 +713,4 @@ def exif_transpose(image, *, in_place=False): return transposed_image elif not in_place: return image.copy() + return None diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 2b6cecc61..770d10025 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -18,6 +18,7 @@ from __future__ import annotations import array +from typing import Sequence from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile @@ -34,11 +35,11 @@ class ImagePalette: Defaults to an empty palette. """ - def __init__(self, mode="RGB", palette=None): + def __init__(self, mode: str = "RGB", palette: Sequence[int] | None = None) -> None: self.mode = mode self.rawmode = None # if set, palette contains raw data self.palette = palette or bytearray() - self.dirty = None + self.dirty: int | None = None @property def palette(self): @@ -127,7 +128,7 @@ class ImagePalette: raise ValueError(msg) from e return index - def getcolor(self, color, image=None): + def getcolor(self, color, image=None) -> int: """Given an rgb tuple, allocate palette entry. .. warning:: This method is experimental. From 1a108281b9b6d894574ec63534a043385549b3be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Feb 2024 14:03:56 +1100 Subject: [PATCH 086/157] Removed unused code --- Tests/test_format_hsv.py | 4 ---- Tests/test_image_paste.py | 1 - 2 files changed, 5 deletions(-) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 73aaae6e7..da909c06c 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -12,10 +12,6 @@ def int_to_float(i): return i / 255 -def str_to_float(i): - return ord(i) / 255 - - def tuple_to_ints(tp): x, y, z = tp return int(x * 255.0), int(y * 255.0), int(z * 255.0) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index ce7345572..2966f724f 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -8,7 +8,6 @@ from .helper import CachedProperty, assert_image_equal class TestImagingPaste: - masks = {} size = 128 def assert_9points_image( From 5ff7d926fd24acc2d6d575959635d59123b308a6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Feb 2024 15:00:38 +1100 Subject: [PATCH 087/157] Added type hints --- Tests/test_features.py | 7 +- Tests/test_file_blp.py | 2 +- Tests/test_file_bmp.py | 4 +- Tests/test_file_im.py | 2 +- Tests/test_file_pcx.py | 6 +- Tests/test_file_pdf.py | 15 ++-- Tests/test_file_tiff.py | 19 ++--- Tests/test_format_hsv.py | 19 +++-- Tests/test_imagechops.py | 6 +- Tests/test_imagedraw2.py | 11 +-- Tests/test_imageenhance.py | 8 ++- Tests/test_imagefile.py | 2 +- Tests/test_imagefont.py | 140 ++++++++++++++++++++++--------------- Tests/test_imageops.py | 22 +++--- Tests/test_imageops_usm.py | 14 ++-- Tests/test_imagepath.py | 13 ++-- 16 files changed, 170 insertions(+), 120 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index de74e9c18..8d2d198ff 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -2,6 +2,7 @@ from __future__ import annotations import io import re +from typing import Callable import pytest @@ -29,7 +30,7 @@ def test_version() -> None: # Check the correctness of the convenience function # and the format of version numbers - def test(name, function) -> None: + def test(name: str, function: Callable[[str], bool]) -> None: version = features.version(name) if not features.check(name): assert version is None @@ -73,12 +74,12 @@ def test_libimagequant_version() -> None: @pytest.mark.parametrize("feature", features.modules) -def test_check_modules(feature) -> None: +def test_check_modules(feature: str) -> None: assert features.check_module(feature) in [True, False] @pytest.mark.parametrize("feature", features.codecs) -def test_check_codecs(feature) -> None: +def test_check_codecs(feature: str) -> None: assert features.check_codec(feature) in [True, False] diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 3904d3bc5..1e2f20c40 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -71,7 +71,7 @@ def test_save(tmp_path: Path) -> None: "Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp", ], ) -def test_crashes(test_file) -> None: +def test_crashes(test_file: str) -> None: with open(test_file, "rb") as f: with Image.open(f) as im: with pytest.raises(OSError): diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index c36466e02..1eaff0c7d 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -16,7 +16,7 @@ from .helper import ( def test_sanity(tmp_path: Path) -> None: - def roundtrip(im) -> None: + def roundtrip(im: Image.Image) -> None: outfile = str(tmp_path / "temp.bmp") im.save(outfile, "BMP") @@ -194,7 +194,7 @@ def test_rle4() -> None: ("Tests/images/bmp/g/pal8rle.bmp", 1064), ), ) -def test_rle8_eof(file_name, length) -> None: +def test_rle8_eof(file_name: str, length: int) -> None: with open(file_name, "rb") as fp: data = fp.read(length) with Image.open(io.BytesIO(data)) as im: diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index f932069b9..036965bf5 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -82,7 +82,7 @@ def test_eoferror() -> None: @pytest.mark.parametrize("mode", ("RGB", "P", "PA")) -def test_roundtrip(mode, tmp_path: Path) -> None: +def test_roundtrip(mode: str, tmp_path: Path) -> None: out = str(tmp_path / "temp.im") im = hopper(mode) im.save(out) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index a2486be40..ab9f9663e 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -9,7 +9,7 @@ from PIL import Image, ImageFile, PcxImagePlugin from .helper import assert_image_equal, hopper -def _roundtrip(tmp_path: Path, im) -> None: +def _roundtrip(tmp_path: Path, im: Image.Image) -> None: f = str(tmp_path / "temp.pcx") im.save(f) with Image.open(f) as im2: @@ -44,7 +44,7 @@ def test_invalid_file() -> None: @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB")) -def test_odd(tmp_path: Path, mode) -> None: +def test_odd(tmp_path: Path, mode: str) -> None: # See issue #523, odd sized images should have a stride that's even. # Not that ImageMagick or GIMP write PCX that way. # We were not handling properly. @@ -89,7 +89,7 @@ def test_large_count(tmp_path: Path) -> None: _roundtrip(tmp_path, im) -def _test_buffer_overflow(tmp_path: Path, im, size: int = 1024) -> None: +def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) -> None: _last = ImageFile.MAXBLOCK ImageFile.MAXBLOCK = size try: diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 65a93c138..d39a86565 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -6,6 +6,7 @@ import os.path import tempfile import time from pathlib import Path +from typing import Any, Generator import pytest @@ -14,7 +15,7 @@ from PIL import Image, PdfParser, features from .helper import hopper, mark_if_feature_version, skip_unless_feature -def helper_save_as_pdf(tmp_path: Path, mode, **kwargs): +def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: # Arrange im = hopper(mode) outfile = str(tmp_path / ("temp_" + mode + ".pdf")) @@ -41,13 +42,13 @@ def helper_save_as_pdf(tmp_path: Path, mode, **kwargs): @pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) -def test_save(tmp_path: Path, mode) -> None: +def test_save(tmp_path: Path, mode: str) -> None: helper_save_as_pdf(tmp_path, mode) @skip_unless_feature("jpg_2000") @pytest.mark.parametrize("mode", ("LA", "RGBA")) -def test_save_alpha(tmp_path: Path, mode) -> None: +def test_save_alpha(tmp_path: Path, mode: str) -> None: helper_save_as_pdf(tmp_path, mode) @@ -112,7 +113,7 @@ def test_resolution(tmp_path: Path) -> None: {"dpi": (75, 150), "resolution": 200}, ), ) -def test_dpi(params, tmp_path: Path) -> None: +def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None: im = hopper() outfile = str(tmp_path / "temp.pdf") @@ -156,7 +157,7 @@ def test_save_all(tmp_path: Path) -> None: assert os.path.getsize(outfile) > 0 # Test appending using a generator - def im_generator(ims): + def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]: yield from ims im.save(outfile, save_all=True, append_images=im_generator(ims)) @@ -226,7 +227,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None: im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) -def check_pdf_pages_consistency(pdf) -> None: +def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None: pages_info = pdf.read_indirect(pdf.pages_ref) assert b"Parent" not in pages_info assert b"Kids" in pages_info @@ -339,7 +340,7 @@ def test_pdf_append_to_bytesio() -> None: @pytest.mark.timeout(1) @pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower") @pytest.mark.parametrize("newline", (b"\r", b"\n")) -def test_redos(newline) -> None: +def test_redos(newline: bytes) -> None: malicious = b" trailer<<>>" + newline * 3456 # This particular exception isn't relevant here. diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a16b76e19..0110948ae 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -4,6 +4,8 @@ import os import warnings from io import BytesIO from pathlib import Path +from types import ModuleType +from typing import Generator import pytest @@ -20,6 +22,7 @@ from .helper import ( is_win32, ) +ElementTree: ModuleType | None try: from defusedxml import ElementTree except ImportError: @@ -156,7 +159,7 @@ class TestFileTiff: "resolution_unit, dpi", [(None, 72.8), (2, 72.8), (3, 184.912)], ) - def test_load_float_dpi(self, resolution_unit, dpi) -> None: + def test_load_float_dpi(self, resolution_unit: int | None, dpi: float) -> None: with Image.open( "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif" ) as im: @@ -284,7 +287,7 @@ class TestFileTiff: ("Tests/images/multipage.tiff", 3), ), ) - def test_n_frames(self, path, n_frames) -> None: + def test_n_frames(self, path: str, n_frames: int) -> None: with Image.open(path) as im: assert im.n_frames == n_frames assert im.is_animated == (n_frames != 1) @@ -402,7 +405,7 @@ class TestFileTiff: assert len_before == len_after + 1 @pytest.mark.parametrize("legacy_api", (False, True)) - def test_load_byte(self, legacy_api) -> None: + def test_load_byte(self, legacy_api: bool) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abc" ret = ifd.load_byte(data, legacy_api) @@ -431,7 +434,7 @@ class TestFileTiff: assert 0x8825 in im.tag_v2 def test_exif(self, tmp_path: Path) -> None: - def check_exif(exif) -> None: + def check_exif(exif: Image.Exif) -> None: assert sorted(exif.keys()) == [ 256, 257, @@ -511,7 +514,7 @@ class TestFileTiff: assert im.getexif()[273] == (1408, 1907) @pytest.mark.parametrize("mode", ("1", "L")) - def test_photometric(self, mode, tmp_path: Path) -> None: + def test_photometric(self, mode: str, tmp_path: Path) -> None: filename = str(tmp_path / "temp.tif") im = hopper(mode) im.save(filename, tiffinfo={262: 0}) @@ -660,7 +663,7 @@ class TestFileTiff: assert_image_equal_tofile(reloaded, infile) @pytest.mark.parametrize("mode", ("P", "PA")) - def test_palette(self, mode, tmp_path: Path) -> None: + def test_palette(self, mode: str, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") im = hopper(mode) @@ -689,7 +692,7 @@ class TestFileTiff: assert reread.n_frames == 3 # Test appending using a generator - def im_generator(ims): + def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]: yield from ims mp = BytesIO() @@ -860,7 +863,7 @@ class TestFileTiff: ], ) @pytest.mark.timeout(2) - def test_oom(self, test_file) -> None: + def test_oom(self, test_file: str) -> None: with pytest.raises(UnidentifiedImageError): with pytest.warns(UserWarning): with Image.open(test_file): diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 73aaae6e7..fe055bf4b 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -2,21 +2,22 @@ from __future__ import annotations import colorsys import itertools +from typing import Callable from PIL import Image from .helper import assert_image_similar, hopper -def int_to_float(i): +def int_to_float(i: int) -> float: return i / 255 -def str_to_float(i): +def str_to_float(i: str) -> float: return ord(i) / 255 -def tuple_to_ints(tp): +def tuple_to_ints(tp: tuple[float, float, float]) -> tuple[int, int, int]: x, y, z = tp return int(x * 255.0), int(y * 255.0), int(z * 255.0) @@ -25,7 +26,7 @@ def test_sanity() -> None: Image.new("HSV", (100, 100)) -def wedge(): +def wedge() -> Image.Image: w = Image._wedge() w90 = w.rotate(90) @@ -49,7 +50,11 @@ def wedge(): return img -def to_xxx_colorsys(im, func, mode): +def to_xxx_colorsys( + im: Image.Image, + func: Callable[[float, float, float], tuple[float, float, float]], + mode: str, +) -> Image.Image: # convert the hard way using the library colorsys routines. (r, g, b) = im.split() @@ -70,11 +75,11 @@ def to_xxx_colorsys(im, func, mode): return hsv -def to_hsv_colorsys(im): +def to_hsv_colorsys(im: Image.Image) -> Image.Image: return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV") -def to_rgb_colorsys(im): +def to_rgb_colorsys(im: Image.Image) -> Image.Image: return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 94f57e066..7e2290c15 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Callable + from PIL import Image, ImageChops from .helper import assert_image_equal, hopper @@ -387,7 +389,9 @@ def test_overlay() -> None: def test_logical() -> None: - def table(op, a, b): + def table( + op: Callable[[Image.Image, Image.Image], Image.Image], a: int, b: int + ) -> tuple[int, int, int, int]: out = [] for x in (a, b): imx = Image.new("1", (1, 1), x) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 07a25b84b..3171eb9ae 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -5,6 +5,7 @@ import os.path import pytest from PIL import Image, ImageDraw, ImageDraw2, features +from PIL._typing import Coords from .helper import ( assert_image_equal, @@ -56,7 +57,7 @@ def test_sanity() -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_ellipse(bbox) -> None: +def test_ellipse(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -84,7 +85,7 @@ def test_ellipse_edge() -> None: @pytest.mark.parametrize("points", POINTS) -def test_line(points) -> None: +def test_line(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -98,7 +99,7 @@ def test_line(points) -> None: @pytest.mark.parametrize("points", POINTS) -def test_line_pen_as_brush(points) -> None: +def test_line_pen_as_brush(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -114,7 +115,7 @@ def test_line_pen_as_brush(points) -> None: @pytest.mark.parametrize("points", POINTS) -def test_polygon(points) -> None: +def test_polygon(points: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -129,7 +130,7 @@ def test_polygon(points) -> None: @pytest.mark.parametrize("bbox", BBOX) -def test_rectangle(bbox) -> None: +def test_rectangle(bbox: Coords) -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 9ce9cda82..6ebc61e1b 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -22,7 +22,7 @@ def test_crash() -> None: ImageEnhance.Sharpness(im).enhance(0.5) -def _half_transparent_image(): +def _half_transparent_image() -> Image.Image: # returns an image, half transparent, half solid im = hopper("RGB") @@ -34,7 +34,9 @@ def _half_transparent_image(): return im -def _check_alpha(im, original, op, amount) -> None: +def _check_alpha( + im: Image.Image, original: Image.Image, op: str, amount: float +) -> None: assert im.getbands() == original.getbands() assert_image_equal( im.getchannel("A"), @@ -44,7 +46,7 @@ def _check_alpha(im, original, op, amount) -> None: @pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness")) -def test_alpha(op) -> None: +def test_alpha(op: str) -> None: # Issue https://github.com/python-pillow/Pillow/issues/899 # Is alpha preserved through image enhancement? diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 491409781..44521a8b3 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -31,7 +31,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK class TestImageFile: def test_parser(self) -> None: - def roundtrip(format): + def roundtrip(format: str) -> tuple[Image.Image, Image.Image]: im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST) if format in ("MSP", "XBM"): im = im.convert("1") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 909026dc8..c79b36ca4 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,11 +7,13 @@ import shutil import sys from io import BytesIO from pathlib import Path +from typing import BinaryIO import pytest from packaging.version import parse as parse_version from PIL import Image, ImageDraw, ImageFont, features +from PIL._typing import StrOrBytesPath from .helper import ( assert_image_equal, @@ -47,11 +49,11 @@ def layout_engine(request): @pytest.fixture(scope="module") -def font(layout_engine): +def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont: return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine) -def test_font_properties(font) -> None: +def test_font_properties(font: ImageFont.FreeTypeFont) -> None: assert font.path == FONT_PATH assert font.size == FONT_SIZE @@ -67,7 +69,9 @@ def test_font_properties(font) -> None: assert font_copy.path == second_font_path -def _render(font, layout_engine): +def _render( + font: StrOrBytesPath | BinaryIO, layout_engine: ImageFont.Layout +) -> Image.Image: txt = "Hello World!" ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=layout_engine) ttf.getbbox(txt) @@ -80,12 +84,12 @@ def _render(font, layout_engine): @pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH))) -def test_font_with_name(layout_engine, font) -> None: +def test_font_with_name(layout_engine: ImageFont.Layout, font: str | Path) -> None: _render(font, layout_engine) -def test_font_with_filelike(layout_engine) -> None: - def _font_as_bytes(): +def test_font_with_filelike(layout_engine: ImageFont.Layout) -> None: + def _font_as_bytes() -> BytesIO: with open(FONT_PATH, "rb") as f: font_bytes = BytesIO(f.read()) return font_bytes @@ -102,12 +106,12 @@ def test_font_with_filelike(layout_engine) -> None: # _render(shared_bytes) -def test_font_with_open_file(layout_engine) -> None: +def test_font_with_open_file(layout_engine: ImageFont.Layout) -> None: with open(FONT_PATH, "rb") as f: _render(f, layout_engine) -def test_render_equal(layout_engine) -> None: +def test_render_equal(layout_engine: ImageFont.Layout) -> None: img_path = _render(FONT_PATH, layout_engine) with open(FONT_PATH, "rb") as f: font_filelike = BytesIO(f.read()) @@ -116,7 +120,7 @@ def test_render_equal(layout_engine) -> None: assert_image_equal(img_path, img_filelike) -def test_non_ascii_path(tmp_path: Path, layout_engine) -> None: +def test_non_ascii_path(tmp_path: Path, layout_engine: ImageFont.Layout) -> None: tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) try: shutil.copy(FONT_PATH, tempfile) @@ -126,7 +130,7 @@ def test_non_ascii_path(tmp_path: Path, layout_engine) -> None: ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine) -def test_transparent_background(font) -> None: +def test_transparent_background(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGBA", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -140,7 +144,7 @@ def test_transparent_background(font) -> None: assert_image_similar_tofile(im.convert("L"), target, 0.01) -def test_I16(font) -> None: +def test_I16(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="I;16", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -153,7 +157,7 @@ def test_I16(font) -> None: assert_image_similar_tofile(im.convert("L"), target, 0.01) -def test_textbbox_equal(font) -> None: +def test_textbbox_equal(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -181,7 +185,13 @@ def test_textbbox_equal(font) -> None: ), ) def test_getlength( - text, mode, fontname, size, layout_engine, length_basic, length_raqm + text: str, + mode: str, + fontname: str, + size: int, + layout_engine: ImageFont.Layout, + length_basic: int, + length_raqm: float, ) -> None: f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine) @@ -207,7 +217,7 @@ def test_float_size() -> None: assert lengths[0] != lengths[1] != lengths[2] -def test_render_multiline(font) -> None: +def test_render_multiline(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) line_spacing = font.getbbox("A")[3] + 4 @@ -223,7 +233,7 @@ def test_render_multiline(font) -> None: assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) -def test_render_multiline_text(font) -> None: +def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None: # Test that text() correctly connects to multiline_text() # and that align defaults to left im = Image.new(mode="RGB", size=(300, 100)) @@ -243,7 +253,9 @@ def test_render_multiline_text(font) -> None: @pytest.mark.parametrize( "align, ext", (("left", ""), ("center", "_center"), ("right", "_right")) ) -def test_render_multiline_text_align(font, align, ext) -> None: +def test_render_multiline_text_align( + font: ImageFont.FreeTypeFont, align: str, ext: str +) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align) @@ -251,7 +263,7 @@ def test_render_multiline_text_align(font, align, ext) -> None: assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01) -def test_unknown_align(font) -> None: +def test_unknown_align(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -260,14 +272,14 @@ def test_unknown_align(font) -> None: draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown") -def test_draw_align(font) -> None: +def test_draw_align(font: ImageFont.FreeTypeFont) -> None: im = Image.new("RGB", (300, 100), "white") draw = ImageDraw.Draw(im) line = "some text" draw.text((100, 40), line, (0, 0, 0), font=font, align="left") -def test_multiline_bbox(font) -> None: +def test_multiline_bbox(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -285,7 +297,7 @@ def test_multiline_bbox(font) -> None: draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4) -def test_multiline_width(font) -> None: +def test_multiline_width(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -295,7 +307,7 @@ def test_multiline_width(font) -> None: ) -def test_multiline_spacing(font) -> None: +def test_multiline_spacing(font: ImageFont.FreeTypeFont) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10) @@ -306,7 +318,9 @@ def test_multiline_spacing(font) -> None: @pytest.mark.parametrize( "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) ) -def test_rotated_transposed_font(font, orientation) -> None: +def test_rotated_transposed_font( + font: ImageFont.FreeTypeFont, orientation: Image.Transpose +) -> None: img_gray = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_gray) word = "testing" @@ -347,7 +361,9 @@ def test_rotated_transposed_font(font, orientation) -> None: Image.Transpose.FLIP_TOP_BOTTOM, ), ) -def test_unrotated_transposed_font(font, orientation) -> None: +def test_unrotated_transposed_font( + font: ImageFont.FreeTypeFont, orientation: Image.Transpose +) -> None: img_gray = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_gray) word = "testing" @@ -382,7 +398,9 @@ def test_unrotated_transposed_font(font, orientation) -> None: @pytest.mark.parametrize( "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) ) -def test_rotated_transposed_font_get_mask(font, orientation) -> None: +def test_rotated_transposed_font_get_mask( + font: ImageFont.FreeTypeFont, orientation: Image.Transpose +) -> None: # Arrange text = "mask this" transposed_font = ImageFont.TransposedFont(font, orientation=orientation) @@ -403,7 +421,9 @@ def test_rotated_transposed_font_get_mask(font, orientation) -> None: Image.Transpose.FLIP_TOP_BOTTOM, ), ) -def test_unrotated_transposed_font_get_mask(font, orientation) -> None: +def test_unrotated_transposed_font_get_mask( + font: ImageFont.FreeTypeFont, orientation: Image.Transpose +) -> None: # Arrange text = "mask this" transposed_font = ImageFont.TransposedFont(font, orientation=orientation) @@ -415,11 +435,11 @@ def test_unrotated_transposed_font_get_mask(font, orientation) -> None: assert mask.size == (108, 13) -def test_free_type_font_get_name(font) -> None: +def test_free_type_font_get_name(font: ImageFont.FreeTypeFont) -> None: assert ("FreeMono", "Regular") == font.getname() -def test_free_type_font_get_metrics(font) -> None: +def test_free_type_font_get_metrics(font: ImageFont.FreeTypeFont) -> None: ascent, descent = font.getmetrics() assert isinstance(ascent, int) @@ -427,7 +447,7 @@ def test_free_type_font_get_metrics(font) -> None: assert (ascent, descent) == (16, 4) -def test_free_type_font_get_mask(font) -> None: +def test_free_type_font_get_mask(font: ImageFont.FreeTypeFont) -> None: # Arrange text = "mask this" @@ -473,16 +493,16 @@ def test_default_font() -> None: @pytest.mark.parametrize("mode", (None, "1", "RGBA")) -def test_getbbox(font, mode) -> None: +def test_getbbox(font: ImageFont.FreeTypeFont, mode: str | None) -> None: assert (0, 4, 12, 16) == font.getbbox("A", mode) -def test_getbbox_empty(font) -> None: +def test_getbbox_empty(font: ImageFont.FreeTypeFont) -> None: # issue #2614, should not crash. assert (0, 0, 0, 0) == font.getbbox("") -def test_render_empty(font) -> None: +def test_render_empty(font: ImageFont.FreeTypeFont) -> None: # issue 2666 im = Image.new(mode="RGB", size=(300, 100)) target = im.copy() @@ -492,7 +512,7 @@ def test_render_empty(font) -> None: assert_image_equal(im, target) -def test_unicode_extended(layout_engine) -> None: +def test_unicode_extended(layout_engine: ImageFont.Layout) -> None: # issue #3777 text = "A\u278A\U0001F12B" target = "Tests/images/unicode_extended.png" @@ -516,7 +536,7 @@ def test_unicode_extended(layout_engine) -> None: ) @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") def test_find_font(monkeypatch, platform, font_directory) -> None: - def _test_fake_loading_font(path_to_fake, fontname) -> None: + def _test_fake_loading_font(path_to_fake: str, fontname: str) -> None: # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) with monkeypatch.context() as m: @@ -567,7 +587,7 @@ def test_find_font(monkeypatch, platform, font_directory) -> None: _test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate") -def test_imagefont_getters(font) -> None: +def test_imagefont_getters(font: ImageFont.FreeTypeFont) -> None: assert font.getmetrics() == (16, 4) assert font.font.ascent == 16 assert font.font.descent == 4 @@ -588,7 +608,7 @@ def test_imagefont_getters(font) -> None: @pytest.mark.parametrize("stroke_width", (0, 2)) -def test_getsize_stroke(font, stroke_width) -> None: +def test_getsize_stroke(font: ImageFont.FreeTypeFont, stroke_width: int) -> None: assert font.getbbox("A", stroke_width=stroke_width) == ( 0 - stroke_width, 4 - stroke_width, @@ -607,7 +627,7 @@ def test_complex_font_settings() -> None: t.getmask("абвг", language="sr") -def test_variation_get(font) -> None: +def test_variation_get(font: ImageFont.FreeTypeFont) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -662,7 +682,7 @@ def test_variation_get(font) -> None: ] -def _check_text(font, path, epsilon): +def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None: im = Image.new("RGB", (100, 75), "white") d = ImageDraw.Draw(im) d.text((10, 10), "Text", font=font, fill="black") @@ -677,7 +697,7 @@ def _check_text(font, path, epsilon): raise -def test_variation_set_by_name(font) -> None: +def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -702,7 +722,7 @@ def test_variation_set_by_name(font) -> None: _check_text(font, "Tests/images/variation_tiny_name.png", 40) -def test_variation_set_by_axes(font) -> None: +def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: freetype = parse_version(features.version_module("freetype2")) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): @@ -737,7 +757,9 @@ def test_variation_set_by_axes(font) -> None: ), ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), ) -def test_anchor(layout_engine, anchor, left, top) -> None: +def test_anchor( + layout_engine: ImageFont.Layout, anchor: str, left: int, top: int +) -> None: name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" @@ -782,7 +804,9 @@ def test_anchor(layout_engine, anchor, left, top) -> None: ("md", "center"), ), ) -def test_anchor_multiline(layout_engine, anchor, align) -> None: +def test_anchor_multiline( + layout_engine: ImageFont.Layout, anchor: str, align: str +) -> None: target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" text = "a\nlong\ntext sample" @@ -800,7 +824,7 @@ def test_anchor_multiline(layout_engine, anchor, align) -> None: assert_image_similar_tofile(im, target, 4) -def test_anchor_invalid(font) -> None: +def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None: im = Image.new("RGB", (100, 100), "white") d = ImageDraw.Draw(im) d.font = font @@ -826,7 +850,7 @@ def test_anchor_invalid(font) -> None: @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) -def test_bitmap_font(layout_engine, bpp) -> None: +def test_bitmap_font(layout_engine: ImageFont.Layout, bpp: int) -> None: text = "Bitmap Font" layout_name = ["basic", "raqm"][layout_engine] target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" @@ -843,7 +867,7 @@ def test_bitmap_font(layout_engine, bpp) -> None: assert_image_equal_tofile(im, target) -def test_bitmap_font_stroke(layout_engine) -> None: +def test_bitmap_font_stroke(layout_engine: ImageFont.Layout) -> None: text = "Bitmap Font" layout_name = ["basic", "raqm"][layout_engine] target = f"Tests/images/bitmap_font_stroke_{layout_name}.png" @@ -861,7 +885,7 @@ def test_bitmap_font_stroke(layout_engine) -> None: @pytest.mark.parametrize("embedded_color", (False, True)) -def test_bitmap_blend(layout_engine, embedded_color) -> None: +def test_bitmap_blend(layout_engine: ImageFont.Layout, embedded_color: bool) -> None: font = ImageFont.truetype( "Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine ) @@ -873,7 +897,7 @@ def test_bitmap_blend(layout_engine, embedded_color) -> None: assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png") -def test_standard_embedded_color(layout_engine) -> None: +def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: txt = "Hello World!" ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) ttf.getbbox(txt) @@ -886,7 +910,7 @@ def test_standard_embedded_color(layout_engine) -> None: @pytest.mark.parametrize("fontmode", ("1", "L", "RGBA")) -def test_float_coord(layout_engine, fontmode): +def test_float_coord(layout_engine: ImageFont.Layout, fontmode: str) -> None: txt = "Hello World!" ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) @@ -908,7 +932,7 @@ def test_float_coord(layout_engine, fontmode): raise -def test_cbdt(layout_engine) -> None: +def test_cbdt(layout_engine: ImageFont.Layout) -> None: try: font = ImageFont.truetype( "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine @@ -925,7 +949,7 @@ def test_cbdt(layout_engine) -> None: pytest.skip("freetype compiled without libpng or CBDT support") -def test_cbdt_mask(layout_engine) -> None: +def test_cbdt_mask(layout_engine: ImageFont.Layout) -> None: try: font = ImageFont.truetype( "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine @@ -942,7 +966,7 @@ def test_cbdt_mask(layout_engine) -> None: pytest.skip("freetype compiled without libpng or CBDT support") -def test_sbix(layout_engine) -> None: +def test_sbix(layout_engine: ImageFont.Layout) -> None: try: font = ImageFont.truetype( "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine @@ -959,7 +983,7 @@ def test_sbix(layout_engine) -> None: pytest.skip("freetype compiled without libpng or SBIX support") -def test_sbix_mask(layout_engine) -> None: +def test_sbix_mask(layout_engine: ImageFont.Layout) -> None: try: font = ImageFont.truetype( "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine @@ -977,7 +1001,7 @@ def test_sbix_mask(layout_engine) -> None: @skip_unless_feature_version("freetype2", "2.10.0") -def test_colr(layout_engine) -> None: +def test_colr(layout_engine: ImageFont.Layout) -> None: font = ImageFont.truetype( "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", size=64, @@ -993,7 +1017,7 @@ def test_colr(layout_engine) -> None: @skip_unless_feature_version("freetype2", "2.10.0") -def test_colr_mask(layout_engine) -> None: +def test_colr_mask(layout_engine: ImageFont.Layout) -> None: font = ImageFont.truetype( "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", size=64, @@ -1008,7 +1032,7 @@ def test_colr_mask(layout_engine) -> None: assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) -def test_woff2(layout_engine) -> None: +def test_woff2(layout_engine: ImageFont.Layout) -> None: try: font = ImageFont.truetype( "Tests/fonts/OpenSans.woff2", @@ -1042,7 +1066,7 @@ def test_render_mono_size() -> None: assert_image_equal_tofile(im, "Tests/images/text_mono.gif") -def test_too_many_characters(font) -> None: +def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None: with pytest.raises(ValueError): font.getlength("A" * 1_000_001) with pytest.raises(ValueError): @@ -1070,7 +1094,7 @@ def test_too_many_characters(font) -> None: "Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf", ], ) -def test_oom(test_file) -> None: +def test_oom(test_file: str) -> None: with open(test_file, "rb") as f: font = ImageFont.truetype(BytesIO(f.read())) with pytest.raises(Image.DecompressionBombError): @@ -1091,6 +1115,8 @@ def test_raqm_missing_warning(monkeypatch) -> None: @pytest.mark.parametrize("size", [-1, 0]) -def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size) -> None: +def test_invalid_truetype_sizes_raise_valueerror( + layout_engine: ImageFont.Layout, size: int +) -> None: with pytest.raises(ValueError): ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 50bf404ae..b320e79c1 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -14,7 +14,7 @@ from .helper import ( class Deformer: - def getmesh(self, im): + def getmesh(self, im: Image.Image) -> list[tuple[tuple[int, ...], tuple[int, ...]]]: x, y = im.size return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] @@ -108,7 +108,7 @@ def test_fit_same_ratio() -> None: @pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512))) -def test_contain(new_size) -> None: +def test_contain(new_size: tuple[int, int]) -> None: im = hopper() new_im = ImageOps.contain(im, new_size) assert new_im.size == (256, 256) @@ -132,7 +132,7 @@ def test_contain_round() -> None: ("hopper.png", (256, 256)), # square ), ) -def test_cover(image_name, expected_size) -> None: +def test_cover(image_name: str, expected_size: tuple[int, int]) -> None: with Image.open("Tests/images/" + image_name) as im: new_im = ImageOps.cover(im, (256, 256)) assert new_im.size == expected_size @@ -168,7 +168,7 @@ def test_pad_round() -> None: @pytest.mark.parametrize("mode", ("P", "PA")) -def test_palette(mode) -> None: +def test_palette(mode: str) -> None: im = hopper(mode) # Expand @@ -210,7 +210,7 @@ def test_scale() -> None: @pytest.mark.parametrize("border", (10, (1, 2, 3, 4))) -def test_expand_palette(border) -> None: +def test_expand_palette(border: int | tuple[int, int, int, int]) -> None: with Image.open("Tests/images/p_16.tga") as im: im_expanded = ImageOps.expand(im, border, (255, 0, 0)) @@ -366,7 +366,7 @@ def test_exif_transpose() -> None: for ext in exts: with Image.open("Tests/images/hopper" + ext) as base_im: - def check(orientation_im) -> None: + def check(orientation_im: Image.Image) -> None: for im in [ orientation_im, orientation_im.copy(), @@ -445,7 +445,7 @@ def test_autocontrast_cutoff() -> None: # Test the cutoff argument of autocontrast with Image.open("Tests/images/bw_gradient.png") as img: - def autocontrast(cutoff): + def autocontrast(cutoff: int | tuple[int, int]): return ImageOps.autocontrast(img, cutoff).histogram() assert autocontrast(10) == autocontrast((10, 10)) @@ -486,20 +486,20 @@ def test_autocontrast_mask_real_input() -> None: assert result_nomask != result assert_tuple_approx_equal( ImageStat.Stat(result, mask=rect_mask).median, - [195, 202, 184], + (195, 202, 184), threshold=2, msg="autocontrast with mask pixel incorrect", ) assert_tuple_approx_equal( ImageStat.Stat(result_nomask).median, - [119, 106, 79], + (119, 106, 79), threshold=2, msg="autocontrast without mask pixel incorrect", ) def test_autocontrast_preserve_tone() -> None: - def autocontrast(mode, preserve_tone): + def autocontrast(mode: str, preserve_tone: bool) -> Image.Image: im = hopper(mode) return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram() @@ -533,7 +533,7 @@ def test_autocontrast_preserve_gradient() -> None: @pytest.mark.parametrize( "color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0)) ) -def test_autocontrast_preserve_one_color(color) -> None: +def test_autocontrast_preserve_one_color(color: tuple[int, int, int]) -> None: img = Image.new("RGB", (10, 10), color) # single color images shouldn't change diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 03302e20f..519d79105 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,12 +1,14 @@ from __future__ import annotations +from typing import Generator + import pytest from PIL import Image, ImageFilter @pytest.fixture -def test_images(): +def test_images() -> Generator[dict[str, Image.Image], None, None]: ims = { "im": Image.open("Tests/images/hopper.ppm"), "snakes": Image.open("Tests/images/color_snakes.png"), @@ -18,7 +20,7 @@ def test_images(): im.close() -def test_filter_api(test_images) -> None: +def test_filter_api(test_images: dict[str, Image.Image]) -> None: im = test_images["im"] test_filter = ImageFilter.GaussianBlur(2.0) @@ -32,7 +34,7 @@ def test_filter_api(test_images) -> None: assert i.size == (128, 128) -def test_usm_formats(test_images) -> None: +def test_usm_formats(test_images: dict[str, Image.Image]) -> None: im = test_images["im"] usm = ImageFilter.UnsharpMask @@ -50,7 +52,7 @@ def test_usm_formats(test_images) -> None: im.convert("YCbCr").filter(usm) -def test_blur_formats(test_images) -> None: +def test_blur_formats(test_images: dict[str, Image.Image]) -> None: im = test_images["im"] blur = ImageFilter.GaussianBlur @@ -68,7 +70,7 @@ def test_blur_formats(test_images) -> None: im.convert("YCbCr").filter(blur) -def test_usm_accuracy(test_images) -> None: +def test_usm_accuracy(test_images: dict[str, Image.Image]) -> None: snakes = test_images["snakes"] src = snakes.convert("RGB") @@ -77,7 +79,7 @@ def test_usm_accuracy(test_images) -> None: assert i.tobytes() == src.tobytes() -def test_blur_accuracy(test_images) -> None: +def test_blur_accuracy(test_images: dict[str, Image.Image]) -> None: snakes = test_images["snakes"] i = snakes.filter(ImageFilter.GaussianBlur(0.4)) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 8ba745f21..bd600b177 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -3,6 +3,7 @@ from __future__ import annotations import array import math import struct +from typing import Sequence import pytest @@ -75,7 +76,9 @@ def test_path_constructors(coords) -> None: [[0.0, 1.0]], ), ) -def test_invalid_path_constructors(coords) -> None: +def test_invalid_path_constructors( + coords: tuple[str, str] | Sequence[Sequence[int]] +) -> None: # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) @@ -93,7 +96,7 @@ def test_invalid_path_constructors(coords) -> None: [0, 1, 2], ), ) -def test_path_odd_number_of_coordinates(coords) -> None: +def test_path_odd_number_of_coordinates(coords: Sequence[int]) -> None: # Act with pytest.raises(ValueError) as e: ImagePath.Path(coords) @@ -111,7 +114,9 @@ def test_path_odd_number_of_coordinates(coords) -> None: (1, (0.0, 0.0, 0.0, 0.0)), ], ) -def test_getbbox(coords, expected) -> None: +def test_getbbox( + coords: int | list[int], expected: tuple[float, float, float, float] +) -> None: # Arrange p = ImagePath.Path(coords) @@ -135,7 +140,7 @@ def test_getbbox_no_args() -> None: (list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]), ], ) -def test_map(coords, expected) -> None: +def test_map(coords: int | list[int], expected: list[tuple[float, float]]) -> None: # Arrange p = ImagePath.Path(coords) From 96fc60d5d2aa0ad13be0951efb1fe990a64f190a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Feb 2024 20:21:25 +1100 Subject: [PATCH 088/157] Removed mypy excludes --- pyproject.toml | 4 ---- tox.ini | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 48c59f2a1..e687f4bcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,7 +140,3 @@ follow_imports = "silent" warn_redundant_casts = true warn_unreachable = true warn_unused_ignores = true -exclude = [ - '^src/PIL/FpxImagePlugin.py$', - '^src/PIL/MicImagePlugin.py$', -] diff --git a/tox.ini b/tox.ini index 8c818df7a..3ef011c9e 100644 --- a/tox.ini +++ b/tox.ini @@ -41,6 +41,7 @@ deps = packaging types-cffi types-defusedxml + types-olefile extras = typing commands = From b6fdf2e9e7a65bf23cac224b2ab96c6b1d2c8449 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 19 Feb 2024 07:54:58 +1100 Subject: [PATCH 089/157] Updated package name for Tidelift --- .github/FUNDING.yml | 2 +- README.md | 2 +- docs/conf.py | 2 +- docs/index.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index e0e6804bf..8fc6bd0ad 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -tidelift: "pypi/Pillow" +tidelift: "pypi/pillow" diff --git a/README.md b/README.md index 6ca870166..9776c40e2 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ As of 2019, Pillow development is src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"> Tidelift + src="https://tidelift.com/badges/package/pypi/pillow?style=flat"> Newest PyPI version diff --git a/docs/conf.py b/docs/conf.py index 9ae7ae605..97289c91d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -326,7 +326,7 @@ linkcheck_allowed_redirects = { r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", - r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", + r"https://tidelift.com/badges/package/pypi/pillow?.*": r"https://img.shields.io/badge/.*", r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", } diff --git a/docs/index.rst b/docs/index.rst index 558369919..bf2feea9a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,7 +49,7 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more Date: Mon, 19 Feb 2024 16:47:07 +0200 Subject: [PATCH 090/157] Install mypy from requirements file So Renovate can update it on a schedule --- .ci/requirements-mypy.txt | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .ci/requirements-mypy.txt diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt new file mode 100644 index 000000000..ed3269460 --- /dev/null +++ b/.ci/requirements-mypy.txt @@ -0,0 +1 @@ +mypy==1.7.1 diff --git a/tox.ini b/tox.ini index 8c818df7a..85800ff8d 100644 --- a/tox.ini +++ b/tox.ini @@ -33,10 +33,10 @@ commands = [testenv:mypy] skip_install = true deps = + -r .ci/requirements-mypy.txt IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython - mypy==1.7.1 numpy packaging types-cffi From 531b1e1b9a6b3f83519d9b6687523474f4a18d83 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:50:06 +0200 Subject: [PATCH 091/157] Remove outdated installation warnings --- docs/installation.rst | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 116bdcf2f..980bbd99d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,15 +9,6 @@ Installation }); -Warnings --------- - -.. warning:: Pillow and PIL cannot co-exist in the same environment. Before installing Pillow, please uninstall PIL. - -.. warning:: Pillow >= 1.0 no longer supports ``import Image``. Please use ``from PIL import Image`` instead. - -.. warning:: Pillow >= 2.1.0 no longer supports ``import _imaging``. Please use ``from PIL.Image import core as _imaging`` instead. - Python Support -------------- From e39765d755cc2d37e79d07f58ebc77a8e44812c6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 20 Feb 2024 15:41:20 +1100 Subject: [PATCH 092/157] Added type hints --- Tests/test_deprecate.py | 6 +++--- Tests/test_file_ico.py | 2 +- Tests/test_file_iptc.py | 2 +- Tests/test_file_msp.py | 2 +- Tests/test_file_png.py | 2 ++ Tests/test_file_psd.py | 2 +- Tests/test_file_tga.py | 4 ++-- Tests/test_file_tiff_metadata.py | 6 ++++-- Tests/test_imagecms.py | 28 ++++++++++++++++++++++------ Tests/test_imagefont.py | 22 ++++++++++++---------- Tests/test_imagegrab.py | 6 ++++-- Tests/test_imagepath.py | 8 +++++--- Tests/test_imageqt.py | 2 +- Tests/test_imageshow.py | 10 ++++++---- Tests/test_imagewin_pointers.py | 2 +- Tests/test_numpy.py | 6 +++--- Tests/test_qt_image_qapplication.py | 4 ++-- Tests/test_qt_image_toqimage.py | 2 +- Tests/test_util.py | 2 +- 19 files changed, 73 insertions(+), 45 deletions(-) diff --git a/Tests/test_deprecate.py b/Tests/test_deprecate.py index 6ffc8f6f5..584d8f91d 100644 --- a/Tests/test_deprecate.py +++ b/Tests/test_deprecate.py @@ -20,7 +20,7 @@ from PIL import _deprecate ), ], ) -def test_version(version, expected) -> None: +def test_version(version: int | None, expected: str) -> None: with pytest.warns(DeprecationWarning, match=expected): _deprecate.deprecate("Old thing", version, "new thing") @@ -46,7 +46,7 @@ def test_unknown_version() -> None: ), ], ) -def test_old_version(deprecated, plural, expected) -> None: +def test_old_version(deprecated: str, plural: bool, expected: str) -> None: expected = r"" with pytest.raises(RuntimeError, match=expected): _deprecate.deprecate(deprecated, 1, plural=plural) @@ -76,7 +76,7 @@ def test_replacement_and_action() -> None: "Upgrade to new thing.", ], ) -def test_action(action) -> None: +def test_action(action: str) -> None: expected = ( r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. " r"Upgrade to new thing\." diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 65f090931..f69a290fa 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -135,7 +135,7 @@ def test_different_bit_depths(tmp_path: Path) -> None: @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA")) -def test_save_to_bytes_bmp(mode) -> None: +def test_save_to_bytes_bmp(mode: str) -> None: output = io.BytesIO() im = hopper(mode) im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)]) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 9c0969437..88c30d468 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -98,7 +98,7 @@ def test_i() -> None: assert ret == 97 -def test_dump(monkeypatch) -> None: +def test_dump(monkeypatch: pytest.MonkeyPatch) -> None: # Arrange c = b"abc" # Temporarily redirect stdout diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index f9f81d114..b0964aabe 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -52,7 +52,7 @@ def test_open_windows_v1() -> None: assert isinstance(im, MspImagePlugin.MspImageFile) -def _assert_file_image_equal(source_path, target_path) -> None: +def _assert_file_image_equal(source_path: str, target_path: str) -> None: with Image.open(source_path) as im: assert_image_equal_tofile(im, target_path) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index d4a634316..c51f56ce7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -6,6 +6,7 @@ import warnings import zlib from io import BytesIO from pathlib import Path +from types import ModuleType from typing import Any import pytest @@ -23,6 +24,7 @@ from .helper import ( skip_unless_feature, ) +ElementTree: ModuleType | None try: from defusedxml import ElementTree except ImportError: diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 7eca8d9b1..e60638b22 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -157,7 +157,7 @@ def test_combined_larger_than_size() -> None: ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), ], ) -def test_crashes(test_file, raises) -> None: +def test_crashes(test_file: str, raises) -> None: with open(test_file, "rb") as f: with pytest.raises(raises): with Image.open(f): diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bd8e522c7..3c6da50c5 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -22,8 +22,8 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} @pytest.mark.parametrize("mode", _MODES) -def test_sanity(mode, tmp_path: Path) -> None: - def roundtrip(original_im) -> None: +def test_sanity(mode: str, tmp_path: Path) -> None: + def roundtrip(original_im: Image.Image) -> None: out = str(tmp_path / "temp.tga") original_im.save(out, rle=rle) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index bb6225d07..d7a18c725 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -189,7 +189,9 @@ def test_iptc(tmp_path: Path) -> None: @pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1"))) -def test_writing_other_types_to_ascii(value, expected, tmp_path: Path) -> None: +def test_writing_other_types_to_ascii( + value: bytes | int, expected: str, tmp_path: Path +) -> None: info = TiffImagePlugin.ImageFileDirectory_v2() tag = TiffTags.TAGS_V2[271] @@ -206,7 +208,7 @@ def test_writing_other_types_to_ascii(value, expected, tmp_path: Path) -> None: @pytest.mark.parametrize("value", (1, IFDRational(1))) -def test_writing_other_types_to_bytes(value, tmp_path: Path) -> None: +def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path) -> None: im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 83fc38ed3..21a0dd75b 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -237,7 +237,7 @@ def test_invalid_color_temperature() -> None: @pytest.mark.parametrize("flag", ("my string", -1)) -def test_invalid_flag(flag) -> None: +def test_invalid_flag(flag: str | int) -> None: with hopper() as im: with pytest.raises( ImageCms.PyCMSError, match="flags must be an integer between 0 and " @@ -335,12 +335,26 @@ def test_extended_information() -> None: o = ImageCms.getOpenProfile(SRGB) p = o.profile - def assert_truncated_tuple_equal(tup1, tup2, digits: int = 10) -> None: + def assert_truncated_tuple_equal( + tup1: tuple[tuple[float, float, float], ...] | tuple[float], + tup2: ( + tuple[tuple[tuple[float, float, float], ...], ...] + | tuple[tuple[float, float, float], ...] + | tuple[float] + ), + digits: int = 10, + ) -> None: # Helper function to reduce precision of tuples of floats # recursively and then check equality. power = 10**digits - def truncate_tuple(tuple_or_float): + def truncate_tuple( + tuple_or_float: ( + tuple[tuple[tuple[float, float, float], ...], ...] + | tuple[tuple[float, float, float], ...] + | tuple[float, ...] + ) + ) -> tuple[tuple[float, ...], ...]: return tuple( ( truncate_tuple(val) @@ -504,8 +518,10 @@ def test_profile_typesafety() -> None: ImageCms.ImageCmsProfile(1).tobytes() -def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel) -> None: - def create_test_image(): +def assert_aux_channel_preserved( + mode: str, transform_in_place: bool, preserved_channel: str +) -> None: + def create_test_image() -> Image.Image: # set up test image with something interesting in the tested aux channel. # fmt: off nine_grid_deltas = [ @@ -633,7 +649,7 @@ def test_auxiliary_channels_isolated() -> None: @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) -def test_rgb_lab(mode) -> None: +def test_rgb_lab(mode: str) -> None: im = Image.new(mode, (1, 1)) converted_im = im.convert("LAB") assert converted_im.getpixel((0, 0)) == (0, 128, 128) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index c79b36ca4..05b5d4716 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,7 +7,7 @@ import shutil import sys from io import BytesIO from pathlib import Path -from typing import BinaryIO +from typing import Any, BinaryIO import pytest from packaging.version import parse as parse_version @@ -44,7 +44,7 @@ def test_sanity() -> None: pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), ], ) -def layout_engine(request): +def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout: return request.param @@ -535,21 +535,23 @@ def test_unicode_extended(layout_engine: ImageFont.Layout) -> None: (("linux", "/usr/local/share/fonts"), ("darwin", "/System/Library/Fonts")), ) @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") -def test_find_font(monkeypatch, platform, font_directory) -> None: +def test_find_font( + monkeypatch: pytest.MonkeyPatch, platform: str, font_directory: str +) -> None: def _test_fake_loading_font(path_to_fake: str, fontname: str) -> None: # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) with monkeypatch.context() as m: m.setattr(ImageFont, "_FreeTypeFont", free_type_font, raising=False) - def loadable_font(filepath, size, index, encoding, *args, **kwargs): + def loadable_font( + filepath: str, size: int, index: int, encoding: str, *args: Any + ): if filepath == path_to_fake: return ImageFont._FreeTypeFont( - FONT_PATH, size, index, encoding, *args, **kwargs + FONT_PATH, size, index, encoding, *args ) - return ImageFont._FreeTypeFont( - filepath, size, index, encoding, *args, **kwargs - ) + return ImageFont._FreeTypeFont(filepath, size, index, encoding, *args) m.setattr(ImageFont, "FreeTypeFont", loadable_font) font = ImageFont.truetype(fontname) @@ -563,7 +565,7 @@ def test_find_font(monkeypatch, platform, font_directory) -> None: if platform == "linux": monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/") - def fake_walker(path): + def fake_walker(path: str) -> list[tuple[str, list[str], list[str]]]: if path == font_directory: return [ ( @@ -1101,7 +1103,7 @@ def test_oom(test_file: str) -> None: font.getmask("Test Text") -def test_raqm_missing_warning(monkeypatch) -> None: +def test_raqm_missing_warning(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False) with pytest.warns(UserWarning) as record: font = ImageFont.truetype( diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 235a2f993..e23adeb70 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -84,6 +84,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200 @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") def test_grabclipboard_file(self) -> None: p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + assert p.stdin is not None p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"') p.communicate() @@ -94,6 +95,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200 @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") def test_grabclipboard_png(self) -> None: p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + assert p.stdin is not None p.stdin.write( rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png") $ms = new-object System.IO.MemoryStream(, $bytes) @@ -113,7 +115,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes) reason="Linux with wl-clipboard only", ) @pytest.mark.parametrize("ext", ("gif", "png", "ico")) - def test_grabclipboard_wl_clipboard(self, ext) -> None: + def test_grabclipboard_wl_clipboard(self, ext: str) -> None: image_path = "Tests/images/hopper." + ext with open(image_path, "rb") as fp: subprocess.call(["wl-copy"], stdin=fp) @@ -128,6 +130,6 @@ $ms = new-object System.IO.MemoryStream(, $bytes) reason="Linux with wl-clipboard only", ) @pytest.mark.parametrize("arg", ("text", "--clear")) - def test_grabclipboard_wl_clipboard_errors(self, arg): + def test_grabclipboard_wl_clipboard_errors(self, arg: str) -> None: subprocess.call(["wl-copy", arg]) assert ImageGrab.grabclipboard() is None diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index bd600b177..9487560af 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -58,7 +58,9 @@ def test_path() -> None: ImagePath.Path((0, 1)), ), ) -def test_path_constructors(coords) -> None: +def test_path_constructors( + coords: Sequence[float] | array.array[float] | ImagePath.Path, +) -> None: # Arrange / Act p = ImagePath.Path(coords) @@ -206,9 +208,9 @@ class Evil: def __init__(self) -> None: self.corrupt = Image.core.path(0x4000000000000000) - def __getitem__(self, i): + def __getitem__(self, i: int) -> bytes: x = self.corrupt[i] return struct.pack("dd", x[0], x[1]) - def __setitem__(self, i, x) -> None: + def __setitem__(self, i: int, x: bytes) -> None: self.corrupt[i] = struct.unpack("dd", x) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 909f97167..88ad1f9ee 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -28,7 +28,7 @@ def test_rgb() -> None: assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) - def checkrgb(r, g, b) -> None: + def checkrgb(r: int, g: int, b: int) -> None: val = ImageQt.rgb(r, g, b) val = val % 2**24 # drop the alpha assert val >> 16 == r diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index f7269d45b..8d741d94a 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from PIL import Image, ImageShow @@ -24,9 +26,9 @@ def test_register() -> None: "order", [-1, 0], ) -def test_viewer_show(order) -> None: +def test_viewer_show(order: int) -> None: class TestViewer(ImageShow.Viewer): - def show_image(self, image, **options) -> bool: + def show_image(self, image: Image.Image, **options: Any) -> bool: self.methodCalled = True return True @@ -48,7 +50,7 @@ def test_viewer_show(order) -> None: reason="Only run on CIs; hangs on Windows CIs", ) @pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA")) -def test_show(mode) -> None: +def test_show(mode: str) -> None: im = hopper(mode) assert ImageShow.show(im) @@ -73,7 +75,7 @@ def test_viewer() -> None: @pytest.mark.parametrize("viewer", ImageShow._viewers) -def test_viewers(viewer) -> None: +def test_viewers(viewer: ImageShow.Viewer) -> None: try: viewer.get_command("test.jpg") except NotImplementedError: diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index c7f633e62..f59ee7284 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -70,7 +70,7 @@ if is_win32(): ] CreateDIBSection.restype = ctypes.wintypes.HBITMAP - def serialize_dib(bi, pixels): + def serialize_dib(bi, pixels) -> bytearray: bf = BITMAPFILEHEADER() bf.bfType = 0x4D42 bf.bfOffBits = ctypes.sizeof(bf) + bi.biSize diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 6ba95c2d7..9f4e6534e 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -14,7 +14,7 @@ TEST_IMAGE_SIZE = (10, 10) def test_numpy_to_image() -> None: - def to_image(dtype, bands: int = 1, boolean: int = 0): + def to_image(dtype, bands: int = 1, boolean: int = 0) -> Image.Image: if bands == 1: if boolean: data = [0, 255] * 50 @@ -99,7 +99,7 @@ def test_1d_array() -> None: assert_image(Image.fromarray(a), "L", (1, 5)) -def _test_img_equals_nparray(img, np) -> None: +def _test_img_equals_nparray(img: Image.Image, np) -> None: assert len(np.shape) >= 2 np_size = np.shape[1], np.shape[0] assert img.size == np_size @@ -157,7 +157,7 @@ def test_save_tiff_uint16() -> None: ("HSV", numpy.uint8), ), ) -def test_to_array(mode, dtype) -> None: +def test_to_array(mode: str, dtype) -> None: img = hopper(mode) # Resize to non-square diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index 7d6c0a8cb..3cd323553 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest -from PIL import ImageQt +from PIL import Image, ImageQt from .helper import assert_image_equal_tofile, assert_image_similar, hopper @@ -37,7 +37,7 @@ if ImageQt.qt_is_installed: lbl.setPixmap(pixmap1.copy()) -def roundtrip(expected) -> None: +def roundtrip(expected: Image.Image) -> None: result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) # Qt saves all pixmaps as rgb assert_image_similar(result, expected.convert("RGB"), 1) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index a222a7d71..6110be707 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -17,7 +17,7 @@ if ImageQt.qt_is_installed: @pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1")) -def test_sanity(mode, tmp_path: Path) -> None: +def test_sanity(mode: str, tmp_path: Path) -> None: src = hopper(mode) data = ImageQt.toqimage(src) diff --git a/Tests/test_util.py b/Tests/test_util.py index 73e4acd55..197ef79ee 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -10,7 +10,7 @@ from PIL import _util @pytest.mark.parametrize( "test_path", ["filename.ext", Path("filename.ext"), PurePath("filename.ext")] ) -def test_is_path(test_path) -> None: +def test_is_path(test_path: str | Path | PurePath) -> None: # Act it_is = _util.is_path(test_path) From a655d7606e2f12f0e7700ef754ed92a6da45f658 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 20 Feb 2024 21:27:30 +1100 Subject: [PATCH 093/157] Simplified type hints --- Tests/test_imagecms.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 21a0dd75b..a7bb31db5 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -6,6 +6,7 @@ import re import shutil from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -336,25 +337,13 @@ def test_extended_information() -> None: p = o.profile def assert_truncated_tuple_equal( - tup1: tuple[tuple[float, float, float], ...] | tuple[float], - tup2: ( - tuple[tuple[tuple[float, float, float], ...], ...] - | tuple[tuple[float, float, float], ...] - | tuple[float] - ), - digits: int = 10, + tup1: tuple[Any, ...], tup2: tuple[Any, ...], digits: int = 10 ) -> None: # Helper function to reduce precision of tuples of floats # recursively and then check equality. power = 10**digits - def truncate_tuple( - tuple_or_float: ( - tuple[tuple[tuple[float, float, float], ...], ...] - | tuple[tuple[float, float, float], ...] - | tuple[float, ...] - ) - ) -> tuple[tuple[float, ...], ...]: + def truncate_tuple(tuple_or_float: tuple[Any, ...]) -> tuple[Any, ...]: return tuple( ( truncate_tuple(val) From 10712be53d575a37d9dc2522c9f8f0f62871f3b6 Mon Sep 17 00:00:00 2001 From: Nulano Date: Tue, 20 Feb 2024 21:21:10 +0100 Subject: [PATCH 094/157] Build docs for Python changes --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4319cc8ff..92e860cb5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,10 +7,12 @@ on: paths: - ".github/workflows/docs.yml" - "docs/**" + - "src/PIL/**" pull_request: paths: - ".github/workflows/docs.yml" - "docs/**" + - "src/PIL/**" workflow_dispatch: permissions: From 481ed446e20095fe1372297228d458ee9366d996 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 20 Feb 2024 21:42:39 +0200 Subject: [PATCH 095/157] Set "COVERAGE_CORE: sysmon" for faster tests on 3.12+ --- .appveyor.yml | 1 + .github/workflows/test-cygwin.yml | 3 +++ .github/workflows/test-mingw.yml | 3 +++ .github/workflows/test-windows.yml | 3 +++ .github/workflows/test.yml | 1 + 5 files changed, 11 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 4c5a7f9ee..b0740b1ac 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,6 +6,7 @@ init: # Uncomment previous line to get RDP access during the build. environment: + COVERAGE_CORE: sysmon EXECUTABLE: python.exe TEST_OPTIONS: DEPLOY: YES diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 2615fb427..4526b9454 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + COVERAGE_CORE: sysmon + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 1c6d15b77..b4e479f12 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + COVERAGE_CORE: sysmon + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 79a2e60b2..d3d1eeaa3 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + COVERAGE_CORE: sysmon + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19f4a6dae..643273e58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,7 @@ concurrency: cancel-in-progress: true env: + COVERAGE_CORE: sysmon FORCE_COLOR: 1 jobs: From 7200f47d315618b64b353c6d4a99860c65ad6df9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 21 Feb 2024 08:11:01 +1100 Subject: [PATCH 096/157] Renamed argument --- Tests/test_imagecms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index a7bb31db5..6be29a70f 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -343,14 +343,14 @@ def test_extended_information() -> None: # recursively and then check equality. power = 10**digits - def truncate_tuple(tuple_or_float: tuple[Any, ...]) -> tuple[Any, ...]: + def truncate_tuple(tuple_value: tuple[Any, ...]) -> tuple[Any, ...]: return tuple( ( truncate_tuple(val) if isinstance(val, tuple) else int(val * power) / power ) - for val in tuple_or_float + for val in tuple_value ) assert truncate_tuple(tup1) == truncate_tuple(tup2) From 097cf182fc72ec3c536ac9fc038db4571d63daf1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 21 Feb 2024 21:26:37 +1100 Subject: [PATCH 097/157] Added py.typed to support type checking --- src/PIL/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/PIL/py.typed diff --git a/src/PIL/py.typed b/src/PIL/py.typed new file mode 100644 index 000000000..e69de29bb From 9115529856cc94226d9bfba165c07813de17ea3c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 21 Feb 2024 21:42:04 +1100 Subject: [PATCH 098/157] Added "Typing :: Typed" classifier --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index e687f4bcf..58c2464bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ classifiers = [ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Viewers", + "Typing :: Typed", ] dynamic = [ "version", From e45477e507c87c65947aea07bc8b081043444517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Noel?= Date: Fri, 16 Feb 2024 15:47:51 +0100 Subject: [PATCH 099/157] fix FLI/FLC decoder for files with a prefix chunk --- Tests/images/2422.flc | Bin 0 -> 14572 bytes Tests/test_file_fli.py | 25 +++++++++++++++++++++++-- src/PIL/FliImagePlugin.py | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 Tests/images/2422.flc diff --git a/Tests/images/2422.flc b/Tests/images/2422.flc new file mode 100644 index 0000000000000000000000000000000000000000..eed5fb59e1b8793964575df338c68f78c4b23f06 GIT binary patch literal 14572 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 hopper.fli, default options. static_test_file = "Tests/images/hopper.fli" -# From https://samples.libav.org/fli-flc/ +# From https://samples.ffmpeg.org/fli-flc/ animated_test_file = "Tests/images/a.fli" +# From https://samples.ffmpeg.org/fli-flc/ +animated_test_file_with_prefix_chunk = "Tests/images/2422.flc" + def test_sanity() -> None: with Image.open(static_test_file) as im: @@ -32,6 +35,24 @@ def test_sanity() -> None: assert im.is_animated +def test_prefix_chunk() -> None: + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + with Image.open(animated_test_file_with_prefix_chunk) as im: + assert im.mode == "P" + assert im.size == (320, 200) + assert im.format == "FLI" + assert im.info["duration"] == 171 + assert im.is_animated + + palette = im.getpalette() + assert palette[3:6] == [255, 255, 255] + assert palette[381:384] == [204, 204, 12] + assert palette[765:] == [252, 0, 0] + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False + + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: def open() -> None: diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 9769761fc..f9e4c731c 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -77,6 +77,7 @@ class FliImageFile(ImageFile.ImageFile): if i16(s, 4) == 0xF100: # prefix chunk; ignore it self.__offset = self.__offset + i32(s) + self.fp.seek(self.__offset) s = self.fp.read(16) if i16(s, 4) == 0xF1FA: From 9441855107058d464540e45a357ee28e6c7bfba6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 22 Feb 2024 20:11:05 +1100 Subject: [PATCH 100/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a8404260f..ce81f2060 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Fixed reading FLI/FLC images with a prefix chunk #7804 + [twolife] + - Update wl-paste handling and return None for some errors in grabclipboard() on Linux #7745 [nik012003, radarhere] From b5c6f20007d71bd00937c3faed214dc083e46d05 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 22 Feb 2024 20:32:46 +1100 Subject: [PATCH 101/157] Added release notes --- docs/releasenotes/10.3.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/10.3.0.rst b/docs/releasenotes/10.3.0.rst index 8772a382d..af31cdb74 100644 --- a/docs/releasenotes/10.3.0.rst +++ b/docs/releasenotes/10.3.0.rst @@ -79,3 +79,9 @@ Portable FloatMap (PFM) images Support has been added for reading and writing grayscale (Pf format) Portable FloatMap (PFM) files containing ``F`` data. + +Release GIL when fetching WebP frames +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Python's Global Interpreter Lock is now released when fetching WebP frames from +the libwebp decoder. From f86a442bd55835cfaf5297f183f59ddd2a5d2557 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 22 Feb 2024 21:51:56 +1100 Subject: [PATCH 102/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ce81f2060..205ffa294 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Release GIL while calling ``WebPAnimDecoderGetNext`` #7782 + [evanmiller, radarhere] + - Fixed reading FLI/FLC images with a prefix chunk #7804 [twolife] From a08df5bc589613a682deb9c5dde5b0432c981d22 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:56:26 +0200 Subject: [PATCH 103/157] Require coverage.py 7.4.2+ for COVERAGE_CORE: sysmon --- .github/workflows/test-windows.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index d3d1eeaa3..c936be559 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -69,8 +69,16 @@ jobs: - name: Print build system information run: python3 .github/workflows/system-info.py - - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma - run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma + - name: Install Python dependencies + run: > + python3 -m pip install + coverage>=7.4.2 + defusedxml + olefile + pyroma + pytest + pytest-cov + pytest-timeout - name: Install dependencies id: install From f11ff462b33e706c6db476c85956d888b8abf683 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 24 Feb 2024 14:53:25 +0200 Subject: [PATCH 104/157] Move outdated installation warnings to 'Deprecations and removals' --- docs/deprecations.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 205fcb9ab..46c8a0701 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -504,3 +504,27 @@ PIL.OleFileIO the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). + +import _imaging +~~~~~~~~~~~~~~~ + +.. versionremoved:: 2.1.0 + +Pillow >= 2.1.0 no longer supports ``import _imaging``. +Please use ``from PIL.Image import core as _imaging`` instead. + +Pillow and PIL +~~~~~~~~~~~~~~ + +Pillow and PIL cannot co-exist in the same environment. +Before installing Pillow, please uninstall PIL. + +.. versionremoved:: 1.0.0 + +import Image +~~~~~~~~~~~~ + +.. versionremoved:: 1.0.0 + +Pillow >= 1.0 no longer supports ``import Image``. +Please use ``from PIL import Image`` instead. From e30298f35661c4427fabd73ddaac53c97fe713c6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 25 Feb 2024 06:53:29 +1100 Subject: [PATCH 105/157] Consistently place versionremoved under heading --- docs/deprecations.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 46c8a0701..8877ccdeb 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -516,11 +516,11 @@ Please use ``from PIL.Image import core as _imaging`` instead. Pillow and PIL ~~~~~~~~~~~~~~ +.. versionremoved:: 1.0.0 + Pillow and PIL cannot co-exist in the same environment. Before installing Pillow, please uninstall PIL. -.. versionremoved:: 1.0.0 - import Image ~~~~~~~~~~~~ From ece34104cb0671ef0f1f59e9924fb1be0bcf351a Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 26 Feb 2024 00:34:54 -0600 Subject: [PATCH 106/157] parametrize test_p_from_rgb_rgba() --- Tests/test_image.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 4c04e0da4..ca4458f88 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -685,15 +685,18 @@ class TestImage: _make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette()) - def test_p_from_rgb_rgba(self) -> None: - for mode, color in [ + @pytest.mark.parametrize( + ("mode", "color"), + ( ("RGB", "#DDEEFF"), ("RGB", (221, 238, 255)), ("RGBA", (221, 238, 255, 255)), - ]: - im = Image.new("P", (100, 100), color) - expected = Image.new(mode, (100, 100), color) - assert_image_equal(im.convert(mode), expected) + ), + ) + def test_p_from_rgb_rgba(self, mode, color) -> None: + im = Image.new("P", (100, 100), color) + expected = Image.new(mode, (100, 100), color) + assert_image_equal(im.convert(mode), expected) def test_no_resource_warning_on_save(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/835 From 7a5b91dd2c165d9bde5ced73f34ba5a4238981ef Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 26 Feb 2024 20:29:47 +1100 Subject: [PATCH 107/157] Removed references to Twitter --- README.md | 3 --- RELEASING.md | 2 +- docs/index.rst | 4 ---- pyproject.toml | 1 - 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 9776c40e2..f142ef563 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,6 @@ As of 2019, Pillow development is Join the chat at https://gitter.im/python-pillow/Pillow - Follow on https://twitter.com/PythonPillow Follow on https://fosstodon.org/@pillow Date: Mon, 26 Feb 2024 07:47:13 -0600 Subject: [PATCH 108/157] use single string for parameter names instead of tuple of strings Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index ca4458f88..cdcbc7072 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -686,7 +686,7 @@ class TestImage: _make_new(im, blank_pa, ImagePalette.ImagePalette()) @pytest.mark.parametrize( - ("mode", "color"), + "mode, color", ( ("RGB", "#DDEEFF"), ("RGB", (221, 238, 255)), From e6785576b179fe730698d57d74c824c374b117ce Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 26 Feb 2024 08:47:30 -0600 Subject: [PATCH 109/157] add typing to test_p_from_rgb_rgba() Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index cdcbc7072..2a4d453e2 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -693,7 +693,7 @@ class TestImage: ("RGBA", (221, 238, 255, 255)), ), ) - def test_p_from_rgb_rgba(self, mode, color) -> None: + def test_p_from_rgb_rgba(self, mode: str, color: str | tuple[int, ...]) -> None: im = Image.new("P", (100, 100), color) expected = Image.new(mode, (100, 100), color) assert_image_equal(im.convert(mode), expected) From 73e49cad2125c7b2d01e5fe3293d8a7312f14801 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 24 Feb 2024 18:31:43 -0500 Subject: [PATCH 110/157] Move installation.rst to installation/*.rst - Add index - Move content to sections - Rearrange sections --- docs/index.rst | 2 +- docs/installation.rst | 597 --------------------- docs/installation/basic-installation.rst | 95 ++++ docs/installation/building-from-source.rst | 298 ++++++++++ docs/installation/index.rst | 11 + docs/{ => installation}/newer-versions.csv | 0 docs/installation/old-versions.rst | 6 + docs/{ => installation}/older-versions.csv | 0 docs/installation/platform-support.rst | 166 ++++++ docs/installation/python-support.rst | 17 + 10 files changed, 594 insertions(+), 598 deletions(-) delete mode 100644 docs/installation.rst create mode 100644 docs/installation/basic-installation.rst create mode 100644 docs/installation/building-from-source.rst create mode 100644 docs/installation/index.rst rename docs/{ => installation}/newer-versions.csv (100%) create mode 100644 docs/installation/old-versions.rst rename docs/{ => installation}/older-versions.csv (100%) create mode 100644 docs/installation/platform-support.rst create mode 100644 docs/installation/python-support.rst diff --git a/docs/index.rst b/docs/index.rst index bf2feea9a..0c7fb690b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -97,7 +97,7 @@ The core image library is designed for fast access to data stored in a few basic .. toctree:: :maxdepth: 2 - installation.rst + installation/index.rst handbook/index.rst reference/index.rst porting.rst diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 980bbd99d..000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,597 +0,0 @@ -Installation -============ - -.. raw:: html - - - -Python Support --------------- - -Pillow supports these Python versions. - -.. csv-table:: Newer versions - :file: newer-versions.csv - :header-rows: 1 - -.. csv-table:: Older versions - :file: older-versions.csv - :header-rows: 1 - -.. _Linux Installation: -.. _macOS Installation: -.. _Windows Installation: -.. _FreeBSD Installation: - -Basic Installation ------------------- - -.. note:: - - The following instructions will install Pillow with support for - most common image formats. See :ref:`external-libraries` for a - full list of external libraries supported. - -Install Pillow with :command:`pip`:: - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade Pillow - -Optionally, install :pypi:`defusedxml` for Pillow to read XMP data, -and :pypi:`olefile` for Pillow to read FPX and MIC images:: - - python3 -m pip install --upgrade defusedxml olefile - - -.. tab:: Linux - - We provide binaries for Linux for each of the supported Python - versions in the manylinux wheel format. These include support for all - optional libraries except libimagequant. Raqm support requires - FriBiDi to be installed separately:: - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade Pillow - - Most major Linux distributions, including Fedora, Ubuntu and ArchLinux - also include Pillow in packages that previously contained PIL e.g. - ``python-imaging``. Debian splits it into two packages, ``python3-pil`` - and ``python3-pil.imagetk``. - -.. tab:: macOS - - We provide binaries for macOS for each of the supported Python - versions in the wheel format. These include support for all optional - libraries except libimagequant. Raqm support requires - FriBiDi to be installed separately:: - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade Pillow - - While we provide binaries for both x86-64 and arm64, we do not provide universal2 - binaries. However, it is simple to combine our current binaries to create one:: - - python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow - python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow - python3 -m pip install delocate - - Then, with the names of the downloaded wheels, use Python to combine them:: - - from delocate.fuse import fuse_wheels - fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl') - -.. tab:: Windows - - We provide Pillow binaries for Windows compiled for the matrix of supported - Pythons in the wheel format. These include x86, x86-64 and arm64 versions - (with the exception of Python 3.8 on arm64). These binaries include support - for all optional libraries except libimagequant and libxcb. Raqm support - requires FriBiDi to be installed separately:: - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade Pillow - - To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_. - -.. tab:: FreeBSD - - Pillow can be installed on FreeBSD via the official Ports or Packages systems: - - **Ports**:: - - cd /usr/ports/graphics/py-pillow && make install clean - - **Packages**:: - - pkg install py38-pillow - - .. note:: - - The `Pillow FreeBSD port - `_ and packages - are tested by the ports team with all supported FreeBSD versions. - - -.. _Building on Linux: -.. _Building on macOS: -.. _Building on Windows: -.. _Building on Windows using MSYS2/MinGW: -.. _Building on FreeBSD: -.. _Building on Android: - -Building From Source --------------------- - -.. _external-libraries: - -External Libraries -^^^^^^^^^^^^^^^^^^ - -.. note:: - - You **do not need to install all supported external libraries** to - use Pillow's basic features. **Zlib** and **libjpeg** are required - by default. - -.. note:: - - There are Dockerfiles in our `Docker images repo - `_ to install the - dependencies for some operating systems. - -Many of Pillow's features require external libraries: - -* **libjpeg** provides JPEG functionality. - - * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and - libjpeg-turbo version **8**. - * Starting with Pillow 3.0.0, libjpeg is required by default. It can be - disabled with the ``-C jpeg=disable`` flag. - -* **zlib** provides access to compressed PNGs - - * Starting with Pillow 3.0.0, zlib is required by default. It can be - disabled with the ``-C zlib=disable`` flag. - -* **libtiff** provides compressed TIFF functionality - - * Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0** - -* **libfreetype** provides type related services - -* **littlecms** provides color management - - * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.16**. - -* **libwebp** provides the WebP format. - - * Pillow has been tested with version **0.1.3**, which does not read - transparent WebP files. Versions **0.3.0** and above support - transparency. - -* **openjpeg** provides JPEG 2000 functionality. - - * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, - **2.4.0** and **2.5.0**. - * Pillow does **not** support the earlier **1.5** series which ships - with Debian Jessie. - -* **libimagequant** provides improved color quantization - - * Pillow has been tested with libimagequant **2.6-4.2.2** - * Libimagequant is licensed GPLv3, which is more restrictive than - the Pillow license, therefore we will not be distributing binaries - with libimagequant support enabled. - -* **libraqm** provides complex text layout support. - - * libraqm provides bidirectional text support (using FriBiDi), - shaping (using HarfBuzz), and proper script itemization. As a - result, Raqm can support most writing systems covered by Unicode. - * libraqm depends on the following libraries: FreeType, HarfBuzz, - FriBiDi, make sure that you install them before installing libraqm - if not available as package in your system. - * Setting text direction or font features is not supported without libraqm. - * Pillow wheels since version 8.2.0 include a modified version of libraqm that - loads libfribidi at runtime if it is installed. - On Windows this requires compiling FriBiDi and installing ``fribidi.dll`` - into a directory listed in the `Dynamic-link library search order (Microsoft Learn) - `_ - (``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected). - See `Build Options`_ to see how to build this version. - * Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime. - -* **libxcb** provides X11 screengrab support. - -.. tab:: Linux - - If you didn't build Python from source, make sure you have Python's - development libraries installed. - - In Debian or Ubuntu:: - - sudo apt-get install python3-dev python3-setuptools - - In Fedora, the command is:: - - sudo dnf install python3-devel redhat-rpm-config - - In Alpine, the command is:: - - sudo apk add python3-dev py3-setuptools - - .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. - - Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with:: - - sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ - libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ - libharfbuzz-dev libfribidi-dev libxcb1-dev - - To install libraqm, ``sudo apt-get install meson`` and then see - ``depends/install_raqm.sh``. - - Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: - - sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ - freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \ - harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel - - Note that the package manager may be yum or DNF, depending on the - exact distribution. - - Prerequisites are installed for **Alpine** with:: - - sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \ - libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \ - libxcb-dev libpng-dev - - See also the ``Dockerfile``\s in the Test Infrastructure repo - (https://github.com/python-pillow/docker-images) for a known working - install process for other tested distros. - -.. tab:: macOS - - The Xcode command line tools are required to compile portions of - Pillow. The tools are installed by running ``xcode-select --install`` - from the command line. The command line tools are required even if you - have the full Xcode package installed. It may be necessary to run - ``sudo xcodebuild -license`` to accept the license prior to using the - tools. - - The easiest way to install external libraries is via `Homebrew - `_. After you install Homebrew, run:: - - brew install libjpeg libtiff little-cms2 openjpeg webp - - To install libraqm on macOS use Homebrew to install its dependencies:: - - brew install freetype harfbuzz fribidi - - Then see ``depends/install_raqm_cmake.sh`` to install libraqm. - -.. tab:: Windows - - We recommend you use prebuilt wheels from PyPI. - If you wish to compile Pillow manually, you can use the build scripts - in the ``winbuild`` directory used for CI testing and development. - These scripts require Visual Studio 2017 or newer and NASM. - - The scripts also install Pillow from the local copy of the source code, so the - `Installing`_ instructions will not be necessary afterwards. - -.. tab:: Windows using MSYS2/MinGW - - To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or - **MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly. - - The following instructions target the 64-bit build, for 32-bit - replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``. - - Make sure you have Python and GCC installed:: - - pacman -S \ - mingw-w64-x86_64-gcc \ - mingw-w64-x86_64-python3 \ - mingw-w64-x86_64-python3-pip \ - mingw-w64-x86_64-python3-setuptools - - Prerequisites are installed on **MSYS2 MinGW 64-bit** with:: - - pacman -S \ - mingw-w64-x86_64-libjpeg-turbo \ - mingw-w64-x86_64-zlib \ - mingw-w64-x86_64-libtiff \ - mingw-w64-x86_64-freetype \ - mingw-w64-x86_64-lcms2 \ - mingw-w64-x86_64-libwebp \ - mingw-w64-x86_64-openjpeg2 \ - mingw-w64-x86_64-libimagequant \ - mingw-w64-x86_64-libraqm - - https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with - MSYS2. To workaround this, before installing Pillow you must run:: - - export SETUPTOOLS_USE_DISTUTILS=stdlib - -.. tab:: FreeBSD - - .. Note:: Only FreeBSD 10 and 11 tested - - Make sure you have Python's development libraries installed:: - - sudo pkg install python3 - - Prerequisites are installed on **FreeBSD 10 or 11** with:: - - sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb - - Then see ``depends/install_raqm_cmake.sh`` to install libraqm. - -.. tab:: Android - - Basic Android support has been added for compilation within the Termux - environment. The dependencies can be installed by:: - - pkg install -y python ndk-sysroot clang make \ - libjpeg-turbo - - This has been tested within the Termux app on ChromeOS, on x86. - -Installing -^^^^^^^^^^ - -Once you have installed the prerequisites, to install Pillow from the source -code on PyPI, run:: - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade Pillow --no-binary :all: - -If the prerequisites are installed in the standard library locations -for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no -additional configuration should be required. If they are installed in -a non-standard location, you may need to configure setuptools to use -those locations by editing :file:`setup.py` or -:file:`pyproject.toml`, or by adding environment variables on the command -line:: - - CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all: - -If Pillow has been previously built without the required -prerequisites, it may be necessary to manually clear the pip cache or -build without cache using the ``--no-cache-dir`` option to force a -build with newly installed external libraries. - -If you would like to install from a local copy of the source code instead, you -can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow`` -or download and extract the `compressed archive from PyPI`_. - -After navigating to the Pillow directory, run:: - - python3 -m pip install --upgrade pip - python3 -m pip install . - -.. _compressed archive from PyPI: https://pypi.org/project/pillow/#files - -Build Options -""""""""""""" - -* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use - multiprocessing to build the extension. Setting ``MAX_CONCURRENCY`` - sets the number of CPUs to use, or can disable parallel building by - using a setting of 1. By default, it uses 4 CPUs, or if 4 are not - available, as many as are present. - -* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``, - ``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``, - ``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``, - ``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``. - Disable building the corresponding feature even if the development - libraries are present on the building machine. - -* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``, - ``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``, - ``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``, - ``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``. - Require that the corresponding feature is built. The build will raise - an exception if the libraries are not found. Webpmux (WebP metadata) - relies on WebP support. Tcl and Tk also must be used together. - -* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``. - These flags are used to compile a modified version of libraqm and - a shim that dynamically loads libfribidi at runtime. These are - used to compile the standard Pillow wheels. Compiling libraqm requires - a C99-compliant compiler. - -* Build flag: ``-C platform-guessing=disable``. Skips all of the - platform dependent guessing of include and library directories for - automated build systems that configure the proper paths in the - environment variables (e.g. Buildroot). - -* Build flag: ``-C debug=true``. Adds a debugging flag to the include and - library search process to dump all paths searched for and found to - stdout. - - -Sample usage:: - - python3 -m pip install --upgrade Pillow -C [feature]=enable - -Platform Support ----------------- - -Current platform support for Pillow. Binary distributions are -contributed for each release on a volunteer basis, but the source -should compile and run everywhere platform support is listed. In -general, we aim to support all current versions of Linux, macOS, and -Windows. - -Continuous Integration Targets -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -These platforms are built and tested for every change. - -+----------------------------------+----------------------------+---------------------+ -| Operating system | Tested Python versions | Tested architecture | -+==================================+============================+=====================+ -| Alpine | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Amazon Linux 2 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Amazon Linux 2023 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Arch | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| CentOS 7 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| CentOS Stream 8 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| CentOS Stream 9 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Debian 11 Bullseye | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Debian 12 Bookworm | 3.11 | x86, x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Fedora 38 | 3.11 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Fedora 39 | 3.12 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Gentoo | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | -+----------------------------------+----------------------------+---------------------+ -| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | -| +----------------------------+---------------------+ -| | 3.10 | arm64v8, ppc64le, | -| | | s390x | -+----------------------------------+----------------------------+---------------------+ -| Windows Server 2016 | 3.8 | x86-64 | -+----------------------------------+----------------------------+---------------------+ -| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | -| +----------------------------+---------------------+ -| | 3.12 | x86 | -| +----------------------------+---------------------+ -| | 3.9 (MinGW) | x86-64 | -| +----------------------------+---------------------+ -| | 3.8, 3.9 (Cygwin) | x86-64 | -+----------------------------------+----------------------------+---------------------+ - - -Other Platforms -^^^^^^^^^^^^^^^ - -These platforms have been reported to work at the versions mentioned. - -.. note:: - - Contributors please test Pillow on your platform then update this - document and send a pull request. - -+----------------------------------+----------------------------+------------------+--------------+ -| Operating system | | Tested Python | | Latest tested | | Tested | -| | | versions | | Pillow version | | processors | -+==================================+============================+==================+==============+ -| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm | -| +----------------------------+------------------+ | -| | 3.7 | 9.5.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | -| +----------------------------+------------------+--------------+ -| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 | -| +----------------------------+------------------+ | -| | 3.6 | 8.4.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | -| +----------------------------+------------------+ | -| | 3.5 | 7.2.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 | -| +----------------------------+------------------+ | -| | 2.7 | 6.0.0 | | -| +----------------------------+------------------+ | -| | 3.4 | 5.4.1 | | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | -| +----------------------------+------------------+ | -| | 3.3 | 4.1.0 | | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Redhat Linux 6 | 2.6 | |x86 | -+----------------------------------+----------------------------+------------------+--------------+ -| CentOS 6.3 | 2.7, 3.3 | |x86 | -+----------------------------------+----------------------------+------------------+--------------+ -| CentOS 8 | 3.9 | 9.0.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | -| | | PyPy5.3.1, PyPy3 v2.4.0 | | | -| +----------------------------+------------------+--------------+ -| | 2.7 | 4.3.0 |x86-64 | -| +----------------------------+------------------+--------------+ -| | 2.7, 3.2 | 3.4.1 |ppc | -+----------------------------------+----------------------------+------------------+--------------+ -| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | -+----------------------------------+----------------------------+------------------+--------------+ -| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm | -| +----------------------------+------------------+ | -| | 2.7 | 6.2.2 | | -+----------------------------------+----------------------------+------------------+--------------+ -| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 10 | 3.7 | 7.1.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ -| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | -+----------------------------------+----------------------------+------------------+--------------+ - -Old Versions ------------- - -You can download old distributions from the `release history at PyPI -`_ and by direct URL access -eg. https://pypi.org/project/pillow/1.0/. diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst new file mode 100644 index 000000000..ff05e7f58 --- /dev/null +++ b/docs/installation/basic-installation.rst @@ -0,0 +1,95 @@ +Basic Installation +------------------ + +.. note:: + + The following instructions will install Pillow with support for + most common image formats. See :ref:`external-libraries` for a + full list of external libraries supported. + +Install Pillow with :command:`pip`:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + +Optionally, install :pypi:`defusedxml` for Pillow to read XMP data, +and :pypi:`olefile` for Pillow to read FPX and MIC images:: + + python3 -m pip install --upgrade defusedxml olefile + + +.. tab:: Linux + + We provide binaries for Linux for each of the supported Python + versions in the manylinux wheel format. These include support for all + optional libraries except libimagequant. Raqm support requires + FriBiDi to be installed separately:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + + Most major Linux distributions, including Fedora, Ubuntu and ArchLinux + also include Pillow in packages that previously contained PIL e.g. + ``python-imaging``. Debian splits it into two packages, ``python3-pil`` + and ``python3-pil.imagetk``. + +.. tab:: macOS + + We provide binaries for macOS for each of the supported Python + versions in the wheel format. These include support for all optional + libraries except libimagequant. Raqm support requires + FriBiDi to be installed separately:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + + While we provide binaries for both x86-64 and arm64, we do not provide universal2 + binaries. However, it is simple to combine our current binaries to create one:: + + python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow + python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow + python3 -m pip install delocate + + Then, with the names of the downloaded wheels, use Python to combine them:: + + from delocate.fuse import fuse_wheels + fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl') + +.. tab:: Windows + + We provide Pillow binaries for Windows compiled for the matrix of supported + Pythons in the wheel format. These include x86, x86-64 and arm64 versions + (with the exception of Python 3.8 on arm64). These binaries include support + for all optional libraries except libimagequant and libxcb. Raqm support + requires FriBiDi to be installed separately:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + + To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_. + +.. tab:: FreeBSD + + Pillow can be installed on FreeBSD via the official Ports or Packages systems: + + **Ports**:: + + cd /usr/ports/graphics/py-pillow && make install clean + + **Packages**:: + + pkg install py38-pillow + + .. note:: + + The `Pillow FreeBSD port + `_ and packages + are tested by the ports team with all supported FreeBSD versions. + + +.. _Building on Linux: +.. _Building on macOS: +.. _Building on Windows: +.. _Building on Windows using MSYS2/MinGW: +.. _Building on FreeBSD: +.. _Building on Android: diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst new file mode 100644 index 000000000..051d51c22 --- /dev/null +++ b/docs/installation/building-from-source.rst @@ -0,0 +1,298 @@ +Building From Source +-------------------- + +.. _external-libraries: + +External Libraries +^^^^^^^^^^^^^^^^^^ + +.. note:: + + You **do not need to install all supported external libraries** to + use Pillow's basic features. **Zlib** and **libjpeg** are required + by default. + +.. note:: + + There are Dockerfiles in our `Docker images repo + `_ to install the + dependencies for some operating systems. + +Many of Pillow's features require external libraries: + +* **libjpeg** provides JPEG functionality. + + * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and + libjpeg-turbo version **8**. + * Starting with Pillow 3.0.0, libjpeg is required by default. It can be + disabled with the ``-C jpeg=disable`` flag. + +* **zlib** provides access to compressed PNGs + + * Starting with Pillow 3.0.0, zlib is required by default. It can be + disabled with the ``-C zlib=disable`` flag. + +* **libtiff** provides compressed TIFF functionality + + * Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0** + +* **libfreetype** provides type related services + +* **littlecms** provides color management + + * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and + above uses liblcms2. Tested with **1.19** and **2.7-2.16**. + +* **libwebp** provides the WebP format. + + * Pillow has been tested with version **0.1.3**, which does not read + transparent WebP files. Versions **0.3.0** and above support + transparency. + +* **openjpeg** provides JPEG 2000 functionality. + + * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, + **2.4.0** and **2.5.0**. + * Pillow does **not** support the earlier **1.5** series which ships + with Debian Jessie. + +* **libimagequant** provides improved color quantization + + * Pillow has been tested with libimagequant **2.6-4.2.2** + * Libimagequant is licensed GPLv3, which is more restrictive than + the Pillow license, therefore we will not be distributing binaries + with libimagequant support enabled. + +* **libraqm** provides complex text layout support. + + * libraqm provides bidirectional text support (using FriBiDi), + shaping (using HarfBuzz), and proper script itemization. As a + result, Raqm can support most writing systems covered by Unicode. + * libraqm depends on the following libraries: FreeType, HarfBuzz, + FriBiDi, make sure that you install them before installing libraqm + if not available as package in your system. + * Setting text direction or font features is not supported without libraqm. + * Pillow wheels since version 8.2.0 include a modified version of libraqm that + loads libfribidi at runtime if it is installed. + On Windows this requires compiling FriBiDi and installing ``fribidi.dll`` + into a directory listed in the `Dynamic-link library search order (Microsoft Learn) + `_ + (``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected). + See `Build Options`_ to see how to build this version. + * Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime. + +* **libxcb** provides X11 screengrab support. + +.. tab:: Linux + + If you didn't build Python from source, make sure you have Python's + development libraries installed. + + In Debian or Ubuntu:: + + sudo apt-get install python3-dev python3-setuptools + + In Fedora, the command is:: + + sudo dnf install python3-devel redhat-rpm-config + + In Alpine, the command is:: + + sudo apk add python3-dev py3-setuptools + + .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. + + Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with:: + + sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ + libharfbuzz-dev libfribidi-dev libxcb1-dev + + To install libraqm, ``sudo apt-get install meson`` and then see + ``depends/install_raqm.sh``. + + Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: + + sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ + freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \ + harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel + + Note that the package manager may be yum or DNF, depending on the + exact distribution. + + Prerequisites are installed for **Alpine** with:: + + sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \ + libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \ + libxcb-dev libpng-dev + + See also the ``Dockerfile``\s in the Test Infrastructure repo + (https://github.com/python-pillow/docker-images) for a known working + install process for other tested distros. + +.. tab:: macOS + + The Xcode command line tools are required to compile portions of + Pillow. The tools are installed by running ``xcode-select --install`` + from the command line. The command line tools are required even if you + have the full Xcode package installed. It may be necessary to run + ``sudo xcodebuild -license`` to accept the license prior to using the + tools. + + The easiest way to install external libraries is via `Homebrew + `_. After you install Homebrew, run:: + + brew install libjpeg libtiff little-cms2 openjpeg webp + + To install libraqm on macOS use Homebrew to install its dependencies:: + + brew install freetype harfbuzz fribidi + + Then see ``depends/install_raqm_cmake.sh`` to install libraqm. + +.. tab:: Windows + + We recommend you use prebuilt wheels from PyPI. + If you wish to compile Pillow manually, you can use the build scripts + in the ``winbuild`` directory used for CI testing and development. + These scripts require Visual Studio 2017 or newer and NASM. + + The scripts also install Pillow from the local copy of the source code, so the + `Installing`_ instructions will not be necessary afterwards. + +.. tab:: Windows using MSYS2/MinGW + + To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or + **MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly. + + The following instructions target the 64-bit build, for 32-bit + replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``. + + Make sure you have Python and GCC installed:: + + pacman -S \ + mingw-w64-x86_64-gcc \ + mingw-w64-x86_64-python3 \ + mingw-w64-x86_64-python3-pip \ + mingw-w64-x86_64-python3-setuptools + + Prerequisites are installed on **MSYS2 MinGW 64-bit** with:: + + pacman -S \ + mingw-w64-x86_64-libjpeg-turbo \ + mingw-w64-x86_64-zlib \ + mingw-w64-x86_64-libtiff \ + mingw-w64-x86_64-freetype \ + mingw-w64-x86_64-lcms2 \ + mingw-w64-x86_64-libwebp \ + mingw-w64-x86_64-openjpeg2 \ + mingw-w64-x86_64-libimagequant \ + mingw-w64-x86_64-libraqm + + https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with + MSYS2. To workaround this, before installing Pillow you must run:: + + export SETUPTOOLS_USE_DISTUTILS=stdlib + +.. tab:: FreeBSD + + .. Note:: Only FreeBSD 10 and 11 tested + + Make sure you have Python's development libraries installed:: + + sudo pkg install python3 + + Prerequisites are installed on **FreeBSD 10 or 11** with:: + + sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb + + Then see ``depends/install_raqm_cmake.sh`` to install libraqm. + +.. tab:: Android + + Basic Android support has been added for compilation within the Termux + environment. The dependencies can be installed by:: + + pkg install -y python ndk-sysroot clang make \ + libjpeg-turbo + + This has been tested within the Termux app on ChromeOS, on x86. + +Installing +^^^^^^^^^^ + +Once you have installed the prerequisites, to install Pillow from the source +code on PyPI, run:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow --no-binary :all: + +If the prerequisites are installed in the standard library locations +for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no +additional configuration should be required. If they are installed in +a non-standard location, you may need to configure setuptools to use +those locations by editing :file:`setup.py` or +:file:`pyproject.toml`, or by adding environment variables on the command +line:: + + CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all: + +If Pillow has been previously built without the required +prerequisites, it may be necessary to manually clear the pip cache or +build without cache using the ``--no-cache-dir`` option to force a +build with newly installed external libraries. + +If you would like to install from a local copy of the source code instead, you +can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow`` +or download and extract the `compressed archive from PyPI`_. + +After navigating to the Pillow directory, run:: + + python3 -m pip install --upgrade pip + python3 -m pip install . + +.. _compressed archive from PyPI: https://pypi.org/project/pillow/#files + +Build Options +""""""""""""" + +* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use + multiprocessing to build the extension. Setting ``MAX_CONCURRENCY`` + sets the number of CPUs to use, or can disable parallel building by + using a setting of 1. By default, it uses 4 CPUs, or if 4 are not + available, as many as are present. + +* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``, + ``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``, + ``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``, + ``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``. + Disable building the corresponding feature even if the development + libraries are present on the building machine. + +* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``, + ``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``, + ``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``, + ``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``. + Require that the corresponding feature is built. The build will raise + an exception if the libraries are not found. Webpmux (WebP metadata) + relies on WebP support. Tcl and Tk also must be used together. + +* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``. + These flags are used to compile a modified version of libraqm and + a shim that dynamically loads libfribidi at runtime. These are + used to compile the standard Pillow wheels. Compiling libraqm requires + a C99-compliant compiler. + +* Build flag: ``-C platform-guessing=disable``. Skips all of the + platform dependent guessing of include and library directories for + automated build systems that configure the proper paths in the + environment variables (e.g. Buildroot). + +* Build flag: ``-C debug=true``. Adds a debugging flag to the include and + library search process to dump all paths searched for and found to + stdout. + + +Sample usage:: + + python3 -m pip install --upgrade Pillow -C [feature]=enable diff --git a/docs/installation/index.rst b/docs/installation/index.rst new file mode 100644 index 000000000..9e6e5eeec --- /dev/null +++ b/docs/installation/index.rst @@ -0,0 +1,11 @@ +Installation +============ + +.. toctree:: + :maxdepth: 2 + + basic-installation + python-support + platform-support + building-from-source + old-versions diff --git a/docs/newer-versions.csv b/docs/installation/newer-versions.csv similarity index 100% rename from docs/newer-versions.csv rename to docs/installation/newer-versions.csv diff --git a/docs/installation/old-versions.rst b/docs/installation/old-versions.rst new file mode 100644 index 000000000..48f3f727d --- /dev/null +++ b/docs/installation/old-versions.rst @@ -0,0 +1,6 @@ +Old Versions +------------ + +You can download old distributions from the `release history at PyPI +`_ and by direct URL access +eg. https://pypi.org/project/pillow/1.0/. diff --git a/docs/older-versions.csv b/docs/installation/older-versions.csv similarity index 100% rename from docs/older-versions.csv rename to docs/installation/older-versions.csv diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst new file mode 100644 index 000000000..da98d4b9e --- /dev/null +++ b/docs/installation/platform-support.rst @@ -0,0 +1,166 @@ +Platform Support +---------------- + +Current platform support for Pillow. Binary distributions are +contributed for each release on a volunteer basis, but the source +should compile and run everywhere platform support is listed. In +general, we aim to support all current versions of Linux, macOS, and +Windows. + +Continuous Integration Targets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These platforms are built and tested for every change. + ++----------------------------------+----------------------------+---------------------+ +| Operating system | Tested Python versions | Tested architecture | ++==================================+============================+=====================+ +| Alpine | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Amazon Linux 2 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Amazon Linux 2023 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Arch | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS 7 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS Stream 8 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS Stream 9 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Debian 11 Bullseye | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Debian 12 Bookworm | 3.11 | x86, x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Fedora 38 | 3.11 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Fedora 39 | 3.12 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Gentoo | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | +| | 3.12, PyPy3 | | ++----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | +| | 3.12, PyPy3 | | +| +----------------------------+---------------------+ +| | 3.10 | arm64v8, ppc64le, | +| | | s390x | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2016 | 3.8 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 | +| | 3.12, PyPy3 | | +| +----------------------------+---------------------+ +| | 3.12 | x86 | +| +----------------------------+---------------------+ +| | 3.9 (MinGW) | x86-64 | +| +----------------------------+---------------------+ +| | 3.8, 3.9 (Cygwin) | x86-64 | ++----------------------------------+----------------------------+---------------------+ + + +Other Platforms +^^^^^^^^^^^^^^^ + +These platforms have been reported to work at the versions mentioned. + +.. note:: + + Contributors please test Pillow on your platform then update this + document and send a pull request. + ++----------------------------------+----------------------------+------------------+--------------+ +| Operating system | | Tested Python | | Latest tested | | Tested | +| | | versions | | Pillow version | | processors | ++==================================+============================+==================+==============+ +| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm | +| +----------------------------+------------------+ | +| | 3.7 | 9.5.0 | | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | +| +----------------------------+------------------+--------------+ +| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 | +| +----------------------------+------------------+ | +| | 3.6 | 8.4.0 | | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | +| +----------------------------+------------------+ | +| | 3.5 | 7.2.0 | | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 | +| +----------------------------+------------------+ | +| | 2.7 | 6.0.0 | | +| +----------------------------+------------------+ | +| | 3.4 | 5.4.1 | | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | +| +----------------------------+------------------+ | +| | 3.3 | 4.1.0 | | ++----------------------------------+----------------------------+------------------+--------------+ +| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Redhat Linux 6 | 2.6 | |x86 | ++----------------------------------+----------------------------+------------------+--------------+ +| CentOS 6.3 | 2.7, 3.3 | |x86 | ++----------------------------------+----------------------------+------------------+--------------+ +| CentOS 8 | 3.9 | 9.0.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | +| | | PyPy5.3.1, PyPy3 v2.4.0 | | | +| +----------------------------+------------------+--------------+ +| | 2.7 | 4.3.0 |x86-64 | +| +----------------------------+------------------+--------------+ +| | 2.7, 3.2 | 3.4.1 |ppc | ++----------------------------------+----------------------------+------------------+--------------+ +| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ +| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | ++----------------------------------+----------------------------+------------------+--------------+ +| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm | +| +----------------------------+------------------+ | +| | 2.7 | 6.2.2 | | ++----------------------------------+----------------------------+------------------+--------------+ +| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 10 | 3.7 | 7.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ diff --git a/docs/installation/python-support.rst b/docs/installation/python-support.rst new file mode 100644 index 000000000..ab075f254 --- /dev/null +++ b/docs/installation/python-support.rst @@ -0,0 +1,17 @@ +Python Support +-------------- + +Pillow supports these Python versions. + +.. csv-table:: Newer versions + :file: newer-versions.csv + :header-rows: 1 + +.. csv-table:: Older versions + :file: older-versions.csv + :header-rows: 1 + +.. _Linux Installation: +.. _macOS Installation: +.. _Windows Installation: +.. _FreeBSD Installation: From ecba08f8c40415159ed0ed02d5b3cb7f154fddfe Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 25 Feb 2024 06:36:03 -0500 Subject: [PATCH 111/157] Add sphinx-reredirects to handle redirects - For example, installation.html now redirects to installation/index.html --- docs/conf.py | 6 ++++++ pyproject.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 97289c91d..0733e2938 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,7 @@ extensions = [ "sphinx_inline_tabs", "sphinx_removed_in", "sphinxext.opengraph", + "sphinx_reredirects", ] intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} @@ -350,3 +351,8 @@ ogp_image = ( "pillow-logo-dark-text-1280x640.png" ) ogp_image_alt = "Pillow" + +# sphinx-reredirects +redirects = { + "installation.html": "installation/index.html" +} diff --git a/pyproject.toml b/pyproject.toml index 58c2464bc..2107800d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ docs = [ "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph", + "sphinx-reredirects", ] fpx = [ "olefile", From fde1ccb555ef275ad087ea70ebc0af31f7b20f8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:37:15 +0000 Subject: [PATCH 112/157] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/conf.py | 4 +--- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0733e2938..874a077a3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -353,6 +353,4 @@ ogp_image = ( ogp_image_alt = "Pillow" # sphinx-reredirects -redirects = { - "installation.html": "installation/index.html" -} +redirects = {"installation.html": "installation/index.html"} diff --git a/pyproject.toml b/pyproject.toml index 2107800d6..c3b469f10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,8 @@ docs = [ "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", - "sphinxext-opengraph", "sphinx-reredirects", + "sphinxext-opengraph", ] fpx = [ "olefile", From a72a5f9ae2239c5478a64761d99aaff8a3499030 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 27 Feb 2024 07:12:21 -0500 Subject: [PATCH 113/157] Leave a note --- docs/conf.py | 2 +- docs/installation/basic-installation.rst | 2 ++ docs/installation/building-from-source.rst | 2 ++ docs/installation/old-versions.rst | 2 ++ docs/installation/platform-support.rst | 2 ++ docs/installation/python-support.rst | 2 ++ 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 874a077a3..b7ad57084 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -353,4 +353,4 @@ ogp_image = ( ogp_image_alt = "Pillow" # sphinx-reredirects -redirects = {"installation.html": "installation/index.html"} +# redirects = {"installation.html": "installation/index.html"} diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index ff05e7f58..486e6863b 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -1,3 +1,5 @@ +.. _basic-installation: + Basic Installation ------------------ diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 051d51c22..1f9715547 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -1,3 +1,5 @@ +.. _building-from-source: + Building From Source -------------------- diff --git a/docs/installation/old-versions.rst b/docs/installation/old-versions.rst index 48f3f727d..445a70d4e 100644 --- a/docs/installation/old-versions.rst +++ b/docs/installation/old-versions.rst @@ -1,3 +1,5 @@ +.. _old-versions: + Old Versions ------------ diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index da98d4b9e..602941c3c 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -1,3 +1,5 @@ +.. _platform-support: + Platform Support ---------------- diff --git a/docs/installation/python-support.rst b/docs/installation/python-support.rst index ab075f254..8d7db8d3a 100644 --- a/docs/installation/python-support.rst +++ b/docs/installation/python-support.rst @@ -1,3 +1,5 @@ +.. _python-support: + Python Support -------------- From fd1cefe2b69193d0cf5c2a52b57820decfd458dd Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 27 Feb 2024 07:21:38 -0500 Subject: [PATCH 114/157] Leave a note - Re-add installation.rst --- docs/installation.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/installation.rst diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 000000000..57659ba48 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,27 @@ +Installation +============ + +Basic Installation +------------------ + +.. Note:: This section has moved to :ref:`basic-installation`. Please update references accordingly. + +Python Support +-------------- + +.. Note:: This section has moved to :ref:`python-support`. Please update references accordingly. + +Platform Support +---------------- + +.. Note:: This section has moved to :ref:`platform-support`. Please update references accordingly. + +Building From Source +-------------------- + +.. Note:: This section has moved to :ref:`building-from-source`. Please update references accordingly. + +Old Versions +------------ + +.. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly. From c00d0191861fd43b39e75742b68ae8df7041b67e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 28 Feb 2024 07:05:47 +1100 Subject: [PATCH 115/157] Updated openjpeg to 2.5.2 --- .github/workflows/wheels-dependencies.sh | 7 +++++-- depends/install_openjpeg.sh | 2 +- docs/installation.rst | 2 +- winbuild/build_prepare.py | 15 +++++---------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 26bf2f6d6..cc8d7e085 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -19,7 +19,7 @@ FREETYPE_VERSION=2.13.2 HARFBUZZ_VERSION=8.3.0 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 -OPENJPEG_VERSION=2.5.0 +OPENJPEG_VERSION=2.5.2 XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 @@ -40,7 +40,7 @@ BROTLI_VERSION=1.1.0 if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then function build_openjpeg { - local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz) + local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-${OPENJPEG_VERSION}.tar.gz) (cd $out_dir \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) @@ -93,6 +93,9 @@ function build { done fi build_openjpeg + if [ -f /usr/local/lib64/libopenjp2.so ]; then + cp /usr/local/lib64/libopenjp2.so /usr/local/lib + fi ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 4f4b81a62..8c2967bc2 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.5.0 +archive=openjpeg-2.5.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index 980bbd99d..de812e3c4 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -177,7 +177,7 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, - **2.4.0** and **2.5.0**. + **2.4.0**, **2.5.0** and **2.5.2**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index df33ea493..2ee9872e6 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -308,21 +308,16 @@ DEPS = { "libs": [r"Lib\MS\*.lib"], }, "openjpeg": { - "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.0.tar.gz", - "filename": "openjpeg-2.5.0.tar.gz", - "dir": "openjpeg-2.5.0", + "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.2.tar.gz", + "filename": "openjpeg-2.5.2.tar.gz", + "dir": "openjpeg-2.5.2", "license": "LICENSE", - "patch": { - r"src\lib\openjp2\ht_dec.c": { - "#ifdef OPJ_COMPILER_MSVC\n return (OPJ_UINT32)__popcnt(val);": "#if defined(OPJ_COMPILER_MSVC) && (defined(_M_IX86) || defined(_M_AMD64))\n return (OPJ_UINT32)__popcnt(val);", # noqa: E501 - } - }, "build": [ *cmds_cmake( "openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF" ), - cmd_mkdir(r"{inc_dir}\openjpeg-2.5.0"), - cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.0"), + cmd_mkdir(r"{inc_dir}\openjpeg-2.5.2"), + cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.2"), ], "libs": [r"bin\*.lib"], }, From dcbe402f77b9a6cbdc09645912404a716e597df9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 29 Feb 2024 20:16:41 +1100 Subject: [PATCH 116/157] Changed SupportsGetMesh protocol to be public --- docs/reference/ImageOps.rst | 2 ++ src/PIL/ImageOps.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index 475253078..051fdcfc9 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -14,6 +14,8 @@ only work on L and RGB images. .. autofunction:: colorize .. autofunction:: crop .. autofunction:: scale +.. autoclass:: SupportsGetMesh + :show-inheritance: .. autofunction:: deform .. autofunction:: equalize .. autofunction:: expand diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 6218c723f..33db8fa50 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -411,7 +411,15 @@ def scale( return image.resize(size, resample) -class _SupportsGetMesh(Protocol): +class SupportsGetMesh(Protocol): + """ + An object that supports the ``getmesh`` method, taking an image as an + argument, and returning a list of tuples. Each tuple contains two tuples, + the source box as a tuple of 4 integers, and a tuple of 8 integers for the + final quadrilateral, in order of top left, bottom left, bottom right, top + right. + """ + def getmesh( self, image: Image.Image ) -> list[ @@ -421,7 +429,7 @@ class _SupportsGetMesh(Protocol): def deform( image: Image.Image, - deformer: _SupportsGetMesh, + deformer: SupportsGetMesh, resample: int = Image.Resampling.BILINEAR, ) -> Image.Image: """ From 0cc1cfb0cc5971d22c4f6294e2b510d74c46afda Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:31:24 +0200 Subject: [PATCH 117/157] Move global into main() to avoid shadowing --- winbuild/build_prepare.py | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2ee9872e6..ede161c44 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -456,11 +456,14 @@ def download_dep(url: str, file: str) -> None: raise RuntimeError(ex) -def extract_dep(url: str, filename: str) -> None: +def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None: import tarfile import zipfile - file = os.path.join(args.depends_dir, filename) + depends_dir = prefs["depends_dir"] + sources_dir = prefs["src_dir"] + + file = os.path.join(depends_dir, filename) if not os.path.exists(file): # First try our mirror mirror_url = ( @@ -499,13 +502,15 @@ def extract_dep(url: str, filename: str) -> None: raise RuntimeError(msg) -def write_script(name: str, lines: list[str]) -> None: - name = os.path.join(args.build_dir, name) +def write_script( + name: str, lines: list[str], prefs: dict[str, str], verbose: bool +) -> None: + name = os.path.join(prefs["build_dir"], name) lines = [line.format(**prefs) for line in lines] print("Writing " + name) with open(name, "w", newline="") as f: f.write(os.linesep.join(lines)) - if args.verbose: + if verbose: for line in lines: print(" " + line) @@ -521,7 +526,7 @@ def get_footer(dep: dict) -> list[str]: return lines -def build_env() -> None: +def build_env(prefs: dict[str, str], verbose: bool) -> None: lines = [ "if defined DISTUTILS_USE_SDK goto end", cmd_set("INCLUDE", "{inc_dir}"), @@ -534,15 +539,17 @@ def build_env() -> None: ":end", "@echo on", ] - write_script("build_env.cmd", lines) + write_script("build_env.cmd", lines, prefs, verbose) -def build_dep(name: str) -> str: +def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: dep = DEPS[name] dir = dep["dir"] file = f"build_dep_{name}.cmd" + license_dir = prefs["license_dir"] + sources_dir = prefs["src_dir"] - extract_dep(dep["url"], dep["filename"]) + extract_dep(dep["url"], dep["filename"], prefs) licenses = dep["license"] if isinstance(licenses, str): @@ -583,11 +590,11 @@ def build_dep(name: str) -> str: *get_footer(dep), ] - write_script(file, lines) + write_script(file, lines, prefs, verbose) return file -def build_dep_all() -> None: +def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> None: lines = [r'call "{build_dir}\build_env.cmd"'] gha_groups = "GITHUB_ACTIONS" in os.environ for dep_name in DEPS: @@ -595,7 +602,7 @@ def build_dep_all() -> None: if dep_name in disabled: print(f"Skipping disabled dependency {dep_name}") continue - script = build_dep(dep_name) + script = build_dep(dep_name, prefs, verbose) if gha_groups: lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') @@ -604,10 +611,10 @@ def build_dep_all() -> None: lines.append("@echo ::endgroup::") print() lines.append("@echo All Pillow dependencies built successfully!") - write_script("build_dep_all.cmd", lines) + write_script("build_dep_all.cmd", lines, prefs, verbose) -if __name__ == "__main__": +def main() -> None: winbuild_dir = os.path.dirname(os.path.realpath(__file__)) pillow_dir = os.path.realpath(os.path.join(winbuild_dir, "..")) @@ -718,12 +725,13 @@ if __name__ == "__main__": "pillow_dir": pillow_dir, "winbuild_dir": winbuild_dir, # Build paths + "bin_dir": bin_dir, "build_dir": args.build_dir, + "depends_dir": args.depends_dir, "inc_dir": inc_dir, "lib_dir": lib_dir, - "bin_dir": bin_dir, - "src_dir": sources_dir, "license_dir": license_dir, + "src_dir": sources_dir, # Compilers / Tools **msvs, "cmake": "cmake.exe", # TODO find CMAKE automatically @@ -736,6 +744,10 @@ if __name__ == "__main__": print() - write_script(".gitignore", ["*"]) - build_env() - build_dep_all() + write_script(".gitignore", ["*"], prefs, args.verbose) + build_env(prefs, args.verbose) + build_dep_all(disabled, prefs, args.verbose) + + +if __name__ == "__main__": + main() From fb3cb60c4c2c9919aef0f1d9ee95dedf2c2533ff Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:09:51 +0200 Subject: [PATCH 118/157] Refactor version numbers into constants --- winbuild/build_prepare.py | 107 +++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ede161c44..fbd0276ed 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -109,13 +109,30 @@ ARCHITECTURES = { "ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"}, } +BROTLI_VERSION = "1.1.0" +FREETYPE_VERSION = "2.13.2" +FRIBIDI_VERSION = "1.0.13" +HARFBUZZ_VERSION = "8.3.0" +JPEGTURBO_VERSION = "3.0.1" +LCMS2_VERSION = "2.16" +LIBPNG_VERSION = "1.6.39" +LIBPNG_DOTLESS = LIBPNG_VERSION.replace(".", "") +LIBPNG_XY = "".join(LIBPNG_VERSION.split(".")[:2]) +LIBWEBP_VERSION = "1.3.2" +LIBXCB_VERSION = "1.16" +OPENJPEG_VERSION = "2.5.2" +TIFF_VERSION = "4.6.0" +XZ_VERSION = "5.4.5" +ZLIB_VERSION = "1.3" +ZLIB_DOTLESS = ZLIB_VERSION.replace(".", "") + # dependencies, listed in order of compilation DEPS = { "libjpeg": { - "url": SF_PROJECTS - + "/libjpeg-turbo/files/3.0.1/libjpeg-turbo-3.0.1.tar.gz/download", - "filename": "libjpeg-turbo-3.0.1.tar.gz", - "dir": "libjpeg-turbo-3.0.1", + "url": SF_PROJECTS + f"/libjpeg-turbo/files/{JPEGTURBO_VERSION}/" + f"libjpeg-turbo-{JPEGTURBO_VERSION}.tar.gz/download", + "filename": f"libjpeg-turbo-{JPEGTURBO_VERSION}.tar.gz", + "dir": f"libjpeg-turbo-{JPEGTURBO_VERSION}", "license": ["README.ijg", "LICENSE.md"], "license_pattern": ( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" @@ -143,9 +160,9 @@ DEPS = { "bins": ["cjpeg.exe", "djpeg.exe"], }, "zlib": { - "url": "https://zlib.net/zlib13.zip", - "filename": "zlib13.zip", - "dir": "zlib-1.3", + "url": f"https://zlib.net/zlib{ZLIB_DOTLESS}.zip", + "filename": f"zlib{ZLIB_DOTLESS}.zip", + "dir": f"zlib-{ZLIB_VERSION}", "license": "README", "license_pattern": "Copyright notice:\n\n(.+)$", "build": [ @@ -157,9 +174,9 @@ DEPS = { "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.5.tar.gz/download", - "filename": "xz-5.4.5.tar.gz", - "dir": "xz-5.4.5", + "url": SF_PROJECTS + f"/lzmautils/files/xz-{XZ_VERSION}.tar.gz/download", + "filename": f"xz-{XZ_VERSION}.tar.gz", + "dir": f"xz-{XZ_VERSION}", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -170,9 +187,9 @@ DEPS = { "libs": [r"liblzma.lib"], }, "libwebp": { - "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.3.2.tar.gz", - "filename": "libwebp-1.3.2.tar.gz", - "dir": "libwebp-1.3.2", + "url": f"http://downloads.webmproject.org/releases/webp/libwebp-{LIBWEBP_VERSION}.tar.gz", + "filename": f"libwebp-{LIBWEBP_VERSION}.tar.gz", + "dir": f"libwebp-{LIBWEBP_VERSION}", "license": "COPYING", "patch": { r"src\enc\picture_csp_enc.c": { @@ -192,9 +209,9 @@ DEPS = { "libs": [r"libsharpyuv.lib", r"libwebp*.lib"], }, "libtiff": { - "url": "https://download.osgeo.org/libtiff/tiff-4.6.0.tar.gz", - "filename": "tiff-4.6.0.tar.gz", - "dir": "tiff-4.6.0", + "url": f"https://download.osgeo.org/libtiff/tiff-{TIFF_VERSION}.tar.gz", + "filename": f"tiff-{TIFF_VERSION}.tar.gz", + "dir": f"tiff-{TIFF_VERSION}", "license": "LICENSE.md", "patch": { r"libtiff\tif_lzma.c": { @@ -224,21 +241,22 @@ DEPS = { "libs": [r"libtiff\*.lib"], }, "libpng": { - "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.39/lpng1639.zip/download", - "filename": "lpng1639.zip", - "dir": "lpng1639", + "url": SF_PROJECTS + f"/libpng/files/libpng{LIBPNG_XY}/{LIBPNG_VERSION}/" + f"lpng{LIBPNG_DOTLESS}.zip/download", + "filename": f"lpng{LIBPNG_DOTLESS}.zip", + "dir": f"lpng{LIBPNG_DOTLESS}", "license": "LICENSE", "build": [ *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"), - cmd_copy("libpng16_static.lib", "libpng16.lib"), + cmd_copy(f"libpng{LIBPNG_XY}_static.lib", f"libpng{LIBPNG_XY}.lib"), ], "headers": [r"png*.h"], - "libs": [r"libpng16.lib"], + "libs": [rf"libpng{LIBPNG_XY}.lib"], }, "brotli": { - "url": "https://github.com/google/brotli/archive/refs/tags/v1.1.0.tar.gz", - "filename": "brotli-1.1.0.tar.gz", - "dir": "brotli-1.1.0", + "url": f"https://github.com/google/brotli/archive/refs/tags/v{BROTLI_VERSION}.tar.gz", + "filename": f"brotli-{BROTLI_VERSION}.tar.gz", + "dir": f"brotli-{BROTLI_VERSION}", "license": "LICENSE", "build": [ *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -247,9 +265,9 @@ DEPS = { "libs": ["*.lib"], }, "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", - "filename": "freetype-2.13.2.tar.gz", - "dir": "freetype-2.13.2", + "url": f"https://download.savannah.gnu.org/releases/freetype/freetype-{FREETYPE_VERSION}.tar.gz", + "filename": f"freetype-{FREETYPE_VERSION}.tar.gz", + "dir": f"freetype-{FREETYPE_VERSION}", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "patch": { r"builds\windows\vc2010\freetype.vcxproj": { @@ -262,7 +280,7 @@ DEPS = { "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI", # noqa: E501 "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 "": "{lib_dir}", # noqa: E501 - "": "zlib.lib;libpng16.lib;brotlicommon.lib;brotlidec.lib", # noqa: E501 + "": f"zlib.lib;libpng{LIBPNG_XY}.lib;brotlicommon.lib;brotlidec.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { # link against harfbuzz.lib @@ -282,9 +300,10 @@ DEPS = { "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], }, "lcms2": { - "url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download", - "filename": "lcms2-2.16.tar.gz", - "dir": "lcms2-2.16", + "url": SF_PROJECTS + + f"/lcms/files/lcms/{LCMS2_VERSION}/lcms2-{LCMS2_VERSION}.tar.gz/download", + "filename": f"lcms2-{LCMS2_VERSION}.tar.gz", + "dir": f"lcms2-{LCMS2_VERSION}", "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { @@ -308,16 +327,18 @@ DEPS = { "libs": [r"Lib\MS\*.lib"], }, "openjpeg": { - "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.2.tar.gz", - "filename": "openjpeg-2.5.2.tar.gz", - "dir": "openjpeg-2.5.2", + "url": f"https://github.com/uclouvain/openjpeg/archive/v{OPENJPEG_VERSION}.tar.gz", + "filename": f"openjpeg-{OPENJPEG_VERSION}.tar.gz", + "dir": f"openjpeg-{OPENJPEG_VERSION}", "license": "LICENSE", "build": [ *cmds_cmake( "openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF" ), - cmd_mkdir(r"{inc_dir}\openjpeg-2.5.2"), - cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.2"), + cmd_mkdir(rf"{{inc_dir}}\openjpeg-{OPENJPEG_VERSION}"), + cmd_copy( + r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{OPENJPEG_VERSION}" + ), ], "libs": [r"bin\*.lib"], }, @@ -343,9 +364,9 @@ DEPS = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip", - "filename": "harfbuzz-8.3.0.zip", - "dir": "harfbuzz-8.3.0", + "url": f"https://github.com/harfbuzz/harfbuzz/archive/{HARFBUZZ_VERSION}.zip", + "filename": f"harfbuzz-{HARFBUZZ_VERSION}.zip", + "dir": f"harfbuzz-{HARFBUZZ_VERSION}", "license": "COPYING", "build": [ *cmds_cmake( @@ -358,12 +379,12 @@ DEPS = { "libs": [r"*.lib"], }, "fribidi": { - "url": "https://github.com/fribidi/fribidi/archive/v1.0.13.zip", - "filename": "fribidi-1.0.13.zip", - "dir": "fribidi-1.0.13", + "url": f"https://github.com/fribidi/fribidi/archive/v{FRIBIDI_VERSION}.zip", + "filename": f"fribidi-{FRIBIDI_VERSION}.zip", + "dir": f"fribidi-{FRIBIDI_VERSION}", "license": "COPYING", "build": [ - cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"), + cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{FRIBIDI_VERSION}-COPYING"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), # generated tab.i files cannot be cross-compiled " ^&^& ".join( From 0b546765b8b12280eaf78807173ebf99be73c306 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:22:22 +0200 Subject: [PATCH 119/157] Refactor constants into dict --- winbuild/build_prepare.py | 127 +++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fbd0276ed..efffbf5ac 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -109,30 +109,33 @@ ARCHITECTURES = { "ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"}, } -BROTLI_VERSION = "1.1.0" -FREETYPE_VERSION = "2.13.2" -FRIBIDI_VERSION = "1.0.13" -HARFBUZZ_VERSION = "8.3.0" -JPEGTURBO_VERSION = "3.0.1" -LCMS2_VERSION = "2.16" -LIBPNG_VERSION = "1.6.39" -LIBPNG_DOTLESS = LIBPNG_VERSION.replace(".", "") -LIBPNG_XY = "".join(LIBPNG_VERSION.split(".")[:2]) -LIBWEBP_VERSION = "1.3.2" -LIBXCB_VERSION = "1.16" -OPENJPEG_VERSION = "2.5.2" -TIFF_VERSION = "4.6.0" -XZ_VERSION = "5.4.5" -ZLIB_VERSION = "1.3" -ZLIB_DOTLESS = ZLIB_VERSION.replace(".", "") +V = { + "BROTLI": "1.1.0", + "FREETYPE": "2.13.2", + "FRIBIDI": "1.0.13", + "HARFBUZZ": "8.3.0", + "JPEGTURBO": "3.0.1", + "LCMS2": "2.16", + "LIBPNG": "1.6.39", + "LIBWEBP": "1.3.2", + "LIBXCB": "1.16", + "OPENJPEG": "2.5.2", + "TIFF": "4.6.0", + "XZ": "5.4.5", + "ZLIB": "1.3", +} +V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "") +V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) +V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "") + # dependencies, listed in order of compilation DEPS = { "libjpeg": { - "url": SF_PROJECTS + f"/libjpeg-turbo/files/{JPEGTURBO_VERSION}/" - f"libjpeg-turbo-{JPEGTURBO_VERSION}.tar.gz/download", - "filename": f"libjpeg-turbo-{JPEGTURBO_VERSION}.tar.gz", - "dir": f"libjpeg-turbo-{JPEGTURBO_VERSION}", + "url": SF_PROJECTS + f"/libjpeg-turbo/files/{V['JPEGTURBO']}/" + f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download", + "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", + "dir": f"libjpeg-turbo-{V['JPEGTURBO']}", "license": ["README.ijg", "LICENSE.md"], "license_pattern": ( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" @@ -160,9 +163,9 @@ DEPS = { "bins": ["cjpeg.exe", "djpeg.exe"], }, "zlib": { - "url": f"https://zlib.net/zlib{ZLIB_DOTLESS}.zip", - "filename": f"zlib{ZLIB_DOTLESS}.zip", - "dir": f"zlib-{ZLIB_VERSION}", + "url": f"https://zlib.net/zlib{V['ZLIB_DOTLESS']}.zip", + "filename": f"zlib{V['ZLIB_DOTLESS']}.zip", + "dir": f"zlib-{V['ZLIB']}", "license": "README", "license_pattern": "Copyright notice:\n\n(.+)$", "build": [ @@ -174,9 +177,9 @@ DEPS = { "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + f"/lzmautils/files/xz-{XZ_VERSION}.tar.gz/download", - "filename": f"xz-{XZ_VERSION}.tar.gz", - "dir": f"xz-{XZ_VERSION}", + "url": SF_PROJECTS + f"/lzmautils/files/xz-{V['XZ']}.tar.gz/download", + "filename": f"xz-{V['XZ']}.tar.gz", + "dir": f"xz-{V['XZ']}", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -187,9 +190,9 @@ DEPS = { "libs": [r"liblzma.lib"], }, "libwebp": { - "url": f"http://downloads.webmproject.org/releases/webp/libwebp-{LIBWEBP_VERSION}.tar.gz", - "filename": f"libwebp-{LIBWEBP_VERSION}.tar.gz", - "dir": f"libwebp-{LIBWEBP_VERSION}", + "url": f"http://downloads.webmproject.org/releases/webp/libwebp-{V['LIBWEBP']}.tar.gz", + "filename": f"libwebp-{V['LIBWEBP']}.tar.gz", + "dir": f"libwebp-{V['LIBWEBP']}", "license": "COPYING", "patch": { r"src\enc\picture_csp_enc.c": { @@ -209,9 +212,9 @@ DEPS = { "libs": [r"libsharpyuv.lib", r"libwebp*.lib"], }, "libtiff": { - "url": f"https://download.osgeo.org/libtiff/tiff-{TIFF_VERSION}.tar.gz", - "filename": f"tiff-{TIFF_VERSION}.tar.gz", - "dir": f"tiff-{TIFF_VERSION}", + "url": f"https://download.osgeo.org/libtiff/tiff-{V['TIFF']}.tar.gz", + "filename": f"tiff-{V['TIFF']}.tar.gz", + "dir": f"tiff-{V['TIFF']}", "license": "LICENSE.md", "patch": { r"libtiff\tif_lzma.c": { @@ -241,22 +244,24 @@ DEPS = { "libs": [r"libtiff\*.lib"], }, "libpng": { - "url": SF_PROJECTS + f"/libpng/files/libpng{LIBPNG_XY}/{LIBPNG_VERSION}/" - f"lpng{LIBPNG_DOTLESS}.zip/download", - "filename": f"lpng{LIBPNG_DOTLESS}.zip", - "dir": f"lpng{LIBPNG_DOTLESS}", + "url": SF_PROJECTS + f"/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/" + f"lpng{V['LIBPNG_DOTLESS']}.zip/download", + "filename": f"lpng{V['LIBPNG_DOTLESS']}.zip", + "dir": f"lpng{V['LIBPNG_DOTLESS']}", "license": "LICENSE", "build": [ *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"), - cmd_copy(f"libpng{LIBPNG_XY}_static.lib", f"libpng{LIBPNG_XY}.lib"), + cmd_copy( + f"libpng{V['LIBPNG_XY']}_static.lib", f"libpng{V['LIBPNG_XY']}.lib" + ), ], "headers": [r"png*.h"], - "libs": [rf"libpng{LIBPNG_XY}.lib"], + "libs": [rf"libpng{V['LIBPNG_XY']}.lib"], }, "brotli": { - "url": f"https://github.com/google/brotli/archive/refs/tags/v{BROTLI_VERSION}.tar.gz", - "filename": f"brotli-{BROTLI_VERSION}.tar.gz", - "dir": f"brotli-{BROTLI_VERSION}", + "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz", + "filename": f"brotli-{V['BROTLI']}.tar.gz", + "dir": f"brotli-{V['BROTLI']}", "license": "LICENSE", "build": [ *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -265,9 +270,9 @@ DEPS = { "libs": ["*.lib"], }, "freetype": { - "url": f"https://download.savannah.gnu.org/releases/freetype/freetype-{FREETYPE_VERSION}.tar.gz", - "filename": f"freetype-{FREETYPE_VERSION}.tar.gz", - "dir": f"freetype-{FREETYPE_VERSION}", + "url": f"https://download.savannah.gnu.org/releases/freetype/freetype-{V['FREETYPE']}.tar.gz", + "filename": f"freetype-{V['FREETYPE']}.tar.gz", + "dir": f"freetype-{V['FREETYPE']}", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "patch": { r"builds\windows\vc2010\freetype.vcxproj": { @@ -280,7 +285,7 @@ DEPS = { "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI", # noqa: E501 "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 "": "{lib_dir}", # noqa: E501 - "": f"zlib.lib;libpng{LIBPNG_XY}.lib;brotlicommon.lib;brotlidec.lib", # noqa: E501 + "": f"zlib.lib;libpng{V['LIBPNG_XY']}.lib;brotlicommon.lib;brotlidec.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { # link against harfbuzz.lib @@ -301,9 +306,9 @@ DEPS = { }, "lcms2": { "url": SF_PROJECTS - + f"/lcms/files/lcms/{LCMS2_VERSION}/lcms2-{LCMS2_VERSION}.tar.gz/download", - "filename": f"lcms2-{LCMS2_VERSION}.tar.gz", - "dir": f"lcms2-{LCMS2_VERSION}", + + f"/lcms/files/lcms/{V['LCMS2']}/lcms2-{V['LCMS2']}.tar.gz/download", + "filename": f"lcms2-{V['LCMS2']}.tar.gz", + "dir": f"lcms2-{V['LCMS2']}", "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { @@ -327,18 +332,16 @@ DEPS = { "libs": [r"Lib\MS\*.lib"], }, "openjpeg": { - "url": f"https://github.com/uclouvain/openjpeg/archive/v{OPENJPEG_VERSION}.tar.gz", - "filename": f"openjpeg-{OPENJPEG_VERSION}.tar.gz", - "dir": f"openjpeg-{OPENJPEG_VERSION}", + "url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz", + "filename": f"openjpeg-{V['OPENJPEG']}.tar.gz", + "dir": f"openjpeg-{V['OPENJPEG']}", "license": "LICENSE", "build": [ *cmds_cmake( "openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF" ), - cmd_mkdir(rf"{{inc_dir}}\openjpeg-{OPENJPEG_VERSION}"), - cmd_copy( - r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{OPENJPEG_VERSION}" - ), + cmd_mkdir(rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"), + cmd_copy(r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"), ], "libs": [r"bin\*.lib"], }, @@ -364,9 +367,9 @@ DEPS = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": f"https://github.com/harfbuzz/harfbuzz/archive/{HARFBUZZ_VERSION}.zip", - "filename": f"harfbuzz-{HARFBUZZ_VERSION}.zip", - "dir": f"harfbuzz-{HARFBUZZ_VERSION}", + "url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip", + "filename": f"harfbuzz-{V['HARFBUZZ']}.zip", + "dir": f"harfbuzz-{V['HARFBUZZ']}", "license": "COPYING", "build": [ *cmds_cmake( @@ -379,12 +382,12 @@ DEPS = { "libs": [r"*.lib"], }, "fribidi": { - "url": f"https://github.com/fribidi/fribidi/archive/v{FRIBIDI_VERSION}.zip", - "filename": f"fribidi-{FRIBIDI_VERSION}.zip", - "dir": f"fribidi-{FRIBIDI_VERSION}", + "url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip", + "filename": f"fribidi-{V['FRIBIDI']}.zip", + "dir": f"fribidi-{V['FRIBIDI']}", "license": "COPYING", "build": [ - cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{FRIBIDI_VERSION}-COPYING"), + cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), # generated tab.i files cannot be cross-compiled " ^&^& ".join( From bdabbd6b0f8f8dd190b6a88b24a3b8087b327572 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Mar 2024 08:21:14 +1100 Subject: [PATCH 120/157] Added sphinx-reredirects to docs Makefile --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index 3b4deb9bf..24fd05aa2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,7 +46,7 @@ clean: -rm -rf $(BUILDDIR)/* install-sphinx: - $(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinxext-opengraph + $(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinx-reredirects sphinxext-opengraph .PHONY: html html: From 99c7f5405f96cc3b49b0536b6081e2ee418de55e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Mar 2024 12:00:21 +1100 Subject: [PATCH 121/157] Do not use packaged pip --- .github/workflows/test-mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index b4e479f12..a07a27c46 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -67,10 +67,10 @@ jobs: mingw-w64-x86_64-python3-cffi \ mingw-w64-x86_64-python3-numpy \ mingw-w64-x86_64-python3-olefile \ - mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python3-setuptools \ mingw-w64-x86_64-python-pyqt6 + python3 -m ensurepip python3 -m pip install pyroma pytest pytest-cov pytest-timeout pushd depends && ./install_extra_test_images.sh && popd From 1f602433ddd408def888569c21eb5808f193f44e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Mar 2024 21:33:26 +1100 Subject: [PATCH 122/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 205ffa294..a4e90cf5c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Match mask size to pasted image size in GifImagePlugin #7779 + [radarhere] + - Release GIL while calling ``WebPAnimDecoderGetNext`` #7782 [evanmiller, radarhere] From 334c26d80b35ba08265bb1008c9fafa552b31d21 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Mar 2024 22:27:26 +1100 Subject: [PATCH 123/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a4e90cf5c..7adcf1b40 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Handle truncated chunks at the end of PNG images #7709 + [lajiyuan, radarhere] + - Match mask size to pasted image size in GifImagePlugin #7779 [radarhere] From c4234800a05e7df079a6266119b9e4cf2a92aca9 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Fri, 1 Mar 2024 08:47:50 -0600 Subject: [PATCH 124/157] parametrize test_seek_mode functions --- Tests/test_file_container.py | 41 +++++++++--------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 813b444db..1c1f58500 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -21,9 +21,16 @@ def test_isatty() -> None: assert container.isatty() is False -def test_seek_mode_0() -> None: +@pytest.mark.parametrize( + "mode, expected_value", + ( + (0, 33), + (1, 66), + (2, 100), + ), +) +def test_seek_mode(mode: int, expected_value: int) -> None: # Arrange - mode = 0 with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -32,35 +39,7 @@ def test_seek_mode_0() -> None: container.seek(33, mode) # Assert - assert container.tell() == 33 - - -def test_seek_mode_1() -> None: - # Arrange - mode = 1 - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) - - # Act - container.seek(33, mode) - container.seek(33, mode) - - # Assert - assert container.tell() == 66 - - -def test_seek_mode_2() -> None: - # Arrange - mode = 2 - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) - - # Act - container.seek(33, mode) - container.seek(33, mode) - - # Assert - assert container.tell() == 100 + assert container.tell() == expected_value @pytest.mark.parametrize("bytesmode", (True, False)) From 6d78d4276900121985e9460c6ec3b39225c56b72 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Mar 2024 13:12:17 +1100 Subject: [PATCH 125/157] Added type hints --- Tests/check_imaging_leaks.py | 5 +++- Tests/check_png_dos.py | 3 +++ Tests/helper.py | 2 +- Tests/test_file_container.py | 8 ++++--- Tests/test_file_jpeg.py | 43 ++++++++++++++++++++-------------- Tests/test_file_jpeg2k.py | 7 +++--- Tests/test_file_libtiff.py | 8 +++++-- Tests/test_file_mpo.py | 11 ++++----- Tests/test_file_png.py | 6 ++--- Tests/test_file_spider.py | 7 +++--- Tests/test_file_tiff.py | 1 + Tests/test_image.py | 4 ++-- Tests/test_image_access.py | 1 + Tests/test_image_fromqimage.py | 42 +++++++++++++++------------------ Tests/test_image_paste.py | 2 +- Tests/test_image_resample.py | 2 +- Tests/test_image_resize.py | 2 +- Tests/test_imagemorph.py | 7 +++--- Tests/test_imageops.py | 15 +++++++++--- Tests/test_imageops_usm.py | 4 ++-- Tests/test_imagesequence.py | 3 ++- Tests/test_imageshow.py | 5 ++-- Tests/test_mode_i16.py | 2 +- Tests/test_tiff_crashes.py | 7 +++--- src/PIL/EpsImagePlugin.py | 2 +- src/PIL/Image.py | 2 +- src/PIL/ImageFile.py | 2 +- 27 files changed, 115 insertions(+), 88 deletions(-) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 890167039..231789ca0 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -23,7 +23,10 @@ def _get_mem_usage() -> float: def _test_leak( - min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any + min_iterations: int, + max_iterations: int, + fn: Callable[..., Image.Image | None], + *args: Any, ) -> None: mem_limit = None for i in range(max_iterations): diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index d65ba6abc..63d6657bc 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -17,6 +17,7 @@ def test_ignore_dos_text() -> None: finally: ImageFile.LOAD_TRUNCATED_IMAGES = False + assert isinstance(im, PngImagePlugin.PngImageFile) for s in im.text.values(): assert len(s) < 1024 * 1024, "Text chunk larger than 1M" @@ -32,6 +33,7 @@ def test_dos_text() -> None: assert msg, "Decompressed Data Too Large" return + assert isinstance(im, PngImagePlugin.PngImageFile) for s in im.text.values(): assert len(s) < 1024 * 1024, "Text chunk larger than 1M" @@ -57,6 +59,7 @@ def test_dos_total_memory() -> None: return total_len = 0 + assert isinstance(im2, PngImagePlugin.PngImageFile) for txt in im2.text.values(): total_len += len(txt) assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M" diff --git a/Tests/helper.py b/Tests/helper.py index b98883946..9849bf655 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -351,7 +351,7 @@ def is_mingw() -> bool: class CachedProperty: - def __init__(self, func: Callable[[Any], None]) -> None: + def __init__(self, func: Callable[[Any], Any]) -> None: self.func = func def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any: diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 1c1f58500..7f76fb47a 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Literal + import pytest from PIL import ContainerIO, Image @@ -22,14 +24,14 @@ def test_isatty() -> None: @pytest.mark.parametrize( - "mode, expected_value", + "mode, expected_position", ( (0, 33), (1, 66), (2, 100), ), ) -def test_seek_mode(mode: int, expected_value: int) -> None: +def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None: # Arrange with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -39,7 +41,7 @@ def test_seek_mode(mode: int, expected_value: int) -> None: container.seek(33, mode) # Assert - assert container.tell() == expected_value + assert container.tell() == expected_position @pytest.mark.parametrize("bytesmode", (True, False)) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 654242148..33f845402 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -6,7 +6,7 @@ import warnings from io import BytesIO from pathlib import Path from types import ModuleType -from typing import Any +from typing import Any, cast import pytest @@ -45,14 +45,20 @@ TEST_FILE = "Tests/images/hopper.jpg" @skip_unless_feature("jpg") class TestFileJpeg: - def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image: + def roundtrip_with_bytes( + self, im: Image.Image, **options: Any + ) -> tuple[JpegImagePlugin.JpegImageFile, int]: out = BytesIO() im.save(out, "JPEG", **options) test_bytes = out.tell() out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only - return im + reloaded = cast(JpegImagePlugin.JpegImageFile, Image.open(out)) + return reloaded, test_bytes + + def roundtrip( + self, im: Image.Image, **options: Any + ) -> JpegImagePlugin.JpegImageFile: + return self.roundtrip_with_bytes(im, **options)[0] def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image: """Generates a very hard to compress file @@ -246,13 +252,13 @@ class TestFileJpeg: im.save(f, progressive=True, quality=94, exif=b" " * 43668) def test_optimize(self) -> None: - im1 = self.roundtrip(hopper()) - im2 = self.roundtrip(hopper(), optimize=0) - im3 = self.roundtrip(hopper(), optimize=1) + im1, im1_bytes = self.roundtrip_with_bytes(hopper()) + im2, im2_bytes = self.roundtrip_with_bytes(hopper(), optimize=0) + im3, im3_bytes = self.roundtrip_with_bytes(hopper(), optimize=1) assert_image_equal(im1, im2) assert_image_equal(im1, im3) - assert im1.bytes >= im2.bytes - assert im1.bytes >= im3.bytes + assert im1_bytes >= im2_bytes + assert im1_bytes >= im3_bytes def test_optimize_large_buffer(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/148 @@ -262,15 +268,15 @@ class TestFileJpeg: im.save(f, format="JPEG", optimize=True) def test_progressive(self) -> None: - im1 = self.roundtrip(hopper()) + im1, im1_bytes = self.roundtrip_with_bytes(hopper()) im2 = self.roundtrip(hopper(), progressive=False) - im3 = self.roundtrip(hopper(), progressive=True) + im3, im3_bytes = self.roundtrip_with_bytes(hopper(), progressive=True) assert not im1.info.get("progressive") assert not im2.info.get("progressive") assert im3.info.get("progressive") assert_image_equal(im1, im3) - assert im1.bytes >= im3.bytes + assert im1_bytes >= im3_bytes def test_progressive_large_buffer(self, tmp_path: Path) -> None: f = str(tmp_path / "temp.jpg") @@ -341,6 +347,7 @@ class TestFileJpeg: assert exif.get_ifd(0x8825) == {} transposed = ImageOps.exif_transpose(im) + assert transposed is not None exif = transposed.getexif() assert exif.get_ifd(0x8825) == {} @@ -419,14 +426,14 @@ class TestFileJpeg: assert im3.info.get("progression") def test_quality(self) -> None: - im1 = self.roundtrip(hopper()) - im2 = self.roundtrip(hopper(), quality=50) + im1, im1_bytes = self.roundtrip_with_bytes(hopper()) + im2, im2_bytes = self.roundtrip_with_bytes(hopper(), quality=50) assert_image(im1, im2.mode, im2.size) - assert im1.bytes >= im2.bytes + assert im1_bytes >= im2_bytes - im3 = self.roundtrip(hopper(), quality=0) + im3, im3_bytes = self.roundtrip_with_bytes(hopper(), quality=0) assert_image(im1, im3.mode, im3.size) - assert im2.bytes > im3.bytes + assert im2_bytes > im3_bytes def test_smooth(self) -> None: im1 = self.roundtrip(hopper()) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index fab19e2ea..b7f8350c7 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -40,10 +40,8 @@ test_card.load() def roundtrip(im: Image.Image, **options: Any) -> Image.Image: out = BytesIO() im.save(out, "JPEG2000", **options) - test_bytes = out.tell() out.seek(0) with Image.open(out) as im: - im.bytes = test_bytes # for testing only im.load() return im @@ -77,7 +75,9 @@ def test_invalid_file() -> None: def test_bytesio() -> None: with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) - assert_image_similar_tofile(test_card, data, 1.0e-3) + with Image.open(data) as im: + im.load() + assert_image_similar(im, test_card, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with @@ -340,6 +340,7 @@ def test_parser_feed() -> None: p.feed(data) # Assert + assert p.image is not None assert p.image.size == (640, 480) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 0994d9904..908464a11 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -27,7 +27,7 @@ from .helper import ( @skip_unless_feature("libtiff") class LibTiffTestCase: - def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None: + def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None: """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit assert im.mode == "1" @@ -524,7 +524,8 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, compression=compression) def test_fp_leak(self) -> None: - im = Image.open("Tests/images/hopper_g4_500.tif") + im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif") + assert im is not None fn = im.fp.fileno() os.fstat(fn) @@ -716,6 +717,7 @@ class TestFileLibTiff(LibTiffTestCase): f.write(src.read()) im = Image.open(tmpfile) + assert isinstance(im, TiffImagePlugin.TiffImageFile) im.n_frames im.close() # Should not raise PermissionError. @@ -1097,6 +1099,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im: # Assert that there are multiple strips + assert isinstance(im, TiffImagePlugin.TiffImageFile) assert len(im.tag_v2[STRIPOFFSETS]) > 1 @pytest.mark.parametrize("argument", (True, False)) @@ -1113,6 +1116,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, **arguments) with Image.open(out) as im: + assert isinstance(im, TiffImagePlugin.TiffImageFile) assert len(im.tag_v2[STRIPOFFSETS]) == 1 finally: TiffImagePlugin.STRIP_SIZE = 65536 diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 4fb00d699..f105428ca 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -2,11 +2,11 @@ from __future__ import annotations import warnings from io import BytesIO -from typing import Any +from typing import Any, cast import pytest -from PIL import Image +from PIL import Image, MpoImagePlugin from .helper import ( assert_image_equal, @@ -20,14 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] pytestmark = skip_unless_feature("jpg") -def roundtrip(im: Image.Image, **options: Any) -> Image.Image: +def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile: out = BytesIO() im.save(out, "MPO", **options) - test_bytes = out.tell() out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only - return im + return cast(MpoImagePlugin.MpoImageFile, Image.open(out)) @pytest.mark.parametrize("test_file", test_files) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 334839f5c..379ef157b 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -7,7 +7,7 @@ import zlib from io import BytesIO from pathlib import Path from types import ModuleType -from typing import Any +from typing import Any, cast import pytest @@ -59,11 +59,11 @@ def load(data: bytes) -> Image.Image: return Image.open(BytesIO(data)) -def roundtrip(im: Image.Image, **options: Any) -> Image.Image: +def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile: out = BytesIO() im.save(out, "PNG", **options) out.seek(0) - return Image.open(out) + return cast(PngImagePlugin.PngImageFile, Image.open(out)) @skip_unless_feature("zlib") diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 75fef1dc6..fe71435cc 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -9,7 +9,7 @@ import pytest from PIL import Image, ImageSequence, SpiderImagePlugin -from .helper import assert_image_equal_tofile, hopper, is_pypy +from .helper import assert_image_equal, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" @@ -152,7 +152,7 @@ def test_nonstack_dos() -> None: assert i <= 1, "Non-stack DOS file test failed" -# for issue #4093 +# for issue #4093s def test_odd_size() -> None: data = BytesIO() width = 100 @@ -160,4 +160,5 @@ def test_odd_size() -> None: im.save(data, format="SPIDER") data.seek(0) - assert_image_equal_tofile(im, data) + with Image.open(data) as im2: + assert_image_equal(im, im2) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 0110948ae..21d52462e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -623,6 +623,7 @@ class TestFileTiff: im.save(outfile, tiffinfo={278: 256}) with Image.open(outfile) as im: + assert isinstance(im, TiffImagePlugin.TiffImageFile) assert im.tag_v2[278] == 256 def test_strip_raw(self) -> None: diff --git a/Tests/test_image.py b/Tests/test_image.py index 2a4d453e2..b4e8e660c 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -138,13 +138,13 @@ class TestImage: assert im.height == 2 with pytest.raises(AttributeError): - im.size = (3, 4) + im.size = (3, 4) # type: ignore[misc] def test_set_mode(self) -> None: im = Image.new("RGB", (1, 1)) with pytest.raises(AttributeError): - im.mode = "P" + im.mode = "P" # type: ignore[misc] def test_invalid_image(self) -> None: im = io.BytesIO(b"") diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 380b89de8..8c42da57a 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -14,6 +14,7 @@ from .helper import assert_image_equal, hopper, is_win32 # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 +cffi: ModuleType | None if os.environ.get("PYTHONOPTIMIZE") == "2": cffi = None else: diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index ea31a9de9..c20123a1b 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,7 +1,6 @@ from __future__ import annotations import warnings -from typing import Generator import pytest @@ -17,19 +16,16 @@ pytestmark = pytest.mark.skipif( not ImageQt.qt_is_installed, reason="Qt bindings are not installed" ) +ims = [ + hopper(), + Image.open("Tests/images/transparent.png"), + Image.open("Tests/images/7x13.png"), +] -@pytest.fixture -def test_images() -> Generator[Image.Image, None, None]: - ims = [ - hopper(), - Image.open("Tests/images/transparent.png"), - Image.open("Tests/images/7x13.png"), - ] - try: - yield ims - finally: - for im in ims: - im.close() + +def teardown_module() -> None: + for im in ims: + im.close() def roundtrip(expected: Image.Image) -> None: @@ -44,26 +40,26 @@ def roundtrip(expected: Image.Image) -> None: assert_image_equal(result, expected.convert("RGB")) -def test_sanity_1(test_images: Generator[Image.Image, None, None]) -> None: - for im in test_images: +def test_sanity_1() -> None: + for im in ims: roundtrip(im.convert("1")) -def test_sanity_rgb(test_images: Generator[Image.Image, None, None]) -> None: - for im in test_images: +def test_sanity_rgb() -> None: + for im in ims: roundtrip(im.convert("RGB")) -def test_sanity_rgba(test_images: Generator[Image.Image, None, None]) -> None: - for im in test_images: +def test_sanity_rgba() -> None: + for im in ims: roundtrip(im.convert("RGBA")) -def test_sanity_l(test_images: Generator[Image.Image, None, None]) -> None: - for im in test_images: +def test_sanity_l() -> None: + for im in ims: roundtrip(im.convert("L")) -def test_sanity_p(test_images: Generator[Image.Image, None, None]) -> None: - for im in test_images: +def test_sanity_p() -> None: + for im in ims: roundtrip(im.convert("P")) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 2966f724f..d8f6b65e0 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -32,7 +32,7 @@ class TestImagingPaste: def assert_9points_paste( self, im: Image.Image, - im2: Image.Image, + im2: Image.Image | str | tuple[int, ...], mask: Image.Image, expected: list[tuple[int, int, int, int]], ) -> None: diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 7090ff9cd..dbe193808 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -237,7 +237,7 @@ class TestCoreResampleConsistency: im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] - def run_case(self, case: tuple[Image.Image, Image.Image]) -> None: + def run_case(self, case: tuple[Image.Image, int | tuple[int, ...]]) -> None: channel, color = case px = channel.load() for x in range(channel.size[0]): diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index a64e4a846..64098f80f 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -154,7 +154,7 @@ class TestImagingCoreResize: def test_unknown_filter(self) -> None: with pytest.raises(ValueError): - self.resize(hopper(), (10, 10), 9) + self.resize(hopper(), (10, 10), 9) # type: ignore[arg-type] def test_cross_platform(self, tmp_path: Path) -> None: # This test is intended for only check for consistent behaviour across diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 46b473d7a..32615cf0e 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -73,15 +73,16 @@ def test_lut(op: str) -> None: def test_no_operator_loaded() -> None: + im = Image.new("L", (1, 1)) mop = ImageMorph.MorphOp() with pytest.raises(Exception) as e: - mop.apply(None) + mop.apply(im) assert str(e.value) == "No operator loaded" with pytest.raises(Exception) as e: - mop.match(None) + mop.match(im) assert str(e.value) == "No operator loaded" with pytest.raises(Exception) as e: - mop.save_lut(None) + mop.save_lut("") assert str(e.value) == "No operator loaded" diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index b320e79c1..d6bdaf450 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -13,8 +13,12 @@ from .helper import ( ) -class Deformer: - def getmesh(self, im: Image.Image) -> list[tuple[tuple[int, ...], tuple[int, ...]]]: +class Deformer(ImageOps.SupportsGetMesh): + def getmesh( + self, im: Image.Image + ) -> list[ + tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] + ]: x, y = im.size return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] @@ -376,6 +380,7 @@ def test_exif_transpose() -> None: else: original_exif = im.info["exif"] transposed_im = ImageOps.exif_transpose(im) + assert transposed_im is not None assert_image_similar(base_im, transposed_im, 17) if orientation_im is base_im: assert "exif" not in im.info @@ -387,6 +392,7 @@ def test_exif_transpose() -> None: # Repeat the operation to test that it does not keep transposing transposed_im2 = ImageOps.exif_transpose(transposed_im) + assert transposed_im2 is not None assert_image_equal(transposed_im2, transposed_im) check(base_im) @@ -402,6 +408,7 @@ def test_exif_transpose() -> None: assert im.getexif()[0x0112] == 3 transposed_im = ImageOps.exif_transpose(im) + assert transposed_im is not None assert 0x0112 not in transposed_im.getexif() transposed_im._reload_exif() @@ -414,12 +421,14 @@ def test_exif_transpose() -> None: assert im.getexif()[0x0112] == 3 transposed_im = ImageOps.exif_transpose(im) + assert transposed_im is not None assert 0x0112 not in transposed_im.getexif() # Orientation set directly on Image.Exif im = hopper() im.getexif()[0x0112] = 3 transposed_im = ImageOps.exif_transpose(im) + assert transposed_im is not None assert 0x0112 not in transposed_im.getexif() @@ -499,7 +508,7 @@ def test_autocontrast_mask_real_input() -> None: def test_autocontrast_preserve_tone() -> None: - def autocontrast(mode: str, preserve_tone: bool) -> Image.Image: + def autocontrast(mode: str, preserve_tone: bool) -> list[int]: im = hopper(mode) return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram() diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 519d79105..c15907a55 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -28,8 +28,8 @@ def test_filter_api(test_images: dict[str, Image.Image]) -> None: assert i.mode == "RGB" assert i.size == (128, 128) - test_filter = ImageFilter.UnsharpMask(2.0, 125, 8) - i = im.filter(test_filter) + test_filter2 = ImageFilter.UnsharpMask(2.0, 125, 8) + i = im.filter(test_filter2) assert i.mode == "RGB" assert i.size == (128, 128) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 7280dded0..7f3a3d141 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -26,7 +26,7 @@ def test_sanity(tmp_path: Path) -> None: assert index == 1 with pytest.raises(AttributeError): - ImageSequence.Iterator(0) + ImageSequence.Iterator(0) # type: ignore[arg-type] def test_iterator() -> None: @@ -72,6 +72,7 @@ def test_consecutive() -> None: for frame in ImageSequence.Iterator(im): if first_frame is None: first_frame = frame.copy() + assert first_frame is not None for frame in ImageSequence.Iterator(im): assert_image_equal(frame, first_frame) break diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 8d741d94a..4e9291fbb 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -68,10 +68,11 @@ def test_show_without_viewers() -> None: def test_viewer() -> None: viewer = ImageShow.Viewer() - assert viewer.get_format(None) is None + im = Image.new("L", (1, 1)) + assert viewer.get_format(im) is None with pytest.raises(NotImplementedError): - viewer.get_command(None) + viewer.get_command("") @pytest.mark.parametrize("viewer", ImageShow._viewers) diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 903f7e0c6..1b01f95ce 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -78,7 +78,7 @@ def test_basic(tmp_path: Path, mode: str) -> None: def test_tobytes() -> None: - def tobytes(mode: str) -> Image.Image: + def tobytes(mode: str) -> bytes: return Image.new(mode, (1, 1), 1).tobytes() order = 1 if Image._ENDIAN == "<" else -1 diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py index f51e8b3a8..073e5415c 100644 --- a/Tests/test_tiff_crashes.py +++ b/Tests/test_tiff_crashes.py @@ -47,9 +47,8 @@ def test_tiff_crashes(test_file: str) -> None: with Image.open(test_file) as im: im.load() except FileNotFoundError: - if not on_ci(): - pytest.skip("test image not found") - return - raise + if on_ci(): + raise + pytest.skip("test image not found") except OSError: pass diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index d2e60aa07..726359c67 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -38,7 +38,7 @@ from ._deprecate import deprecate split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") -gs_binary = None +gs_binary: str | bool | None = None gs_windows_binary = None diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ba81a22c7..b344e987e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -75,7 +75,7 @@ class DecompressionBombError(Exception): # Limit to around a quarter gigabyte for a 24-bit (3 bpp) image -MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3) +MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3) try: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index e929b665e..a654dea27 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -384,7 +384,7 @@ class Parser: """ incremental = None - image = None + image: Image.Image | None = None data = None decoder = None offset = 0 From d6a3f89e271f62a780c25e5e504bc84ba6b8576f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Mar 2024 15:39:43 +1100 Subject: [PATCH 126/157] Open 16-bit grayscale PNGs as I;16 --- Tests/images/16_bit_binary_pgm.png | Bin 578 -> 0 bytes Tests/images/16_bit_binary_pgm.tiff | Bin 0 -> 8134 bytes .../images/cmx3g8_wv_1998.260_0745_mcidas.png | Bin 305116 -> 0 bytes .../cmx3g8_wv_1998.260_0745_mcidas.tiff | Bin 0 -> 2880134 bytes Tests/images/hopper_emboss.bmp | Bin 49206 -> 49206 bytes Tests/images/hopper_emboss_I.png | Bin 13273 -> 0 bytes Tests/images/hopper_emboss_more.bmp | Bin 49206 -> 49206 bytes Tests/images/hopper_emboss_more_I.png | Bin 14624 -> 0 bytes Tests/images/imagedraw_rectangle_I.png | Bin 180 -> 0 bytes Tests/images/imagedraw_rectangle_I.tiff | Bin 0 -> 20122 bytes Tests/test_file_mcidas.py | 2 +- Tests/test_file_png.py | 6 ++--- Tests/test_file_ppm.py | 2 +- Tests/test_image_filter.py | 24 ++---------------- Tests/test_imagedraw.py | 2 +- src/PIL/Image.py | 2 +- src/PIL/PngImagePlugin.py | 6 ++--- src/libImaging/Convert.c | 16 ++++++++++++ 18 files changed, 28 insertions(+), 32 deletions(-) delete mode 100644 Tests/images/16_bit_binary_pgm.png create mode 100644 Tests/images/16_bit_binary_pgm.tiff delete mode 100644 Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png create mode 100644 Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff delete mode 100644 Tests/images/hopper_emboss_I.png delete mode 100644 Tests/images/hopper_emboss_more_I.png delete mode 100644 Tests/images/imagedraw_rectangle_I.png create mode 100644 Tests/images/imagedraw_rectangle_I.tiff diff --git a/Tests/images/16_bit_binary_pgm.png b/Tests/images/16_bit_binary_pgm.png deleted file mode 100644 index 918be1ad41d738db5afe429469538df9e580d7d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 578 zcmeAS@N?(olHy`uVBq!ia0vp^B0!uX03;Yb2TTqIQY`6?zK#rxZ3_%vOp6EdnUcKS zUH<82DMc#HK4B-bTr#q6iAL1q1 z{qwxDzj){Gw?)6NJgvI-RO`H|?TezqM@9b*J9v-^+F?loI?shG!vX~#}WkDbP(*O@n6{{LwJ@>sh&JI}bpEGv&)mKCFxtS4-#_c(C+#VMyf z7N7nw>2yHn=>sj^UrsIEe|Bm4<)!LRF4;c6H1*-7n~#Mo3tp}$uH5|Kl2}QmY++^X zyH8$=D$hPm-RXU~$NTYKZ|CKwKif{iz#K_Rf*wV__0?4*9 kFqpgI_iPjmx%nxXX_dG&y!q;^4%EQl>FVdQ&MBb@0M*s{ZvX%Q diff --git a/Tests/images/16_bit_binary_pgm.tiff b/Tests/images/16_bit_binary_pgm.tiff new file mode 100644 index 0000000000000000000000000000000000000000..1ce808bcfbf463ace43cbf7b41905a4ac832a662 GIT binary patch literal 8134 zcmb{1{YRZ)9LMqZIjt>;G}*B^v}~enB5M=b9I~m%rpYF<2a!!=Nn{h*M6!u&B6|?o zL^hFZBAdt_L^hF4BAdu2l1-#t&Hcmq1Kt-eJ}%$;*ZaEe`%_(gj4U7Qw5Mro4*w0c$nW^J3FqM+hF>#1*S89 z!SrGiOnDn%y0Z?ZAHTqKVGT@gR>5@SCzu|rfa%XNn658@>C-nbomvFbvjs3^e+_SR zQTsfYmgc}z^%+d#AHbBH4dKXyl>M)q_#iSHYBM1XFhdnAR?XslFae({*4fz6hp)S}<+bfT_6(O!Mc!R8a}0krbG6&w{D5 z0!%CAV5&V0rpc3FDl7$4UkR8tkAvx2F_=CUfvM~$n4T7bDJ%48rnV%Qz8?hB`2?6= mhmzBDXg`?l=Yr{X4w$Zly4du7ADB*Lg6VN6s!gHtR@XmS-^@M$ literal 0 HcmV?d00001 diff --git a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png deleted file mode 100644 index 2b84283b7a934d492b5a4ef71fc072e7fcc638ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305116 zcmV(tK4gd{MH8nD~{ zMF0TC1VV-`sf{#cpl^7td?PKz2fU%QBUiaPmGi(3pDKiwQ@gvK65wW)7oaARQ|lYm z?x<2u6>c{hZIP_?Qbq>up}7k8klgS))7iBuE3R)ZyBbRxta6O_Bs&OL>ACDG52@cM zo>jQ7YBAHMj4nS@FzzJzianpil(PekR*j}SBT+~)09zlBR~;Tr9D06E$1*-!p{a94xjH5E-$G(&VW9Fo57IhqBu7t#W8jmZ+q`|l&jiIIb_A!kz}qjk;q7C?rKSLzsiDE_I%td%CZ1 zq{W^%+{t+6TW&D_$jne_N|2K!&O(g`Y zkc6iWVr&n~PUw76g44bH3hvkzp#5Ix696iv=K+9yY^jm*>Z_DdB1a96>6U9WVhb_K z4qiO0h{XwO0AQPt0w;E9jQt5M*$y$ftZ?j-(f)Mh|GZa<(RbsaE3#)MJLWA$^{ z9jGix1;wW{lc!Q>IgN>(7pD|1s2dl+6rQ16KlySUG|I;95mY+C_rFFhLXPeI(ny?8 z)MjxiT`O}LReqzOn3pK1PnA}_Pdhsp&gxjE9JzmzW~Whdh*9eUJX8K!hAo;zUs2s< z2y{+;XWMfU;cW6%a^+FCW1RDN;}$vcmj3*-&#gS%b=Q=Bz&@8%*xbC|Q{Yfuj z5xlG!7|(0gqQARc{Z0sv`Co#_!pKjsq+f>w>ir~^6Aiab7iBlou%pL1zm~F`z|6%o z3$XH&drFdQvVP%4;i=&zt?WUr42NAo^_b+N9C2I{PG(~?AI~7Aom}FW5La)bufi*Y z(=4*XC{uq6{h9KE`)uAn0X^ns^Sf_(0}+&4dyNzmEGOqv^9;^QIs@M%`$;wyFF(Iru~V8S4|}3b*EFxwK)8n&4Mk zJqS1|{gRgH`}UJ7(!a87^3iWB&`avZ<2x0$hQ4w8uFME`5yl}s<2+(C0;-7XSJB8C zqvM+hSS;Ja-GGhYZ#MjWqJC2Ie+5O0>@)$Ze?JhaO8run4RQ=}e8B&}FV=N8x>m~j zTY9Ft=NROpDH>O*0uIXPH)i%3jOH);-Y;TA0yd|dO8jOE8-0ygN#ZGRkRZ20*(L|m zaZzl90(Ljj(XrHnJf0s1yvmg|l4NEC#Iie>BnQl-+X!_kk-ssQzLAHq8!+zrYsbz5 z4G;J5&odoR`h{^2NB)OVZ?q`f>zx^*C8b>sE~F-*Rqj?08dTh6nK-5F`~@73b96fH z7BaK)NHt1y;@fu_7W6G8mrYPu1G^P`TmjK7tm-++Ebq3 zvxC8#3=~rMGx-mjSbqzXnn`g|nd-7foZUp+xNfE6&X0|%yZ_=m3m12L`wncq>WjIO zx6u;0?2776GnYMZ$qMAWnsPQrC0e5B^# zY28k|+W+d7oKD!C3&Z^~V#hTPbcUfAXcThDat?O(qS**ys8J*3;>xT8*1rdGCI=Pf z2O$E(x71MXcF*g6cv6s`JQ;2qRHhaN|i(v?ahoQi&i6O#MWI;$YxL1^b)GY^h1<3LN>ZQ*AXNDo=k=fnoIpsDcZ3_p6?=2 z6)#3DpQZ0yk5U|rPvHcNZxp~R%eRoDNcQ{w1r-6tldS!ibAU#`p+Ak^ z)11@_kaR!$$98#I5wc+>6hANH9yrZ&Irji(#eSxj3PDe@pQt}6x;f`W10NKtz1*X! zdfdobm5{VR@8I))=3H@{)OP&1+1lV?tJ6oIB8?B}o>wn(2mGAhE5r5?Jm)M0yBrEK zxDnMBPH-;=X&X7?t!rC$O+neV{3thtJKp%8pocrbKRdvx^auCH0VvTbb~r70PS1|@ zm*(AvxY=i?5*^a3*~@r!Y^^MtCFkFgV&uy^4LtSQ?xg50{e+)O`SLZ6<@O!#^ge?| zbrf9E6YMEbzE8UaoP(s+SigO@fy1I*E>GJ(S;=$q)ZlQocRpmy-QA$j{udd!qZ;Zx zRTKKNZ))a!y%`85L%{7;rY-Y8DfWatk}){K1_DyUJ`DMDF7vX~2Q%<2SF}^V;Du^DyC-wF_)t7&fe^RP`3;lt@A^jJ= z>ffpS|Ho;zZ!vfO;Sru*s1W)6)~9c!zc%+PXURBRe-ZEA(ssW!xog9zy?HLW&D7E|jI>I>6-?4|LpUPwkvBYmmPJX)#0#Cj3$sqqI!(m=|S_8^5 z{`~nTs9xydQ)*1?E=o!>PKK|fDIiiBIr|qI4*@*5p*^pS%Z5krBar3qU&o0*8Afmp zvUe%bl;khqi;b?C7nRpF?lR;bBbNM;V_$E)0N=6&LlNR;*}J_PE2}uc0`cF=f&^xm5M*OP@f9O@mX|dsh90pyf{lZg! zmiIS{=!t@<9+6g~W1(}-6Y!xVZz;6WzvZ)I?leCG6_hF&x?D5@{L^WMd!G9<{g^&! z*Pd5$_?|ZDmBBy;pQ|z-`;V31@;~}obBT2y)QOsFEdT!Ct&Y6=F3lHjS$&eD=5s3j z+5A5p4dsDT z^wq&0J&Qdp=Ste|;kQ)k{_aXyKdX(KL)pT05Mks5dsR&cB;_Z(l`A1SgKBMwzM}LA zST4SJmqWsLd6G)t_P@qM#}mWQ3OL0ac(PcJ?}1{o*ORI*M%r*)f~L5S+e%osy6#v` zMrRIQViKZSnh7Z4pRBm-nv=yCoV9{_BtqNla#6_PbULsCvdg+{x3`GWKCL}{)g)2P z237BL*Itz zucIIFRAeJdeb_!Y(mkV=c3&nt^0Y$kH)8L6muw=XNjFm-W63^`f^0QkpVqOf4kO%s zqnz@guD!&@*Cz0Irm-$4BpZJ0-DT--{r7thGCI+ed|ZIXo_^favSHNx4<%{i?Pwm) z#P20){LD+r4W|HvUh~C9uAK+}7he7ygns`&L#`3<8~G-FhF{lj=+%F>+u<)9ds^-< zz|U+;M=_{k_YwTQtnO0lGW#yBJoEAT_4kCQx@F9>DW17dnJ3WYqS3)Ab(ZA>{2|}d z;dN!dMm^>Mc$^7Vi=3lR&iG?5XQ>?$;;dv+Jpq5nA3866v(KfgbH?sF;*N;EmDluQ5!iwCqR`$M(LjHk$ zr5OOw{@3*tQ+b@d5nRX3gW0JXPOxM$h4^0^00tp9OV!ZeAv(4o0@{k zYvnlV21;|%3r3-fMXD+4YWHD)itHoa2O!{VQri@B{1zky+7$9RU>X%#e|akbs4p|@ zqTUCTK(!1 z_DQWBCH)F2g|H_YRbCwNmhz-vGGMBrk3(mru|-+QN`D3WyeH)eHa8k{#Caks%{qT1 zxt?VZp8g&ymGJ9XTpYl9TNR4{Z;o~2b42;UPe1=_? z>Uh_w3?+<#FX7?v!6#52K1K;M#IwV(;GFbw_;G!ZWmMAL47C6K0uaqN)JsC65ZT#8 zQ&5ci0??fbG@2NK^By54WMWB)Arh(h18|b+otlEd{%!<-bSt99|Jvg0WM+3gf+UNF zN|DS!_xfN@Lx94aVjLoZ5xZ0(Y~y6HF>{BbOyRub$qWQ)<1SLSwC>v+fKBC;rt%Kx zGY=-98id%)JpM3QvdJ=4KciA@3t8$RA83*OjT|ymW^qXQ^i9sw=IPq(KVV0m-t1ub z=M2PJ1W6;`0?X6-1V;JW5>GGyU{eyllY{hWD&z^&LwWLfUy>LZ@-sL?uW>fM6K^2S zYhx$)curvCRCQ?WxCI=K>t{Jk5h*DC^0g>2mCkq%FF3J#_hj-GvFht&&tLNTHBccF zmE)sI+kw+KwlQugxh3`#2X0PzhxAoqS2%X8tTPC9ELpf0{u>y2wVOgcjGKY)fpbEP z^c}UMP-2sC%4Cow>c^Z4?-{2uxkRGl-1nP(10^0UK2H?BRqZ*+Zg)Atfl=2!b94EJd4pJfRaRLI$({qO55A+TQ`B3KOE(+Fft>y|)m!tvBj zRXT$orW@r48nklhfp+cj{k-Mq$%8$PWhzBKfZxn_@+j}0cPQu@ecDnEaM&f^H|j?U zAwToYkn{Ia{i%5SL|+tj!a?G%%$#`;o$k@AUpOkYomO~C%OL17-Qr^C$y(SM_*AsVMJ=Zz!D&A zCgHCn;_}*+eLSF+SD@}NeEOMZ4fLthIaC)UwEIkl0n_VGants{-vzM*jn>8d^|)Rc zV}$LYYVc9`Ttrshhl|7&O%Q0C$;d!3TEy@hyOb*ZgE44l0)zjYH3G;FUI@Ym70el~ z9wW%LO{nL*0|W_I`Qlum!}!INMfy9-c8$7*{?N!7c>Ay>hqnwy9}zU^f~n8IH-o)6 z!|3nW(=Xt}h08L(_d6SOM`c&mu%-PD2!G?#s$rn?NFOGpXRagOmfH#|O3zgPPk|r1 zqg~r5v_H_y)BMAB;!5qD_f*j@NP?Fu75>3o^I}!gqXpX)4mbh3}pVCq7i<)8Q_TbDx)~MCf|;jbZ;8 zHX6AH?~(6D7UTH_zQ-)ej$;;Y^X`6kq_}BG9PT3p!f6IJjxp~+g5w!(f0v~~NIj%| z-eL8{-m;4C6BHD`bWWY;S4JLz1(ze=6ZcGmU6~EqKVD}xjns(4aq5ihx*A}#?n%m4 zerK(UP~m9!RREHEE|bA}1jO zxqLE=@P<~-L%ZCOJxlLhkIDhUmb*j#KA>5 z+%;FJc|=tk2xTt#{Gx$x47m)IiQzd-&cFDM+yue?Z`4?Xg@^tdItsjgpH-Q}tIhkW zF30+XEoB`sDz@|CitibOa(R6J8;JcSUvJ$0Y7fHz@UY=fTIjzx4FjCa911nePkUP- zmBz{VmmDkiopcihfL$AT7hFDpVSHgo1&4~&tePt-Fzhd9m-0R5q#S)&avW-^*_pCC ze|`g>Tn;JsiGjxz z=Yzv2r+A=v+w8t8?+%^kY?HGY`Cre{8EXVSK5Z{U#Z`g_DNa;t3K{!jxhm%#?H^R4 z64bbJXGlhhZdkYIjbKI~)HH}NLIaE_nls3Fo6e%>$}2>cVy7kSz*&h5`;UdIT(K)h zn+%i3OdFNg(HjY=T;J%Bnny8R_+z`;vAjezuwuoONCBx`{aauGs0rS;h@giaX|`2j zKD%^+QDouN?tcT`Kp!#dms5Z%ec0^lVPf~~+RS?Lv>N@TiMoN%{RYx@=UTZC80p?& zZ4Q+ZYv(Qg)x)_oqr@uVQ=3!KqrZo5%P>TxJxk)%trEW5LR5YQlLC+Lz*J`1O71Cp z-h18ml*^Cz-@(}E`kaS-1%cjLvf(#arn2Xc-G7E(s4Dak1K3w> zfd})skz5_Aer$V-rEKPSlxnz{lGD{lW&GVUjC9}2^t|yabIDZBo^|@dA#h3;zWu9B ztNmerO5*V0H71^71)qD^3Ah}eJxJ4i*4}FEx4a4GQJ&7j8vUb#^T2(Z#do}!Qq8;( zVva8rYjKd|iTdfi2|E<45((YsSJ0%~(C#0OI>emERpeoNGP&}kGEl+c%r9f_B~Ee( z*7s}RhL>J-mT{`S;H21Qno17c>iTpF-0P(6A3Yj=$cZ<0Q-bDhgQT0)YzANcyWrU zQGhO$zc<|)J!E#rK|$j$p=74>K@!QWxsNhcDL(8^-*ChIJ}K7AL&Pe$JuX3XNdJEE zF8L*3M8l3W^$AL|&G%B9`u}<>AD+to#p_V2<@2{N%d?~S2|i*SVb9yZy1cE=#MNJ;0fz@6P0 z2b3-xzg;I98EpqOeZiscXdl3FdUVPGILZe+ZSJpD0T2DmsWe=>m*fOl(XDG$8S0U; zMaU;!#rMN&cL?=3%lKTTp3BCub#fY!Cw{6 z<_SOA_(W4V1;U%-d(X|d9MlO~Uwb6ObW>n=roA`h%yi7M1X%UJqU9Y0j^7TUjYr7x zUfh8^C5kk0nxxNEMI}2 zia(ko$(tL;W;{t!#T!Pz9e4omKmIyG^Qi`m&)(fj^mVrS`nd=+L8V<d zf<_;lAE&&L6Fz?0<*9TxX>gd$%UwwQlT;;ec}GQFl)$K;ur&PxE}yu4SotE5TvRUz zYX8jW^WGSAPoz;wKQ?Ao@KtHt@q4&C(EG~wvYA>Kgx1F8ZPZ}T=+ zF^2PdIU5DMnQ9V24IWc?SIK1rjvx=Uj;oC}lt+@wnS_gPqku=7?4v7~pBMd)XYlQ&#)^ER|yHqT8|*58>S86wwHZZ+ngf!6OQ z`N&;!NPjAIhEML2MfK}BBT_!+c@Cu4-Q`tky@MzBjdP7sm0toUd`H=DcrRct^DS52 zQudO)Y>|j@hJllj&^Ns9uD=8R60hPPX;7qB9_%ULu<7whlIIa-rO!Au)t$z76zD^K z_4NG4;qLxcr7JD6KXNomJ6_y^p7ptEHi>e611x|2zOTb>JbIvTfM2dlm9hCq-}n^o z9`PmFFjK1UPd+c={6eDLkvy*@Z_aM*UlK)%WC^fOLMY_>eLy4SWJl8`etfdw|_*WW1ygblvZ8IlaWEc%@rJc{d32OI)70e+*>%m%s=u zg5Q1{1!1{O82p@y=TCPxqIOCMi zw-m##xu1dDbtrq>cJ#=>$KC>ky?ivAqfa;jrThLQfkDgVBHwx>M%u&hKE-L{Vvu76 ziSr2vQC*xg+5N3c@h{>&+BRJY~zwv7g3D#pqc>pw zBE%EtrRQ>!wqR%3w0!Z3FiQM{=ZW9UrGgIyj2#GRRJDJ;^FVKedMhL&D9LT6h7DO7 z7Zf}$x@Iss6JJl|*-v3Gkh^*qw1E z%@^2d34ewe@#OMF9`dCVj^8+f94Bl9F<$+qU02>m?@|P@Qqifn;?SC`sg++PN`3}D zTyJnZEiSZkP;TDi_Z{IpNcHsFu2A{{^@S+A`WhZG8_)f)Nq7Vpf@pqDs8@qyA)-*0rtA0@hAdT!lI@Z~;(#Jv%|hm6Y) zx%JMM5~eJbBsU*FtS

6ORu}9H$nj*XMpNtlOi9ES}+hTzpwkDXZ}>Ke7^zfm2fI1 zZ2vv4?Of?tQZtUA(tZVQ%O@dETm~a>W$Znkg`Vr>Bj(lI8wJ`CMkt?fVmn3iNR2Kj zLxqb%;3%J#U66a&ak*f3$AqRIX6CBf8XnP_4Vw`q%X>ET(`OPFx!6dBDM(MN~5@iQ5e%BJ|B2cFv1C+ z<2GMJ(H-z1y_N+)Rf9)oSf+I1_$^RzrBJ@mF65t#B{y?aGSl(yE6A^(GYKA?2a>{i z&l}ED`e)dAeoIM8cAR#njgaf<9(jEALN{`0?9x0{k5hxqHWhBB#RbmZ>byxuF6Rl{ z?B`y5vzJ@<5AOx7Vd92)q0|OHQ5suHS>g-#Y^LN&w14&E1LavXyl_OPyv_8ZYJ(6M z<7TI{^&>7jQM<#*N}iQds>Mq!NK?kVQZq9_h#a!U|CB?$8SGaZw06m;4g3FK^b(#; z;+Wp6)KzxojetwG4OBOl4x{i(Pb)v*_&nON4}P%?KEAr24(X2D_T)IEbI7Ya?T*$5 z^L3P|j>ciD<}=j1z6Ao1&vRBAwx9UZCXJIRwHSx=m9nm9-T=Y~*7DnnQl6y}paVuv z_W`fTU5^+koqWl{hr97UY;)s<%^Ba(^LYi{FQSPLRQ?OR2j2F^zIoqh&oB5~56pl-+z=zPs05yWKByUMGja2yPFT z<5SE4tt2`0%TVrIcIHq@+2slMcNe_vZQOdDzD$=nq_oXldICbVpGnHZIg@kY;6mIt z3GQ@-HYd3p<|e}@5E*E@`@NCXw-ow<3gdfezXjf;uMG&_^|c8ri=D(J44tJCoEsw2@(9l>NjiT za5)%Sl^9f{v8JDmBumRN$IOLcX9Ic9cM9qN?#pN;^RlF0#)$;FOY4Hy2!O=|Wg})b zzF=wl66a6l?H#c+;IfJV{bFQq1i}zW8sVFL>*FD>f5;cC3MZN_C6)H=kz^4R#j z8docWa?RMmr%}SI@D;v^0)6+Is6EP1;!?WelsePMUGpbt{=y_RuBWH-Ow+4O1K5q~ zCMpiki&CxOH(;TaCJb;p`jL$X~V51}=SvsE1Xm#dLD{BjFLM<;>Hg)ppcqThwU2 zY1F;ZG{cj^Ak@Yq#t8RgQT+rvUqUq%@X9lM3h6?tDuq+L*X3w0a3&z*q0WU;w1aLW z`eK9M&gq#pU_GXnlv4Rx!Cp%zO9>kh@zm_;BP_)>>cB^G{`zQoq-M`Q(A)jK$1^-< z$HX2njb+1qqmUc#6tj`$MQZ1=0=&5HaHjM`k&m1_&L<3^lxMzCVnraIx~sO^S)*E( z9@1G?4awalLD@^gY?3(VlB}+?nrf!}dl>1S6NjOxgwVepz1tn1z?V=;n`1)m$>GwP zA$a8w(E4%;kAR2Gt>Ykj+ue1-T;}k^qLjDRewbk=&|N*w&;gKsV2KJtfkZxhHug5@ z9QYaRj7KP*UL!}l7kRr39dWEaNd}9lKWKlXcaO9-dE(eKiH6go22NmQBc{;VXKw(- zobn9aU2o-t(uWwM;~g-PPImyo8IjHtpI<_bQ+vM3FN5RX5iN!8&^+aDW*>pdtga{` z#sCBGqGZ=+YL#Tt4--eub=B4-#Xm!OailS_DNTu^{@$r`BxG2phea)^Git4)A|+$55# z$Di68@|gz)!bCvG%LxHnG-CaJd|T(_Hb8A)1XGYg>`#_!&y}|;Y@Mm_5uKs%${FQM zgYg~gc*3E7Resd20|A#evpm-M6Oh!0;0q9r0og8`aYr`O?|8Rl>*0 z)qyiyqf%O{Mqe39n)7Gj4{Nnu=)Y>%g0A~r`aaWF6v z)uiM;0cRPXPEP&uG^x<0bw@yYex)QI&kYZ`KW&{_`+4|gbXaRJ2(*+VN!wR^PW;T( z*n_s9`+juy5LWDN;0lK|_#W!Aj1tr0#kS_DX)tM!oBJ*oO}>_)iqIpyybM5g&g43F zb;njK4s9t=iVmk)`Jq?Bn{v-;Le=x5!}Ws0Hd#vfHHA%rP>SID0L$;=$+LYVWXWnt zCUCgkF;29q^4|c3e(y&?=Gphcjey^{J10JJyFP)eX^Y2xR{b$D_N@lTRpJ&!^;GRT zrWrR+lMY1v&thf29#@xuwN_(k7RNHgQP2n0gX%EDBw*;_@ANdxHY~{ zrW()5mk(t>PMN->_tR|3UZmOx$Y~GKBG^=m|q9 ze)O~x>RIf$%^2A7Ioyqu+qYcC2HPHkCmAB&q|VLXq$qoiPn0$9gZjhw0N)+9E*n(_ zbgg8Ov5^`qLOI3b?$>>x>l(*u;PL8pRu@tuYnGT4`l8w6JIL4hbr`)QYvee>Mo{m_ zliJR?V3z5PR^3P2*HeR@cw()z&=OMawAqcfJr&XXqe5H2qrpUR-Z)0kJC2Y_leHy&r}b+t{8@sp===tD zL#C2~++ViQk#TxETQijjl0;xk62sCdP8Eu16q%B;d>S5cg#%^I4 zIu)~1N4h7HOwKE$iV<^BNV?IqA`P64kTyW%{ip z)_tmiR6ar1^z|wvsL;O2+P~|zCjhHeGK$X0>$x}*kF-hdKRW5I*eT+%)L2k-< z4`;R}uH1>bN{s;9fiP7{05qj-1lcIoXE^|-;<3@XyNiRYXMbOi9XQ(cEoZ2w4JGY84frG&#KU8prWMb`Yp{-ruMbqx$QWcskzJ( zd8&;typrG}wg-s_2>wsrbIY6u$-mkZZl&3k+0zE3XM0)XuCFqSqxZ|JAxi3o=*3p# zp%i;1TRJciRSS_Qwyu{muCOa#_Q0qirZY|>bX?o4CgnP4#R72;Tz7XAujH8FRhDcW z(>kbZb52;RZdYyta}+b_>Te~>Q--fQ`(iQ}4JAfRIMs}Ul zJ_4L}C1o7GD;uRIBsgG_`wLlL%CmM3r=q`r%T&juEM1p685p#csiol(x1fHEfbOfr zH(-1{B@dq0&{9vFJWXB{`9QzopTV>xk!85Ncpu9pXcYMGGI6?}X@m@%Fcv%*S)-7B zE5FIJbJ$CJEDCqN-D}ClmoemB8?0eeiTjI$iWSs>V6Qp*UoR~>bfQ`)$s~%zbSaU@ zL%^XGyXvSW4)!Tuy*ZLM8Tgh>B6A@Sl}<1iy8K8C!6~-s3?Tr7?WYAaf{V?qMeD+0 z>CGzp_7aM&G%Zb_H^N+yMiX;W()8(=YN=E#T_EPbZ{axxjR`okgHIdU^}0_(F)OG86J2CEP*;t-qu5Kx>36Q}Z}dj%sj<^^ zoH@R72&28*h0zA&+0%ML(g%D?AiPwL5&Pm$R~}6rYPB544gKN{ZbRP+0GY95!>zfK zkzH1^HGL}pwMNlRc8|JY$QY9JDRt)X00&*sF+>z z&=W_8xA>H<$tVMoHg%@`Kmp!-?PEzwf$4Pl8MXk@HD%G`BdLs}<8*O?HDH#?b zK}7|yiE}rD$*$Dc92o8L8(bQ}Z?ur9yC({&5GCq9W#@~nE@$9$O(oVhzz-a`NUKw~ z(&>5oR5bE;qOPT~o?_V6WD1}wvGJFZrm5@||3$OcicQB~E+pE3T2_^!-=y$(g)BI~=ZB-kMbTo7>;<)4~lB2GIMX zlg_Tjtks;A)gqUdzG0X3CheIPH-YZQ{~N&@9|GPiNxU}6vYZu+Q)zRGxqQZM zCX8^0;6PfXq9M4Hd0CHy9z`mW+6b{7v}d|FcAWFkC`7?Mb$V38*0bDPyK+4?#aC!W zL&RgLWX-tAcyQxR%{gBaR+pmkNw_qr>W+X5T|Ya~8^J~Qbi}R}EH;td7=3QmT77nmF>hO18@E=G({fi7~~f7d7>=&-<%r9dw++>B=-L{T zL!p)8P9zOSx;re|mM03E8trc(m~!#UFSUh#Uo-x&{C8nt)-qZ00hBS2%$IIrKv^@GF)W8sf2N9fC>Cc|fw zrDz}0oO)<*VF+1?l6u+YdFHs+-MF4xDGwo?EzPB<#I?fNqI~|YdoD{hQ6uCkZqu~V zlUMBRAx9`L6-46#K2`uEGgm2W@!AZ%5#W@H=%6`{fsJmC6b@CYt;~R^QKU)_#W^ad zB@|`?K1N9)`Plp=bxjeF3?fRs-mk$%gJ(*Jk$G$?`&OZphg||5Gp^yR zjxx<_(P-bbp)glzf4o9piaqMe<%1OLXi6N@CF~d8A@{O4)8savMv*} zJ721AF-cB!3AjN5%6U;5yL4r~Ea5>)=k>hN$^GSSDt+f0@H7ev%C!R_8aeMjyULI> zz%N1h3iJYL*V%HZ3OC_=wLkEb#_sQegT7dm%Q>9x&ua}AIF#~%T94H92_H4CPb*%n zIUi>InqJsTGb4mau{w*w&iG+e#vslA`$6oyb6!R0n`jI-S?WTt7K3H*bvFeM+jQo( zh2cnd=5c5Ur)X9tn@j6qa&ywx{*6#qp-52mv#sk!==1XwQ>9EFtLvs>@Q|#Y_XRwq=j@ULS_xP1W=@m1pH$HCg?YTE@B3DNzffqku;KF0_vC z_hIImy&|fCusN8FXZ(iW{YGBS7=D47ca#_HS|FTPq9JB^Y!IgR#l>rBC>*bW2 zTttu7(`TI_dYg_kf|x{oMV-f@7Dj_aeVXmNBJF4Y4rdS~ndMq^&}=BZXvjiAmG(rl94P`uJQcpAWQ zM})XZd_COONY_L2{+58HHL$Ifg}2_}2_#dKL^c5PxnA?l0c!_SuV(H@QD1a(&9>CN zPivend~)0D`{J8>98N)(# zaf;(xX<3=!XG@VTb9N;H#)fZD;LBRp$VICIq}Q87;GO^9!p<4hsxj>H!Svg6D(X+1 z%{9Q!r5E)Q%eiCh|0ht4v+;P=oD#;O6VHfd86W59N2Hs2|0sd=}y zf2i-5!=}Lgmcz27cuzA(uU2W6f(nyp_W+RxQfK_^+T8yxOqD=^2__ z$o;yh`l4rUAm)Ke1WY5)8@od&PnCSBkR%-QCRunhW~OE}nfyZJ3@R!N!m@^2zTEdE zcl7D2!WZ{AB+n;Lb1H0y?SP$5T27@NdRAP3d6%l#kebp{9q4HbOX5QAK3=n2AD3cs zcS4aMEyIZ5vG*KS<(+_JQi-Yfx>+5Zy%Dhaa%U>7Pc^W9D8aBhq$dK9(_<=`MaJd^ zQ`y?*Kc#?1X@poLI?Y3A)0AAZtF>Hpmm_JbPD+78FYUz$7fdNPuX`g$F_=2K-AQsi z2Wb(-l;^XLJprY~F)!^w0(_4lWJ9JNee_T?SX<6l}kO~Pdpcu>pr(w ziDC+=Ru!M-q#a9Evp0!Ru+=TjpOxN#GFPtXz)>LwM-Fo<1xJA41R4G&GMJ>Ha2sWb zc_{O^#&jc;Q;8?4@p*VyRJLuJc9peW+A+nIwC_DAsDAQcDrS#Wyb@1cWP{qz-1%lN^i}c#@poUT925HWe%z{^1gs}HQCx1>Y*d6hbKJOl%Wc;D-$B`)>{Cq z7pnDO%!D^ihu9kD9^_$L`3Vn>pH+rL!Yz$#e(=3A?c3;$9H*Jiia#Y4YSNb`eFn^M zoHRmYZq`uxp_CEa=)!S4oUtlW2@Hp8PZ5ojl5tPqYTXwtKNjqlT}tY0NtGIoQj1ssQZAXZJXhh{*t&qe1?L<-M27Hy@6Vg3T5U$)Uk$x|CX zrTx2aVh&q=W6(UZC|q$ZgZW;xYD@U9UVp<(hgr^ZO<_7I&-N zCt$!2$>Vk!xgx|NGp~ILa2QZB`EAC~j?IpComUMbuhzTNplY(E0UB)zxBz7NHRw^& zspvEcrvK#}4xT0_*`j>WcwATDGL{D?HA1fO$o-n_y6~XORd1xU2O_Y?17__7zm8lu z*>>ns4-zYi*=86+%XGm`( zxNqs@acI$%lLuPYU#SO@nna@eFbCsV7{jvuLSCi?a5{Vy^(N z6p_fu9oN@wlXs@H0|Q|N6^T!!INVW6p+bJYufne-PH$;4&ZrTfE~x0jn>i5NMu0Pv zdC?o$?9?so`>fkBPMSrPx+J$6_*5%LBI}fJ*R5JkAw+F<9Jw8SLmq101+ok6JXo&$?mTQhvl7u{%--C_;M*l zT^6gXFJsSV=sFtQ_#WH}+1-3T5+yHBO~H8Zl4t$4e^V?_#MySmG((?1O=uCS!6Z3Z zOv*g`tDaQ>uq_d*0&B_hlo-(O3F)s4NcWJl`r+z;FwC9gOLc9_Tx+D?HX6Da$jgGb zs74>8_u#6hZTRU{d15n2b#YdDY;CBM-zLjfA#4HOC``N_?j{9pbcg`;a~u&*Q|RW+ zD2FE-m4_{pnzIcBSkG|jJ0I({y^v{^agn{G=Ce0v9GKh;F$fneAS-0fsUcv)e>Xlw zW_IcdxjYV7a9CKp*ix#L=iBa#Gi_tM zx65tE8(v1;VK7WdX%|e*L5p?mX*}4{*0q)m_;D7fuNGHDAg` z7{Kj3Sn<<)v>~spR}+d=dcFtHM*3KWhuLD+-RHYi=i62H=t(icSWT8oqFv#sYE2r! zx9Jv_fVEzhkwgF56$Lyhk(8C%f_P4%RK? z9i!OsK+4sRP;-0tYCFD{=)=oJh8vT{ebxa@R(9iTQn`A;%uj~tF<#~ZR4(6IUP9Ko zxxD5iie(&lX(*YB+u8w{XMp&0hH5&!k(ATk+f!VW8Wqo-++RmK@HR;(=Ey}kmMC8N zXt9&lxlB_@ew1p#*&tNeCa-J;vU0Mk3OayVuVUBhNPFqd8}C4{w$6ohPI*1Sl}WCD<4lHO*S=K5s_>ld7wJ3xn{*6pj^Yek+SUqY4lZc_ z9*K1#vXrD~o8s7EB(;AZmJWX5&^RX39&bsTzHED`S0a6!B~UOU#!H!&D}j04T||TQ z(-T+*VXZQ8kbF+144YO7&eJBpo^K=Qgsd`kN5SM1j`nxfX?VrL()Z0^@};hNOQpg{ zi&p~g8B&Q|ZcIN!phZSk-sQ39UPi4I_Yke(kUOx0lp}0?ImUmTk2r8Xj9(V#_)6uxfy7{ z`J3AsbBcv9hGuR1s-wx!P1W-KpqYe(JmwaaWU`Q!kyKiLSvfp{j+I@V7#>S6m((jZ zIM!U!VRa#xIpP%>k&joOyY7DX`_($Az1zThnZL%L-O^^>(q%L>JiRNF}2FEZ3Hp?ShUT1oY0%k50s0g0F?+1x9NEwE6 zFilG8K%n(2>*DM^9SpR0dtDDTnVHuu zhMJ?1KCELHQCcT=IvVBWVYyEmNLf>zcGmPsIC;p8H78xxg&yBO?xEvDe{f~_h#?SN zs1b7MeI+M6&7-~!%B7K}443X`NF*+o%+IFSM=^7~M0CJTP#937&@F2i;m%`PpZ^-d zfdFw7^Fb%&!!=41uZ9oOsk`#uwy4uq%tzZwXapb($wpDZs_f>Oj$ruhel`V8DypS+ zr%sM#0y?P?)ovZCr^1=xYQcfHcc0b`<>VfKwAd_mxHa#pkWKhk& z^E-u`l78IinbYWuQw$h#cZMHTW0OuGMo>nP?y-gYCeLOra-^5i_(;dNal+W;VA!@ z^ijrJRpF>C_fm<0iv{u;zgK84C+p4YeZtrJ=(Uqb$E@J@sA2@wv^N=Tq_Y+0UJ+_8 z5fZ_6(88VYb~Oxck!_SYc|R`cW9!YV+t@g^#Dtq?+P>`;5JPLyS#i}4G{V^04xxQn zY43I6MbFRCJ~An=WOOfA+36YPrr5PrK1$)*c?5LBqJl%sIjR_}e;4+{3|rOr1f-?2 zWRjE9+2z*!_pRa=Tu0#37ZOHiz>dDD!rEINgJ$;zAvJPGa8zDE9W_*qSs!`dW-v)JIl9W!Z9bAdc8K_N*9*y~7C<=92@z+9tf%c9 z#-U^-2ZvwdJfUwXXzuzc@>k+f{4KU+qr@n3P>$f_I9&5q5faH`MeYWU5a+IB=JjO+ zn!H2k7Mf|z+XG2@4*3*2%XT_)MF4cmC@0g6RHe!SP`dBHt|INK!^9}fK7uvH5SopG z7n%V#P-A?7!QKcowHwGU);P)apqLS`UQSQ`Gx4qj&FN*DY5R%B=#>h`{bxNVt$fHEkSRsnV3=^gA&VFRvn%+b<=T zfGQ4_l%GH$n0g$fvmVG{Lk+#r9@8fDAJzG zxcuK#Q>D47P9#!>m$~n54mKl9P1@eBZKIWM;}Ie=-PK~zTp!yQU|?+$9jaVe?_t38 zLb+wno?`wKyp_aR}{BLZZU1+C8bEycC2%$2oELg99+PxOAOfa z=#LUSrF?+n0w3}}VR`&`e=e7v3$GiB`TFfYkD=q-;+Q~X5C)nIY-y1)!>`3QlSqD2 zhqEgo&@YV4MhGs=%{8MEsmjM;i|2^=DdOIR$7VG4B(sb*Z^9bwE;1|`mvdaDAFd8Y znH;|iT`@wwa`3l{ZSXH?7En3P;CJP_BLblY3iy=M0hFtqbAs(KjyY%GbVrZc zWSt{0bGzI&3OStfH!M%ZFe%{8v~hOeD7{eH0k7WyB=@avjUg9l7eUA?>H2a{_@QhW z9jxe6(O+vR>`5|;d39fH+rQ(aNm9UjhN}+RG%a<`Q3z-e3QohaPg*Xm3uSK(I<m^yXOBOCBksGQ-7=OO(wFbe$LcU z2-4lw4CVgCvM;rY(mLq^)+dP5gq2FZj<6MAgDrA0kQDSVIQPy!+_lR)Op=DpN1O$X z0BnZ#90_`Yl_OQB_-A8~Z45c1kb&qb2+HP?FP%e-6ETSy=>41~Z~AqxpJIxO*S$=x zaMmLkcWv@>W^Qs|EbKL=#Z(DWWphwv7)Dxy>^+=)l%?Y&8DGS;woxVcnbj51ZJ_wv;b zlYhlHR03TKtd5Ceh=x;gda0}}rWDJXF3jUlE7JLZT*?_@9)-RZ@v>}t=_d}A6q83^ z`RlKEZFgVpN7GQ8pp+z&YoXWmzAKJCjYBj9I+Hj#lTl|)U>ckfT$$;Ug1M@(wJuajZH}BlR~qDHX^qx#Si-3d%RR66`uX>6VE- z7PL>wPcVQYdht!UUen5S%E2|lV#O7ndzv10YMN5LutBNymC7ZJtyGG-vg<$WjFrk~ zWX6KzEfdH&Ie4WcLi;f8gw8S zTCp9}AaUc`)GV1h#C&8iP65O>xB6%k!?mB=j2(eO9chb35a7n&lD1GGCN|%U4tbgv zRsGu(nrLfA*bJ<(eAN_;E#D#pfbV%Wj9SO#<`}8b&@z`sWbK?@Xw%Tr zZa;=%S-X7Nl5%c`x{Q|=DuvPFn7i?NS_)^*EZalQNKc&8OeEM8oehoPTzJ`S(4v`; zWt!My8YGPXeX$EUX{{WGeynp&-ceG>Ou09Ny$FRr{kYDVTdB8S_zjk;c zJcDCSQ_eSo(eHj==&rBQifzfvH`}W#OXD*hk|dzb}9Rp#6VeF0RHotiGw7rOLi}=4Lwiu2L-U z7i_0FV2Yxsi+%a3=khFcM82*bZ<{hzhW$*}392P07=*G&=rq!3EdrwjldzZuJw=(c zOcwv;s5N0N`j#@GPo7~as-QpQ4Ip*HNN-2E-rnD1mVROAEI7Tu!>GYRE=><)C zJGn^NM$Jb>SK8J$3sbKHVRT>f?IbUbiL%WxXzkOOzs3>7Uk5x2?%jvQxp~_JO8CIoGTd5zF!NT1LqRi zKXIMiP_(r*LcT8>qd6&Oiw1@KOk>-$$k<&pV_r9l^6vA6aPeg)tq!cyT>zY#YjmWH zrB19Q!ESJ_)Nx-o-@qpY=nT1hU@iQ!0(Q zCwlw=d(Zdj1EPSAWSWZ{T?&YMd8z;1;fi^8g{MC9K8VdR9_Sn1Ru6BuM4W2o_UngW z49zpzuB77hMi|M_%6@l%Gkr%eamKGAPa+M0tJ8!^chZh9MB+x0yWIbVIi;OXWYG_= zv7yqf3CO%>#tF(uRbP_K-f&^98%k-cz92nJWN>Dx&M#C%$2qnKBDnixOj1Z))I%*i z-F$~K%GB`yMeHhyzWg{PX~yx&*o1nP?RWwE^)vV`r-_L8{$`wd&@IG9&dQsN}kEXx^V zGZIM!nG4})Q|%l;dXwOFck!FL$=Dtl2eXyo6|X2IY0lZ-C}p(!g*c2!PcBYq%f^&} z7A4ZZ4%7!PST?*fo+|vRtS$)}t-4b#5w;#}G$#}aJiGKm>C@|gqObKK0L~l6Km^8T zm4hGdvRp!+<8M|c9gzNIKw}|X^k0TIg@%t28jg(~zYJ$dPEwXzMi6?Xr1uoRn~-J3 zCP$>-8)1!-No&!hmX|xDzr{h84>wMaUJ~)xUiTc^{eI1t#N4mt$z6sHuJxi5f*;q{ zQrU*o9CJ3HMzDo|9L{hmgDMSMe9`6;b~JoTc;Hks_EUEm<@M$lDR19P7Y8+3wa!7= zxtxm4DMl!~+0*k9rL#6hCoVXJv|&Dk%SG~tn=DD0!9JF(#?{F8bwM%4q%H%_hrP{H zi}p-)F0i3-p%7_l1cZ^asITU zo#&(!gVEKI$2+#j&BAM*8if>6T3c(hbYHG26>yM~CAJl0fOllKYZ`6az7;2PlNE)c zbZ!ihUWKxPxu7S?Qeq%DQWlSt0v3~9H@r&Eb}sZrn4h_Er92IFi;;X-$Ipbo+)N6u znv*lW*00V}9q(BtM?sfC(_MMVj}if}xxY=Ch6|ug9zZn`DXd%zr_qL|e9pV407#7) z$UX9l5qRYKpkqa8mATQHSr?|lHAy~H}Aaz%{Tcy$#29HY)pHKmN zN&;zHm!EdCq@N&jm8Vy?ktqb)<6ND_l8w_8G2Hd;+qJ2$ngm70uHLekB1){3$~>_1zwo+Ws*sQNUU8YOAA%mX zS^Vy6ljD-AbzSDAz5_Cm_GYSDcjGRPbTXf)*234G0=Cs;?6Z!0^1@@vgi0smi*!(Gu4vxwf(}wdJo*jWzrg*=5$5?=K>UIz82y8MDyQ4~B{NwtaNItQ-Uz8D20dcv`;mFM z8DIAc18DC??bTlGy^Ou#BBGZ*D_|h*%|as}#%^J4L|Z*YQfUKnPP)lq!J5N4xj85H zr0wQ+#ML>t-50+uSIw}gWw&sK;Unjbe0$2Zm--APZb$8O8sWN@kWluQhpp?h<)7!g zh{Fz-5ua;KfltIrw|=@$ICP}u($neknKO30CkP>laldfguc<@bFVU{_ttB1Q2VUw223uc8QEEtN5B>x0Dr+%zdb zTfKaJFiQh~`e*Or|V&ayO8_qttS=5g8H_@`pm~LdotGpmo4_^=?@^Ba+{?c=hPQ9C*C1W zlV>2rUPG2{@u4)GaZ*7UItKi9Z6A>fJ+!-pNo3S=rApCVZx|_G#xDc+a`3DPN!qZl z#hOIvQ1yB$4&A$cf^ym2IB?6ceYq}55+z>k;A`5ezJKWD?55m_ahU;xIVWC#sx0tE zf}8pLGhC{&8aq-W4}R-wtQTi*;f}RM5S{;grP9J>SDL*Nud16@KWvU2CaO1=trZ(N z6{iE~>@BC*jIUv=5fEZ)N`sYOpGx~ZoYM~K@DZvURBz8~@{3z1+2xpw!?lgMB5dtq zefqRM5nxn$yWsnB2W(GF=&c^Zx4EbH0sO4pD8zg+kdsptNt|aa+V1$6E5OCokPGL= zkd!X-K_d+J)V+EPbXZ&h*7GJc04darl*X`n zRso7t&$?b2v%6dZv{#y5a0Y(JJ8|Z9e=hsPamQr60`P@9!C#lNP>cL+l<-lur}6+| zubKH?_t+fl8F*_yE!F6#4-ds>Aen@0>-VPh0$Y2EMRoW4h1YV6yyjjk=SU~0i+YZ` zMpM%EuO2EPB5|s&3Jdpeeaz&}_ynv4c`T-WLXu`+7kR;YEyCqLF5f7q80=#&n=;Dj z%Gb-FO6~Kfk;CaOzCcZFYB{zYRW$#)@31Y+Y8oXpPJLI!8)15~)=uRlxO_M zwiH!xPM1l7LfSS!0uP{WcP`Qlgrb=yil4m9k@pi-nKpP*3FR5s`ZB|m*Wt>rma|9* zhmm8{qCAvjrA_9A;ZOD;_D8gkuC-v5(AOcVcJNz?R&O5KQ+p|6G^{C3rj-G$t+Za; zup)i4MMS}yPNaUnRF$xkhQ-rJr$$H>?a?ls)Ld(9OYkg@fH!o0k$Q(x1=d(9e{a(` znABo~d9{Mo0;oo+s+(8wS}9;NRyCejE-fAtP{~F?i6H&9ZzT-as5FY>J&l|Q?4%VK zITudVN=`CLcu}$?RTGt)FxQ~5HX<_^)M!)7t+b~#Hd+0M9HB15J!L@JbTnDt!n1Ly zQQjp(6Dhnh%E1r~lE=ASTZ|xUr>L#aO_ah{YGAS(kEyiEw{0&meRlp+I%m4%+?l59 zMyc>so10kpC1+Yk|9RinJ?=XtP3EahEh(GVb0Fr9h6`3&aXv4!fI7b9z|nL- zqFpY6J7wS7f6-WXmk(h`jqiTH?%q16z1v$qLLqjxH`=17<0}Wl4#I&SF>>w*vP>!T zuXD$Z^}A>Ui^g1-EY4*2f=zLMNZ*`gEG#A(Mn~6i|JF^S+2VSv_0 z>2)v~gz1WmcG{NW+jQp!joIB(1j~2%M;7XkyXptH`(zD*noPdWkDWm1Nhu3dadP|7UufoyiRoW zBG14w0HIgcnhd+C-i@HsbE>;~pM4~F0hgr)P`Iyl;H~$i8XT2iTS^GX9zIM3_fmW= zsKCm(vK5FAoK-3+}xsXGH5kSEZcsUpyI)Nh7O<<=8Vd-KY;m^~>$1;h*W4T2C;;DC6IZ9*!cc0NH&lEvoCTlWXG4HOXy@ zo-}6-`rYr>J$@J$p!(aI)n@<>%N5v0``2sA@ubEUYO;tvTnYifcOPM>pd2h=8GzS% zujrD)DdDX0?dq;cJX2}?hZ{~#x;#waAE4wB;0ASvP5v?kSpAIlEG6=F_iL&gyTMIz z24V3bEkR9G1lW`oBz;8CTr-xZ(ytoumXd0O{FDq&O0p|CoKzyCln8l2mhN0&s%Ba=Ty*wdhNxRUUA$P;t?rkVk4Y5L+V7^OD9Yw~vY0ei8B!VXNI* zbr>1QV*mi&&Kw+mW|~qL3Ur{V?ntBGo0mmXMWyw|*ZS?HJCA}2EGx?7x=JQ9AKv%t zfc=ZG7U#&zt8?;#B3e=~5Xujc$!(%z$kSY}gaXjpyS-QbGTMCV7R%%%YkZba{(fCF zb=PLrv1SSW89(ove%@hywUhnO!zrV^+pE1HoBP~Io?~ZsbTwM^%mJ8TtXYjy{Fj>`qLqG5P4aWP=tGya|XNWT-X}#m+ zpNq9yXn6^kMMj8_1ZV^?q6nCaSMcZir9ifhG4yRx_MO405IW`BZ-t2i7{#>!xnEH( z$mi6tK>#RhY_w^O_jX~F=5D?>$)oz&0o%tL{6aKyb>i(pwPo6yk8tymukfD;Kr$Nc`cTE51xZ9%YJjGtE zy`1Mu{MAlz6G@G#&?wwGz78JzqASw-dnD%+LJ#-YQbI}%yDrjLW!=;|0eA9GWF$`w zNV0F^%eSkS(6&wih-Lsel>@F{L1QgR!lYLJHETdYSr{^(JFa2B zOn533YGZZc6vH!_7@Gv2I-s=?io3!p{Dwx$2tyQo9j`D$0izM#jn?>| ztw=&gR;~468PY9x1YN0?!i4s2@7Z#|GzIVX{r=&5-0$Tf_wk0_-t%=L%QU>qg|xU2 zaZKH>`-STst!tb}=EXF-mTH@}o3>(Hsr6d9cGYP5(tPA;^LF-kw#wx;YLf5?4yxgw zq-g{9KDJD&k~x$#h6Dh-Ty~dj)jt{>m%b&puT_+Twnxy}*}1q`&1aDai^hei9S;AI zc-h3(NMAtM_rAQ^`pUD}lxEUj+sEgDgbr#0?>{fR-KXq*E4)@4_!EMDGk*YSPc297 z{fFACy%C)Yb~RQ}FR=$Q+1I^{ZIUp{)>je*)41Wa*zN)ytG*i*-ZYdN8(}`FfHtk; z8|YD;I+ks)-vWM8;0+n~BQi_qlEuP(-v5-YZ@TUAi%cPsqj*x28s!hus zzB~1JHc?du8L-9Hp@*%85x7VLaKFF8lhnXJEj}I!yt#-sI#PJze|p{wU)#RCCPQ<{ zV|uTDHUu*7xYc(X$ebn(hJ2;2meWBQV+G>0(g7VmZs1OE{UXOpSTQM2@LSJ~6 zdTJEgZv&U6TLYKEcz1m^zT{FRb+gjKn#(i@vV?mOg-TTO?#>HQBlXNXa9{LcY<=e# z_l4rJTlhj9LNrawIlo^IbUgG%h_g`e#bT-flfLqE9Iw^NqR#YfsaMroDHt8nzuoam z9?y{T3~cS9V`|?DV;@^!yRaNF*q-)!r^GJHkW zzUn|aUgiqc%@A>O2d|m~P?Z?#)dch3)vk8^!Jn(W((1=*5J(i5maJH{okrc&u14+E zF0G+CEeKe~;Aj_cv%Z81fSohG`;1AtXVh`EcYFWgcD2`^*D?bc{b&5Vf1qa&d!Ezh zSul-j4vb)k%KXs793f7u7v>1)?TTN?3(?he5a#Z9iNtOVqDJjncO%R=Lt>tdv~?&% zkg#2e#OzWz)VVVHmz)6nTxfNTE1H!bI@6%pC}Ot_S6T+Zw$69=cZ4xN8ezaj5N&(T z*v4o&`6?Jn|9<~m_w_UG7y5eI)L!$6w~qIWQqEQ9=epnbdl`jqi8QM`VRepr&pqR; z(=eQUX!7X6j57&YsaZ~5-E9r`dEI1Cpsef0=wQ%nshVq6x!r84QwFf5N2oZs)gs}t z>x&%AmSG#g+&Mh){~idHoG04sQhh14Wqi7feWycQ*^ylA=Ce>n*Zm1LD=k`cL+Q`; zgCE@2-PhLW_v(>vT*o0|8-@H1Yb=>crJ+`{ zEKefq;^dciYnG9RA$$j7*F~g8Js4X4K@(txSK*_##k)6Cyl8t#0S9hF|4NG(Mt8pA zylBwkXp;vyiMrOxYHwEpQlk6VPcZX$ze<8;^+J3-aSvPrh(pP)eD~>{zje0qGB1lmHNBKh57}K7+nS@>o1~ssx*X zXDxymrF=)2>AM&-KpNYuS6(tk1fjLkL)4g@1EvEOLtMX4z>2}M58T&^(JeXj+!yQl zCQe%)hdxucA7x7Dl<8rHi@fTo5ypzPNgQYL^;Np)@N zj4i?7`{k5)%xEEKog?hp!lbb!83ot!f4|xr1O117@bkWhHy(|VnGmf5kg{*_p5H?Z zGm}i+T&!fMUC!}+`JS@v7Z^ADlb-F84`ZloB8_enR7u^_bNI34@^!{=E127i&FiB+ zs)0yt?*Wl_1oOQsy|E0ySxVZ$L)RG|jyGwqi+rzi*6+e_4xDR`>|wSNp|?v{0^vM> z`*mM<{qX(5wN{VOUVpCkF5R|zt`fA~LW*q%05w0#;#@dZMDl&V0bDB@6LI7IGBbDm z+_!4n1W$gYB`n9y6A6zB*RovueIg74LIg$)CTWem=ao=uDnf}u%5R{OgYh?%o;YwA zja=xm?EbzLE1HB>KIaQxv*5m!PB$oI*{j38qtcG+GgPJycz+n zU(S0|H6j(sjeVT8JT07FV7b1f3txxyL?mA9q!`QS(-G^&D+fOptt-C!{l0nm`X}@@ z`~IZa)vRF0iM5l-K-a*Y-mc7Z+1uo56x2M{9#* zY%isq7r?FGEeQ%5q}Oca*0+OXYp#0Q+wikjQhn(pjq+Ji1ZMzmSTcWK@@8nQm5rAO z9ljm|VU~4nxgfLDx;Qu>GSP|=*kYPvcI@tWIc%M7Ar3Oe9QYbT<{{{~>q(`swQSae8l^F9qhpTM-lR6B^v?3VU2aC-)nA5~ zH&cP`7~u5=#`m9>TiGsjK;mQVjRArhhe{a|-<;e08X52Vb-(X^zvh!z>J0HZeJBO?{Wf0_^>-+j`i35n!RCBGDiw8%5x+6Xz#oYesg@nttN!2D_r;Mhw-|( z(fkfubeKBFQD1JQ>xVI4iuP`=KNsHZ-TYJPUWb~iikzU2#)G_XyZI;nUN1u$I%jKr z7FDI6WHn%SP3~`x?nCDuBxfeZk~SZCsx>$nnqEh(H0%r$ZX;0X;f|Cu;lBYc(u8)T zTww}~xSt^eT6ixV_9h+E(ryLMiPt$mZvc+<+Z!zz=Jt1hz(3(Y(s5|Jcr4g-zv? z>s%IJiE3P;XW6+KOkOX8Hr2OF$K}|@pTs2kQkJQaEzE0LwRC~uRUGGjLX(AZ;n`A} zV(qR+O<48qs#LR@9qmSBQXn?rM4}sMb5w; zF6~nJh8qP8G4drUxVy zpLK&b_%AhouE0449C$jK_L=sUagRnA^ZlM7)@nVzYwB>LgkgG>@UfC^WUGX8r5UX+ zqkl94fcrHa7C?Ldxl|FSW-YNy3H?|A)^3V*l2y6U>Y3Bf^!-L!a+~8ym!7pk{O!>! zZ1S{3)`)!q{)E#`xg9b;00>$gQcp(6;X<#baGlGX^{gyg#v7qGFSr@duOg4I-P6(8 z&&~s(pZPp!UiW0~j_YQ58#`uPQC2=$r=H&cu-+UxXRY`Bo-?6r5yiOqenU3z_Z2-C z>#Ivo22FQ|-`qc6Digq(!imc8Q@(FHt>`M(fzZij2@!~$r_;v==b#wMY3M>#tHNAH zTk5W$ay1zQ(4`NJbYho!$Y4};r4Hzq#>nvZP0Dn;QZBNP3{&uyqT)NQnIo14C)I){ zGcbCsHxmzG$ZcFKUU8a!9ydu+y7#}QAp03)@C#L5e<|TH(qdM6KLKwzh7@N2us#E} zCsf1~O~wU#JgD4MTjE-Y`I|6l@utX8aR#8~iOH2@_2Z|;b5oz;vTZxFmqS%!R3}oN z-@94f_-k@ezbLIuWh@^TZrA#1l++TYx;tO_YZ)Z0hTG5$q)VbOxpx54E2iA$o`N?9 zS6j@#U8^&!71R|rTz~Yvum>8P+Qd-{Ei8GltY1)grTfitkqSiWMo5sJx8(PXr!G6G zQ*S0w2Xs;COCHk2MQUavQ))y?1sZgXCcXB+1XV7{uQ11?hQd$X;m(7BAjxbByk9!_pC0sAr~FZXpnT3p4Y z5H`K8v2xi*neS_DLA)@Z`1tc$8&&!4^S!9;4cxE$`nhge=E}QWf27}uWTV+y?c~r} zhE@snGJOJJ4e9smrl0rC;5DKzImft+=gYMqw4^*sqiWB>gI_I^dA7`h&4iP1ixdyhwm3K-9nyc-1M$ny^vM)Mb)& z>C@>_kNe9KARHNR6)^6s^t^OYLP;}8`uu6M)SCJ>U35 z8KOS1!+YKkc!oc2^>{~Fjc{E}+RdJa| zms7=uDpeBSx^$!3)$BhNRC)DH=u0M0&ip%EVa1T=VdlMz5MB9XHcJl5e9i0sxScYA z*ymYH?)leKXr7jEqej|R%17c&tK5{zsj~?Nu(mP1S9`se>YO+>bp^;gt#2&t2=0b^ z0PWpqo`!MHXQ&Z%g?Q{*8+EV2OTXL2U|@OB$&7H>;%QC-^|QZcOQ~C$L}jiYX9wCQ!~jq;dLnbaalm^gJXs z^8$z_@ZuBprm$-ZGbl-T%)Yef5)jPaLS{eLw3u&qlmCFM00Kj6%S(AT*P}ZTyWSx> z7Jbv%hU{r;q*1<+x3M~Y?EWrThC!d_9oITY!6bbDdEwpO>e9(nr<^_|w!^NS6T;YU zgi(|NkV6m7tsQEymn~+QHb<8|^`XVdmprw=u{ulxF;p9|bxZQT8)pLZkaQ~K`YuC?d8-|rv#89(^p>H2qP zzhxbpYu@A}&WqWog}%eo#@-2!7Z(rPmq>d^5Uw z8+C#)pQ4|9RQ*I~>K(?WzIBt!$wu-EIK`RV!)XYfzQYT5BOB)6j4djhEWhT zVmQuL(){6`j-Nab`s=ey0`wIY@?sLFxMIp9*M|vtGy^AxiO?A#kCUQf<^nHetlu)t z_ze}%QDVxeaqI2MU$PPj*CZvXmprqs%2?I!x~i`uQJc!5EOWjsq8V@L-MFZy)umyZ zPdpVdkXIV%JmcLFxduJNhm=Fh9HQIyh1Ci3{G_wHHO^GPKv#vf`B^o*wHez zU8HsO%k#`ysb6!8c3bXDuo{wl^AG(DyzXIUoH~9cS9a7`)g+b_-G)3MD(w5u)%tqH z&Fg;uT=%QdJUM6{aKO2nyBK(lfcdu4)l)~CuGve>U-RPYi8Aq&kEIHB-HfVj6%3Q^ z;?od)Md7hX5FOI5|LktPoW3L}g08!(Bden&nt#Bcp72Q_}P zQD^Jo1)C>n)2yO?lY!R>QHGg@nisS(uP=GZzJy8`>gKVybV^Maf)ReP=^j3MPP%J= zT5#<*($*kr!!QG}lU=#VyMyH*#_CsZ|H`A(0`yRCatS@;EIE@|*p2qd&HuC?t&pNG zJ`*@n35FeGUnhasFUU$MWe#Ns`?I^2dl}#`pHFW7z9aVoICHLhZq_T*=`>~RYjgDL zW{BgI*6XftzkUWfu;PYez5DjM`KJGjo79>2Zm;%Q^gCWZ@7Wsnb<@u^x01KMUY4XF zuG_V$4NOg{i}TSO^{PX-e$;yV7rQ0XnxUvPBFOzB6f;99hN;~gwv3vcE_lw~_KR0A z69tA|dG6%?A}iN!N}p+Dr5nMP^O@V-K#}ELDQo?thF!QQU&kwL_fU!Qf|88e?j-x5 zG)@SrVTH*>Du+Ae%CoihV8tLiz2pwxd(!4lNIGFLDZx*o=R#1dQ z#-(~fp2H}|6_?M>OI_s;jl(K9;~2`*@}`{R?%C9dMp(1iCZFd%;QOuN_I52*dH@o& zRVsUj{k@UVy<=!s&T!62$Q`{`w zjHyW}J6y=EEG9Eh&w-;4>JYmAPh4zsfA0)FhV0Y zM*YfbT)nd^mOMe?p`{m1H{&SQPHj4r)=V?j)96h!D_W&`oUyc|=!1Pm9NX%_IxeRK zLwitwX0y)@9+x#`Pla=BA-!Z!DK$$|3T<9lDkam00Ns*qe7yt)JVa6FEB@TKP<78v_QtY&9%iq&Vn2w!w zz+~TE@8l{=Gon+lOFSQLUYHct$-bB4pfi93wAPLQ=!w6n1-_TEch8OD9#$+6uo95VF?CX!WHGvd`U@@%OK<}!o2qrWib*Yb3ncF1U=){Ak7qy!DuLWbGbdkK4+>Hs-2 zap+U&ja*#9W|g6F`@MgN{4UnnuFw~$k*g21Rue`s28X+b8xdq&m#aji?Dw@kGT&uv z&sbx7uXlgioYWUBukKJIiQ36KKrwsIOFmWu=mt<=ZUA#YjKB2dKcu)GMEMQ1)Ox$? zMkpa@M#EimX!F>rJWG}|DXa~I(+uTgODU|gfh*~FWv<;Y%G&g}zBx)8s0l+zayJ5e zJ1;6ii^Z6{8AA=5Gl$f>T6~5!9)w1>-m%W68@9|mUGz|$T|He|yyD%W?s!tvCT(?$ z+h>t=07BJbmdiKG*FcTB+sY4iqwSulDv2?y-=idTq?@(1Pq%+<#$Q1{h9$; z*KM5%^ZmFl)oHwhA`zJ+Ue0MjxV6i;Ym3W!4CLL&_l0ry`+dLA^)&Hlw4@aYI>gO0 zT=}tf6VT0y-S5jn1kHNSoWb8ee7{y(h{K9xO#Qs?OE+@P_K=svV7+I#uR063)6>v( zve{bg+F-3>yq>$rHc}e{0JVR=wE-D6@v|bvVr979d)Ra_7GO73$o&XLAv@gRxTrjh zcAgW&gg)-;IPSKxNGl(VELO|4FK>ChTahR zQ-b7jVCAlKH$yZ@R_|;tmjP(r%q^V++mzhHg4$i%k>}uB%;6ib__*EZ44XsO;pOWd z^E*zjCMKiQ|!@0V+}e_G?~2MjDfDvNLe6+1`8d7z8FC%lrg{jlp)Iul`0}y1CWQmv!<;O}eWoMOU8# zUt7{c_LmybtBy`$2E|Wq7s{wm_Pw{Sw`T*(q;bdo0qGT3{X$L`T3nJu1)N)Qn=bmQ z=&;mP+5oyQ#l0`p+GM|%G)EoSp56#|J){|?T{%u}|Bvl782aYVz9`xF_v^7qD2}OX z%6c=_oT2aXW*yB{549Tf&udSB3U|u6I|L*2qK!`U`$nbja_YFHqOJ29=r>q;t)r-s zb4bJ7)UKRd`I3Y>(-aoSle%hgG2)8DB>0Snjh!@Mtl8vVd!~p&&#`ry7VR0 zkX80Z=&5w%ziTqQ<4Rbp`wGHkY6S2v^U4kVfeQE+^XMQqljLyVjlt7GR}@L)9L z-3IUbb@LDX;D^xVWp;nA#(#Qy>yxDQai{q{Sq@)XeYZC{`p?_#dGHQfkwwbCz!*2( z9hmzke%|-|Ci5kZE+@6Eax2GMX6?dzI=Y-T7RNM)=RkTDpALl_o)JP7!Wm*66$}?8 zk^QI&yW!y`_OSKNc}Lmv&mwP2Q4e=JqVhfvhbD3R|#teH`ZK!)W{`KA>M7vGJIU{jfTZGg zWEclJ8Fx6N#HJty#2mX{_xPdPeL&tHayP2YFEzs!(55&#(z{byhvm;X!d?Zk%?w;T zCtpwVT$}1g`-pL{r_f3##2!-dcFM1d+X!U}^x)!MC|qDx)8ULsYI9kp;F=>KjQh9* zER205srlFo->{Tp@zLw4G`~3(;CbF|##!36Q4r|T9${mhDfz2~oJl)jErISeHdPL{{jxi=v0L0sObvGdUQ%t`t?sjs1V|lKf9R? zV?qyi4B3h-v&s{lacGhBOqYY4f*Ig+u_kx(D(i3anOtH7>1MwF9UwZNZKCH;2*x<_v?PIVs6){^a2CPsGg?Wo%>#VC+h$i-F2;Sp|e(1h_E_HTOd$;Qk{-?KT5{jNQ z+o<2~ACe89>-BiA_HOSs#1h)0mJ(B38&=Us`ypoZZ=PNT0CkH?yI?(s;ZJw^K0Mbm z)hgYJscZM7qp4v#K6Lh!k4SO*H}v6(7%GmhR%zv%*xt_`VW^ooU<->^r9vJ|VDxwK(M{(u_VZS}&fHw(?kF9L--2HX~HhPikKAW=MEzL83! zgzx)hCoR@K^-DgayOeBEb4kYtFAwGjTTMj@3JPSu?I67^B|X&5@Y5&=o4aFO;Y<=y zJ~J2xa(7aCgfved$$WCN<9zd%QteJ5=zdFM*jVOAPTy)WYoK#cGlj-#)b(nFdtE3R z;WRG*@P_!hB0&3JRBKr8D|#b1m5h2ve0Drd(Y4vpaN%AULYl@v(K-vJY+gMG{Cqi0 z*~=`0Z#;O({hd60hV{=^=*w`gotY&^BRv(zs@BX=@LDY~*PFi7n%-!BKD!a1rlyT1 zTK;W|HuKhhIL}tiw@91|Rz0VTwzlXxDZB-9dpJ6*jVRQ?b0r*LJ80rdaJUnW8`S%70%qS# z^Rdz^7)4qya%P(G@p6z$p&-xBv*HK9*Gx-q^7t8VlVtc=FF1mv4~=!*b5CD)&Lp?} zXD(LB{veg?CtKI=_4y0{=s)k9ei#?7Km6xfk#(!NM%n;5QPwV7fl^x&5O#f#k8}A~ zHU&N0t{6!DW~dD7Oq|r%jyByxSNw^=H?J+R{4?(P;f2xO?P~oGEhCWUr$APejoX^@ z#HD8&CY^QRW$$YYj`^0(|M1Uyo%Zu{o6MsWw&W7Je!9lD#b{TfcCD?W(py~oGrVO~ zoiBRQu&)*qdL0Z>$NG|N?Ybh>1jo(dIQ8rG#0t+EE_~N#`839L?G=Y``&VBXSdU6o zMKcB6WS>-p35~YtO<^^T#aDQncHVdfPV(X8B`Zq;*6rc%15mK;Nli91TKK(*s%A>r z8n%v}TrsevVT<#?C~B#ZB#SC+M+S1&98!%MYbpcbS&hQ)YE+}?u)$Zp!Ym!-ywb!` zT20bz@s3n8eUVM>M8>Z70WX&?-3)BTN%8^*h4(-=OO$zznA1q34;^9I(3VP+3R9L& zUU9!QX3=4r!qyn?Q<+}=t7}asB+&}znFGwgrr1+t6NGN2Ski?pNVER3G+4c8nF{i} zIMtNhm?oew2jQX`LO11y`GjH)aKAr2V~Zd!58&l{SDFzCSkWSG!i$2JGrB4$Zt}7G z<$}428Ut{SHUX+$prSJDV6v=wY|BWRX*?~j_B!KY>J{9&-{CO60A8Rqr&S08LV8D&qdk^7l5gUZDH&u z`P&k5~L${9m~p$zRxw03I#Wg=RM1fL&?5!yGt9IH-K%0mXX^by;)Z% z3u|bpu}0R=cNXhNaUqAvT{dM7ZxY%!c3rJ@qpV}@-(kFNZtwQ`!|jc(AAF(tnS$lY zea(*-{JHR6QfbUGrwQ8`#U=Za!`i@Fvg;M!ub=B4H{b7KPM3O2Ij;3B zQd)EA`FaOU9V zkd^XckM;Gn%}ZSn?>5>$-;fEMT}Kat_7?vKKbT)EOx0J#dL}3dR_teo_aL|mc*Rv0 z8du!~jj;Kxwg{%k?k6;|zkV}P+?Zrr44S5`Q-$zLa3fS8v=Zgb`^>V)Y|9Y?DaBot z8+F~p;h7;z-KNuUnv|g+7usGdz zcLI*jH;gc!+qW3C_M1k?j(}DE;P;HUJHFI!0A@6;F+L|-zojWaI!{93P7FyiEeCw- zO-?cnow=rO0nATHXjCw3N$zh32FGpbp}ki5Mu1z)5GGPH)fsT}hrs~LQngH9S>(x; zWN+-ojb3ULLJWi!YNK;68Sja)DNX}%`tOaFp&)(9diLVcDA9@B1Uh8NkLkiym8RKi z!feCAn95uGIL%g~ewZ5}?#YmH0jk+)^&e_oPuz*AOxg$gTHt4XpAwwZo|(F0VDfU5 z>*qZ;{Isbk3{g*(=iZMk2H&r>U6u611`Ghftth?4sf}81C)wT(fR8e`@f1s0OS<0R z`m8(^Uew6u-?;6wQB)M26OOm>?n~(XM^nl>g;3O`HwvkYBlvC1AGn@!Iml$(NF^co z`C-)Mo%zhjK-i!28AACdQ_7ooczfeG7|1nWlc&z_Whxpy%kvb4xj;=|U&UMoB()Q$U9x7(aJI?*NcRCRK`&FsMzyBrOH<-9^J1D&8%U5_b`G(kXPy@@7RqU84-}&z zEhHR97Z6mi)suRxO(Pr^+5#v<7$CmgY|tdHo{Vf$0cG-sGl=Xe(DL^;tKus(yh0k| zs_a@rW(uJn<+G_!Yk4qtyXJct831%sis1p!Wy#Zr)WCUt`5yY6EP0`h;rGy9s_N}q zWOczAi_aQMjbw`obe@f8H~gs;f0`t1=9gOS=2JZ*N}9CDNOkCq0^UvmS`(qbp*3Pj zTB)*#@uH_(^yM)o2L)Btw@qIh&D;>$yy`w0>1PqFjvQwDRa4AeDqYia&CqD}jr(=K zf9M`J&$rM>Yw3f77 zA^zMNftLaSGY!0R%{I72N8Hi0ch3(9>!!zFHFjrTA{ILyLX$idx81JeY(HVBBb8-0Q@UtM81a!a*$lACT(K>UTPRi4<~c_pJ4t*U4JVj`lzyK~+n9?+MgD_6 zV;GIjYp#?$I6f<&+#ZKh(-98*H_TpOyf;d+`uXt5g@Nw+xxpo`i2`KRmUT(vRy~^x zYtErYUSkasv##4KR(QXE=!fvSc}1~gTj>@0EzNiS{<+$F^;vm0@N2DD@A>7t^+}i4 z{Q}zSe|qEgzF+tCbKxcC91vvWbw4nl6a#Tp*7J7m{PpU~%VcveUXfCy4qnou5o;T1 zdk5oi#!cTpG$VBDl|B;3oI1jK4yjeUJe5_a0b23MOJ}YT&VtjVBl|t{S+ajdD@rOW z{A>fmt5$B&0WS3Aj((L#ay+Wi zMuY>SJB&-s@yNS}JIUM2?=rZQGLrZpLs&!!abN^ahf^UN{F47!q6s6!kSb7q!H630 z27CvA?F<&l-w;b-t9KDcC8)v_7ItU?xsx-O^}UvAY4f4`>fx+cPpbw)o|bsK zC*P@Nkb4S7n?owORc2`G^mQJPk%2eY;G(uv3!|ONhSBEYcJTeh)=HtnzSTF!Tb%3W zs~~znU~MB>t;a6UKOg6QaVyejUey|5UqlAuY`MH)38e=JZh@4V+-McGzS1n8Rw@}q zbEW~_^WXgVPGu5@GmL;WP#VRRLFW5@$d&c>TL}_f1%k}p{9OL1iHtMvo#%J@%ogd} z$ASgw2*$dXXiCVkc7)T2t4yO*Z+0$>xvlg1!GFdN zZu)tvJ5M$bzS2UM2X=k!NSuJRp&2P{= zD+ykP^3`7Qk}|g|$kpHWJw=C|GaQ}cvdJnqFnjEkj()69s+i;4>u$=O0B5Sgt)ga0 zNX|dC+!Do9kU+RF)*_I*Rv$?yi(p;U@c{-BSW7b@FNZ)?K*?N9R$r zdF0G^1~#gj;l@=hT9KAr09#&_KKb%lrX!40jx}A>Nfzpj09c!JXAs)ecR=wsOfb95 z1XD(WYNC;QXQ0U>1ewek(B1E>swlR4>jV~F2E!_uVQOqcN3!p+lSZxs8X}4w^#=447gknk-W#pjnaqHW=_-<}2GERn`8Q(n~wV@t5Mw?0V9YLSNY}tutiPim5TWG16~zAvF-C z{&OKjBhc)-`qCDWdCaA<$wDX6k&c8O4668~MyQk2L)xG_A2MSq9q-^C5Q*3=ywu3G z5*K5B=oJpLy?<^ZgI}kI&}*hVu>0baP}$5W$xE?+3K9sP_3f@O!U<5f?z#>e&WAHO zR9%hhz4ij@;Uclihwtr6f~0bq>@_%rIYq=)UE}H#qJqYU!`u9v?y=lir1NLoQAMFt zlrzda>lxP(3biX&#RO7L`G*|R>uOGj_&sN}nmfVwnqzD*>?i2({a+;W5*88&&B(#u z#dxC5q(aB9mjsen+%D@r?;j$S^V;ENYEE4rK$w%=Hb18^8V3sd76OJ#nfYg(g7$lF zirw^|{-5`M@H3-+(Fu%o@|c|AK&lnc`cMP^=@>G)){6LAE;KX(>0Fd$8^CfIx|LZ^ z3eG!SXT$fB?WxbT;nMkJw6{0~TU-rh`99;OKi5q^?^%m!FwK{}%ouE=wJnz2E+&@9 zXS>!(LVI`Zm|IBK=r#GFtNlD_I3QmnlxNddOI&(JQuLUC`CLh7N{d3y57b{y7m~K^ zLU+|?ruRney;ey8x>8>293%li`-e8KlHJU~Cu1=W(x;)et?j|&q34kn&mLoEWAVmJZjGd%##OS#_%h=Y=TMt4=mo49x!46;J9CVAY%aM`Mh- zMf~TJSdoNTa^N&pJ(uk2Re5h#XAn6IlV93e%1e!8wI|IRQdV6C46Ki|^kXc_=yrUD z5z6d2nd)b1=3I#S3o90FBTJ!C;Y?uZp()p6DffA4<`VY4ci`=1Fe=e#Q$EI-H;UxB zh0f=u1$kbU?>^$mWy~^MVZ4s z8;TKD>h@t}k4?!(XlaY%{DfpGHLyb{e~@P7Sf@-Tph)AShaY5~vR!veY*~UH{I|ZO zlp2W}(geBl+qin1Mn!H+S2>C{r^9qo%dkTwmB7#|c7JhjDcG_^TmPb1{%Fn-w5Jxp z4zI@8U^Cs^)<@LSF#*=LY1;$~&7*l8d3q_SL>5y-WSc2N)pQphbcSJHhLag?Lnq?E zeR=Y_m6-L?yNT_Ubd!`JEyViYYBgftTsb7qw%ATST;y}M`<&D$Y(5pd9u)4reuw%# z2V$GDi!j1QaWZxK{B1I|2k@#%BjjhsI>!ng&5vvw9j~r6+MYQl98pGjQM127VKj0} zx-7LOBe|GTR}W&MwSB6Zf#5?0{Alk$coo9RO{ygHvK>FJo z1+E!vew=eim;rJf8!7Ec*WkQDS*v=kzT63XnSBgoYSxwmdmrwNmKRRnR=J!#9Np&qFjLmFX zF)`5|x}CvZUu5mmZ6hr-V|%K5AVm`anK!Vgy=7~si;oHY3*EVNWgd^O!VFtK^>pO` zmO<75HFz1eR`EFi(acmOd!s5KYg?L^mfY)|u(YCsm!1vvF-5Dry$^o&bz1Svq|;%p z&a+(xxc$RO4Iul?L)2>6_3cyY2>{!~Tu)-X5mv`+G6l~{fZq?81#1J1(&*cHDYFrIUbPe0&|Eya950)&04j=8CKw1@#9jTqLR!#k#v zW8%hBS{4)ZX43#fjad-^&J&rw!)xS{`Z?K`8ibH-Vs{r$*7SkJ4O3cURp(O#BD4#t zC48oGqRDzquwD#{RBSVn&LtMN^^q;nnXZ3M&B;qmvjfMDc+75IaZ@udke7O2N`nOnE*{n)Pyq z6QJvZ+b#&(f>*cY4h2@vW65u-^s=gFy31#RL%v^RS04v073lRp)UNhUH5ZwNPq#Ye z9VN#d3JP&MzNK#7^$N>W<141nj zW!=mXbq)uItU2OntIZ~HG4?p_nE;$yM-)ah8yAe?AROeXk)uXs%tvg%9)%{$t)~F553UmggWW^6S)h`#o-=!{k(7b z!OsQ8_2>G7cIn3!#Mvi3MZP>Nk-|Rv*3I%k201MV0_#V67SGn(8&`Xy%Ns#&zG-P1 zLk&5`C0}|Cu4@M5Z#|#1f$`_HwssC+#n1d&(A{S!I^IG-<8)IxGJ_Ssa^*UcwI?G} zWT82#+I#WWx2bBJan3;EaDu$3rd_)IRh+M{Wnf=s!1`IzPCi2Wf#1Q=NLzK-hdJ6m zOyJmv)~r@F4!G8c3{g&oG%Iufsah#hh2RJt>Euhk(^ zs;qA0V}K7Y58`x%dGbL0qgY;7eGSffU)oq+NPyZf#dvXti~nKWb@kvfVq1;bI?_h7 z1Z<}1sdlS19zE*Vlz+^&ufCbH_%@={PJsPP zyG68coUesn1vXL*tk-((-Eklnk?KM{b9eDk?+=eECE56GgkG+hanzZV#>I361J+!f zgD@4w?$)WkNv^4h7Yn~e-gT|#oTKYZ=+EbU$$Jg9E#FJhw9TD19Fx#yCdP3@ z77!w%JCtQYI&FD-%Kj899GA1X`AMj>cx@wqYF(`*mm~JLp=U1DUOB zM%~cZATMia(&)~X3bjcNU2;6H#p@r1LSb#OZw^XWEjS;jro8vjLuxO8^<_udtLS23B6dGyy{aKIALE-bTM(?o1;H&Qudm48L3#+OjWAA6S z(ZwWjYM<4^W+^Jhm{1#VV=a9wU;-CogF|eCEhUz)jjk?^0yAOMe9_(&Hr&7+)JG%x z+^!mq!ZFDPKeFt0tB&S1eqX{Bg7aNcUs*i=|?4Bk_J>Dl*;^ z%fiee#!mg!lsPWe%L2xXxr*zg5h%2+hb51%$rm7WxJd0b@^j0FvaUt_=fxcz-);zF zr>j%9dkz|Np$F%UmwA*Jf-~$oZI2$|OdGF0f32r8+Q7S!n29$-qIQgJHdLxZQyAXd z5Z4&5o904*Gh2SWOeIsa0)PgIK|MVU^d0w<@X(UylY z&-x@y%u`$+Kr{tQeN5>Pbe>cxTq&2c__}pNdWp<&6gaIpzL5*!#NIj0lb6Qty1om9 z)D6zd47=|4&!v$1=(@`VIx}AJy`GKiD~*tKPu5de`-BcjiTI{dT_5GUw9uB3XK*!nYb!z4 z6Lx!>pnD|X9R4zMNRB>_fCs<(XSq3hqv^z*5kj~Ea0I1c^sb!X{nR*M55nWZ$~Fqg z^R882%Dl>|et`r@DIcld{&WiRCm_)g*}Zy~)%PM)V;c^pJZRyo5knhTN-2hND& zl*8-JIQZNh&N-Q~mxSzZnE2*zSL{fgkUh*4@Or@9C5bTHwMfNBidLqlvgeH<-m9}I zIZG{YgK_~mdAuyfX`oP6oDs*t^UNVPK6gpm>o8P4Q>~1*_lg5ZrymW|Q|G(mbsJ5u zeD8G3T|?>Fre`l4^dWM$|Ux6VL)Ff(^Fmq*3mP|xK`SHY68?o zkWnxQNjXXi3B{+pD?MB9`U*r!jcSs7iOJ6H$!vD>dOsPj4Zm|iqv=Yfr01mU_`u z*|!;dG=hAXrLW%K+w0G(QG4r`r%aN3bprmBv-JmxIAKnpfEjw4IR?IvMR0m^W4e*% zDF?_{cAS=6EXL<2LpVh^qggdf!E*Bx(7!^*iv!OB}*f(&jRV&v9}bs@bnx)gDb zGgrckaNIG(TS_j^*yi&>N1d=4@TxubZB6(Hah7qA9R(!LnUYKw90$DBl7LDR(`QVZ zR%V8TNh!bmpr?_#KwR$(yY@1tX;h8n05O_#^!E8~VmU*&T&=HrzO3e@d!$}J^fP|0 z`+dLWhD}g=H){Qlm};Yq_G)iHqJ#_g5*}t$d4t}yQEUN|{oUT^YxTmiJv?-vT^L>? z(e3eL)m8T$iD~TOeO*?>M)3*AS|$8$2GCc36{jp~8F7`d+9U+;TAdWlnddb&j(tYw z`AcehD^{cV9YQI>RvlGSgF>1%Oh`#f8{T{rC_=a0L~Nw@rcmX{Sh&ewvhpEs!!M?~ zp%VErei&E05r)x@OJ?COY{SWtQjwZ{Lm7is*}A5J$1bJ9i)B7xm{QS;E+8EmWIeYLYVZ=n#X@BICs3y4 zAkVk}*%^msWk(|&aV}P&rwBN_UzGtp=Fb4oF37SYwVCC7{e~?f9*WlQkiN3Jz@%Q% zzo?OW7CI8$X!a_S zmQFb(7~!sK3aeBKjr?8MP?zUy$5&!qAr;F3z_3wFjbLMR;}`Q1Ft*+d0Pd49oaBn@ zZr?_%;SAU)WG5K~EG^wi2{)tE<|seIGg1x0h^M4{pMha3q}{RQ9vpC{VB=wfs$6q= z>L#hs>Wvgvc5PTbusKhFEz;~TfRMvpey0JFZcUR`m~xP-=PYmap(K0mNt~WVeCh)m z93zt%Vk=6kii8JkD8KK#eE<0wC|||XzzN4BtJ0B7vGl|m!9D9%V-eibXo?M6eT4%# zSqYf2EcE+Y`L7?k-*=x8wm<#P>ks|mHDAh`Jl{XpGVjLy`WgS>n;ZSX9|rFC&G$8p ztwuNB*FAnN_kF+)`g8ry_%r@snryi4*AI))sHf*-&F=DD5UmnHr&D8YBv?dwsy9*r=(d`_zc;M~RJ);;5Tt-CQ> zT|bO8^|LE{GGlDPs{!2H{yCO%V2aRWVAW)Nn#{xUCaPiB^yW*@`t3mIx{^xG`6-+P zO4&u_e554Gmw7W_4Z3fz3qZwH@?-f8V~AcCSH|T@YMz)D2KFgQxOKqSSNS9;)ox1@ zgLi03ujzQnlvnquI~?&Qm6f`^*Ht9+R9A^=drDjo%4wC#bath=*vno*Pzrd9*u{m; z_}TNNfPDrlF{;R)i%qUcCdBJ_bJK8;l!0xLmFwZ|#kjnje@V9cb!dOcQy+k6&PysU znhP2^R(9PHAuiM&cuN_UqcJJ~;fVVIf61mw+FV%fSNN770oo#d6v{F}s?hL~-EW^M zVJ%>ni%WIeWa%7A3|Uy=mgS{9-Y=!l+lBWZ0?VtT)4WuJ$j7PYSjvnIj&1wCsee+= zBxb)U$V#NAY1bX7%;>^>=wVL*@>n$U{z|2!exio6n+4``9`bdmX$CIp3~H}Ggm)oP z-UsjmN-xLyo;I9Wy;2@)4EI_emPaF1fovY~QVm4t>3)P%kIezkR#`4r9;Deq==HD? zu~0rl;yrM{AwYVXkLaiV=CNamK)K*eNiz0nC|zBR(BpSgrMEfhAYW^QsBw;Vqnf5O zy;L4&L=v}W!YorX5t`SI;Wva*VN~@%(^ht)UABQL(t$Tv4nxyi0 z5T3rj!CjpkhZXeT5=~{3!=^6n%hHBBb9V~WDyW#d^Sg|Nt>0sm3_y7AD2YUk(i%!j zCOPqUX{_%0c>{m?pYi{qKfU$Vr?c^A{Ph1H|Ma^z`k(PX?>}fXU+0p(-#Ry#N(8ab z&tBXwIq0D?H5Ml8*Yz%aHW1knU2CoF~W+#dUQ&6*tPLoA@PTRg{8>(+I&kQB3NTBXEJ+uG$#Lf++G%>6g?S zC2ZRPJioqvN8yJdAjLa91>@I4I#MYwlX2DM;44)*!{kqA%#%@6!@fagrW_ZbI5D}D z?0?H z;phYv!ncB8QW!yV!e1#ZyJZr-N(5jtWKE_Hw97^P_I3@piwgye+;`=AsId%*^6ZPV z+s-!2iV8OQiaI--0dZl@TD+t&=!FYQXp=koZO;jd`_MTSY7UrGvMZu5N7~vpmm^`& zxkS}Ln&%VN+rpMP&R4o}6f7Xp&y_wo2C3_>t znnKP*;3$WwCym_amp3Vc(%i%N=O}Mjzh`esz`3N-itWm3Tdp*+9FrlZY`w&nN_;+SUFAdW zBkcRP3XQ_aagc%b`?9}Gx^}gf$@qe+IgK=brj2S8Y~?8A`ZYCTh=byFuMe#+gHJzp z5Vn0|tx(rI3%sqteVsJ4_Z9>P1eVKJo#CRd@ESv}`}H&K7hY3;awE%RC3z9fnuD)4 z+N+ToOGEYSN(0GyTa5Y(kpJ}~W822x!)(KG#C_FAg%ayMLuTf_j)kDT5lrx=rl50Q zTkgMPq0%s?#UFAU%>^u%Ajv57wzL;js@k^-F`yPOxm2$J`pFSMm@E`4m<7H(r3Irj zxsGuem|TspLoYG9@{K_DTl#DWC?*nubY;ykSx)}+!{TZ3MHuH?r%q2S$1yK2MP-(9?a_@^Xzg+JUVguD%mYfow2?)J}pPs;8c zq665rscDQfDRP|p8GE&U{f;F&El#W@2dqwhL33)CeO zOVMfLVuMbWV%%$PiY9UTMaY#LhF2$eDKfCAMdD|I79w$YKcBLrPJ(vvzFC-VNhh$6-*f@!%T-U2jio$JSK;?ahX(_zn&0I#- zYv4bQvShGbCKP)#qS6O=jft!VF6P0afdxsFlEp5ZL%g?8!(xS&`jOFVQkpkooRqK1 zb#OpWdc#&Kc@7q5fp?t2#cLQ3P>m#|-5q!R&HZoRiL~!Cdh9g@!iUqnmyp~wbbg|O za;CQDz@Ag1lv`<4ika4}lCPuKHD{x`@!iU_DI*1J?USwBS}3``m+6?I*4w+i^i@vd zo+F^U&;1P5zlU43IQ3f30qMX8fN*!+U1pQ@d3fj+QCnX<*j+EiX4JeGVI7|9BK#njy}Uv07n@*FlgF^T za^JjlFv3Zx4pYp7s?Ru?MFlu7=?E#F+LEuF-gKZf(zI@68tFZ`zYCI)NKQ>MzeNud zJGsfdf^p2LRXG8U&vD5_VQB%d^$|vX(zE1QLcy@VzO-@~~8xsu!gZv+7H8W1+~Dt{6^Zc0hp1h_E$gXd{ez4)mQA6kX=2{K?C8MQnC zPd>|!E$Pb$#}5z;0a4Yg6fc}7I_@bX2^esnQxJuQ6PDcdb`>AR=JaGZa1^s`$TAiv z%GMQ1oQ*rX{<7+?-z%E3kW=rbB;9kG+dEswTV4yGQVwTMcDP8K)KMVpr084A>_A&D zv5jxLy3Oj*j&y~JO6*O=ee31ny~(lMQOXZaBrCmBs3Wcp z-O-x#-i{0B2$jqedV&aQ>e%Qcki99;^4;7w`AL4RQI022+0raCm+Q6U+(9-@x^vP9 zH*(J>w+XxJ%-JNpb|;_TuDZBS{IrI~P(fND5Vy}-udWQWO=jCEo{l0EqhavLFl-76a`R16#TBo<3lN{`>#>GyMS#OGIPUnto+5@hq z?kPz62=>J`It$GqN(CUTxjV!hM_*5SkxYC9o0>bknnjKSEh^n9V}Z`R za@$AF(@oc_;W&uwUQ*A4lOhv=;`JU>KPXT;rJPC+y=kNUWHJ=nH^TEP4g1JFZ>XSf zhvx5V%=H`Ehv8MH6pjC2K={cWnrAX|T6A~GIs0)XHOuVsJiXx!Qj0I`kv@g)+Hrrn zmLlcY<;efZxnqAOJ5X(?*yxw|mSP#j;iQJ+yqshD{xUgB$?-fhTdkMoZ~si3{E%d@sy^tmjz|wD*k8<=CmW1C$~S6y_)@_Z*;DG^GVBZQ`my zx9Id@ru1~M#>PH(cfBGyp+YeFvw#6(_uf(>H_>7J8*L@tqHtO(GYk?(5tO z>Bic=?U|X_Qd*UghZ)%9DWo;^;9>pqIm4b@wU-7bhh2BqlFO0rL5>0GEnGOJfjII8 z_UE#vNlGwzEGjRty7m4-!Itq$jpV~DDv8Aj9hm8mV&A-S7ii4SR`f}S4=XVNo5REB z!MLQfoZf+|&+B}|`rSC|(=57K;$^e;O!r2DGrX4rkE9+m|74DZ?Q`rhvdoMfVj892 z4{+S*$!RnOclj6QrBT`C+f!DX1uf7&dy?mp^hA-dhQ39vTn>=dNf~4N8cd4W@Qb2As^g%__o-g2Upv1E5^`I5H%!v-h=h5E?x_o(p8 zwfOb|^A^MlF^@G#xEC0Z(%Ys+i3{lF7B3AJJvWKRB|5>x8AXi6@+vCRbjxXSsm_o(yA^N%$6%&-UbMmCdD<2ArqLiGC`XwsQeC zlpK`xD)0alYHg#G6Q{-jn;uByboFA$Xix*t`>@vX>z@d(T31pBI2eL)977c!!Q+KP z>7Lh}$SJi#zynTE+U|0Cjrpzm9jGdeFG}}tH&&@xjP7_*nCEP)r7;WETv6)($aoOxOD62keW9FG~NkVJkfeYZv;1Y zH7A{O>}h^g3k9~EMaP#RqkXgH$nS`xf@d{xR60hTiZlaTp-A0x1=m&EjxfR>Os#^7 z+iYu&6e&xI5;Z~v-Q{)H(gai2J)J1q*yg}|4>$wNJ8ySK?q&{78Awxx<`SSixI>j3 z>@x)bF_Y)tAk{9rmx$F#B6K>CRB68RN1Hvh=&JLzLU^k%seS@6aNIa&XWUEOhI`)B zrhDgdabGe6Yj?30!0wt5XI&1MXT{J%4|kptuMdBuRoINXO5VL?9e-LF3imtGw00;o z11fv>aPvZtBuHgBJ!G6paL6T~&^0_Rd(3id5%y>I^js==<`r#5mZzx{i|ylCL**nx zuV!{N4^Ogoq0&h2X_f@`9S*LC$k|n%dm?UL=l8~v&K&1Ki}gny4sIkbS6fi2VC%aUPb9Rc}xt3UTOZ>3|u^Rh)=wilLmjH{>W8eqnGkH zaTxCUDbG*&-EqzL3rh8*Z@#qP#deXOykUWX(wvhN+MkYaDW(J|BT^0J9%&j@{*9Ep zw3Byt^1}5_0LSVv{?EHXuYbu-mbB zgci^jbipD%ZO(3!|N1O^P>mtVV!P6E)ZIaPp~UJ?lQ^Qhr*to*O?f$%8eQ! zI;Jl9^sYWBc3yr;>YrjLu&YD7lG+m7^NOa2e2DnGvD;?Jf?f}(Lg7Ja3|?Wueu{zz z@NwRoF_IE#qiR5uq!CUsDxbKiFx*}Gs)kCr^lEwq+Y0rtkAn*gfOjP{t&bEO7IG?X z_Br{cL$@2#Hv{d0 znO@ccUQZ$*Y#E=e;9WmbH(n!0XiiGK*+@nzfwZ3UjsbBsF8rv}A3)SaN>oMb8bHtl z8Q7It(cVIuA4NT+8<67}B#@a+##9I$Ae6|@UgYDuFVlahX6ff6N6W8n!Idk&t}fe( z#ZtBy7wp^%599P9G3ue9CbK7YQn=Vh>`A{PVC20s#BEt1LV``3PwZL$bBBI z=5L-6vHSORJ;SfX3C?3 z=6uy|Lzq$9d2yM?fNQs`CNUz=o*sq*9eCHWR1%R_^^L~?Kg+l0>==I9_I$q>{4(@U z-y^aJwp052C>T0y2XdboUgD!)yaP_D+5A4P1k2w&hMJ^*X98~O;$Z4H8N6gpTHa*t zD5m9P1a)7NM(ctUd*irMQ=lzZDODDg6qqE=H#WwT}uXXIny7DHrl(r=a-*gQ4=U-8H-&% zH!QLV7Swoj^4JKL+O0fxw99Sf@$8L7wPhSB{|Hh_=&tK><2u9l7B96(u+qXSDy$U2 z*U##dX|lFEg|R1i)n-5TbI6u77lhI}+$>R#u z^-=1XD?M%1K$Q!uSQzM+f%c9qt zj!{1=2il9)$oc>C_OI8r<2t$^_;E(s(WR5OM%Ssb0;bRunL<-wiu|}4Q}u&*lhRr{ zGP@BY_L?F=0HpW;K#;1Z3?H-wm7Ui9m7Ggfueq-l-unn9Ia|4Pn*m`oy+a3zK3jFh zyipNS;481Zkw$Ulh;4EY(L98HqtI@!TpNceW@Lol{|v}}^);{2l9tUZdT<)f+yRss zVE|!3p1-4%9I)nyGDjrJO7kS>1L{x`VQh$wJ(jL zhS8MDc$1~|)Rt+fc%HB<+=FXsdC!z2 zR{=(vY)3GPTneMPg!1jVu}yWH!VV*-mr9-zj>y@p%*#%=aSTAfcFOMMc}&`gO264o z6F#WHuC`cDKJEKID0P|~^s~CbxGU-$LDl5>Yb;AM8>aZmeJ3&0=)C7aeg*^WYL|R; z*JT+&_E|#`!|@ECyN7XCi9;*ADM+&?rV$=zZn9rv%F{^yx4IbrpkUOu>Jp5y4C?PZ zn?XgWArCVSqCzCMuQ|aB|a+Xl% zw7)r&naHGWmI26){3$t{T-e4(0!X`jFKxtFsCL>4c z-~5ljK-T=s;WZ57Sw0;xWcwS1a?&Ko*Vv7l*~X7Q#oJgH^|#|}w9M&3uhAymVOMGd z#|O>wtJ8lMOlaeR;$OpOsYk~g6Drw4sh%Zn6Ptl_=~8z?GPzx2m zvd1NuQ)e-*$jWup8Jrd;1}{#SkuFnWbJr;TNLtzR{Qo=fJ{i`FD1BLTe3YqtQHCs? z$Ze~2FGov8JF)7w^;scmo$Pa1n)TKdtXDEEVYL5b5l0r8bP2&o&2KF6)X1Ld0%7yg z0oY6KjV4|QP>74F@J^k;71%g1qT;!eX=g3hiBDyme>+eIezRKarh-&DLSHbfbfwb> zbD|K;rm~RacA-EUv%xttP?fLJh8b9>)|3^3U!*7>2^K|Z$8i9QSz*1#Xh*5)V5en( zzQJ=YhrdxOfWw!~R{T}2fwd<(wwn(mqbkbecmxzTO~A2R2~|15&ct3+QR9?$1ldDv z5J%H9ba)%7#8RfTRFc}%!4YV%N$2BD<%~+OrlsXoIr@{yfQj|E%cP*R2{y~wt;wR+ z-G@)Ph%L!cwKo^nkXdb}k5Ed1p)*+a_@3M4yxXsV?Sy`_r1So=cy8*&Ay!QyAK@qA$@!_vi+%%FiEAfpfrMh2mr}o;lP`a z)@6%7=wbR0%gKKZjq)nAzhMSsIjnh3fGyub@tw0$cgk+)86P)DX)%yEa7^uEpUys_Nh~ch@*ZJ*|BC?}O-~=g?D;`q zCal2va2Y5kP#HR|wEqb;)53Zgsd*M)AgwK3{$`oFa|skUrR_%_#j(7^KNS33 zOYC>?4WIR}KqL8R0xAAtHpjA=JC|Ge$^M7vcfmE9OdSes8%_W0z@RwG?>-F^d-G-o z(4?CyOH(efCOdsGy9%{j4{}EwipFbc_sZp|S4UuAt8;Qi+`~3vU@u+f#Ua)D`aLke zX6z9HY}kQQ>LJFp5wLN#M;OpqjbAL2Yc2TVx+O5s{@0--grJDls5Zf3YQcn=sme-m zdZk&ODJyJ2J%C{$DI%&;4!M~P$;i=i1{*IdTRPhm6J^$Vn4DG3er(Wd31KMp0FsA2 z;&K?E*mOWjsqUY1JT4`Mq_WEGZM2=TRtgn(Oy8N$nvc+7bA(6ft^#>c1_m(qg|VfG=UGtnc@)Mr%>SwQ6oN5B@AWX5G;w=Pf`OKH-~tAA zt)_KQRden*bh{{m14aOQ7o(lN?*;(svo&?b@fmZn<{(QKI_~gMUOGz+#CJK$p3x}% zqDUCP_7O!fReU}7LQVbB6vtB-3ACE8>yAKrd-1!EGqu|?8F|XWX`cvI#i6QRapY>P z)?Z0|EVD)r{_If84UD+=+9;MV3Olo%8ZGIL>8?yw5S6x=5&%vO@da#+?n+DF_VZ48 z$zv(m*SRENtDGiRk$;voH)?hm7e$m|ZNKckLhu>6XUu*^yBfjgXErKxAN2&RKa;HP zv8~Z)&OLV*18uYm)JS$=O&-5d*dV+sk$K&T8nBNxlort%Y5kXPNrqifDyw|Ry=PBR zvpbIu0>Uo`ee&7_j%hXKgzu|gp4lU=l?a;%1CTqA*S#vi7pa3pi}Nz8;*gqiG<>9l ze@X5cq68UFYIb*c>rITlalQ>`zxUVsasgL7Ht~qnxw5w%X?&f_afEVMjP$2 zPZL!sC-j!)s^lU=BP1QNMSjJ!d(c}WMy?A1Wxmb2ZP4A^d6Q;SlVQrW)0>LKRSO8e z8*Mj(G6W+$**{#U4WKxfB(E5wY&*raOw9_yfp8Ec9na>LD9P!zjMIfdV@@ z+fJ$_z(CR_#l9^?2xdV7&W}5%4lEV`q2~vM02{xOVmM`0g`CbRr%S{}#ibZ!k)1TS z@M81W)6UD@ z@p&INf5*+}pWe=Pw7G9G+9fi<WNlx?LN`IAXHpJ#q_9JusDXmc@AM^+uT z4nhjQ*m^?F?<(7ylW5Lzi(`lviYE2zPQ^MOjHjIBz*vC|YyKkmTpL_G50YmYIPlYa zU;PMAeK+Gts?I>s$O6E&OYUWNfoKWLj6KtCb_7bPqqw&`jiHVOgz=@kZH$;>o{-nb z?2>?Er#BFd0BX_2qVlnDSoJSTPlj)+!w!7rfH~^nqb#%qnUpT!fUmMvDtr_nN;txq zD*z>u0jT5M*F#GWb%@%}$Px(uZdim2+RN3+pi-J-`Io%=ZgrwoDFU!ne}p8q-T>PR z8o)e^&`$Jr79u@8jU~mmEoj}rOF{az-n5wOI`T3eR)>4UOXi^F1+I;PX_#^X-}t1M zJIJP2GWWJHf>pD_bnYX)FZOX*U1~sZss!0ftR5}0aQSQS5KH13BmB@s!6+qnGkc@< zzch1Zk1%C*6}Hhbtm1EDwD|g|ulm3 zV#1@28U1OmiIe6^^MnNiV5IQ2ANk(5WPij4E%gdJzj`J@-$P#vNBnOmvrSh^X;F9Hwry1N|?(OXi{b|M)xpk zLR!?vCWk}0UT`@4elif#)5g*D6Rl^=qI3frRb?|06*DF_N@#bqmG)yt@wd&bA0)m+19F^#MoHUGWNn&jyLILWh-w3uJo|8*q|+c@pl#FAqUK*f^2#WTq(W4+JaRJl<>u%!a48oS{zk=nham5erxCmy8M^%%PY zit@OWs`PdAaO!L1W#=BU&@;pky$`vM5x_h{*9fqs+su)6wF{SqY6{(M3X1g9v@_CM zY|$RtDEi+Assa}JiQu~|YwD1ivFD^+3CV3VYpb*PeTcbC?P_PB#iAhg$KJY`wB2h97jvBrDpySzrc1sYApGjayYFV0o%ZG(_GJc z9AKH#GUj9I!%>Z-2rWisq%x9se-8s)!>)@XD)jC~%W9hU0gCLU{i0gUi0af#VY#k} z*LkpEN}8U>t@>Jv*2#Bo^Xj`+mox3=ymvz(OKnwUbPxR(hs zgS@&-DzH#qOoioYM3`AmjZ(OOm2Z)JiGuJ|i7aJTvgURm?I&hDD;IK0&a$?6E-jk_ z(Pkj*-${lyEhhupuj>U%ISqvQpx7o^LqeKgc{3}|XD;(319?{1&c3usTl-_yllD-h znikH#xKZ$h!OXMKbm(Ihq!HW%QXx8!rV=0Wc)8l4BTj=u(ni>y;a4j<`Sc`Ld96IF z=Y%7*hX6T3P(53wZI~@$Q7coT;VRbTJvomsWuj4;mL_JNDk%I8G(yTKDg)yCMfOc` z;RVBTP~6@kZwhII)%Q)n>f>p-SMjDcPpbS5_#u7R0v8TLnR6Zdeh%#asDUIAuufGyLHlwWv62tiRlt0So$hF)ncO&$? zSelX)PGKz4VAE3i%-Hp!NHb2`y+uWbZ=Ux9>g}9K5|x60aDs#Z^T~IASGJ^U3BJ4T zk%iA?51DVF)ZNU}(=)B(Y*(XpwBtiK+l9Pbu_%#KP3VpA)Vfh^G-x@crzdK(r0x9B zNP*ltWoA^@l*)M@_klCqaN5^dX>oO_Y^sJ*I@6QS_fViCB{%9uH>qZ+zHtLm-N$GY z^AhkTE$1T0$SHS3hs5Gw;WVFk?$}UA>g_HU7o(3it&~0NbuEgecdpDWt|?N=ZA9z` zm*a0><^Kp9Sx5N&N-52dEUn11R61)LDUlV|bQYr#7-4tqH$d?nY1~la7T?ndGDm3` zuIP5_)e0inoD0F_d&6vP;AfAATnaU5>-^qP-mmLPHXmp{fEb1ffFx$1Bevj_T+Hq0L7@--hy49_fTimMPs#9i6n58Ho6xjt^{n~t>G?t@j04sYu>xz zT=PnL=GtOzmnSlrfV>V7P%M!NW1+o)#Z}@!X|~nBW0q>P+X39U*xoi9N`N{?2_Az& zD}?lyGCB`Xp(G{h>e2oa57Rgm)qMiRzi~_Uhdo2GUwGI{;pJ)LA5ln_`C3{b%Hj&; zcql<8Ycx_ivRN0QBMv8d%57sR3R(CP17VSkau5(gYMrXWuwM-CRHm{%n>T@5_R#_G zZzKf{9Xd5htr{Us750oIt@|{ws9H}@WX<$YTZuwy0QMx&df~o)(+{M3JaT9+Z zSuLkK^hm#iF42S8LKSOr*i8B(lv-tz&{6rge9yPH1$PlGy-+W{pDpL#9qjiZx6*a- zv@;lJr}%e8nQEu*BBU@;HE^h1^S>f-M+OQio*7ET$4&{~U~z)wbDR|zc$mk-e6;DE zJ-kr@{C(c1NIc36$b}z6`)&xu3w6!rIGSmZv<& z*|;nemNV#!jFH!N&o-~fr1`fRLovz(c1NffOm;yGC5qJKLtHk?WY^o9!W(5=2q!t#@V2MTkJFZlIc|jFJuey^q3b_Hr`xZfK>4#{5Ks?i+o)|Ry8xUC>Qn0 zDRlcqi0`&4UWQX9=H4jg7MjXa7fssrgb7Jz=briJIPnY&m~rTVHFHrKieXDO2l^d( zK*rkituf;%Y*T`BbS$NgMoC>@q^a7LHG1&MmG%nDApfY6IW>O$6bTFN{F}p%G!Kms zT_IdJ`ji^gkE%kGD*O+9*We|TWe5E5M3qPACmreX0>Bj^Ang13{KI#@uleD3DL3rX z*+}l7vt2qqrr-Tb_xT<7fdRA&qm8CJV}dV+;0P_PfhyD}oF&nk`OF;yi@mn1>6cXq z(t1xh>%41yJ5!ly0dE1GBRcFR=zDkdirkz-R_C226<;#jOxl}|FADBVGVGpeL_k>M z(hOsBT5qI0Uz`{&TatF&>3aNr<_P6>To_rEqcG2G=Bl5r9;go0(4PynC4yiNkvA{7 zqhJ|E)&-J7lYn)GcpdGlEK#k?Jbwc^E=;~1()QdX$33}5{ySkwu(+%O=Y4=c?UH>q zDy!y%F3*63_8q?`Sec5X?#Jyf`vOJ-8@awK)I(GzYcHfkm%g%sMlZD>i^L8bi;3Gdha zuTr3;XFmhIlY2dBxt8s~#*L=5JUx~uyR(ffM`vD2n9nlU3bks~zjV71-e(}p{F zDzN^WwJ-oxYs#f4l#ZsQYZepSg36YPQGVpH(_$5WU<>VErz*?gw2O;MN^W|$wj?@o zgH85SULu4FHpYxTa}2!@DtVc;s-z{%!6mnHZT{_&mdnucWVH;EU%SwQMo@)ObCr9+ zgtwtnRvbdE2P4&Z>15AdPnCC1nPG>|)4mG<8*hv}H$vL^TtcuW){b-Fn`+PLQx32X z_%f!dqRMzTnOp@Vk&qsgvknB$;tY0C{#}hw^WSpnjd2lCP=45YwYvP3dKL5THxsSn zK0z+Sz~iLF4~_v?nLwl9GuV8$#<9t-m+3}v`ZET0^{(lhxb~8_o4k}G1u3vqyL`ke zl~7jH^BKn3!gh*g}CI6T9D+xb+;dumXcM2>oNU@_=VT9k?RWycrH~Mk^kPsw~R8rZM z#+a$r;u|hzCJrEs(kO-AoXQyJ(a=g`IVBw&WNwuzS-bZ}0Ym6D#ji6Zr+%Wp zd%{7QBbH5}#j(=&I&^mV{sw}3j{|)+YMDo0QV_q+meR6hGcc8M`g@oxYu*;x zzm(a)M%9j?c9PUlI^D+S$w(a}QcRS!+^AyeOHGtVxKu{d^{;dA6as35bt8X?CY6`% zON!(5kVH*sQc$Z1Rn688t}>-bFLm}EFxDj!(E8`H%0EHd(ogR+$SpupvU^IS8NBwc z5|xt-49KM!Cx+kC7WOG;haLzmS-u_Tz_t(#jGzEpZrJ4>2<@JD_M7!Cw_<|Oi;0=v z@=|Bi_cLK^(zdWuI;Je!%Y!3_MIPr`0)t2Ry_>8Id#XfPrbgQxGOQE%lkwOH8&w{= zFp``;rR*Vxk>?^!_c5`PVF33Dy)|^=k?rX9Tf>$po#-2l%m8BxVV@VE&vSr>d|z+e z=R9>NKJ&AUb~H+1o)!#tkNdc<`}~fZ?@P*1aLAo)wBy6=Y{NoP0cz7w9CE^LRmQ3L zp5Xu<-v31O@usNQZT2p%K!05!exSFdk%MrTpgb3O()(>nFacY-v}vv;kYmoO5{JY&}L- z>6nMGvQkQk24bYZfX!;u2x-<8XKIvOS5}d&Wyk9|ouN{b-UwtSoAc4x-T86 zQvO>*^J~$ESzCX1VC$aKw7l-q%!ztN)=aEaEW>zemnc!h8X>*o4aQT(MnlWEJ9%^9aXOK+>WbKi93W#=$b+C*%k`%BgGGkg5Os@ z&r(bBoJ2Rbws-++-IDTU62xUn&9$4=Ybl1{+>c9(T;fi#U11YPbp>oDs{F*-8=B?c z487QYcmWSY+4a%VyEdw13J7&P&h7FNjKYewRUTiK8~e%jmr@xHc$N}_WsO2L1RRw1 z_{G?C*#c%u&V<}b&AE0Qee)(F}4 zWux!zIKJn7nJK7JD5jW{dJCv<=w>SFNFnfje9!wrM{j34KivA~#AxT7^BRG`H{Zv7 z-WP!LL%7=2u68b=qH(TL$nAjCgO<6wgk)=NhyPAX+OXFbMrK+yMvoqe$ykv?y0777I$O#@si71nhUQoPlUV#l|G}s5eCA3(MV%O zaEh52XeQ-_J*`pvU1sg-o!4BJ;NeqU9WocQtQ%`@(K>V>UfGu3Q|cQ(^e30;0kk&R zef$yt7Wo$)M=6G!9d}l~iBHAa?QgKIb=xr>6+@mYf}0@4sC0TWC4_%M_vFq+@0(a% zkE*d7pn0-yP0Fd(hi+(U@o%0lXP?@TFFB}_&^kxmpB59As+MMb_MX`FPEL02SyUZR$e;R;5z1W^x5`?JPpQs;G9O~9Knq++S$%_e9jNIvt35JPZjw- z?gL#eTsZBlVNiipg=&4il@uK2plOssEjduKQjexx>h%xC$6AtVj;6i1PRegn6MNYE zo3PT*GGtV`anvW+&WL0AY!F-MQ5{-?HkVlB{Pnj#yA+&TZL??c;QPv> zYuU%!mdY)X=2&`s&v7qDu^GU|N!zl9D`kE@l)cu@E4>TUs&-J4=^8P2a-n<7mMH1E6 zp@-iC+Lt^|q0d0i`w%bwbXWCuHWDR?!q>>`gpYh?qR%w>xPmHCqg`&`w5y#T{+#V- zm+vd;H7xPklc4H#Ynh^ur3M+PcCv^zN;rF`la{EIM|=u>Gx7oOGF!&) zu+&j{;P8~{0BnRXZ%Y}ctF)|?@Qo&i&8i#4VZiHuNzQ!M9DmwERxynSMOC_ltD3CA z&o1!d!r`rJa#G~YsxpC*mCT4U!tWr{%G=By;c_$jef@oXans$=+trTGfqwV<{L&X+ zy6Jo3oQ{^cBlbBy{jck+v>g?q+YcA^1LUo zoK4)vd&xBbtYR~851Ti19Q0tAMf@(O|EQ78X>;Zq_92+jE?kZDqY{n0wEUG6&w2dN zSTC(A*pi|%=B(j~wQuRHuJzEm#8tj>=JZ5~>e%pjgtSgS!wCDhy`*pGHJVS41_Lko z!`^K#xU0F@Ltcc)jY?jiT4rRb7?MK|d=#&8h-Gzlp+?9X7%n44{3>ICEiVHG!ZyLL zDYQ*ms2u^1Zh|*X51L~+@{{t*###jW6~`B7|56f!Z2Sq?5>!X8)1H;JfP?YMyc@0z zZkfpZp1*Eiw?UZDWbzFPQeS02eXSmx*c(}HM}~ptp<=jAQkM`JsmYHE&7S}l83xiD zf~yO?MNI$QU$}anDCVM`v!pWs%qiO5(JG7qB&XxBz4Zw6;MW@R+xw(`Fks@3 zg$pgTV~zq#3PXDn1*v=B9c6zu36%nS!efNE^IwA-*#C8}e>xdLh3nHuz-#J8!HCWp z00@CWl^UCI2R8~EEulmkY5i1*-Lw~790XG*8YQDIO@zrAaE31oK0%P95s>y(!`Z;Q ziyNfh44%#vy9M-|rr-<=y$AR{zW84E1%UjD`F0}S9opO6?Yr91jz%J_fCb8*awp)h znQI8vY>}lF55T(`zvBG_qv9{2KF*2Dlm6}rkHf;>;mYj9VZ${_HPmt&UP)NsVV^+w zHY(`~46M8M4~GF<+9${*NdJvN?MU@4uFkNAHOJyG1||m&(~CRDhMg}S$@>tETwQYm zG3R4-rYQ8BIw{@9eg1v?&42g%M3?K6rV%sl1IPEeFT1RXfi_zI^uMmZ@E0|nE~UB) z3)KTC)NK#_4@|;82R8)r&Yi-_ucR#NE3d!x?B#llr{IP+4No^TTAX}_Jm!_a7e8j2uZ7LJ1o+?7uUiHd&~+9o#hy?y8)u+p2u$A)Uv}h z)6MS}1ChhOdri{wlN=$><#4V2C(zPVMkb|q@vkGF16UU+&lTt*>eiUjMn>$MN`Zlg z2~STI*i_UAQSBt4_yfb=ddA3p6XFu@znlsO^QX!+!FXCZ3OOTi#RHw}tL$}XZ#grb zUvW>;Wbl{UP1c=Eb*W~cm>`}qVi*2ZJhJC+C-KEAn!`j$GdIhfQkyD_T@cgU;8=nD z@1jWTTA7PtNhLSQw8Z?^8uQkZySB@G9>8{|#?u+Te^r&cF1UO%dii7rg%Of#WId{? zc~AR3O-!tbweP2Pg4B&O5?qysrSz~u(Y))8?0bGu@t`CfBP$$AtD7l7UY`P6qcr>) zH;a18b59Qd=2bF)+ykmNfwJu&UUL?_kNZNO#^Cg`i$X>1nzxU-8Q2DZk(Lz5U3zS_ z7fGcisXgA7a5Koa=?;rpRPwQV&kUQl?VhtyLT)S!E%9~$*1ZDp= zuYs8#1Ll&TcuGuMO%NW<4wq3Mi*)< zr5K?T$J04Bwd+(q($&KRq>e;cw&l!=s}+RWeV!y*@zV3I9g=_8Wr&!h3Z_t@CpFDz z^0KgJS*XerOsb@8q_ae1tpSzU1N`!`x$b!_K|>M7l46*fP)Fs>vAx>49c+rhH_O?q zL-s4F^|?0#O4C82(g@G$N+{7P4m9lLHS*i@NS_|A1z>kSY^HPjwLSl1&w|0=aIWnv z`($xqbQRH_OvPaUV-v>OgM_($(9%=2Nxr)d0O2I5%hA`UF$IS>F$nGFTDt-X80aBzSn)~O{D->496aJNYB=B zN}nAV?f4wHKGZJkdwwu)8>M$@VD%tt8LufJEqi%ZU-q9PaCs)wnP^lz3z}T#Ak^pO z+>dc+6w)}<>J5yNm4@}aE&oU0@;n~W->7vg4wUpfl7n!?5Xy~=nx}*(lWGJkH@)Is zyuJB*%-Qok=KL|e?;AIhgS&JXeD}Ct9in#5d(6uI@g$d?Bn=1^PC%z1-u0 zQ0)QBq5DrOQRP~pFDdj$LpNE5^|xW^Kf$ENuQ;Zu|5D%WO1V*zfWq%gX#i!%kKwC8 zrf&JaH3S#lZ;<6e``4kOHF{L~=~)1?r5!qg!mudDNI3%>K1tvvOZqfxBc!gKo_m$G z(I)j7Mj9Y1P#DteX$CB1RhHr1!}7;V#4xF9@dKof`p@;>1%5d2+{DR#YIR~>5Zbs^YgF_RhzmV6CQLw~|z z*VDVtU%;XuyZ>TWjO3~%+DvF{;~Z+P*(Z_#G{O}n(pJzmxV1heoz#&U!EWxfS)+&d zU(}_Y36g58v0WXP=*C zcl)^2k-;*vP#eH~e9v$HyMOx^-lyC7;SaZCN-e#cKKN^V`lmOt`Dd-G#{>2Iy!ku6 z*G*r1@jdSgXxE27^f^BC;dXwGIYz7&{+s3Bc|FY`r{+dG-Tf4!jheNOz>w!i-CDHK z!+!A=eubwPb%c#7MV4esL(8qHiAJORR9(?&XWyKe<|?>;lp#J7b`Zvoxu!f8&{BrASzk;lfxPco_9rvsnWW|>_{XMwX_1ib|L)3D zda^Q4H-gkzE!6yp$MYP&2VPjt8O7+d}i=ZtAF-{o1L z!&^oF4#^n>&p-^q#GV6jLY_do{el>EBUX7;kJDXJ0c$>mFp*dlGWD)>>@_9%FQT+T z2EiNU*5))g>&6$a9j03jBh|>4mqwP;ANISfl)*@;q!Nmf-IM<( z$y#KFDh>v28ZZzhcm+)jDRW;%sO5kV=n^glQQjwJGsUM}htNwjhi&ArwX_;1$9x{Zyhhb;_MhGvp z&&#*dK6;@oway&`Y+Z5LH>xeQz%pAg+~-Rv6;CuC=hK*%fs*5j*4!VBCEk|Y&2393(QE^r*_P-$_X1p2$%C!e&)48zhs&|nf^zEqP1|Y0JuvMjL{xH|7 zI|Uy03i#x>;FBPA@d~Z+O z`$BJ#ZS%e!YxhH+o-%na0+!M7T0Yn|@eJ?c&W*rJAKHze0oZ!Y&^Dn34{ei{p#PAs z{QB~1(*GAB3{@a>bZ6~FhTt&l3JBY;P$e%a{d>?QXCO|@1Y`wIM5NH&94ILQC4Vy= zZDDglg=czd4Wu;6xCu1HapBq!`?;ZB zv8>jxn01R+*J;F1t4-gDi(vely+Ld#=iR3vB2;|mu(v3Cu1mLHLpXLfn`SvtujC1# zw^>i7#CNqcjJ?)e1WaF7^FLKtiiyO%6)2&@c z*!Sye?-OZz)4r51X@_0ke%=FoA7A>8FUek@DaabMtow;}I+E;y_DjwS_I7-zirLT`@Vy1PiY_jX#Kv#&aAYw7jDf1Llpf8j&ZAlyZri1dtU zOdsFtrtkH|m+ni(J%Dz7j=#oVKD7hIiF57RoVNZF-DLfM1=apaaZO7$A)1=ThtfrcE zLi}@$rdleBS8oJub}Ma-GoqNc8H3?M5m(T<$e_HDX>P8kg)e_8ttS(Qk%6#+NNYP* zF;(la(qZJ9XUc3PixkNeF2+f78j@a8B9EfDrE`@mT*nd+T%{aEEp>TRP3Ymxt=(zJ zt%yfDPf8+vcSO0Xu}IVO4Pu@fC}E-`JB(@?nEAZ*=h4H`fHY)`Lq5A#sQDY_#3KV+ zd-F!I+WCAKd4WvO2oMFai*X{{Q+Or2?AZ;n_2=4MmBnNbNyKo{_rUOdjXWc0IaN4? zb0gG+ko@0xo+;$+97yxL5<}}bu0onReHt~Q23X3xG&+&iH>_UqdAgk zBsG{&!BQ4=nd+~HAjgVd>A;uWR?EJW0r2Cp8(aj@0AN%loFj$J%%IY+{ag7zafd3} zL21yx)Tqo6+c_-l)c8jObY#$Gj`Uvc67qfh-TycJSN|`*$9?#YTUgPT*#ofM%M+tr zAD*8`3A=~X=&$kD@gL`>e@m;Cv-|p<-|_eH_wl{%>pnnjwE4jaeCR{+ z83L~Z@SDD{0j|9a zq(eWJh;cx@DZc)l=`Irm+5nQgv!~0 zKINtOo~&2LI{l_CIm=zvvZ{OOuU@E(nBEBQK-OwcdYwY$v%rtMc5BZz?YX@C7%7>t zh0V_N1m*Wot^k$imr~CfLp!8cBdlhIq748DCX%Hk=!jPQ(?ZF6)wd{St63znwPf~1 zQy`u*h!B=fvP6!5YjiYp@gK#1tdb0%Y_msoSTK{p6J(3^Z)B}iQ(yxh{#?#M$*(AR z*%cJkCk6p6YIKliq0VBuX&_(R!|9eX;HR!wr1m{NhN~xO-)O^|%~LMYN|@a!#vl12 zviFLKn&EkgTkaFP&Hjh|Sbt)bRz5{=_@`1D>HBVEW#*0(+g_d1;nF6Onp9DiT*^~M zH2Bkgs^y0hxkxo-)YeZQdNA30oI}TwxloZ<_8H9wat|J7# z>8!%bgr==Xky^O;rMQ|F@1Fi*tK(FQk1zc_zWwj<#XJ+(PBHKfG4Uo{K>H|9!&O^T zf=5x_xz9(5bCJfO1K2sEro5wIXB^rr*Kq`3>3taptNHhguW23Fw6M_$IUCq`Un@Ra zeV!?diIG&tQ0LVwma@Zm>=mvnN(ps}hSjf;tq*xVSGo>bhO}IUWHyfbybtud1L52* zptFm89tE%+>JtEL|C%gd88_ebU;H=yef@pi7u<*(Dzv_GWp&WuKz66=^D)K-i&A0|u2s8lolnx5dP z9_@|CE39h!!%po3=T}_%UAfrDe;4u_=$GGHm)=H(A6y(OCFocxye#pf|c`T$8b>$Mk&EE1!ii>o3KC#s$5Oy9w~&< zJz-J#i%r}=Y5vAgWFVPelG}RnOAeEkqofwjbJ{JI3`CR9jLi`ihc3G}@^!552q&W-I~vbsxMme5y1g}HF8=?`XiGQ8&XBF7AH0_m-I?v!wq z)(pWj^00u&NI&}Y*WlhLDVN`bT3ae+|BedEL`<+=*a*>#qL2#Y$~&AK#KEIS-*@7opOyMOs_{C#~d{a`aj zJ3eRYA8Lmgg=a9jl{$yrsK^D$A<$DgQmU360f+4`l@vTEvjl9M8@_u?HtP`_spqJxmy)}j`MPwsV%jFJO9EOsJ|wibDksx!s$`M zyT6)*4i%aMu$dELQVyR>?G8@JZMN5!?`vKPqZFdFP5b?5M>{@eyMlS%zw~$io4@_r zZ@ZQS48P84)+F?WHX479zsBeK5BiKxmot7Md6>^dMbb00$(>clqSt<}6%55FyO&(> z@{aBn!?89FGTLDqM7`xvf=7vVxHZLfulK#INtwe=^A6tf5~aNf)hIvz+8lMmv;KA9 z&8p?w3R}gNTZQhR@*LwbxOP2RffL`;sVr*Q@&%g^cw%;pG zQ8KGlh>Q7{Jx*aiub{+olyb~_-+K-i_{8mzBi2tGHJxraQT zy0*sP%`F?Z86+q2-<@DsR4M|Jx<`R?9D)>-JD#*;$!BB*%9e8bZWYT#`vaN|&*U-&);=bMwt{>|PU;Fy zsv4T$7f(Ke)fHkp1ptcgeMq{d7*#hJTHc8pLAm8H7KcoZUF8Y&v=XA85p)O7_NN;G zHr^1Fzi?tR??da3gW+W0Fy5rkgpbWf=q>+kE^zx<_n(_!oF0`7C}3!=Bv_E$)h+(XROF768mtr0(;gYb4-7{NqN zDZwTwzktbK=VzhT5fux9EujyK1NqP5cF9Vbrzp1Lk>|BcA7-?;QbdI~{=O0cnE_>)>GYER%Y}e=f;B$QFbF|*BFiZPF*%9+$vu=VOW=amO%G2wxKwnZu${Qt@3l&CH(wP-Wk9e5X+77T#c_yv^z6RHkYRs;{X*IFTt_CBeDq^9J=2d}OTl!F;~o z8h}(}i-@gRK-z+Qo2DRh?&R${wb2eQR|_1deT( z%s?I90smo1<**;>T+zhM#~K0hS}sTQd1>2HI&#*f}T4 zX55qGq)U2~o1(I#CjiB&ef|p1Fl_-`amlJW;8>u)yzFop@|8a-q z{Jac_tV$tsb?8o`lvvf1MCr$c+4Fr2=dxKQE~U0EP0xFssjFQ5%i=HwrYmGVbr}H!m{WqG=BP{C??cvz8l+D) z3c81X%emRpZvrT8sp(dg6s272411QxUl{wfbrZGJHug3si_Lx`u!+c zpXpxtea~SyDNXz=%hQn`nu#-FiO8NLS0PI~vYZ{PcUFkf7Qroy}ifm;} zZhIQPQyLN1(-=NJr8)an%d0WLnZDhoPaKr``)&!!#uz?)0)L z08bV9b3GAg*cI6SP3=$cu1qLxv^6{eEmP#Ul+=Bq$*JQM<6P6a$xO+~u<*EtoM7qm z(wjyAr$AW0@I|1z7vIJQMDiv6PfGvBIFS|&P9w?R?4+1f!B{6NF;A#@4{0_{0jH%wvofoiP*fDDu z&o%0st-ORHV^K(v+p;Pv6*toxH6@*WT~x8>lP=rSBU-v|oNOBqoM)#TM5=xI3LO63 zyXs=jQ~BJ!o6_%b0#-J@i}}m&=De^qKe7aSh+~)@-3b7y+VYD9zo%A6A75x>l4;aO zUldFI&3C`AFWqz>K)W!q*)Qux4`$)ePpYKTf&=Oj8*V&)0vvw9(Ghl-r0b?P13L z@zmWczt3|n-ur)p{|DePJ`S4=ACW*;yt2l-WVyryPEzma6IKXmbCl*a>}gYKhnJ;( zE4|WsJsV0trpu!|!7AaEghF@oNNVghrcl`M(Y$;sl40a?vVd z?F?CroJOH?ss<(%La>q=5>*o*oFan>o;)?d%O0g)7g#PBH9tY}Lp!(i759DlMoOjC z5(>0*r>@V97Rsd4m*tV?09ic@7r6MfFiMZ1ikdE@W=!4Z{0zKIs^mK}w#(6jJKWEzJOj<7jBAgh@y+_Na@MN!eX`E!7BSWC?zFJF&dfx|O9mQ6nq@D~)T1+fR36^erIkA( zkYiho7O0-z7Hd7GBSMWX;AZ$$60^svxi>H@+^yfI-HLeJ1Kb9#d6Q>2p;%f__q~Ja z=j}0tR2Q5fz=nD*+>WG8HqH^in-N@iMNqu%VUdly|rZ!U3wHM(;dd zfFW)8a@Nv*7o~-x4C4Mn-j^?JU?PDd-z^oN^0;$SZWjx#|Zgeh&|8xbhgAGyp%nXQ%c9 zeWkz)CCcVq;_Pd%$+z7Vj}rC#{0lP+oBrY4X@mb0Ozga~xR(PjPFW5#M!@8S8c%1T zY2^&pg*{*0cL&$Pse`UCijZ2uCy$P(Do~gtY1w^HJPIm`D=7qD7!=d^7PTwp2l+PM z8qR(I?<8i>ZYc%%yspNz`XY8+Eqgsuc}iI1Y5N`0_-k{4!4SPH`@{6A!m_G9Y znM3vwqXE%{3&oRNzEc!}>%IfDWslBQEWJ=P@pr|Vm-&XOEt814?tv{_`*%X}hGIE6W+hT0;K)S>E8 zeh;CYP~=M(rB5nYsqp0L84Dfln`2^0KQWc9T~Y%NUhPs_{S?H75ihK;o!Bn(fthem zld7~tZ={N>n3+RN)RFd1cZ+qUOg6nfd)g^Jm$0}W;nBqumaSM{!Hp&Q5Bvx|!}XMk zCgY@0aKx$iaRA86ON1cP>oUwQSiJe=AoxGEXl|WyEe9`@G;fs#z{8^63zp_MABriq zTubRjh;OXh?tZ3DSldDSuYx}HMsi}A(`+^|x~>E|r``R&?(<7C5Y^u9Xu5jG`Fy0< z3*h?nPyefb(9U*=q-6JmvW`vw=a+E;?ZVlH?eWdfY?rWt)IYRRw`YXWr*-Fa&T`(q zdW6rw)HW0d(IWg{imp!x6dCC~zxL-hrE)FTW6r~j(6=SAE3x$dYbl}_v`O3OW}Eak zL+2^41Ngx))F?kcf>P-}8eN5#Jku|*NOj9c_tE}2K-5ZgC+~NATZl##$MxXdU|#j$ z)dVV1amrUsW9bJQH|g6>VJQtjdqzhzekuhAf3tnTQ#$`07>jgHdAyA{!;kqKf8UqH zv;npK<8PJ2C_SxRDbKfq=D%oU`6Fxx5YYa$4yFN9RA#|q2612(6lCZ$-$O|iY)ENg zE+Qxno2iI!52bTP@_R>)j+JXM9zuGC%N`{(IkP&;6{8x2l}4PXT-7L8eWB{=mYdTv z#hxA~1=^&^!ah%G*rh0>iZev54quOgC;=Tu@aE|9KkN#9lMGxgm>IVF5)~@8(92-~ z!?1T;uKC@axft{8=?SPjb5fJSJQs2S@fWAgW-GZF}e_aI06n=3?XDiVG=)nuUaB7bJIRk`QA(ZytdCUgb9E@^F^#4^>&HKZi zu%RB|jaJR0Z6ALF!w+^0EtGkiI{atQE;;6H);xD{uk-ivOh|4Cy*uDhb{F+h^OXE@|LMoMlt(H;`0X+=wAlq^U zD9|GNw};8lc8d~^XR19I^0O24xt!aF5l(5{skV0wo&)B>cOUnGBkgnQV%sH5=!^_) zAsfC|u-y8G?G9##sa(r$3Y!A~sA@o_LZRNV?L57^vM9Sh9UKNkHfd+AW(iRRY=6_+ zNe1CGvQ*}<(;8u*2TWgTshW#Z@(>ow?$QYSO>K~m)yL9gx%NvZ0yCbJj_)1LQ72)N zQFUw--c;Vls%l}Z@4S}i2q$_79WjXQ1iVH5oHj#rg{!tb0n=AVrUS(iuQ+(PQI z=hY+4gq7qo8a+$sw51Fs>Us8-QfXhGfiO?X*so??YCXjzLQa!1Zo#XBMg&v&cM_t?zg?jpuY)7?<31u*DVSrp<=|t z1oO*=hq}k)|2azZvqOH|pr;uC8tr4QzoGnZ^GZwD_xZtxKitj_fAHaU3Izs<8x24) zjc3i~EUnMd+VhU;<`>yy$2&>v-4@Li>D zWMvaQo6XjlWjKzMuH_se1=>iz@HL1z{6J&FGwSRG2v2=AY22$T8!%b?+QIc0#anOK zL5DgK@I$f)TlJaLfo%0UHYpHehdZr#JvHo~y(>IQ(yigfyn&YQ&tNLw)?T+po z_wBe+SK}$B?W7cPjj(S{3b|DBIN;D`A+w!?;vMZpbs2bn9qj*Z8jZAUIb&1GKPeEK z(9Kwu6f1A=zfvPB*?|xi)bnqHv7`vspoVcUeWv%9r;oEuKd$Z|beqlyx_q-Q{OXl8 zKBKLl*Y3iVZ}yuuE!TeqDb-WAo9gXE6#{*OEy9q@Z{aJMPPZ|o(6WnVLDOTvuwIE7WzJiDPnWl z9#)SD-Mstre9uT^s`Kszyf&n}$RQ}jMjwBK3zn;CfVOy_@dMw1Z^p;xqouEIY4|6)5=yKcfY2l(WSIU|I=-;99m++)zcI-FnDd1$j zoJ~&=Ueh3M*M~p-gU{KnwDLn$O$>A3K{yOR_9u>c*-blT9vI;&o1rI+VYB0XfZM1ObpMH;nJF1#6Ll=nudec9%7l|>~5@VLhF_L-LS0-GZ)iQ3BT**whqVpvUr zRU)eVwtTZxgrDIjoS!N2=9dSz_O!1#O4&Li_kg7(wY<+w48+~k=pM^uw>{r0zCxn%A0RTUj$JxRuinJJ*!?+Q^5et$1vo8 zIWR)WC=fMIq2K{`E)%kZE~igt^Y3E9KEONHK(+Hbmga8xxo@a+`!#_ACIP`Pq|JWe zl(TRZ-hGQTLw#V&d>3DiVmm$uVp4V!STRr9>5?9&0#TaH8y>P|UoQR@2FxU-@J51En@~L|YawKA zA{7sCI)g~AQ~^>?o&lftd7odpPq{P!z`W#rn*U0-s09R=p*V4`^ojLubx!3sVUGzc zO?F~smw`89B|htUPLcg)9AEmLK)XJteLh0U$ZdwIzV$}7h|S->nZg;n$&-D~D=L6f z90A)$L*l4v|3lBObQF3`Xq42EXK8#Eq(Uc{@>7-wMX}#6=8icYb5woO_TY%K-D&V& zNDErd52|XxiMS*0pw3o*h-Xn*i%&h(V<#ggDRRE!QRV32<`T-x!LP0n2(x@hlYWS> z-!<;zJ^?*1D(whaqXVHw=`0WX?K2x$i~(*IBUtOj zHgmXy)CeQYH2^Rg=^W%Ptai1doq8`R`geTCm+lKRTj6uI(*jUQ<{q~5#eARAdKOL5 zs9m_))#yIuYltVNG-_|`~cNhT1}@V2I3Vw+%@m?Z9{PfwsUJNsq^*TVTPixKg<;mn%X0XtXp>i z{~~1ruh9^bg3qZGGh4?M+tr{0Nb86^=X@%Ckt_WmLFS%YjUMXhI2+2p+ApU^sj_5b zfA^9+zKK1F%DPI-5jU2_@_rMv7NG1bEzxm$5b; zVIO9A+cm9Q_nJ001T}XZRs)cost%qzp-_X66e~D8w7e$c7@t2wzicj#-ycgz7H@4db^tMVvr zH+Rd#7($VhZVu-R94CEFh8oBIQ3By=dBF*3UVD|01NQ3ZGtQg$x&kYuUnpQ~jvkv+ zeSdDs7J49?Gfd15q&xt)`+a`tOE^L8YUA$HWRYHb0@HbQGcZjcF%3t%KHQErcMln? z4iPn?LRj!4%{85e2Bg;^C)@0*HN3!Su+rO;xsD1*L{T#_9({= z1oM^}VYK5z?E=|s2B2Nj43wbKW9}%nOJwd6z&gwOo}No?lG!fjH2tn{1sApBbK;uv znL!SbtA~3}$IUH|j0DM$rSE??$Y_~+T{;roi)R(pW8#TVG@z64I?P|ME-R>^oC`sUK4J#^!u^ay{_)2bX} zgAy<{JJAJW1N55GV@9KmObBg5;P(GvQM9A_%D{Gz^MeR@wbRMVCs|bOXJX za{^cs^2W2v?(<(eJaJ za_t0+wtBaQEd?XqRjA=>7$L_&euoo>tm&nXyJP~3k;1?|?qi0eqPMeAQv-nD%YIxG z=;7LbO3%@QM~2~4IOXj@c}HOBB^=8(g8Kn0MR_jq2rCvt)oCEt8NkwZnm1FskEuHI z6$hD9DUiZ3ufQQlv4*bx?zB_f<+78cvt&n@k#7b|_&m?Av&wu(wogwWGm<7nY6F*i zEwj%$nv-k7fXo#4agXoyrTdbv=uso%i8j;NoSwCR_eOFOaqcx+SX6T51UVB%k}&Ep7#aT8OEGT zD%WUdyV`W85q!qfJCLWW3i_mP0RBD}*P-P-xGzFk`9&q=Przmnbv-?vE}~636*<`O z=s}*{jJ2kO^nGl}kHApf4S;V0JSeoh;l7c@fKcCM09}o%rq~2`vj;pjGGW!=jAtpi z!I0l?`=}ep)HawtD=(VxDv5ILk9@Q|mHeLGQ>9$#jhxz)TBZ69-B;SDH#a3}gw(hG zA@Fn7Ydp}*Bmccb0KHIt01CgmZu2x+a(PK{3ZXsPaV`i0Shgl7J5gb40S}Ri!>Lgz z(@g;KJ&Om`Bq=Y(-DP2fN#}6_+bA`~NB?`M-H3icg%Dpr2a-wAiy!rQqUuXJn~LF! zwtYH&yPFjyrkLxydf|MP8&U(C_T2_&0M@C~WT3TLhao4Uozu>YDiDEB*zNN=Qj*@& zqXb!I4yA_nUdik6vz^;LB{VAUln~mvd0ySWo_VoOiTll>QlXIqaUL2RX4j_A^LfWT z#GigiuvW+T^bg}ICs?OxGxz?cWJBoAQ77tI8+KkP?;@%>q;HR`Mzf`d;V55IAtiNy z0}H>>hnqYl zQ%0!TQ!LT3Ld>uDPOpctHISi)c||!;mJoIR?uc(0SdAEk0c?!Ap7T>~fvsx5W#mO3 zp|%@foAudGt1Xw_%-=Dny~|CSYT&TF)rs31fyANaGbxOd%j!GUWG8ipjs32*2p4s+ z8gCdF_j&U@zI3x(>B38UvN`4uTs*xId<0E1bWBbd=yU&3WP=4=p1NmwUthXe;}UG; zq(&e9bTO^8j?k6)?lR{bVI4Z{x-uO>Jp|~_(v(byE_HJ!V{fP3D%>HXtBrPixb;Tu zkdH(3HrE0I&pWecn@Ip|zX#6g>Jqx=m;T1z$KUL3cFGKU+-IXE@e_^o z(>1=5q*;i3?#ZF_3f_*Az>4vFj8b1ZA;DZint)c&MQEZcV( zqU9;=Zof|eVGd2$X!I+t5xfNBPsYMp9>3D_BR;R$^`jrlxZRYoblS`myV6+?-f#^- zRx;~k0P2R@!mFm#op1p0JA|6Zo6XC<*&TbEdI-LJbr-G?lFHHuJquq0C>N2r7=l>_ zmvN>K?4iHpWLg}hiHJYGGAdZ)c_sCkhT++O%=OP^5L@}F+|AOxosm*d@K`XFvMQTu zU~)1pDc9NGK;>Rl@)Ur#)=}%j@HV4lzD`WP;mYd?z0M{nv*dKQ{D%3xxda+$ZP_H^ zpdOIYX|dND*W|>|x(>ifry5J0Mrvi6U*NOrQaZ>kQt08Tpkw;?8-2L-c8Q$a2*b`9 zDL0aEaJ$5%5yr}>>(C<-dq(~i3(RC3eovGe3MvOPRtsx((|Y4ud919?U& zGjNnef0O;4wDh@e7h*ziEH!-^HWNq>{K0!1auzI?Ygyo8={#F(LQB_jUW2Hx+Z9KT-vr#peSW8**~NUC2H=G;of^}8X+vfE zP`hwi=rzH{u+U`b52I1+q1Im4ePD>ubhZ;6<34f7$-^!t?-AS-_3csW>mx+)s61U- zjavUqQ|)TU=V%lRHQ>CDFW2Ln_zsG%o+tE%n3umj} z98AKlhJE_ZE(0-6L?kBoSY_bhf(LH}PN^ZyVUB=@KuE`@m%OYdzVJ#ge5PuJ`rC&o z{(l1-=j(5wmy$hZHrj9~&`4|S*ba~TlOrHS+T>{)o?}U=u$K7|1cRG=lLO7wGyHHY`BrxFuHVeI9#F3YzsZ?i|T1l-6oEYXG*i61Qp zD!Gdc%QaIj4}kWsBPkyoGp)!h_GCmUgP~7tmg&tW&?u}@qefE3#lpTaY2U{N)n*es zO71WgS$_{SIh53Hbe$pv{GQTsYNeH)RXqy9l7KHh3^h^U=AfzQtAZzsuFLPaYW~Py z20}aQQpaTy$x9rEe}9_ly!XU9W!haXSD=u~fbH6GP~UyufrGrYyPLsfp;z)j0$5NVksT#>xP)Q-*G`v(b2>$u=KIQb$xM#TAJkK*4!7o@r z8*R*huQpTWJMQ93A8K~VUxN1~K(Cqc(T*Uwn_&!Kxazc7DlM=~drt_>8d~8-rEhA9 z3L`!BnzuLGHlJ;Xc#|f(1LS!Z3(3%W^pWQh;pxE{f8OQAgntu`3tKrOce1DQs^tu0KXMxwrKDdgu3) z+j+4#x=#6Ef{rY<3sUS^$I#q+|MW((moKfLErPRhhBo zj-U0p8MHaXMWkV`UG^r21cVO_yKdL#Z2UPr%0RxKigseSIeGVce9!w3Bk2Ng-u$I6 z-Qzy)Ay>RmJ3a^80GsvmV9^m%Z{*5C!mIT#V3>1p+C?M^aACcQswW~mJgiD zKA2!PN-8#+Y14y2XbX;od3Jl@()MK;VppQb6@%yX>|LZ*Ch(yaUU2MpR*W+*L~bmxxB1( zr!g6V(PpjEwNeS1rKdnxvidP>)XQCou??omb=jxnJ5a~T>Xn2}T7-4#=f|grH-f}F z?15-7OupPmf1$!C&wZQA_AlWLs7%T#L6Bx!3Y*}NE=G2gY=qwclBuM;7`0aXx}160 zEzV;o9B=kdq8vk>DD*5;ESNf-M|Ls!g-`0K%A4m*5k9)Eh82TkFYgRDPU1f1xU)Xp z+bK8sUhQg^yH?CWG)q!Z(|L36rv7CK2xq{ZGUB2klY4+gUU44*fPAb%N8J$9>eRPB zue7McWz>Ob6wNt7z8*U66KH3n*4vQVe;Fk1q$%f^hSldlkWAJS{REEPTk5AF z@2<0PBMo+Kr$#~8dB~$(?P@xY+8hv0Z_d`&o^E4TZx_{5gkRkBy@cB}jZ8P$KlFio zg;3^~&A}C|o4&nuwcvm`ERSuMI?@=Lw=X4_-uoFZ@D>2rXrsgeyPdRA74$(eIHy1|ks$vKFrkQr<_7sn?g_z!fJb%TSH2T$SufSMq(>K^F#kg)R`Ez!7{SxGk zVkrHNA)F%yhna}6)AcJoT5dU%Wdm1+-vqa++<+ECr-`acHL=KK-q z6z9)gCWFa0UyRubPLylO>y>Jwf~Vtp{Lp?o=xPwMpz$h94amVXH$<#PP{YqfCG`N7 zlY$yNGKEic%Z`ubm0`iP^fu%u!Qv?@`P)1;E_G@qr>eZe60hY0&qg)9DQ<5kqS+iJ z9d7*GY|FJM9I3{7l7_dEGJwm}pcC509F!C;CRnU>rWvFI(gNq)hV`9XAMYgu?-O3q z>Jy`nh@m1AVcLgwGjKu|O3qIdAn{By7;yE*gPjee zp1uof=U#Z4h9c(?cHO#O^m6kI-~t2s5fJs;x8rlp8~LR6EJ0(c6w1}>ZJM62*6T8Y z^=}2O0foN|d&?1Yk}rXh`b+4w6Wwk>%p)-xd<^7~9UKQsUWV683`BPCS6jb*S=ygN zN7v69Clh23KC<*zaXIOryXE{8niUHv4!(pKb;o?rl?Zp6$ur zU!7lm@XB-Qdl`mE$alBBDvsmK)ock2JP{q4(J1a4+E#;?P+E~qcJNNwIW%fv4}z=( zVt>>RA+VY}=ANNosz@!QEyRq|y*vy+b_oYa^5L-n$yoK)5$BBTF4Ctr{>*Rt=^PSA zLt7P zq3J2T;1^C?Se=`1*bAR0xBx6WI9_gN+EtA(D%dZ9rn@wSUY=DF#vW*sPqv11{Lq#^ zP#4&a_get|yHezmt8HD=y`0YcwB$U?6T#-} zMS_4`!dR;$<@r@N_?=WS_gOMZe=8k+9SQqhVAPtG4Mt$!FnILx4p=uKR5)Hd;`VEqR@0Lj4^J<6Q8}N14F6|8OQ2Zy zw>F(qu{&UfNpGR`9;S810N-qP-Srd_>SYGP*Y+A^A|6nMs0dd>a2T%Ol{&EfX5|^^ zN136LBqOW?VqCfE+O3~tnO(XL4(A(9j%{dP&STVx%H&+$Zp85xQFD$Gc9hu9Gr~Og z3^Wpz%#|>Ks2pnes$COv02uA~@Z8^2@gs61~$r?jiK4QDENn+ zN8}eNXl~}-mmaFp6A~=mU&rMnt@95~wUq9&7THrTd^7i0gVpGSnG0)4B=f2&y_!aq zN2Rfp;!t8>afq3mJE~*miVH%&HrJuw)teV8bul!kb=$>1r&;*0xH{KSB!tMX&ul4P z`}uyfwS7QL2G#s7EAFhzEW!5x>nX!|)20R3Pi!*feYERy zw%&BkV7ZMm#ya$N8Xs!cJlzZef6tq~xO*GzYUezuC}wE4(d2JqUWYYnLg@18_~Xm> zabM`@?QExfB)&HigYMKl%nyIKo$dG>a@E$Dr~PimoE=A4yH7g~Fl_ZaVKTJQ#$U$= zQtz{z7UA3Wf3&#F*OwJxMC2F>CnN z?f+_Rri^0-RU`#f>&jT(-;=Y!Aj-ojm5l4KFH(_}n5u^U_C3pP&XM8jtbG+w-!g%r z-|Rc$H3^^N!;L?Ogj7Mkq%}V>F>DvG-`Bi|VJb^{L#GCr*%b*|J6?RZAt$>nf;qv4 z>n&r?+pr+VNG2hk#>}KKrB6Hp)edj-guUfl_v?J@6sX?*XG1QG6NR{N#?%LR*|C(r zTV;Zc5zp_gyzG>d585$``nMsT3Py_2I{(rP&&?Vx)#{& zsUgmWWH)O_`3y6y19mJUj{!)|uw1e+#Zt3VFR!w&Pc1jv(WqVRn&GH(W=yxB>V;KM zV>`gGaCg@ITR$lY{Zim@_c>z;pD}B0JA&6VVLvRAT;_Hoz)pr9>4Wpy(j8Ev@XO#f zK9F$xa+$ydwyY5XbXnl6KT`Cjn3nKGncXkjsD0?j4ULAn!tb76rIE1Gi+>RA0^)*gl;0+v`w)y-(DRLqB zDrwO6^nPdf(b9L1&TQZlp| zXoID6Rnmn+@ZLBj2Qr)u_02Ug^S5wngy=POm4WpXLOQ#)dfg%CGF)xw&TX^5$UvUR zH9Rux3Pvo&sdRwH;SlfR3PvUO*n|f> zNlK{G5{YCfm=+A4YFRjEw^B1jLHHl{agWy9rFQ`~0&cytaQfWUXFmKZcoM+XF;4(p z;C8n2bGE~jF@0zAJkHrJQv!Q%(#&UVElBEWX4G0ZW`^;aPyTN-S$_110&$e@=;<5c zO@92v{%=y|9aAZc((jWr)~_?&1+M__lY`R#P$Gpl1MpcR&nLTgI7@8I^X?^R%lsKN(m~ixC+9L5OJvLinEQIBF-7lS}dRw%vAGdyq-j~;+Y{aKCQ zVm!pvt!4Le4PX~s`Fur>Xw$4Espg00 zF6cwjIX=(+Dc8WK(?M3V@T7kIdjNW4p@mUT|M}1>W2T|lF1cR5L)(B<;hPu0-(61l zTo|?sNI)8+ad$(N;Q=3A-RkZVGb(tV;lG7JE@f4kjH$8&%JA0SNmBXzS$^*EImgYM zU4HG;8Hb?i$)X5i2EFu!+p6GMj^r|Ye(C8qxg_x>ydcd_z<|s@4iqFACPjc2C~q7_ zBjg1x4@)p5oZ9;+%VEl?K64(m$<&MvHr}b@BBiEv`G3SVPyNrOP^W=PI{Yh!Wj50B zHl>{{P2$eEOgNyq=_rkaZ;r*tJaEA=Ff$&ee6*?Ay}xqij3;RVw8WP18Cf zAYjF^HDw25Q*wv%w3D;PeTA23mVogKc+$VBT*Ii&bOMU;%Ng^LpTeH+KIa{BIgT4M z)3gjXSr}e90upjGH@Ol2OeUqqrR1))6Xfq7FOJHD86If!>j`>!9$1|w6-V)2`uWxo zGSGqcFSbdo!xcGO;5MFqLS2OFE9yeUU{T7)MoLENAP=^(Q{{(rO6mOaRHskgQV}T{ zrSu-sGLYpD-QfzF!lJDhPDi$Q6T4glS+ ziK#v0qN(|&HJ!Mpc;ETH?xRsVW_V{CbC>W-U))Ed#((t>rnfu1&pt0tkYK0|=v51o zdpqq`Qm+e#zC}Jcm7VNdJ4Ob=3zVsi)06!BdeA!q1CRalq2xL%tV-76;)>}$<2^tL zt}W@nPvr}?mswpbtEoM?c0hkOa}8LZtCIaZR*m~(h-T_}E>Ut9jZN7N*Np;8n{rWV z|N9Oc_O%!+H!D{cqDmap^!t>3A+q~-GgVdQor<-nI(E#cW^c(hN8mJ5&hL3&Hv{eZ zaJzz0$?`FEl2Gt2XZHMa?j0y6HKje)xiK4}78I*>QmJ{)Su@-+gwDp*JH?;eJioUn zH+{H#H#8~!_2j8md*2_0*(e1E31qlbHU_PDpGo)2q1g!m-L+D zwPQd|XRz)^Y?cTHY)7zXd5tmK1sZpZNa-^07AS#hKJ)PeY`7_K7E z5?lsR_*4b!qAmg5JnQ6?3>G@uWqk;xjpU6&xzzs}r8hv9Pr=+J(6TE!sU2Us8EE`D zJ_m;Pz5Du}_qcl-@&&G8pPz6i0RxTNCC26uUJTf@6Hn7b=&?0&YFbFG)~oahOzuQi z=sogJZL6FBY~I)}AR7``RRmP#oU z8>Kev*5mdildKB&CA6a7b^bdHC#E`#ZR>Kz*pv%28<~{(Uu*=<3Fpk;%}^GN<2F}m>vNt2 z%mH_{j`Ksf8r^)K-xEA%Q*;2weS8@w&USsycBq*Ngm=#v^OU5YgtXRXkr2Q}AvBD9 z{n=*6nbRHy8x=+ddwy_|)#tEtw!Ii>EkDvz-=_W!`jYY^mA#qw`T?@%8_dG@hb^dQ&6$3Y&fC-lzH$qAjgWj&LH!>|LfXEH=>MQs86r2b-y2id=$T&_x>O7^eFk7= zAHQ_)AX5qnWaY3~f+lxUp+<@Z8%WKXI|Xw>iFR}Q3O z`qU`+rdLqRQKwz$f1IX#ufc;NkL=(GMqe6^r(NHo;J+A<)*$(o+4xX9rY}7xVgt|* zG5|KH&-s-!K!QnOp&6EeWyiyKS95DMHA?9RGf?p;88ktPALj$7 zvCDDb_^{MAJ3<&RbE3YbL`Q(a62ywRAFrDL&bZg3QuOd|tY0imX>4j(u*-ySy45=TIbWNTfrjAN^WO%r3> zdZo!#zU{~GcQ_3@{$BU#X8~p3P44KtXZXL@%AGU-1gD!pdCnawyAx9+l#j|xpky*l zI+jx2mCj59ZZMV>l8?(Da)wb)Df@>PV7FQWa$9G(Ntw*JWBJ}G&y3re$OyY zmfgZp-Bc;S6}rYFLBWNLU?E-?6DhHM(hm`_vekIttK(I^u z5%W1$XAeSkN@(iuVbp|&Se%p>p;6T;e>ATmOw4sfD;ze^H)#XWZ1i%R=3>W9@DIM9 zz2mR28igw@t#_rqOM@DZS$?-mk-lKG|Fefd(zhyJm%l~4R-X4lp?0~kAa%>>5H)hE z(kss%vPR(ZD$}pLPz)a=iIVW8%|4;h)TtmCY8L32R8uj6c(c&2(v$E|Y=$Z-p}gay zGm4patjXS3gUgsaN_moVV+`7vWsdPAum3Eb9+xCnnGmC#HMd7c&YRoL{J#2PF zoD+gs(!*_=BN)fLrMr_>sgg+-olPhD_qz4k5f;PZ)E*bU$*LteyVm@Bqrq^MvhpRr zIfCbx0uKob?d}j1W0F&yoQ6|BZw;?xAY5(5+c}WDKZ)Ub=m#50JcHimIcz3hXJUm@ zc01?gF5L`Dl3KxT8932FU<}z^yy6V!*KJ{-n+dM+>pj!zP(?VqDeq5k$-cBY+V$bj z(Rw&3nbuix$T5H}-)%kGHPz4Nbi~N?&j!u661?kdZER5X-Cgnlq_rT>$PN&rq)$%K z+ePd3w!ZD4m;$+vz0Ph3JpHL+uPHlewpiiD@IQkcNBR*X=g}r-mlmKLz*yb~@VKrq z;#^7g#P3EbTx-d2N#EpX>@-@83?|*3kr%oD0AnLPS;9g?TQM4)UUqi~YQIw)c>bRgfl!oq zDb^$MP9=Y`%u56CfGNO##V-(2H(n8p+ij`eHxyrs*QaWt4{tS z{9;W{K-jk~i?X3N+wn$C9U?Cy8mN&{hrYp% zINV2&V0e9#S9t~YvPtfYOU{4e`6{@LY*!FC-6%9Ou{Q!-{b>{&0AXia?*Alh3;(iM zn|^wjf@V_Ra`I3mw7o%6vv4Y8CPS7TX(|Zv2zG@yCZeaU>yI|`DWp@$TgoIDYcJbP z&$Zabg`>|D-+9d0?mzQm&sl+sNyNf0%iz!ZKtEl^6_0}C*oLMM)Ckb`kug23r(Uew zXMV?iM&>AEQg6wA$9}|ymyH$2709OF=9GnV9y&riW`c2ag7;d!(< zjO1hO&>3+WfR}yrpksapp@V!BJC8Q>(lon*MMD|K{5S#-KR029)NHe}(TCg7#@+Ap zJMKfzz6EnIx3mID%SLk(fXx$R`)wLg!O^hvz9MG~S?<#uGF$i1+v4sbOd=Zv`c20f z$O*=1XQS3XG*8`yPtP;RoL2+=_D|!SVvt_pN-hfZ47C=nYI@GrLNGfIQG$n1q`C8| zyOW{!hY>Gt#`Zk{F=sEE8Am(y7TCDED|3ef`W*Yqbj#_u@8XC$AQ`U8!a-th!^_I4i5XNSpn zf+~em+)Ec_9C*~yCj4w{w902A_ip_ysb%-h^DF(Aoj6V@xdj5RFqbBpA_oxG!5=}f zGVlMPUBN%u$=e-Q$tMuEL94*3xACNxB8C{bZD%>gI>IR7xMbY^zeiA7-i|>Af=OjE z!RkkstLNwGNM_96?wL{X&}}8O(!64ZTlpTKcz!B%{~h_S3j;qWiwpz>=g}vVy@|G{ zuqjJ3f2Z1tIu}*AB-tvp)1V!O@1~?a!GM}1rj*2li`e$GwRGB!hQo z_9l&&uCJ0SXl+K}m2rDJ0STrWt=pp5MSYxmp&eANsNl>i{J_!EQ0l>DL z-|yqT?(<9c`MsuT1K127U`j6XQ&7Da{+2pmGZ6u|-mlqhq>%1^HL^WIZM803knSZl z*IXkTgZ>UQXSCDKIkSAd@Gxuw%t!=C!cw`BT<)UASA08Uv?b;4=zb5gTv#)7hgVP9 zw}G>forZ+S)x-SAxrgg#C!~j*YNQ4O{Im}QO<4vOC~jX(8^RH(*OztXF$ld#Dl|1O zjDUbu#yWsBeYa;pq2vIbChnLM z@;b~k*I$Cs1AWTIUc4ya#pq;}$@kY>;oB;%TB;fY@$EUbg6Gf!h8aGhFp=%boypx` zK6Tr#>p`||zgXT9g%H!e^_%r}mc7;|pmeQ`)`P;-N7gsAuCz{B#RN?6C()<$PN~Y) zXz?K~=Fr3Y+{2Wq*W^a=Df!g_@AdjOjsweIxD>FlH_Qa^(t*-ahp`!Jy~sR&_nOiK zcX^u6J@t}xx!pm;;^bywk@k7dq61mgY?1$3PI0;H%X%)G`(cJ=9+B$J7;=RPfZ%#^ zb-n}W@@_-W=-#7h&HxI1p6p-NJ2le)i$HY05`W(PzU013uSaij+}e21&#LPcGdRP1 zewqOb+2cP!jT*H>?&>_nydp7~&U?r=_*ixxwe6;(X*7~wX7tIGHdg=H9$G`o2hdKC z7&ena3>VchWM>F_(>k(z3aC%1Cofgv8v(fF3#tsr$;j(VviDkhEkGao;L|^7hwVdZ z`%*I}SR=&f0|w-~j)3p->iNsl-jdGRr<{)L!gZQ;A)r^qkv-K@J_j;e=8}EQDPI>8 zb3_yM(Va5Vljo1HN)5|boDoX!)*WivRi?iD9$4UYp4GhZ2e?n%c18fA68&57?Sz*Q zQswm9nJ|~ymM&PSGRNw zD~@+#M*LN~Q4MF`m&|xlNnN6SJ5uO*dx#YF=}Er&lnr~sc;fbx48McRes!1ws{NZP zvEnJ>w7{>1#-ECjORG4)f;RE@Uik@(=a=K1Rl8}iX(b`POIX&i5dg6qU@=Se6 zI~mCjJn_~igC-O+PJJhKS%IgIw}n8dA}-LBcLb}#l|w1T=Px(x32?w?#8%*ZMUK^| z)?{qbV72c{MaGiSH|b7K4K_0GyYEsUV3CDyhCQ;TYAj1om>7=M(|RNOthAedJ3}xk zv;t|8ppEPqQu%cneORn1{EK87PDxeHg!Ko&e~6!Xl+yCnIhufBR85?bN(N)EW!iNw zjZ`6BWFAuuwjHb9=+Ol1xh9gIQd`6$_?wHf)gQq!Td~NEWYST}jq~^jQc@h1WlWB8 zkMRIp@m5z*_&aa$oOFkN1|m7HV(2v6Vo!d(OOQFL8bKuqZX0*lEXuS8MBdkz=9l%r z;JgPOb|<@6jIb0TcIZIvh8kqDk_%UY}!yRQ7 zk5A+?Op{%+8u)H|VX)R^H`Lmx!KK0A?E!Nd)Q5Z+hwpJ8_mG=FDV+OeK}Qs_ ztUvV~dfRcr!|uROpp%JCUzv8~KZGJbs8tA)_M{m2FW*F|b!I&>FJ4{^pF)st!?Dzb zkb*`Ehw_{koC>>;1L0!-+A>L!*K?R#Q;Dd_qA4eCrpjVU$W}^GtnQI(UG_NVuOMrv zfnhbu&)6+Zg#1=G>|ar+SvPa=lw0bILR)Xp>&pLT9A6d#T>Z|$CEJNPm-R*%?Zk`|=w@*D&{7oS z_u9bHydD%VT!s#yTPWjc^Ag|Jm%eoC=`wX>`DfEZP_>y-T{yAzFM`9o{A2)Phz&DO zbhmS0SimC5j+^z?MhI~OQ~Fz14?T&fXA!-R8+UDkDXkf{!Qs^ivzz_T$d$|MNs ztWBK3PbE3E=1+9rAv%C!5?Or!+tZf~FI&>_O#4p0GCU05;R^4xHx` zIPXYf0P&nkhA^q?FZo+KDr=6ZYf}N}qC`OqK?V{hn!k&=>$R9^y-_;R;rSWnyWk*s zrRltBQ@$59j2eZKK0#3v&of{Hx8K*z_q?nD;ZpN%?{xsv7q5FfyU@N}sN16EDvV7_ zD9tq|;Dr0@dOO3F?Bq8E0su}i^TW49%Xor0vpA~D zn`nOyJJ~DGx`es}XoPAOyYw)A1b@@dKpQ$$9!}IH?{V)S=5xtQD%55dG=p()lleX& z?@(I#T=wwH)zdUskmV6! zY&-do9j?oa46ixg&bv1X4$^5ZT%SYks2npY0tCMMO?gFYB!;@#T=y5{FQsRbU_!&p*D7C)ck2&A^X2E8YwI=356UNzyak-6SLY zUR%b;Nq=Zl@;=x8eWb~0$Cd&SHsLB;Xa7_ljRO0fz&mr2f9*3eX!M@%>SNhusNj4# zC{_#vMT-n~=F0BheHxFr$g(d!(4~7)(C5q-h$b!p63BFFFx6^0q`-?Hu|Y1C%KL8K z!+9RXQzK91la{o=DJkiJIBPIW^w;K`sCI|E0R*K~@^22Z(5B_Q9!`n8zlS}l0cuwx zRgrQ4+xNWB?**Rz?+?l6sCy$!qgjFG+YkP7RSv09+7Wc)kRFUd#*=a6II*OcdNxzI zJX;IRT|}4?*HoHHe*wX?J+tNA)zW(jSBP{*Os!$;DIAN#=wu%cPRhgmG>CDz6d`h6 z`eW1gO(_jPNssw`axUi}DB(E|`}Lx3?j&TFwHfzi{$%)A87sv*jIWZ8WLjyY0}xGP zUP5hYnswqP>1`T%-A3Q3##V?cu-zrw38VP2g;@hN@oWs_dW=+KgmcXR|$MFIz!s?2=|=obt_!z?q;k;H=IpxDISe*+3CCZ=BUN7Pk|ah?b3iuS%(nb1=(53 z*gFKQ=58)hi@zpM;g`?)D7Z?dR0O29 zne`eddp|Y0V2L(h!yEdPSTm*%lw3%?S zB{0Ldo#qMm_veZp**mFH_VO%CaLai5RIyhKmoB6mwT|@-(_*@?{1z&vn(TNGcDh7_~x$e|Bg)aDI0cqonx(h>!ef3)K|XCU+64klgTc?%>Gd)C$#XEsuP+e_XQOGRHJV@6 zOK0^ol=e{L&7{-)r_tv8!8d=$O<$H9OtlTk?&>7SY}`)tcC@PKQ1;!SCTKZvP$1KS9S&^Lm$^$iCq5A8{fnLz_Ga^#4v%TeF|r5vgHYI?fgUg!v0j`ENGaL=y}rhd)weZa zAiee!=D)B#_?V}k|{V-)cwMt&+0Ca$rw&?I^9Zfj_T^#VsD|QzgAeOYYLkF z+Lx>nq?`#4`PHqGu#BeXpO{C$!w)3Xo?SQmGQ25s&VJ#oCQCf;iO(|zOeE0jj$KBi2&gr7F z*6(Ron&pBXeHZ+2+&fD@f+V~r%h~?Dyp@;7F@iHF0qs0=&@od5N~X5})?(j&0{D zVP9j(oB1)lLnqD-xPrWtsbYBQ$h;fXeJ}i^lqtnwI|pnR6!21Nw>!m2om?iYo9$wl zQ$D_X=tT;maG1||-gX9GNDI7eVL#*%RLxT8ZKjo)S#64EGpS2jc9BdlwbcCEA@4W% z{J89! zC=|Bg!N4-^4dU}K43-^!4lX8l%a}qu&r69CxKOoTS9$(amo2sBsbbMGvN=fdE<;>) zh9)M{d4y-@9LHu3=9XS3Gea*9I{?<}2O?PhRATJ|>m!t9*%MR~X)5VzxFzGSjW{RV zZU%M0qBwdx0rU`r->ofp6VB!J4|1yVN(iS?Tr}g{#@_hhp zw4+fw0lC)5<9FzDU_=?(G^0m5J}0hr&iB{OcBROtzPVxtsLCh9*E-D|i<@VJHEjTC zx7%o18BNXeD20DBq_ju%JEzc^TT7M*hG%LD0rPdHpSXP~XY*h*<4R1(PLZA%^n-Le zsh(iG=Jr1<-CuKA{?az}XNZ`DUHkkQQ0n0f)kirz!}u^N&|}W;xC60Ga&3CT9r?Kt z@L=q(DGHn)gAqO+%XnF@D_L4>pY1q%k*(rnN+Gv?6CTKM1c^qXs-K;x*@21sfio=3 z2rz{=GI}lffU|bn`s{oo)p(01WWk#dd2-ybk_*Rm%C3C0{KV+@d3PM&>wDewolC^{ z9F6P(5)NK!H^w@)(6+TIirMNzT|Kd0a)c`xdL#RQuu>zrH%?WyrN(BcurI{^#(WQ9 zwwRfFRF(*~9ob=~Im4%IY$o#zyn9+gZ<6%C@cXC&Oi*96s-VCyzyI~=7&^h(1@?L? z>I6AGl#`@J+V2LO8b$tsMF;A%g$(4;6Qyp zoo%PXG#-2C4Ga|LfR>XIL6ub+tcv!1=vUB0%^PjtY80+Cau}<;sV8<6XevKhlJ@0F z_qX8p3u7Bx8ZZIiz7Q4@4@5K-GAWcdLw4U%h+=PN4Kq*kpwUX-ccT@z1ZQ$wWJ0}2 z5xP?UtIHyK{$TgeiA_ZzDN{EH(7BzHtKjSSM&}vDV8%SUD}*0Qn^>pfFB# zKdJ|6!rdJe))F0EOvw`vV>skb15xG{@-b!Cw2*z!XTEoxNvQNe+b_2=*|29In1L=I zdk8Qn-qV@-fV&TT{Qx+{K&T|$*AL_3z#%Q=6kS5K2~5uUoO7tR*G$i28XwbO#P9}v zxbf$lw~98B_Bii8zf%b|bNp;qJ7+*~yJSUo@OSXC!f!?xTm6Ia9s{py3=@Sy`2fA2R0WY&X$n!|+ag`ctDB!OQ!{6XczPlRG zgDe0ZLlYO+?{CJuJv90Qx-(4a~FM^SlgDVK%fov+f%1ht=eLuL;O|NoIO}@iIT=7a z9~k`brWig>;8hj?Y3Bi@f7n#p2rJ|oxN1W3cC5@aAe37pxakJl>vpA2KB@uEs?uz6 zFE6U2|AjdSfM_megG{9jDo{y(bOxlp(&KZZ$4M{a?BrSbC?rGgojQC8=a^zJM3v*X znejhAcy{FBxLDr~pzWD#yxMT^X24x;)Xy2tqW;n%m5WL%3BhL>Z zqCx6lDI8~%rH?v?92H^nkBlq%^u?6kOl$(w-Ovcr971qEbl;V31ZtPu|7kph(|oh> z-cAI`r_njuCHxLC+`z1b5Vg_HO73Qv9cHVYvb#L& z3ZG+^wvT@JD1m+Ban^ib0MjV^oH!+@cLWpLdpRWyXsp$AmrdQ>hp(pDY05zEK}Kss ze>*yVfZ(dxmx(Dmo=S%s=`Ucv8}kbj#4#3Ln>GWc&c+YahhhANW9IV=8v}Afi?;du z)L(+S-HLNQhY)PHBJ@{K=Qj3{7jpZv6NTtKPpo}SC7;pV?;>Qj4g zsYk8V%IOV{0^L2RY)|l>PcM`Mh4yduDGA7aV(a`}3XDXQ;3}*94tV~QN-hI#L~)F+ zLZ$-1OpoAeQ&T7gA%@P$Q)LT^OAob-oN+U*piV)T-TS~HX9j1;P&Yp>x$)QZ=C?NM#I5_6zl4x8S6`q()$v^fy2lY8>J9OY6K08a@pMwK??9&c~@1!^3qopqfg(a z?M9~1?kG^?qu%gzhGk+A%wznh$O{gvKep2@TkjxK#ml8#A zIvF!@Ubfv{uDvqN853;0hJBwTqrM5Ln=k7!%FJl!7;`63ZWJZkF@(xHX~-(kq4N;t z`?GO1+G%@B(r+56QF&h$f_bKWI+d9LzZNCjF$y`Ctd!hXR*;&>-qs0(y=I}>u4!~a zKk-5GZNQ$w=X+`^UFFH#M?>DQ=7-h^&_hlK1;cSl%kX|ct4fywn8;{#dk(XBqcn@c z$GX&<0A1zbtKpcvqY)@Mi-**O)S25(GbYcw%bwtbYX+a&_h;Q<2(ZLZ^fnD=ul)jX z7eCK`_B_J797{Ras)|NMm#Q=JkkltR5HzU>uI*(?iCS=3Ji$t*QhNe^_&qs~pRgWh zQeVk6|T{|ZX)488u{DNTOuN3?{gcBC8 zq376N3R64nruhiL$4rS1Z4`*0d_K_hQVg9QSv|M8+4Lq+SK(76|Ae~i0oeea2iMtd zBr~PN42vjR2()=S3%IQvrC+=(esIYbL&<@SD=V!UyxM$0>&3s>5fs5jh#hN6!B91w z0lPu4OUm%mgkhH_$XG%GGf?==RtI{*{J~#TrG{McaLQipJd;F?>|1A1sc<<4V7CC8 zQe*qfK%C|PHD{tBN4a(>5R2gIPL^7ihAC6mbivEC)9=MljK2*E7Nwn;H%H7U$vx#N z45V2_#BMC(JU(K2*nJ0`IcMhS9+Bfqk_)S^gwwnQOidhrNTebd&+m{XLRP-UyIsAL{?! z2*I|XXTf(9SD+q;jc|%~ENAtl1T;-fY$sLN!${4DQVIHfe9wE_=l8;K>luM-)+Stv z0O;LpTxBkw--Sp@jh5-6J65BRjuKnlk%8a_h5`c`C=@&#J#{s0MH1?L(hA{oW*{!h zs!jo2$|{8sxN|Xy05`1xH3{Vt6eeTjvDUNXE#`%HcKu254i$l>+V)M$VMWm0U0>1t6_iI(sy0u}?%$|o5z zgfcRUyHkvywS0GQpehR!1`rkdn*khOzUfQffi4$VbPLL5%zngnA^d53x$5Vi?^a7} z>$nRqT9$T&SRdW@LL+}?il_Hj20lY5x(p2OrwGdVp0~rE0tY@39A{4@<;7Vtxnvl~ zAFPNT)6ia#doeB>!QTgL^z+Ra^DBIzeRRxSm>;&+9LW_vJ=BE#13_`OC#V2YrV+A} z%X>Jx+c12wkBy5;(ShVbqA}l)OPN~zWtASm#GwGA`ZrahiSj2M%#I!%U@4Pd_xW9(@1Mrhl8e3Ht8m;v&rf{TbvTc zp;3Hpj=>oZ1FZ8g5LTrgZw8;2^fZ!BPMp4v3@$sKbH*8ZV`*?jvADQSyRVfnkXPV3 z7v<5#K2w^G!=a648BTKnm%kM|s^9@9aMe=WXwgPiHMu_TVK~L&Tvb_qERCHR@hHci z1ki5hQYt0%SDJy>5XpZEk71cn0t)H%?N#jm4$vbdmEf5VZSpBS{Z#gU3bciON0s5S z>#V^Gy*lfXmHmnO{(bG+dgZX=GFFqd5#C%7^vm&9N-ZIyKw4@Qx+}BI8MsIp+tr4c z&Q5*O$V*qHtIwOokJH{Fn=#VO3>lgi!b$a6PF~!6&rr2tT|&R(G%)WayKpUDS6!^g zBn@>lPQf^68MDtnQw^VOv21)3q7SMYZBu8G_q@IrKQ}dLbm+3*m3q@$E#rJw1A~(v z+Ea%a-JW8LZEOcWyl;Hieo5J&rXjvxYiTy+Q7G-aI*CVbr(K{kd%De_c|L9*N`OIJ#-Urp*k zGMAS$C7Rgvf@@8+hnP2#@*cx$aSyqYXF%IiOL~t`CytmGc2dy$H~}dLFV`%~5*3RK z0nc`|M@XlHrSnFlUe&!=F3%E-r$s$0yb*(waz`P3VvR^-8^kgmRJsA5OM7zLaT@6< z1Rp3$`@}h0x674S-9+)X-4Yqjuuv10~=#90N{8+a}knvTG=%`2b3mIpYXBjbu{WdWdvBgL%Qo_H3V@wPm^-0-CS45K`?L^M zKzG$QZk2Aeix8g~E3n~EdA8Xy$IUG8$Z-iDMREu#^rkarPo)#q&FFB&OJtQh!b$p= z9}3|%+R@HN_wl9syt!Q;d`^t%T9BJdN4w@bmbH)Vt&b)~;YOAoMkiZpo;!oFSoX(< z)J8#7(46l1lXg!}s94!3eB3h`;dftBHk_@0&R|&WhtHIIN_n5ZwdTs}Sr(r8+mCzm z=Jz^Z(?Xf71f_CR=veN0x-P=vUf#e<4gdcQPb*pmUfNC5osK`#7Kaw>-@xzWt0QBj zQl)vKvInn!?8(Y;gkMh(`4Y6x(}LHRloi%e$TO2)Dl>MIKqCZYQ9J2lC{->$JU1JE zLA+eC?oSibR05&y0Y@@z+GX=L8M4*I7fhdM&-=KK?|IV~-}Alzob3WcfeGEx7O?2^ zqrBXB?P_gpxgBPRQBmx)1-uleoPh;Qg^QRJ_XwBk1851ealQm!x;dnKEjXoR;J8yL zCblZ?bJ`!`Ma@)8y~0jzgoxE$ik5c(RAvUkeyOUcBzH}oB13Oy>|ug!Qdk{y$M}#d z2b9OK{*_^floZ~z(Kn;4hc`pT7)44e=6x_D6*_F}G_vPXC+wyODu4IA!(ijV&r1^P zUp=cUu9@S@_xRF%$lmN}uH&Fm4i7y+Z>^N~r>5wvXW5^iB$do8K^KWPwMfgk)Z0C2!(zpeGHWM%IEN$4fj&ZzMABTtST3f}H9#c?G@#6y&AwKQ`{e{qnBP2+boT!!@+lkUE?e zCE_cN)iB(>`LJAT=)GZ2u52dVu)S9fpSZYEa61DrOc?-S=yfZ}JkWZ;_P`*R`xOJ8 zW;9}-9!u|sVd{LwX(Z5op4qI9^S59&vNYDW(lWNm+$29d1yBtkT*cEB)ACqK>TJVz zN5}Xa?QEARngm#=2gQblnwwF=rk2TvvR4#r-D~f1md=<+n1N{wg<0Pyd7uWA|<&McSm{X%s!YN5UfC zOPKbS?j~`EONw3T@1$z~zD!Z6to5lkdSs@s?g?`EH&S#e&aVSyx5CQW0<7mD*uC|7 zZY^RJ1w-gaNFaa5m+o_ZJMcch81qW$u+#t$LvX4+oI8gub9EoE?oRoR>aJF$9ZZ2r zK&apgL{MTUX7c&YlxU17J&Hzs;Tid{cOzZIvzM|Mf+L35%-h)t)vPSBdk*Dqw}nIb za40YJXe3uIL|KiZaugGO)K4}8pmw#(uJZsOYV6$CJnr72LD>^0wwiki+ai*E;N&$A zMtD%7o6`*?g25Q;z1@3k<$2?7snBz};FnZ~nQwk zs9o~ry6zpuPl?A-5yHuMc?XM^tv&RGVt^?<%^d*v?wIiaqtRB~pmov=RA|8=AW5wA zRfmR87wIv3zMna&^MXSyY0u?t#Q8xlEII7b9xcVU-cFf0k-QNG5bsLvO-J5n>gG~u zhW)a7D>V<9O*a{4Ecv}VHT7t^MWF21(bCp8X(`do_;NF@W7d8^-)%>J8zThs&D|yz z{u*TNl8zwvu)t7u$LMCSaPA3G8;c>`4O8|Mbyq6vJPrwX?l^C54J z^?{7w?8r6gf7H{X8bs;NS=C+vSu6kdsy7=DHL z%>FldsMe)yE9w$P;V=MQ-tP{UDO``;vrUVfCdjeb!xP3+Mkoer7kH35fgiz)*U&;e`}b`!-!64&x@P9Nim(% z2p@5W^#+bK3VlaLMrttDD80enNcKcJVt<`AB;yCz=nVdKj{782!4CgB@Kom2w_o;S zRYItl5b|85A1X;BO!XN}-x&o77?6)-oZlfk=%)s$xA|f0-l$zdpThh=*{_%94XOPA z>NvbR`uD=|z3zE!#&uu1bH6ytZ0;75)aN3VgX6B{Dr@+ z534(K{u|ay!UJrih)9gyUw&_rHYvQp zTyZJo{RE74997!yIFEeR7I3hv`OzSo!U^XBOrG;@Iwr`+M* z-(bfRe9`YvHGQ-=J;Y#5P99I~G~Ncg;3vaX{Ow)6c;*y+FQ^`#EflelmRSnnpQan3Lpe!J-i z<{eqB7zhSYsx5SUl10osy&a0biwn}hRd=n4JvY*sp=H@B5OO<^mC5Gde5SNIiZsiN zNU;Un6a&N{ywnvoN6Wl%)aQqJcJMyrf+0Dk0D$KgE!&w~i`2+QQH}i28guBkBWPBO zYc0%JoE6h0VoNALKNXk$4*wAz%!IWxFPzAJQ$>EH^CZr>}Bq zId9YwQSPdDRNB3?%faaa0FgOhib~MVO%O?b*7C82RJy70(6pwUN7$*MB7;lS^&K{v zoF<;``h<&Yx?Z#%!j+V!K4obl3`CV&Yh*hq1t^9@g|;NGg>ug2fHd~u>(%Yoefi!}Xt&9Czpra`Xtc=DYg<|PmW^$2={1r5Fc(0EVboCe zaVZFS0d!F#8Exi0Vcs1bf2x9s_efp&=@dd{5* zH5R|;eJK}_iE?W$?iU56b%O8X?s7Kj3PFsJZ{`7*%5OWwP&69z!`|(b5V}k6<_$si z)~5Hk58H3H8-SzREwroh=+fp>qNp%qp83-MolmLf*tUVXC}v9#w1-j?DRQt-T9c-H z9QL=}#i(;9qlziDWDdm&DMlZlx*Pp1fm9u2)wo`i)i7%;N|11FvI^_pB-w}%tzv5cBCz-RPi^e*(@0X!1dEWg?Uwp@x&~bhEuinmf z-lu$!WXK!5>?@RWg&U~-KCAlrYfbUmM431_1TySXgLWGoRiz{a8uUgjz>~pJO_^s{ zB`Fp{2v>}_^}VLjjrQpa#9D94yq4It5B-KxqZf$`<2iRnt3ZD(M5$E8trS-dJX72M z&5HngaTwV6ot_)2Kl@M8a_y$ZITy;QKBeV(*BEBJ$?zs12Vk}i$eE^b^E^Rj3lBS? z6kcQsr-i?8@C;VPm6hj41wHewm6q_YUHM1tixRrqtx;~+<`DKRJL?=z|4pq>XYeq{n?y*ZxqZcH$@^BpvW4f zNO0CkD(pW`sZ0jvS3U75pf}aP>)1#xs36;697SOSVjy0UwuB73ObMDvw1ArZ8q&zg z@B`QaL(NnK-(6fRyj?21C1(4kni$^^(jt|Tk^{SfNf*G>%nsm+T*89wW%@?u2_yEb zWC(blqu@~KQc5N0Nd0xrWy`5aj^1+n%!$b-W2)bl-h7{7#QuBt`~33X<4gAiww*@7 zxY@o87=ZzxoifXDc=m<4$Gs~6qg|h~9c@VH+&r0k&LAtCKs!FC+|N9kI$zW=C7iza z(tXHx-;_I~ohRqs#2YWybU;q==-fSmYT1*DQF>RZF~qH5E<@;;>1n|pp&S_;Wm*OiNS8KFJP&l5}C_hU3OODoZM$z=S7vo4vvpEw7aG#K)BKL_g9VhLQm<@A=g6x~q1Ox+~ob)ab zbCG0~epjCi`h#8TNdXPSpxx zexy)2(z}`1VxIqIpx9A;b+3f7^H=|Yi{iU3lDY0=+SB`gI?5Dnzh`P1ZUKXlt4SM$ zk8Ij7PjC;R%I73WwQ&V2_uGH zHIc>rcCr8vqTxkCff^;XiUO74W@ji9%_8F(^Ecg-W;tnFd5XAm_hECQBE!71^|i8{NJ2Z#bSouG@G&=XWi1W1 z0i@kZ5~ZF^NklMv2=NU}TD_6~tLauxq@E)@nkpEl1ZBBl9q4E|>0y*qB?7{zQ%qp( zQrqM-EmYJmms#g|MJ|J zQ}U5}&CfvL-zO)EE15v_UOR{R%-Wi2KG#5%6u2klmPNCto;U9EKJN2-eo1x(l_E8g zedbBK+b$)WObqSiRztr!Uz8)7pI-sr2L{IHY<=!WHZ{(MjIP-msEu}@qkrkX?%uA? z*}CkSFvfk|*Y}h!w$1WWJrko>cCyCzn(n9OV_-^iO}{>-3?sPRfTE(oRvFE4kNOe@ zJ^%Kw5&&pt+_k)Qoq($Q&6Iwl^@Ev^Fe&Y!1-{C03HU`atw!LJ=Q@PIYwZg|mDqG9 z&VoUUQl8g*l|omR0iSI+l$g1Z3c0ynM))u+k*$#^-Y&J)Kw)4HUAPG}D(RHg2E#4$ zsMe%{?gDm5f0>l-f%ALb=RNM;j?dY+UD6`mT*IgAu+Mgw=Ei6g-UStD5||5t!xPtC z-DGGFc>l5*J?>15T+`YLDVm23OM%LBP7j(~b$J&`PtrY?P_X&-*)(bt@)^;Y#Hpln z>N`m$9R#hRq-^91dJg>87DnMZM2->C>hl9vq&G0-*zD<6{-xfHeEo^*P~Gdx_zTS{ z-Qeg4|I6IJo!hSKYGUXQ_2m-^$GN6mAAx55P;w=j!i08m6>NrGj?E`YC; z$Qv(SJea!~r8#1Ri0C8n%dD6deKVG#WLU*u^c$si;wsYKCoR!$)Gvto)=Or% z3sS)4lyC5w=d;hbOtU=O9kR??jpnOOYXyKtM#z1lqaXcfhp4e)UFy@p$EoI&;jI<` zOe1IGT`lDSB58$R2`d(FxJwWX#D-mfR?|Z;gM3hZgvVJf=ktzS1B*0hS-P`Y! z9-rNLmXjy^ml++q*@7^DeUByvu(xi5MEojrKU3NZR_PbTEYIXoaAo$84I#7oJ) zJI->}i|yZXrfjEu(alOLmt|;W8KI&&WYXMFtrr_e!5dCVQ9UdPV#}>|*ecE~2y3JR zKR-mJEj=h;^R8heQF}oS8aZKIeZf>Q8_Z-;t|v{ZV(4iAE=a6#b*0bW*t(POY&jj` zm~xWzD?IOQ6I@&@RBvN5F~d&GDUSeg+!SkH91TB>3GljN5$j|<6(JLvBk=Jg&fH7u zz(<~IZhB`mQO0L0>G?zJW$`W42dU$tu?tP%H?UsfBB35rS4FRT&n-SRRVid(qi?8> za6PrV-RI5s39YZ6N0{qc8Kt@z?HcxGO3QF=q^1_YOknH%8e=+XEOpP$Pi~jf2%rJ4 zxNuMKfukKZSHcrLu6~cJuUQcRT;wxgU8rJaw7X?*JK5J{O_&)rE-?z)Q-jzQU;u6& zYGk{CjgiC**5^?A^IGGFf-1WT^~RyYc(beWyI_;{l$eDJdN=M8biZI*sXgAO-I*Lm zQm5A_Fxd){4fdjC6kjS^s{&WrPxym^%IV)MyjP6c0^%aO@b;!9PMg!peNDHNn24^q z8cPGvq(+n4eO~?U*8uFQrK2Bww4Ln+bvQ?HlyU8 z*kEj7$+;~XbwDzod{ShdbwpwF)=T35@;k0K%!dC}h5AbQ2QKlaBwwCZdZR>>`BT@w ze}##+9DgQW5MQXtb)FC-jlZBm@f6=}D_9l;PUYScm)|>HfBTq!rRE1CP04u+st&01 z-)9nd8tTG*NnSRWFyOV0d5Dq?Hg7UpEp`ejOq4T*I*rp-YQldb*3DOLx#i*uH($L9 zTWOm;uYU8@?{P`;Ll_NA3=KV9=Q`5H+sN_7W=E0_bo|wv1C_IOU?*I=@vkP8#|0| zEC|hKB?DFp4ZO~x9YSIkt-OZRGEhDJ->6SK>8;#?56bW2cU$?n|2Opy9*JP>Z*H%S z?*=v#$N)4IAiH0+6av}Eq8SBG3XvtefHqJ3;;wabsh-oDG>=Y`P7gLM>M{}siFgP@ zO{{#lTl>6U1aQ-b(F8Xdg*gDa5=4~`+ds~G=&gTP9|^k8Yv2UXY21QyUC{#Aov12w zcUQt2b?vu79V4z(@l$-+%V_IEP?PsV_HHNPEBCUchMb0bzv{X-4^-DuaLQxFH44s_ zcoxMzgj^3;y-@IIE1p8H`wKZrS!+u{N&Fc*H%?Hv)MZs4tv%Wn@A06AH+e*Vla&j) z_LSa)5S(HHj7~=F1AUI&*YKwZhjx#P^EZsu1Kw#;KiUsdr=j`hQlaUjzV6?plLjV$ z^O|Uo6-cUIK#qB5^WKs?+XXoHmQ9t>ykznjsSN>d6xbgnh7{ccm!V>$trny1(oab8 zX(oL!B)+HD+S@(q?{&(4_wcA9BBN-a6H|ZexD5Q!6PT%!7f;|jg*lCvyq%J-D%ArQ z?^xgEdgAgfV`wEWsQE<2XXPBbhww8RguN5eDmhi5?uohCxF<7qBtsY0N_>B83aD~-dU z3g%;yiuKbS0!(Zs3sM6R{j<}$Cxc$e!&-Z zQgaqO_R)VbnGzVg2afpwTR^10{AtwFxA(AFuLEzrLL|4tMoY4X8&ws-<4KB0i$tYt zs@#a6n6$R{G|Jnt&T2jaly;?Cyd8@kSsy#N#@(;JQ~*8fFUVUPqv;hRg9deCYDXtI zRQy`=Y%>tF7pimO=!f-8RTaYaemvJPt>0ElP(_a=?7P1;i*ZRwZ0_-E%C^}*o)VW* ztUyL~?&4HrQCtO+v-la;<@wC!yAc7g zs$%zj4r|XUydpr>`C`3dy_HQd0)!Dj>v7?R2I)}?xXYPSz;`%S%<@GA(>=`1dCf-) z@1kv9ufzbJDn-hBrs_?`GV$c$~vWbPQ;NLKmmRk;QdnyzA zI)TbB`b|0w(B^xtdwwAYqifjnq`th~`U#L>w^L(UCK+DmT0t!hDq%a5Fr-mV&XRaA14t=#7{Qu$Q+ zRW5x(<5%2oxgXk?J$7F|O#cRpzhHZT@mI=Ce)>#7S;cA9eM%!qFb^7D!%oM`ytmq) zU~wtkkR+xT3~y9cg|qf;s-IQZ9mqqSZKQI?#%tbF^Iu--i32V555JUZcuV}@ckD!X zDysCK{bRMfaHeHh$PiwpkBOV^(Q9#ntP~s%1~dW!6)N1`YK@2ncsW}6OpF$vuekf~ z`0fAj?|F@D1#MKS4I(QW&q@V5{6?$-!$|k$Wrbjb5bD$*rV8b)-)gV+w%QIPfqG2= z3b@e+bsJ;D^5SY2m{Q&D6|%J;@Uqpa8Ud(~lI~MAvCydG!tU3mQ`+ZrKW&h!jZG=G z)=GI>Cv}Kp3-@YmOTVlhC#yEU+F{tN_n|U*xG0yhPnC!hrBN@Qc(c6+yEK+5q3$?@ z{_trJVhg~^^f=7;cdez7H}>-FKd-kjL@Q^s3{^Z@Elag6Z_F6Tvslua-%CmUTQ)x_ zXV{j=#TR~ue;DRXJXxv74U`zfdhtm=8lA*0aM_v6Jntmg{2#4(ww$s+y4oP`#+#JK zz$<%>TYJ0R$hj_Wq3PZ@6Xkl@3g5)yFc3&mIYi6)st)xkh1!etxO?VAKX)bzY zm@3aC){xq1EhRQ?s%xnMVVZety6gS$)gsC<$J<4pJpT%(nC$b(Z4gSnrS-y=vKe!G|TWYl;$qj+Iz zsZ>D7A}9O%mB;z_jt#M9#0|)j{DcF8&Vy;*Hbdt!Bi!xoIFq^Mq<-|}>36E&=B<|K zs;Q;l`%avdylhl>imDR-76nM$WD!s^#yy1(Jg@rUlyGiC7@{Bh8+@N+;B5^(F_*fp z>7ycGb>(BZQJ}K*jXv#3Jl7y9hIil5YyEGDKbF|#7KipRFtq;vcj<4-rSyl8*#8BH zf06iO?v9)N-TtP+k@6b|)sahmDfQy%ouap3&zef^TMS_YA8$1(CH%+<%!Ln9U|e+= zp#RdGZy%&gL1ae2esV}GAoLOFA__wgOV0A5gcQ>1@JM>Yo*VB?Kx(k>b@gBQ?f>*& z`PH*GFO6bUZN5KQ7BQe6!M>>-?3TUZw!YXj*_rs6WP{MokL??@MH} zmB;&hCaWjmLmvHPf3=>9qRr=WDZ5#)IrpEXpuJlAA7_3Xb9lPadB{jsPB7H3l-Jjy zIc88R_?;TJAloeH>Bc-u`M3A$$I}PavXT5w?=v}Oh8SIXw)DKutKajMd%8bxsjLfv z6qC_#5uJVhHZV;MyP6s%*q4{yk_broWTmZ~FF3(jIm`YBa=eez|8a z>Wb&=LH{vRVmIbew>gFE7$udcG)8N3i#D2wlQ(MM$>A&gl9cZCeBN^LZ@c6gcSAc) zI#UEu*_&vrz*1U#$_%cnZw)3BznDD7Mtgh9P24w>HSsr*N*l_|+2uL70#Qckk;LK} z05>Apn~W93XZVu-!lPp)zQS3sHV;vSz1QC`e+^;$oH7*NVZOlrI~*V2_*?#~a!)$G zV|_x*PyAlyj~>at$Ch!)_Pp1sIiE`AzQT+D(4P4VEyxf6;~#HkVm$fIv&8oP`{}@( zzJ#A{H9qTfUf$RFYB&Ao)K~I~UQm{H693lQ5#fCvji5iuJeocHhckY91J=<+N8u7M`uXJ{jaeYwH=Ij;X-WwTGIXQws6VCTUY~!8YdR z7D`L2S6lu>`NqtK{%zRD&uDkb3uHXX?@K2^CQc=oxQw@L-$9kWv2&1X-cfAPyJD8= zB#Lr?m(O>{?Lxz5!I1(hQQ_gzBfs^zqTHf;eyl{z7+=P;|rR_wgz>WnBs_T8d9gxLm!iy8X^~7k#C)d3B;`;<2o!TL)+=Rd!sreeL zNq}|!5a+#k?Rl+RA$>g?jk^9gaYP0-e{w6a5P7u>6?WUUa+`DW2pYFvhDj0))`l9X zu-YQ&L|m+BxRK9Vy#9_`a!_n~hx+^WmJ~x(PL28_(BEk#wq7DexkBD)UEcLPZYk#rTwqna?e}te&OPK-m(l)bglqWHK&-7 zV|n1&%nme@$*CyDv)9j%|mTpZ8vX89=_Aq2w2MpK$RL`}+kp zq1b=8r}J+y<}?uVb~*I^giP!PUfqI+FK8dAz$Qk)LV?nE1lHnzs^E$UN{01$fSg&*d{JTRmks?a+vCh zk@X@F32Nd*xTKO%&P2jyjQ8%p^3VKd{L`;?PgjPFg4p&_NBE`_-aF0;9Q7Q4MuR3b z4g0Y3F3@uZO)q9JR_*#wq;JVFg?PNo6=J1d z){}T^iZ%JGUDjG4-ZBIfFTg`jbsCgcPA*c zoJO4{Oysk*vqV8YPQ7scr1|A6t)!lWV&o!<+3CQAlM`hDPXY#|GA{yAN_Bfw3LEN} zzfgRO-Q@h3dyELjuk958qT#b=3M%VnpfeT!DIUGf#jRaw+-w$#dH+k8>7uEvesaSY zYt4aBIsJ)3<+a~&gX_X8&UdWMl5NS=*auc$(eN|e#<`=%M0d_irS*bDn~9SmG^nc& zy7}r)eWbOaJrKB{b(DU6;yPbFe0keb)7vaN?CI%|;~mB$zikv&tnH)nm+B=GHm@~L z)nLhFm?}L2zzj<3Q!7J5oEsSJn)k$@NqpewBs8h3Syq%@)-$@p6~ac*r?z9a)dr0m zX0ykV*7tEY7&Tn+mSScyNweo6AV z&Tf7$ZX$kxk}1f!wd{_$!rCFfr*E_S6E=RQ=p`b8Kfb9?^6_o0&#r|%VXRa!1%%>W z*y^=hF5b|8@fNr_Y=ICb@o^+2Z?|At8I&Arbo0O(Nd&Y6Gm;>>ld)}5K}lHg7JYZS z`)~UP|BQdm3#tsZP*;iZ?DA%L#QMxsx7k%9yoBe`k9P2J9*t&Fq%3G+_>FZyUf%1O z_1(C|YXr8ZQq2#c(I>>(EIfDI{g!+B=_`b~0PC~xL!}mNTauMFdPaHDw9yoz=Ri)) z%a-)Py}nQOws}}N{9vzcaQ5wNQ6ie#vIjXRcqU>Xwr^tZyMAQ5F$r1P zo~}y3sn?qfy9C1E-vj;X7q0f(esRf#TbAOzS?3mQ(L2VhVP0N4p{9!Ap!4X|s2?Y0 z2?`PG^{*Sp=W&pnl`Vn$(+5~PT-o3Kg&kQCTN}sQ;)&q&`yHB(R}Ra!L_3`TURJR^ zkB4b$zwF;YBM)O7JYuo-4^ui-? z7Dcv?!pn2iS89jBq)B`y8i_Y$ZndmFTk;mY2uC(7Iq4tgzJKxeS{rn8eXZPVG7cFsU8wXa3+2 zn%-*Ls?!!rZ1j=lX^8>&mHLut35zkYQRKh2{R&raXF=l=4Kl_ph2yp1dLi5j@yKwT7rw0b$D4#{>~tn zS~b#j47``bwv@Ja&`qT2^%hWZvZdg$PzG6R#7QC1nI*}y4XXvC%G%n9of1iQNri;i z8n!KB5ADvryqEAIgFn(In(OH%hOD!I7cN#`+@jP(+d*@krCH(KzvFI~q|k2OW)A=w z=tn0xXtp*sz%u9(%dZYy5+M=0E*n4Jqtt zqhR25V`>?u`oS1uoob}heF0OiyjhlJ)?Sq2Ipsyy%XzTW4^~fC6hF#ZjoRLKNPChp zH$IhpV&zvF-sy=n)}NPMf&ICr_7VPgvHjO|zH%iA2m?rdR!%+Ih?R=He7}cAU&QQZ z;hztXdFOZb~Q$$p89qum7LbK z_`eLKHwr=LqV<4{y9c;$>vR*7i9JW5+@cgVVz1S-F03ECcBBlK>#x_7G#u+`d+P=c zQ-fX&F;=S?y_=9a;uQ8h@AK{#uTiE3zS}*nc@6*iF4H{Zm_PVoJ3pxWeCi2jriF!x z(+p@}^CG@RQ%QL)t@E_=m}gsaS%W64&6^{@j0>rz_DaJCh2m9Bsk~sX=kJzml;oWE zB-?M^@`9G}8ZMjLUEky~{72l+G`_wys=mYcTZ{|WLs?5aksm6`zlYiIG@k|IOF4th zJD#`uW1s2oO8rgBN@(ogZ@vCUjQn9nw0Qm_j9jBa`H~cBd%WP!5O(>=o?gxY~bEr1;Hg)8-dFm9>0WJZ%G#ZTYTp3_$MlYX8jtNB$YV z?H+z1$jFLVQC@=v_-5Lu192q+5Mm<@YRCCAf9BB+97|kG=d?DllS`|Y)p%YL=RE*D z+cCCM@U4RJoB&SKEm#{-RWwW_7r_0LBTWNVF z$Sm67xFYQ>TrDUYaXN`LF#L3diwv}WH(DYFmxZ?|Z`r1qrg|daXIk4vBq*(%+`)=q+EqJ!#o%wU!oKsk^c+Lv@ zJeQxW1n#+^CjdI5ZTb=MeocUQsYSq2>oja~qODti^(&z3znUI&JWt~ODZO|pJ>}8aa9ZMG%voua zr^svm?hTi0ut&-rIlw+X3G%|HhR+Swn$QqWTM_LRG+;%oSRk#=lr?)79`=ukOt7vV z@tHMT9kteul~3IbNF7Pts;#9^$l%cx&&2yv@-QzBlvOKHSmpNKZy`yVZ*O-BhW`q6 zi2oo072zd$Dxd9Oyv$tSb^mdA$=Jy-Wsp(lp4_s&+^Z_T+~bmau93A+@%zMipLf@L zNypKS^T2^a?AwG```Xm8VK*sR>p6X9rY70wmly5;AQ>NXue?Cp4TDK?0+2$XjSs%T zd4>F%#)teztv{$tpWtk@zWW%LNnZnghu*GkBR)JzWZ>H#`*(B&MY+EH=0`E_e*ne< z+IL(DvfxwVt;BdOwWpR!T1_q`c3%DoPQ@qj^TAEt;ECs_n(wk^c`JsaVAxB2gOba% zt%fIcD!r%nN6ZPzt1iH`{HsxHd){0)u4z1QHUOaiFC;X;M0I=cn+|w+G_j#XSwBytOlG-z+r$XRb+f%jDrZ7`hwaspf z>*Ml< zCz1elb5Fv*)w9}4<8IK{lv}BkrrxZU@l1o%t<}R6Te0Ie?R}}iski9)&tXb_QZJ^C zsp8A_TL4e-xcM*hdS)iV+lDljUk@#<2M=>X7CDDLxzOx?_g*m!OpWYni=Zkl$r+FN zy=qOp_Q+a@wQlXY1UpZatmsB9yU^{=#M?+k%I$=Pjp7!xWwyD-#TpED-Y1X(#TlZ3 zv7$7yiuZ#CozxGZ6MWB`uMW{`ps;v~dpHqH{ZAoY-;}F=Bfa&-hb?0B6>pThgu4cg zfc^@%Br#}D91n<}p352_u0mIu@x#RD2*BHdrN#Y!NV}iFXW#PL!AycZ%%peR?hLO1 z-_6ALHW0^&SMqCyl#-^`MX{fkZHgm@IPd%hIX|t6;?={>D{|jhzYUEy;k1(3zXUsI z5+mTHeoow8JcpGw#xrr|s}IqIpVEQ9S>mVo(@HIQP@|jwnJx8(EV4~btP zN_v*W#ZO{vYiQN^_2!Y(_j zR4G(I(O*kYFj{1dZNakCLAfSOfaFD=1`QAIr$+o3lZ?BopN%#Dse%dMw(k(s+|LWV zj`@OTrzU`r{KgByC~JNB@A}0TNVAi4IpCHm_O|hnXz}oQruf1*bhrC- zpZow!Bz25KFNpwRepJs07rtc4#Q(6!ul{1EO75XIl6F0*d*1z;x0uO^ngxbpqF?ip ztLu0ErYjL32Ea5|tN{sNmr*H3qbcY-^={HxS$Xcy2o20@atn9NsS)oha@&?Al}?x{ zlxJ3^>TgpqBWBzIYhfDi8(C2RP`Om_gCd{~U^{8+?@Sr8wW9{N(=izhydcL)&IYw@ z7i+ipJ*T2=QA()mxz}l4qqJfTlY8Va5~mR7Hw;_rOWZRoT{%Rzm8d%(?>(rgW%H5l z69V`RuxkgN%jjq)b(5kePT9I_r2}y((B{cwgn(mQl_5o}iq_#H&CfR2>M8XdPP2l0 z@MsmivvF%zS<8OB;Fwy-cfY3d65I{vecrN8kYg;P@F9mBIE3Ul5j>sMLpahn*=V(W z4VTs1up2I-%=+!S+@lA4%m5!H?;SiP5kU4H@gzEe-h*2tm!ukkKP4%2yu1TmDEI`O zU-I1Hk^E8(g)1b_=w}+frSTlQzpMM%H~NvcOIkr+AubjE1&WlFH}rSL@i{dw|3+I* zQf2{fd4G$kVdA|fDK0_R{YZSx#F!-v_@VZ?jY3 zy_DSHzM0D!0MLIQF!4)Z4IPD{)Fjcc>LUP>Uuh{{jI0cXkUFZ6WOz;VOPg8)s4r%@ zu)9Tml|L{39oPBWu6d1H?s?(2T|-X;MHjGJTqnH>*VKOSFZq}J;9z2!MclO38f#6g z1Y*~Ks3*axbG%J!_gn50ps9X_@Q-lu1{!V6z7d8s^kWMXTE7^vgkx-0zqV=-B=!lB z%Vic|R>c-gAjUpr8)_Qcn_&|d6SZFjbl zd?JsQrzF_e<#yIWbmpEHU-QDn8j{UELX*yesV%$r!79EAkoZD^l?@^4BkVz2_&#$? zf_uY9*+f>~PQApXKW1f!tnMvA8q;j5%2kBRtd6Au<^YDO|1H)#U*dC4z+ z+a<0R79q*G5r@d66b_9~m}oThDjGhTbI_if<^~MWejA&phsOx;wgK^DyW0e?*X>Ze z4SUM)QNr)!SvYaKm$$$@Xkl4UhN~(@VL)HBGSVtdE40$O2usRDuf4inc z_or6;?rIt-CSlgOI*8SGHHgcq8$M1P^L{0#Jwsb1VuhBs_V3F)l=PQVKDJdBX@{XS zW$)HTt3haK-)v>~jW~y<1}ii7uKBkV#T8@F0W)gTq;|-09RbW4_e3ItJ3V)?DCuDPSteUZ$IoPTvLu+2C1j&Kr0rW2JsdpH-yvSkWC<7J0u&lxW} zE-5l7=)YN&0X%$tE5D`R?qNwb2Juo%Tt3yn$xkrc>WWQq$qP*Y`+x%g@jIRxm?p7b z%&=gc_tX!>p8t&hiQoP!ch}Q(9$u!9S%cRmv5Jr57PZcg1OMv(YX95#;R7_ol3K)4 z0nqfuL|GT5uX49IHvd@j9lhecTT~>;3gIf0eLf`=6xQO8OC9&AmvjvKIvw#L)TkSU zSeto-tqx$L55>=xo{|-g}{*+`ZGV((;1gYRFD`@6gUxPc>iKrZ64W>cz!^ik()Rn?uI!lD;sVxC{ zVG@Tnz-TUDS;-PCY=!`}x}OmBuMCes(AlD&H&21)HU`ncM1%GJaD|eIS2^NDuRfLe zV1IM}%6(q^i(mdnF6fD~Q8)w>yZ42O_0%#0)b&c5)32iGbkYO>4-N;Q2CYVp-$D}o zdG%|{et?${@}N54L>l_HhkM|8?r=+Ep?PEWPxDnZFht z{3_5i)Qz#s(g$_62IW@2pCkg$XZZ##01RT9*<(cPrU3I2 z4&u~GQ9RIdi`qM31V~X2%7bXUC~>jt3SfI0O*{J=Vf|RMbhyIUtA{Z8kyhVwd0(SH zklq@7_9mBFX@6Te4O^MC%W7$gcVC3SJtbVNP-OYNev3JIifz<;TJ`5Omm;RB9v_Y5 zU`$#to0_yvyGZ6sw2#xj#lu(&jj92-3<99c<>&*0oFU~=S#KrGCa>Y`0gJc4f1l*< z@?Ly~Pwa27O6kpaxxbesOI>{_=EO8%vnq}Ze5F&78a)EbKXvxXkAY zW5bDOdkee|64hU&M_{i}&^}bivT;kfD>WcpDI{eb#4LZ=!#?J7e)!|@3;sjCY-M7M z-XT2j(Gx@D-ofRuBi2zmtY{9is`@cX7?1>CL&JWD2N(rbJ^{RrVjF> zOx=8A3XXkfT=YBdxo#{3j+0K3qtnDl_2q6?zsEIy+due+|L6FfcUPO=sUKlz+98Za za`dD9%pX4ZFpHL89&+$cJD{fr*`}P|8F$*%7n-xY10z{VqqX8bPGnf#Qv!r{F9jGu zw~Ga-T4u3X{7^Q1hTKbdpBL8E`e-z%{hS9MvRvyH6u4d5Vi0{a3Kkn#n>Z4=AIiNa zu1_yvMvST+v7NYziNARWkXUMax%DrVRpqW}j#GaNhF%pu)FKbKh^o|tFk+)DsYPkD zHQemfOH#~+?~EdcY~VWM z*my!9{%HzZo5~8m+c$~blfoa0Fpvc8$Q4vEQ}imK^FA-WaE%LB_Vxo7W;%VA51Q05 z^s9(dmBw**Rmhvpj_G~fU`nkZTB>bHN+SEJYKlv#1RjwfCOAf0T9`(NYyQ>`DShqh zg;6}{5lL9BwHW3t4@JZkBVQ#(UrpMFeL46PNCZqODWZjaXY z;bUAzqo%55^VpBJ8608;!ds>%iH$?0KJa~{!~=*$7KF#opn3TH%4uH5xt>zO$#6Id zjZN2Z_w#O*<2aO^>uiCBEh+D-?Vkjjfh2Lzdl^LiR3ew$F?>l6vn6lua;y+GX~mR% zB(vSP&ui#+3mb6sbt>&RkLGt0)KOkiRO|yI#j}(VQK}f9=5_T6j=a9h%9qDjG=rQ> zHVNy)kRynm%CrpitA0*VGr@lm>Lusiv*hP`d(K{NM6cGgR`Pvbe6`EfM=q z))Ie83Bt#nZIq{0UnZQNF}&8q3)gaJxh%h6YH*$#$Fi~<|BM-;{}^TBX7mE24M%?= z#CKNJkE_)%)kaZel#sGbiI;cwdi4wIG6#V8ryb+K(P_d|C8+RlUVOL!f`9tI&VQXhy+LfQn?d#aekLdjI+$i$2|>1nTN^~% zq$KQhOP|%Kfz4Vr(E^zGGD@3F zP+u@5c?I+&O{S^{MbUaoW93qRu|hseGw{x6{*n=k;q<#uPdy#CJe)|Lz;oHKF=)D= z$a?DbB=+-4cmyrk#>FN;KZM|DnXQW!NB!NSGD5Xp@RXt){_P{{zD0IUvu~jF$w!w= zc{0ZNUTdYM9AMjoOs(~PU8X6y<-%{f`z*5dEO*Cs#1=O4z1hXuccz$`&Vy(sE+wsD^K zc~1!Z+*vxMW@~W0nKY~)9y|bPzP%*BNwU^>qg{KXc)i4wyd<6v7;j!`K(pA6WKLvP z6UU}_ExZEbjkyhM-xwucC#jX6FOPE1^V|B$ug4hGcNcqZ@}1~i#wewLg zDO$ebR)DeJKHAgXZIMOGu*LdSV;tjW93b}Cs^(uZRi(emN!3HYMK2)d8Ev_?{a#`E zX;!B;KK7}aB*EAr=M*(RmH3nfp1a+6D_B}uvGA7jR=<^ZQj99_NH)&%$ychRC=5kxG^3*F2e>qRe4>}FLIAng%E{?I%`I^Fvl+8&m z&IW#iRy@CZ`2uSl;L?krjQXz98{5s`D;{y|L{8n!8iMTB9LxV)~DcJaKZR0r=#XY`-<@ykF5`=s5_#O#s|dAuc*)+=BC3-!C(@ zOMXt0qtQ&FjtjC@{ulbG359+4i?4Z48i$Y^jn=6qSs#(T8x4}5@iR!4PzLBZCJpwy z=Y?PVGp}`pMzG7KBJfaAw3lJvhpMnK0k^1EcHMMXQ!$#9tU}e0fhUJ|aaJR08 z$|o_0`xO|-Gda#SCExCXn`zrbg{7tu1MZP#GLhF>_?8lO+X9m*#stLXW5K|JdP`M= zT+J~L^Dvc3MNEZB9gQaX!7k7@;uT!Ia4HqD1tUro_VZTep_kE00aAZ*Qjk~gHtHde zl$G};dPeUw6SB1BM#nOsY(ds3_G0+y&gp6*qV@i%pw++cTWW4wYW>E)@FGsEe~t6n zU+>?1flNfUg!gZ$0qWsy==Zt$sBrT3*p|i5G+PPx#+li$sAi2irr%iE^rJ17n&Diy z)T7?Cy-JCj!34laf!NCbwT)}G^vB15{+vdV4z!*6J9tO6R=*|Qq=(lMpPn}8{3@g& z&w_IP#_+eTtfU}W03OdUZ%OL8#a^(+@2vIIsULFSryZchW7h~x|De6CO-Z!r?^p5q zMc#TT?JG8drAtd2F{1ZMi(%@L;JKqPp2=Wy$z%QZ1GRtiExJGYY1&I(rD@CgV9wCe zWjwnmPN-SY{+M$=&BY^e7Bpe9!IJmgv9Z?f}i7@h|+y z&rls?ymFqA48qU%FA{XnNc^EzuAl1p&%FjC_VgD{^+8+MuoDCHAG4x+1PI8+h!(P? z5EkKycoPA=t`4t1ciyY z$inTDrS?k|-r&J^Oi7>L248>Il!R8v`N{6{IoZ2c$Wh{rC8nB9e+NbZ5aOJ^-;@nR zty|*!m6jc)Cv_Y1bTd&D<*M z<)$2>K?S{~&|fos`ecbw{hDY1O%-iVc;65_B<=EBW|eonKYk*!X4wUgZ(z<_%d1%=zEo?gjtRZal+o;I_}sv6U%>zBZ+ zEc@!Q9?WX$DiKuiW;WUdL`F^ZS@hp9K!qpJIL26FhpUBtI#54BG29R;>JRjPRUvNb zLzEW^5TTy_Y<oA=FcD>VzwVWkkF$X{ndPX7*DBE}lf2uP^izQ-P)SY>z`8 z*}z7*RN^Kd#p8_|z8WFrQS!+#v2pSSlyR;lpS87)))-JB6yrYa#$cCthQ?mByKBgI zS2N)ma>XIX_!)<)grp`AQ~k>*1h1(3guwUrN62-he5++2N7?2Id)u4uj-2bIeR3ct zNy2{QKq0lQbfnL3q&)HGcegxXwDj8q`6wehjNR!0>$AMqWFzjYD!9^Pbycbh)`!{Q zF>_z`Saw+aal}8SFMhQ5Jl_6(Fa0@*LJ{mU3P7l$_V0e2Z0k=+bqDb0HGH4K&V$0=ij&Oqi zq>>X4Y>&5~{}BIKb?cFcT!0!abS^RxFROYfDOUTVn2<JUV%H1t z@!Nj;{~Z6!-~K!9liDFa{a^Uk`7@965NhcRj-9>3&G)*vpkKIfsY;^WyGiYkqm$4{ zlUkf3d2y;I8Xv);11PxzIf=uPyX$dUQE{o6>Ve^XZ`(ya&B1_+NzpTHAIaCClJ=nJ zxy1_h6c__*i`R>mvUxO{WyA>4-Gt4N6coj#UMtZsxVAp$Ts#}R%C8>sK;cp5QKH>_ zVSDRDH^2}=_XWm=D{mmW#h;t!!1`hNIEFqA>m!ODD=CjoEtAX(?Y(vgvi`ip(Uli< z)K3SPr{7yy#p^1JYkSha?@3@nr1*2tIDqgGSs&|VF+KTg$K7#WVxM?6<<>v=Ek(s@ zE2J(dgucps48>@14_t%swUL9is;$$-JYMA-(zt~NIwkeRuy3=Dv+XYTl!=HquX!(J zxB*zRYF_dBlYY^Z4F3~ zhW@p6y@_QJOuIaTYC35V1+d=h*4e@gq1SN?r6$ZZ3rkWf(neVOI#zICN-4Z#{)@eg zl{9s)yJ9$0-Rmh(RL~H6nxwuOrWE=mLC-!+)LZDRwzxsS>ntyZnoyL#&|IG+a+5@X zmbh6l=Oj~@lzUH`3cQ79c=Un2vAw7YE1#3iGJur>Gq7&gO$GqLW8}8Pzz5Zl^NK5f zOG-1nmXK16eT{6mVY_NBAA7-crF{5ak$nH_I0`_M`q7rD`M^4%O;bmEs84)T2w&oQ zI4cM*LNk}AoFm078mZw=shETQP&e=+Nti~56#DpeHB@L(V#l46m3oD^1&4|nX-*HA z9xhQyftCBN!p~S3pOQS|f*$jj{cn^iWs00W9ynTh_pWLS@O1JN3_DFv>O?90^N7K* z0kG?sNcWteCs7yF)c5vx6hj@--T~m@bz?w{_H*e6*K+k_7n7DF#-Q_%qa8Tp=!?&q zk>tT0@F$KJbd_=qHa0Ega%p+N0z4xe9x5e!DNf}o)=bHhyEOoy{}>y~$lH2uwr%nw zG17`uwy7pIbWuMwAld4C6PE%rpvAK!e5vHJv0w9_@qds1oBug~rwW|&X#bM`jsHLV zBL`@v2;z443%}#{yp|6t)WNRM*{+BtIl40MW#i0L3{!nKCaGoAekg!%q*i;;U#(|h z-PDqX`eB3(I*XjWlmo`R1M~Q?r}GTrF8Sy$E%zN;G?|N_n2Cjww;+v^+$YQX080 zWE)=qei?^9-RCXqyP_C+bwl^#~ck1rn9r@YDLD{R^Z!q@GhPV;6A}u*H{JFgiROtG-#}JS#kDriZG~ z$mWaot(gd@`<}3<8_Ce; z@C!Ha(MT7dh`p_J_>5lfhqrpDC~2lU#acsx{3QH?7h7)MSH1)$O z{gzdlm`>_?-?qG+~sj&G_lp02w8j$NR4pkMwN6{~YCo!1=B{CmtrL?*mtbP~Mf_oPG(u z&xJSG@>1ZL$8Y%`FHid{0mijSqo|Af2LzoW#Vb1QVYtL{y=T3=6wkTmVBB!g=>rNalFkp z=Qir@8F#<>#Y-W?I8N&7AW}}Mrw9An{*icT?5oiNNu|Y3lCK`28MmOndhY6zC8dBk zX;CnaoE$5j7m&1UYQv6;-f@EdV;VxwO4|T&0ooJ$MrA3luIQm~a`&=7v*!r1n4u8v zQ5o(QcbkOd`}}SHv;W`x|L`v$H0eA#^`G%GfBFyq@M_WeD8R4$%5VQ2>ly?7fIVC+ z?K-`?9_{9%kt}NgF->PhL)E~%F~}aepjQ5Yfkx-iY3c{=-_suKWz*i?Dr9AQOZk~P zp&M0k)Nsg0JH=brM~k1e?2FKE!ZW9OY<0JA-r;_#=|Q0;AnMjv78g_<9;zmoos_py zfx-NK@IafO-0ic9ZHHjJ%o`iw)TRcI2~)BH@KhO`D~K`8pao&rey zms4fbSpgJaUBF^D;p$r~e=0G}^j$HSRX$xqKZui8Rhgan$UcUcaLUaL8ZT^5ypG)M zp3C>{7x@VCD4uB9$8mSGtKWRth5apk-EHcp+fDpkvHuJtrNPt_Dd(%qgu)b50y-WR zdQ2TH%_eVg4*A;xi(k`gn6CGY8l%`gV-I9SPM;+SZLkeTN4=HXn=3akbsunX)!Io6 z8++@89D6X5Z_r{J28^RGD0bbxC?=-Bhs|5k zygasslh_o|mvx+zi@iGkBCS@22p~X-QczmqEASTgV|VYJVQnTx7=Wzzn0W0T>oOgj z#Y`o2C!|4heXq{Ef_{(m^c9q5BVc|G{oU3ZKNHBY#!G9(IAroUHp+8Vo?^)+Od^l{ zdb_DAEsYmo62j+^f@72Nlu}kOU&OdtDpC93QM|RcCV{nj0D#9Sh~kPpCYuYCJDUe; zLApe*%7O}@?da4G{u#$fQz0Nl|Kt{lQ)5*7zdQeVgqLbfAMcU!8}^|%u{WZc^tKu9SWV}0`m^_li}VQ!oNO=@cN2e8dvZ`E1_FQ?UllJ-K` z8&&MTnyXdw;rof-)=N)V@;cT}D1zr)`2;iX@Vw;;-d5`yjL73!qAKu7*4UwlD_M&V zcQwJE8ZR>~h~8z)N%QG@H4naC)3*3(3J}$Q$7830N>m{BYrXm=iHh<7`IW0(%XrN+ zNq*YVj%NPQ4*N%)WREhpZB=tCBNeumt6B= zz4s4GlQ%_y=K3%+O$?exr*?Eb(Sqa`954PraSBbK8R$z)0G7}SE|xxQeJ%NoTUXTU zl`dSc&6DjN4L!}71VJ*WIq}}l8@81ioYiM2cZk|a+_aI-t9=l?3*tDRRtP=L6ds&PzWAd8)czYT%h7aAt&!+Q4>w=Q*}_(5)2?L z!fp@h_j%1q3AMTL0DJ6`#rCT^FHqg z7*`q9-g-h$84sXM*2!uXgVHV9T>a`nxi4(xtpYdcvhn)+CaaUfmhqEu_&)&9vs zLbG4Qn_u0ilNC63&&vbjiM>aaD^oAlRRQ9Z+nU{>{QH_WoVt2n6!1DTL#XPTIAi;QnctGKxkU(w*YK8kkFviJhswaqF42%VZHAbYLMhNt$~}KS(z~x{ zk8FC0bZRfY!{-iZxnk?jj|sHqRtd&tn7y?$ zP|_=W%d=RN^AdFQgQ*LC@kk-M#(z>Q`7K9{_s(G%7R*?w`_sl@)h2Tnc8aHy;dt$7+*;TqS`~cGPTjG~KtHn0Lf1Y-44cmu1VwqdEXX~ zZIz5Ik>^w^1;kad0VF%-vdoXbK;5IBYfl+&SNW2)7e#ds3dVDa5d$WcB-UxLpOZ4d zB!y5)LOCGO6fQoSG+!cosm{sFjpr^F-e>*WE zrc~(G-vHn~Z~nzU{gQj4AN}B?9XL2EFvQP!KwN~5*p`Qf2+phPO`ywPHuWPbMkSVK z5S^M`dE>LXTUf-=RoJ$cAzvZlJs7>j{%Zj4Ym!h^t)EpK6D_^l{ba4dktUGv82g`n=`tfEsx+7h8~}C|tSsMdwne9Y}&%_B*qi2RtPq z+c%RH$fz4v#kV}i<6^ruM|*uiY}8eivOW~zTW4qK|ZhIA-or#T3%Ym3{3m zY=HaM*j2&cSM0MXYtDCmlkChTYj;ri=BYAr+Z%g!i8JX2e?4z zfuqq$?H&=wp@zKk9yI9uX+Qm6^23Kk-N-hRD*fWuZg6drx9i>D>y#sAcz?d$6iL~7 zA*kZ?uS3;5juC$K*1lo;UrmBo$G|tWP*MpsLf-S@d#+nwRhej;5#UK-j+N6j0)@d2}!F^}t3Lz28DAp-&t zhvh!>0fwmCq|^L#$HI$wz;MZZy5D{Pde_AOXe34Ott|u3^-xoLr$uS3HRK}{heY`; zmv>_;%c*=&VRgi$XQX*Y|5=b{p*VyBpt2_0%I-QHIf>D4jK5%Vq9T9+ufO45xBSb} z2sjSSC=P!?t+Iq&ggByd!?Q&qxdE%z1Qf<+TYPXT6X*<$!%|7PP&Sv7(@Kh}zA*`sXKuiG_$ z&kGjBb6|bK8SNT3U!vg{&3wwPvTTaFp)LEfp zBK{?~uJHy8z&-h{ya|R~9b?8S6~}X6b9a>)chk#v&ilMsJ%n~9GB%TCh%%!C!)BUM z+iFYvrd6BgOza-{;O1H;sf(8EcdLQ}v6dgM#`v7P&kI-kT>)ENx8Q7?u(mYnXf$cX zYE-VLH0zi)>hA64Z~p1;DJg-mn#MKNqr)Bw4#++HIw=6jJLtVatSth$a(oZJd}gha zs$^z!Sn~DLa9=O`UNsMR9{{D%1FPn`v*>WEuG*k=@Sc~2agJb9St^Vm9vc?)v>=vD?cv zbib+ZwE=oFsRn+74?g%`^QW6h8gS*l(DLT{e37r>xA)J6=^LO9MsRd_(~m z_FAu$*=m%O{E#oD{#a8>eeg7{+)?US$pL6gjpIRaUyup0IKq)7p zpw{~K^YsX6?8WerrHl+|;Kd;Fy{EsWYKrz`zh@~dA69CrV{Rtrh2mzl2Da*`o z+73NIrT;ozt=RxMn1-h+W^5{hH@0eZ7KBV>b4&ubUqZ^S;c!Ek99lY)AM^$++gkfQ z(64^sYQOWI_q^skzz2SC2+iduyzQh?zvaU3xNx7uu5dw}#pfaF_eo-HqGjBI^IGCU zOP-0IE=p-Gp4q0@ur0zA`Y1T>^TIVRzE6!_?!2WFANXt)DjIU1&$yiXZfwW~*zAr(t+&XIS7999_lrvNZw@O>+ zYD-s&^swR=+++EYkAC!j^`HL3KjRQOU3uNNg!ZsiS}R*wY0(Rw@(SJzJkd+W=B?e! z(>Z?IZ@b2t_Ym}>(|PnINTIFE8tzkV+F3weEzWcpZTfBrS;R|ea3za9;5w_gHdn=c zg%Lt&qO+B6-V!8C?CtKlN|rL$Q)r~A^x+k1#9Dt*-!$y3+bZ*$EYEapg{Ae2HIQ(L zHUp8G&%oz8M?_!Pd&)h>z7htly6hA~g(!jT3k*EH-|IQ6Q>87dUIX-shvi2BxgwQ5 zy3lu@*;=FP`9&C3dS9Xgwv@cSqQ2&N@r8?*s!vQz?GT!f`@H({%|Cc)#?9V>Ndp~n zH}E?D;#>rPMws++es=vD7W7LKg9Zt)8gjxuj#NiiC$aHQz-F^^Yqr*!m&go4=}MWd zhLc6ZTK$|X&P?pPP40pC9cdKf0zU5(+jaAt{;j=|1lxoqt1jgo1q`;gWcXuc>=0|6 z2fSOBhw1L>=@$Uw?zgOiDOOTVmov{!YUspA!=QnI;V~rLj7aVwi5j2h?k+OsB->vU z!^cbMh}!z9@)$-b@i&r>CU32&oq_=s(FQq=G_SCyR z^l;=oWitSeyjh{X!URwZ!T{*MLKQK@@#`;=BWkHms#*1G`vn!3;>N$?eRFYZV_f+e z&awueHk0+MUGtj1=P&;=e;ee$&-}r`H1G4`3-_|qjk{fZwcq*ME@+S$NJV+l4VWhJ zAtcJCyJGJ8q1ma|IV{}W&!Wxb7tn3o@4$1BWa|y6crSj!vC$;k97d>LWkcmnja$E3 zVOY^)@2S}jV(b&oQfv(EVJbi@%wiA-NUKH`A#5WVA!sCAH4XosQ_E+FmguvD?i4wm zYY!l1mivMSP3qVeV@ipIjU>2;pBid~6Z{+Zi>jE#SQP>UX#6 zJ63>ny5776URMfYPI!PM&2>uUXB^{a{!CSihqDsvjg4YWrY;GPyngVf+zzwD25X0_ zkdmkhTXnzsH7@xbDd4If>wL{pTv>a6rk3(r$ys^#QBpM-0;aA3rAr8F7FL(MJ*-Mz z)Baoe-2=eJfuHd+s8d(NKq$0}8dkf1cYoJXw%fAlYMWTqQS&XYJV0DYj#V;rtk<~r zbcsdx5wkwjUIhc@OL9TM#tGU|Sa}O=e;aXPPOM}>Lz{%HOglA)pTR*#9b=R7qq=#oSXx3p|IVA;3^VNx zQTO-x%!~D2Y`doN3V=?7`q8Kp-|PJ3>HC|wFQ}I&6r?~#Gf52h;IUnbP)|LTAi`L& zdGE}@h#~Nd)uY;z157jd(Hq|Okw(GgM*1tMp7$)RQP6k#%vgBP(*pb|(ND3OF1x3j{GL0)NWUdP##8)99v>+=&FXmGj{P+i z*dNl@aM|b1zxU$)_8Y7yv))VKC-(P@_hgcfq&&{gIU>hnOM>T0KHyNK%)rWtLVcGg ztBI2`CW#x{+vXCUY$Ik6b4$La&B0VtVD#rWgrO@{@HV%w-!@xrkp6d4g2RkTR(DVwdCmx01Stl)=SE z7Zy!Ol!tF2(G*tO1VdsPMOfNBf7@^Wr{5j(XZ#Fe;ObZVZ5QK&DvcMec@4joK~(T) zWMT)0RhIf8M>`tLwV~bT-7j9JxMFJB7i?&nxK?g>)V|m(Xx4M0)4f5KGU@>>T-vV! z?ThdcFoEw39A)ApW=OojGqx#jR+wFcDz=(U$RbYu0!|ZKV4|==HD8+`Put-ivKBxs zW1?p@C-KqB2D)mj+T+pYMx1C9{~g$(Wun3|6RUU3ZFEb7Tj%%C=%h0X#>j~))Hf5! zTg`hw)Gx^Qd7ta;ed^te(}Zci9w{;LY;2&mj>{iH{blrtNCfRjCV~rmC5KB~g29QNvD- zc|V2XY-PF5AlTTax#NO-jGeU(xm>gyDE=aGv3sd zGHV-lIMw2si(^GR$gj7n9zpU zS)7J}4@%*tg31d`lIjVpFZw-jUhB^C#VuIhbu}@Gh5i#`(xfxV1XJ(Nol9akgv6zd zz%-oxCvEm_d{d+xmi5*MIA3ZFr{FDd`u%`K{03cf)7V?Nns%Cqzj>%$?m$>+q|AmF zrD(q|^owuF(s(r{w?EXX5F+i^7@2dU8g){M#jCoF{>vAdlT5z(nJc!pxxQ2NCEe|w zcO&D%D{t(jo=VJKChb7J()>OGDaufyutIrynvvVGesTKu?VaVV-@McPB}sYWl>dbC z%=uKi_6*+o@@v{L$x9h54D??MbQRtSYzO{yhdz_ zTtG-Qo2koQ^KFVpo>E8)CX)(An>WGc$0)Vntgc=2mb9swFK`mOO3`RiSA{dRDog|l znE|&;*JUpl9+YY{WQy2Jb@S5PI%}}Z2qqY=l2+c5w)mA1?`0($4%7~PuFsb1Cl{t( z*Wc1N=mtbDw~&I5!3UH4%%Ax)NDllA50~YFH>Z^AV>E8jo>8S>&Eo9i_a0;7GLrdi zD3HsxDJq_F=?ezbySD7;-hSmTR>s0)5uu1WK5Yx;T%yFFu42%L8KUiJ{uzsDD_Irt z#W}j&D~qlHj&d-`fuBZ_*8QT5m0OA`y4p%tSXbchnN$l^Qj-5sd*?N{OLTOksFjOH zNSRxetL6>U##Pe2l#zI;V0L~jg=psQN|?JsEMXM1W39$Zm1t{ zs6x0xUx)j^d`z`G)Q`e|+sM6L$r~9av_?HCJw{2Sv1aEj7k=Bdlqc_R^#`4kMtgzQ3)OuYI7=q;_;VF#rt$7c~})LW%8@0E~Bf$g-&|gE|fE)j>kjf`qc- zZ=>uUXB%-<2a@4!I`XEYaO${o5;D2W!dR)R{{kA@J6gY@CkJAedCWNY9zq=Sc+KVz z_IDXZP=F=eJFLC*D(9FUJ8YDZN~5m4p1!VW{(KMQ-p)V)un=*T4weTN)Yq-kV1ULU z$M|U^ZWd9DK@0I_z+Y(quRcK35-CYhy5empJIOGWcuFHSTCmF!f*1q#Hxj$`${;T9 z0|uV*M?5>u_ay1P6c`VaLV~%^==Wsh|5UbOXM_pD^B?lqE6j2*A=+kYpsi}AY%b~* z#_pUcvZg%R#Q1YEI^Oabf9@aHF7$}>+hBh9reu_rEib`;aZ0_XH)!#OOR2Y3ztxOV zvgN%=%uj43RaHJyva5vK-d-R)(^C~UJ!2_^Qv4}FUMowtxyn?D7yqU8J=j1tmLqV% zBq>EJm;If%z$QTQ76qFPn8<8KH?>u&TM*jK_qR=+TsC^ki+zn-l1IUniu+vi(v%MH z5-a}IH7v9rX9%S(00|JHiuOn{3z;Wj1w3K}VVZ|}9;YfXCPp``+0u$Sh+@2Hs$?|X zgh!g?C?~3{SC$esRJBkA*v}BMs(-eYEuc4g-^PrUcMVf>{Cba7wq5pJur~F` zbv5vc3-A-1LcGrW{EXwIhPLLh2kv_B)=R7OP58UJjhFhdM7?HKQkH+`}Q_1Ylz zd8`U9u&4*p2X;C!opjRBGl~{eI$T^daSuT9$riycd&GhLZcys(Ei&I~i|;%d_35|+ zh}~n)iyLdNPl~aB=l;BjRq@1tspX#U+?P20dV^Y1)4$_2_s{hKtX8i7z3!qhN~hfT z`;73};#=C?Z;2xf%9EO&8a{QEu^PF=_p?qje3o?5r1Ovi$9Z&}BO`?kKT;^8Knh}i zCizf1_otV62%sj7!B^9wh|}m==>%mH`o-#aP3Ujg`#aK`SLus)g!h)da84`n!1V#+ zhjN>^FNUr7=k}7-!QpQ=D#icR{A8{hezLlmX+Ynwt+#JqYL-_{{ZTDAK zFZxH%dEl|S3-W)7t6jM$(q_s&9l5^8CYF>yzpTgsNNU{hr!2VdE^-MgNrE_Bk=RuL zYZR|7ecAN^;GP%$kzcvw;=5m?d^>IhFij2pYAQUfca6@Y(>TuK1XR>yz^_4PvPEiC zHV?qD{#YIi1`{v#zc%<}k7u-7G=ylH|0!6lsiwN!w{%HCQ`I#R`!DgF=*OC(#8zLo z{+tiD^p-rg3FgGU$unZ}03_NAz=U<3A+&@khWO2kC){^HlUT3s&piPkdv!I{wz~P^ zMHq-OM&ZcWuk%-1oM!Ky z=%k_iC^(ilrF~r)qhloi*p)#u4zVhONPf-LTh>o`{YLMam%`e&MmWY(e1uv1$X1Gl zv7UwJkEF~G9RWt(m|54~fE=t}f9lHRPP_ef1%J|j#P<|w(urX`kUVv(9nH_f3fMl1 zLKAH5qrUk4ntAFcKf@ab_!vMjuyJbj-Dgi%aH#0z)njWEWX-#K-u+s4Mb4}H#{#;p z9!<1Fv#yaOQ|I8$8x~*dI);S$oV`CdVS5s)aYEn$exDN_aQw@ppt*>%3R!Cuo-73rYfRbn!`sP88L$f;15?pEqXMZpE zY4rKqtXS#c#rw;&`R1nbq)(Ix_8IU@F~ekgwj9B$diA%89^h1ZqHk2S*W6Kj!QVoZqa{BQqFn}>=R$g5)iKy%*H2u z@fk`$EFlm}X|<;1X4N!Y)D*O9UgNi2=kNJ@{*If|*&Q}@YcXAjTB(Nyica){>7=H| z>uo!X-jHMyABp*~iKmP8ne8^IfvY_#-1x=wOiKVTfnlgf8mT$-5O2I+kr$5BxP_bv zP%KqqUKN_uUoFC1>|TtWDK5GOLJ;8e8)}PQ{od7L?$kNdMkW@^eXfsqTpi~%G0=`v zH%AxLg9_qpeoCQ$mA*Z(QRk}P@OPrQbq))*zAfIM%_jPy3a+x8JroEFjo80t;oAfc zNvotOwbKQ26ZV0DX0z94ty38Rb#?pqjlP%}ROLuBML)aR01fRM#O{p%t@cFQ1aNPA z2(d%AOj)K7oL7%4JBk1-esk+k5q*_i<$DB-0;8NMJpz9Aw z6drcwP-jDJe+uw`oYsog%Ht>xc%G{sbmOnx zzLoa~t8EKz$=Ak-R@1N_CyWPCbw5oEqxQVMB3MJ+NtFk6E_41ph*R~8A)Q?kP z4^;`9hVv^`08l)H@R=i7Yz5Mp@D+2>^m|g-)8_qat5Oxzsh^o&1}gT`VM9gt=rtx@ zCc0yJ;^iLeYIF!X#hxMQocPeYO4&zZjb-yYc*_`Q>tk(3Pd4fvq&gOC4MB{Rzb>jD zXtt)@0MmSoVt12*dFondp-LBySa*a`lk%2z_FKLGtR#X%!%$%TW0wQ)7}q{G7q(We@5}K##?sS&l(lidy?|R<@rzb8OAvS z*0(yXJdLeRqg3|~QbWnvl8uM8MOLk9^7|66^$PQu^(tq4uWP^f<$dJ+iK-V`{>^a< zobr;J(jUV`|A6be2h|}w@IFfHSj#?JMi^L;j=q-vPO&A%XO6c+l0=p0dv$o&8N#RN z`sVyb5=*nnXGq1-j=B(vQu3A?s$eLY)@389g(C^;(KJg&uUmQ7eV|vr#&5gi8W-zf z{-)?3ZY(Zk80jd3Ib9{sWJ8N+o&WnHBHSS6Cz=1>DrzGwtTDT1tqova(2Gi(hS+vk! z0_a63C;HJ3p{e$Mc}`%RJ3XDd5*?qhEMEosPf`C%k5fKd5Ad;u=iO6 zQo{XlIds(5_=}CoT9n=ryPDsYSVYrLhAkyF`*locj+Hyo&5O+xs{6dh)ir$c41b!^ z8$#WJ7(Pl@X3S7P0|Qw8vZY+OO4exrD!wlW_Kwv-jQ1+A{5= zPbH|*em97JXAeoGGfEnD>POeGIEGys)F6IM3e$OXbl^T!y&B%0mfE*kE3i4es13!_ zdj+EBen;}FD?Q7{x4v~~Gauvb>K~kTrIGJTLn(rqUwSfD4PrH1oIzonx?C|MdZl|q zq?q^CwuOzesm9G*W)Ac|Av0FAVnl2}oDrpt<|8s9YA2P4E18R@wAjVo)uq*j1>y0H zI_Fdc2S>d`Yr`}BK~SvNUc#w>qzrwqt?zJoi>P>D1z>x)Ab2v$iLap;CGHPivjnUr z2EO{;*QY7c9lo9Bx`}iJPp>;i;j42|SB#Gf<0$6y;Vq6fdq3n>Y~tp?Cz%Q;3TMLY zDSZ=fXsdGVQsyPG;z-Hi!D@XIj|*hpVZF3}y`L|j+Dl%+5j1&=*1uwJcNtjg4P@z- z_xGM8nH;39xkpr(e0X|dcGwBCVOoZTk#@~3MehFo=}r9TxBNiePU}PAl?20~dQTDviiQ@`&wjz)dm0d|l3yvN-yz6MTj{j$IcF-_{)o&ggKX7bGB7L?xI zL_cv6#DanH;6_bG41UYtwe_VB(ijZbrjbjm-=e{5NWzAR7e=jYp0@Eu!-7d_WhPGV zL+q60SJG{=yH~N>)_r%3b#2dm0%RNHq#5FB@mh%CNK_aF&0EPPth-uOg-WkoJ>KpK zSaY`B?cxinxOWQ}RKZiqoUNnfl#%7OFS;lgC2jzRP2YYs#k@2($Xz%oXu=j2pR;b}@*I*&KjDs$z!o0w&4yY@-6 zYVq3hmYY@C@4)gN(|L3?62=Ixz|r9!89#l^gi6+E&GF%UOxNI2yojM3$X7)J0GgP@ zuCQfJV{2#tNzog2*K6T_L&t59bG&pLX;5O^bfjKrzN3QJM`+~!Qx4b2T55@{9h>?& z(r@F~snwB)Q_hAL-W$s7Ku`>)D-`d6s003_MA2M6e@A%@5I0!Bd{d_ zbk#f0#Q>6#iHWz7-(^c|b0(KrG-%poyX=7Ykj&TEd`js8b}K`CBE zwx?01wv-81a}vS_9Jq=k$@^mXYJF_fIlt1pwIH*#t#yos`LsBEv<66~ z;QL2^H>(VzF7ZaJ;*xDL&gzNnNq?E@XzG7_pN&;;vYa*6jTkUh-tY5MR&P0%A&sz*{`coX)H$<6$+5W5ZMG2HlbA4k()m>bbAS< z;`N{bj^};q4SiwgX*!^JY;MtFPzy5`_+1i0zlw8JnmA zrO{GLL2bNLCtpzmq6#4^r)_>xv8NzNR#nc_IrBhSPb8zwQnHQ{Q=>VoWkZ83g%&Rv zi$?wUcQ=Km{HwT^&-Us!|H{qx3An#H%mm$dm-TH5|6OgHcB(doLPu}5FvV!Ub5g82 z0KbV^G4-mLhjAf#f+mK0kpSmt>H3@z#-oL!`TxdaEDcu$OW$oC%Ctuj z<4ss@Kniw+!Eqn7C*r+?m(<)4slEre5r6SOEl84Z;z`>Fy53+4-~zs-;X6`WZ{s{D zV)m{t%Z~N+V_i|x*FtyoyQ8g3h>lK;R46a_0pG(=IhDPsrlw3iJD-r0Kg@@%h6QVT z%l^8Ixo|BG%3js$eZS81MB?<@`to}kMkDYdG1c@2ZR~Fy2G{(pv^+2gkbI=rDJye>a`dDr^=7=iS;uH`apl|5*9SEpZ%3eVKaQ&8UI^9c_XH6jjWjlMQEFsnw-(kiXkW2E7 zGTZS{+9NA~QO{Nc8yk1O`^6Woj;1~srb*|)>M=Y43w}ek-bQNa_dA9BlIOMP?JeW6 z2w%%Vd-ILu$_oXePc5r-$n2Fj!FB-Ix2Nx!*h(2)dcPuI0*0}omV7Zix641(J^U;Z z4yLBQSTFZ6asIUn$Z7WQQ+O_OD;ppH@iSrPCHJ`7nh!byHtVM?J@R+jAB-#@wM6S0 zFH`Q|)FgUL*j1xN7D6-r;hT@79uW<_ga$6O;wiFHMvK2APODiI{-Q4DDy3-y%t_o& z2_(grOpq^^eJoEWv13_0==3XOEHyuq3OC`{`j$9JXqy~0^ySYYUS}5X)5NU^7}fT$ zhj&Qy%{?!^+V8m870rAe&bD58a7~>>w8lO2tr7Gn%6XOwBE{kKUrzpP};W5 z4Zr_zr|w0#DBFsyl<+n+jf3c`MM-I3Ynh(F>nlWdYeRU|`s1eM0VHmXG$Wb{&BH!C zuU8$7q>{1wudJtAiFaV+b$4ET$?v#WPsFack9l-z2h@An;$}`$|2N?)^_`!{Y-(^3 z=4Wxn`?S z+=S7TpRKR0j;r4%gw6xUc{JiYV&xR`(7Vo0ntK>#7%bBhTw=1Bm_q&bw1pB1fCwsX z7T1yc;Ni=tJB~p@$@BVd%+}q8_%VO#(+ky4|Hf;5e$es){XIQG77Y@Yrw>?sKN$~+ zcqkOPB}}hv99k;Zmoy5DZ=B-uo0@nl&#$HOyLZTMx$JbVnk~H>U+eEwJ ze(vsCDr7=SeiY_^7&95g_m?~aFJ3?fcEP7awAWwyCfnwU&(Cp?H|CaP`*e7ZKsJ3U zUWk?BTeG%HoOV@3-9H5vneF6{sQ014ggo(>>7Cvo| zDU}mrQ9r@owggV490uy-u3Nb=20Po8>o4(P5bNA%b~B${vtBPh&{JE3rk^Ei8eAse zg;z%e#OrL&-!bx4YGdc}RtBk8{5csuJV@ES${JeO=sZLfRfdThOgnu`0MxfG904wx zC67<5+c-jW&f-NQ%~|55Vr+Ts78Sz;g4z!rOqbg1{{ovr%G;cg*v?UnK!(%W?#~Z@ zKnsC2Z91!UZMDh-9rwvJ<4pS2R97i>aPz&H+A>{b5e+Qu7&I}<85|Anv$gE%ZKK5{ z&a3~n-}#FR_X$8uTq2=A5&)gt*kC)&H7{K4!kyJtrlI#dV%>CVMijH6YT4-X7N*Cv zAKCm=f!q%X3^g%MOzP^Nwo{jRG}iA<8my%v<5;b>@v{?$I=`E(O`T=tPudMY*=Lb4 z3Y^E2DJQM?7}xKfV;{Ejh0%Vi(R?m8_btAqM)4+oHT)@YY6*b9u-Xq)ak3SR6vAokI|PUI5nh#U9pw2T0gCY^roXmEoar@;8_`Xxz@$`M$+ z?WW@GlzZIs!f(6z9(tOQ?X2%Z_2TV0wLQfmapDC2>pn01;sS8UG5q*NmXe;h}cAUSZW~a>38o}qti&G+6?lX*$Og?+LXX%>wL66li zswBN>AK^UHDEKSfz?Pxs)ZRFZLWtUb*Wy(|F1gx2{12~V5NqUV694oc{+T57Kh6^R z<=gTz+;F8VM!D*^56xbuLJ=gPU7h;b(KgGm4r~ zX2Rt?OUV1Z7s5% zN3pA>{xp7*)Q{M386n^!6!g0zj6dw>`ub9Zh&7;7!oPoa2%0Jr76{DNFT`oRj7)tt z30hzf3 zl{}rVF-=|5Ls#~h&Ztg0^^VqtcF#-hc~3jZMT|kb{E9R&X&!8xVq4!W1+5}o!o?cH zGS;d1Th`pp=R~ZRkQKI^vwCSPYEbu{Wyay8awo%o&Y9A*-2=$`&@BG$w<=x4GCT<{Ghe=x(2XM z57X|)No;=dg@pLUT#q8fN#g4g*9%!2Nn5H=L{DjtOK<{cG^yzYQmu}N){LHb^g-Ga za;f#3w-Jh8e@;fnvksRhdF1SG5wkd_!daeJJZ;|9_%#6Eb(}K#Ov@K3`tin~MBYG7 z;-w@(h_as*CqeCt_D0WNDnt*%4DstK*=Yz$YI>G?mJu)ce7A+2Z!rt6<@awoexTz$ ziL1FEX?U%baeV5XKfJS3i*ANY#_#*Q@GJlHt6glS0kU|fehB?v9VVs;OfXqu1YR_L zK~=sYjaq7}IpPhFg|Dr7;g^wuucU2>3?Qq3DTrcGx#&%8G#n34Wv%^8pP2)HEYIGK z*d%3r)$C%wEpf!KQA0oYz%dT^wI$drSWkSi|9<(bsH6ZdaX2lBN;x&S%2?i;*XD!( zFJHv6CD~9F3PFtJDjAytAnS`t>jpkBAW@3yNo2vyAN};%APuk;bqA1Nx1L>OA&!StbZEy1$#}uZw zgx9vdA5?>uoWP;AMX81N4$K3R-g;nLIzwPWrIqA$QHiaKJf_s`VwjZ^pQiVhe^KGL4nS5gnOiY|+ zkNU7^M!2A^O*;$O`~%mfJRps7OQPS<8CdhIjQ2gt{P6kQlJY#Pm$8fL0OHv_u`4&v zTBb?;kj1C;?62H95_!x3RoRBCt48I}G05}`RWSQK)cXg(?buV#T$zzcsZJrnE2oU2 zE11^Wkz1_6d`-VLC~oL7m2CPtUXUw)==WIB4V!OFM_5&X4q4v8b%*4-LG>QDtWUYz zLY*ctbu2;rRCAc5{j#SyZ&ud)s!rk56I}SN;2+d-UTJ(7K`-y1*6ZO^3eNg*U&#V@5ZZ38G#ZEPooy&P{X z@pmP1V~6`zBSY1aZ?{Uutc>F*HlNG94r~R-$GMN1|0&h_+mV>(s(B3#ehYJcp+R0= zzvbE5&(D(Q*|S}*ieZM9F>;!7oaVxVp3nW4`{}RMN7-;N>ZoSi0^9?qe(~FG?3hPC zg#K$3)d>DOqs#(xLi`e>DA5$;MUe9yQxZ>y3kC8)(X@P9a8lsqhC0-u_!-8EV>skY z8N#QyiQkzr0BQK~;r*hd`PbV9I^;etzWee!tuQvbW?gei;m?=CAE=Fd{)4;y8QQJ|{cnO9Of0t)ioo(UC5y0mh7eOidnBhXHhGsTmB++OI zeBjk0Vl)f5O|*|hV~A&sxXxVP@Xg}fTqJ-~5b840SN%>`)p|KC+0h_+@&C-dMe$n$ zUTeRYvif0-19^M4_O+xU4_AA=kJD+D7I&ekQgiBgRBfwzUwS#=%hC2F9N&V|1D;c$ z=QiFE@(Dt-GDXpOJu5A0x%(OlBq`4lOCkL`a(}QHea(0lF!ibQpRZYfz~Hh&*r2vZ7@bvKbC)RVH(SDvkXNxk3uEWkaFz@pNEzE z`nKic3ox1g7{Ky=xpHkmk^2_P>RXo*fgwJJkZ#RmZByHfNW-ssVdX=Sa{o1*SHt@c zK$^$mUYF_DQJpmDRPS@Yhn@tqH>-smuox1%%xb9=#2EUOx&gMYCMq>U$HL4wnD^4sjo zdsTzF4zd>9j|B#yh8YyQ~95uaSjJ#3uTl!t!RQDRw zf1dRvadLETRWs82t&HYvf@0)}ml}Ui(h`Y)zZ<5eAve8-UIo7OJPc2$oJswILr(O* zD7u_lD4Wn#Q_hp}CKj&SdUf+`V8DezUMwFBm{?>AQo<`cT}c(T5}BbUi*HH|aD`Bx ziOXagQ7+|)icC!%&XY!L^y-CaDgZ5b<38_kH}KKv42+bR=H2h}?pMDD1{$@a`D=Xwf4cpBZ+T_mhTq!7h9I%NmJ1fr*!&)NXAnYLg`{5SQA} zyiIGf9GOZBGrdm_aSrO(e!2f^jaswMxu;9#t;J1+(B{IMwgX7^{Y3#2tF_<74K0CU z7jC(Q2r;So^P<50k#WNzd^XyK>Ha$le6(-D4fPrgH8e1C5>pH!&{RWq2U*Cn^8~#* zq^01|Mdhi=l&Cm2iuF#kZcD0;V)xcJnl1_tZCBr{_8V`~wxinpi-lKoBMqOIjRTMb zk^8**-PSc0A&`d}yYZ^%!3KCB?(qEVDP%q6#IQNO#-_QwTvTE2#B|cBcb~Sj16Yf- z?V-xKDgrgn^>i!fOp4DS_Su<*E%A|GEsm4PLwzyqs!HlP6$^$_WN)5<+#-Q%A zuhTsGA=G`&uUR%XlbyD;dIytAxJf^5?p;`|?E|*cj@3Rr{b;u_kLBTBr&=kiJ&}wm zF?g?%M=ljL1)tLsRvqQBVaV`UF|p!g^I!Y|Z)lnErf7XNpuEu%NlMNqDn)59JmY~e(Ukqu$@%n- z`R~#j7g{jhay1cGT0FJwdBA%1ZQjB4vnTeb|CcE{01d(|0R8vADFH)YXSAFujUvu! zsrP`1r~eMN{f{Ip6lhzBW5VlYhBI4!aqW-P)Xdhbb=?`|ma@uPmKXakxlg?jZmjLt znaZm&9a@0{E#xU}wA`uy@bQOwj z>_f#n-JsUlrs!G!lA8eOF5X5yY5imRZ zC4!3%VxJ>WQ)o9o?>+tWeNZgMg=t_?rXfzn)ub5GVq)uZ zd3r6uk?Y2Zn7yF?Jd@rj3E-2Mu_M`3$Y-rwT3pN?CZ1JaWO_GS@(|mQE`AYDHtoL4fF0^U!Dy4Pr3HBWTPhZH6Zd5{j*xl4n?o+JmIx^zHOuBb5Cj5R)nRa|x2lSBZQ`4!f`Y2`09v|-%y zp4Ys^Px*CK3h-*XTu)6~p#Ph4=b^oWPWAr>yrF)JNvx)dv3NuAqetJi;=voq1IGw~ zZgGPVGnYD=8jP0O)4Z*$kaQ$#jX+f8 z=hY4{Z0xMH!~5FSX#?U;$+CyZ3WvV!thV)f^sgR1a2_Q<4hIGFqLKzA*zg^abxie* zn_7%s<#(oVRNh3Xx4b;6Qb(LD-BE5>>&=Ln1Ds(5XwRfQ zdL#*{)vqkS--iE|iH8f{+rjzR2wkXtp}|BYMjdG!l-zbAlkwBtPZI1kWCOJadufH%Nv z?*TM~yg77Bl78>`!=Gvok4Mc_TM9P`h%J@L7`T)*Y|UXAhM*%-dx2Nym6Q_VyNhi5 zmf)=|*}QBe_q5lNjiS#bUsA-lJZQ-f_yvoUa=hioJ4;WMaL4v7N%}p2kLuVvC4Ykf zMP}S`-xaZ(QY}I7lUie+eH*;RH66>YMrYmYkLf{AJio#)Qz-hY#DV&**# z3%`dm0tmrPg@Ep2Z&#mSFKgA{hG>wt0E9~E9$@naG-s1xYi3urIJRVM2^ZKg=NL$; zvi2l-u0k!NWnW-Zh{OA+imdQnkkqDK4HsbfpB$#G`O4b_c6O#O|KZ)%c`k+1d}f}e zP6B}Y>$(P059($R*p#BvLQS*TDhwcD5zRvY0RR9=L_t()EHcPi_tE$8^=YAk_GX2O zwu|q6&0B140J(SbnT=B0J#t4|VVW4qwT7OA;~es2>-;gp8nLREh}rBHYz*Hp15T(4 z(*jZJEKaAXHnqZ)q&y3w*3pR7B$_c^t|SM>dn4zwYTdz zFgFb((XlC}Ro)bdq0s}K=E3V;R~5FySRe|?dc0Bh_1K*1pg&p(r#O#P19HR(`8q3x zoK-i3+2|2Y{RsahInUvYj{?^-S6q0{yH`ptKE0-|hEwnc`x7*)m0(d_!?|?x(j%2} zbCH^4rmBR-F+zF~fV!$v^I*HUN_p{l$kC2=;5c52o7=aH(jVj8?S{Kdf=G+@4 z4Bm_l=QS?A#=2)2f_`+8L-fi!S%%)@1g`Nbzw#>>$N6*qYaV>?%NXwITM7B_JEdKg z^S$^Dt~}m+!~Q7$a@@myaVaTDVr$7c5%o+0XsZ@Eju%gPeadNkBRoxZ=~yPKb6e!QontD`-Xy(7wUmc{Y&=I{4S^A%pq?bWjKR<=q7UB%}#DCFT}Di5@RlVp|*@Q z%yLjqxZk!=exVGsE0Ut3=Lm0v_JTng)ZJpen19zx6uO2tO}At+fVMo0ch_@SC*;LW zUF^kSJ4c-v1K0$Y7dta{;V!jJalYm2@5Rh%)y}Zlg`|uHAy9{kQUhs-lX__B1J%S> zOxQ%|@Wn(}9NaGqaQNRJVz+>`q_^0WOIJj+%}~)bgunq5x=pkQ6*qALik6j>{Va&} z!W4>#w`bgCD|ZD=l`9$1P1q(EE02+d_UTJx4z+XP~7pZK@gT2@e3uVk#| zeOGaJjgXIhcq?tpl0oRBO1SVVHuL+~h-OPY(l7BRNzcj7ewTCM=DS~?thrcE5JLU$ zQ_qAqGA_`H(MBM5Q%*0sHo9@s;^7AM>~l*y6^N72nf$AdRsl$SpwSvlCI*tw8i^Cu z;1F#Slr?w5=x;sjR9mxSz|IxNMsY3s%zwxGUXttkhp0R@YBq6{QvEINpcPrc_-4P& zD0)CrpJy?w$Ip3KdimGsR?BUN6WoGByxNm zo!7kkJ?vI=KzU-nJ3#Zm&!A5I?zfkWSDt&cfD6F)}GsM1uz$pc<)&G*Es z?yHkPUhB*Dmp44JBquw0c{jk<6^LO{Uvum)G;Z#dpssyT8HT+}l5>lD zCnya=sQvp5drQ9!S7~=v2nE!mk@6n7g~p&G zt7+R-oz%iychoZ2m|szZ&Eng(X*YcM+sYu_dZP%j`9B_BQ*BWSCSt9T>;`r#Sz&99 z7`#Fd%)5p`6If@(0I(nRZeD9|Z{5t;C|yivvm{xbxNHRMi^* zOeGxJTLZS`x_FvG@EAVDy(b$iZ*RRG0zekp*vDJK@L&RPx-(Ylld9;3wT0T6 zXld1d>HUzCVSnaDTk5-pGzSA(3Onniu4ujRy)HV8RuuK3Y%FD@%^=bJ`*?$*%{t#) zcsuha>vImlrM{sU-{YCwG z&2&3ze;D{udIiYi+OfO+6`CKI?<;d*H1`2&|7a04~I6n=~x4 zClREn3NjLBuvMS8LT$sgk3Cm}pT4E3Cn5vV`$j$<;gBJCvcm1^?F9rAa?9N>zT{dL z!>qoU+PZ0dlUgv!8Z)jZwJ)8cjAQ_4PlBn=XCZ#H%z8_g#oN;NG5aA$Bc^urh|aMUnz$HieTS3G=UFn6&FAHB!mM5#0(qV{ zy%tA|HxSdL^UEp{r;N32$lTj8?{(hbL^6np_WQ)CW|UFN?aA{#sDv;IUnPYKLjVuW z%|#*=dqQ znUptqrbJ$>)Y|U9OXCIC`}3|R7)GVy%YhhATN!>(^USf-*D-{?aH zFe@H!z{aLh+UU)1$D*K6o!a2OCrOLh#E#I=i=gCTBrC1ICce4wpHCjC&28N=H)xV1 zOBw^T~vD^KecU(%qh29 z@`x1(3`FleFtzoA7$F*u5Fm-ORoz7-AdgePOzSC-=$6ysYf;32g&i41zQ*Ju+Z?7q zxEdiqQnqZio5-&oD;MW9y>95nv>DH|X6snJMf{;uqcuM{Z#$Hg$F|j!a;em-;+o?Q z?CKW}>oX`Y3%>gA#5{ja_o7I$V6?_51fwLxi+_lE(HvpCvhfsC7Q>LnNtLn0F^_ER z`F8-vCHK^CHTKjCxjyg^2)>BfO)(AyhFZra!$YPp@9N-w=+?el=4x z7iEdZq4d%C;wxx?wr<3J^+y59l0*xf&mf+Y;;E%S!&&LG8UT}N&UFX^pqURyMD)Q+ zDB;!ulNcf#)6asR>vFJrUZU59**yZ8JNJ74>%P$gBu77lmsNUrY*f>keXdv~Iu#=#kXzU2u;6@J?Qc7D%^iV>IL&IK*;4+!_EE2?7%J8F zbfRI&9vx}jmc%a@_)#kIt)#rZMM%9%<<~E%8kf$qQqu>uS>eCQypi^|CR1+x`E4Zy zO#V04A3te%DS^)lcH%t6kXz)*Eqz&|-)Z?+?xR2WPdWTjg1=C)mroq4mU?q&+zsuT zH(z{p__^bxDK%o4N;p^G_}U3n;(jyDFg~oYd(eLyh>09%vJf$%T2kXO!0HtoDd^1V zjf7Z~piydP(neT@vv{{rv5D^hd|*D&nMxY&C}T@%*}PgRH^58$(`P0IV4HVP$)w`{ zeg4YTe#gzXP}dL-woeiJX&wPgyI;y44H}rluoHDB#=5y~379G1k00&w+|8OLTdP^y z(0t1f6?vB$xAI07rqh=ahp8XQ52f)Ab4_#%%7|IgR)Q7k>xt7BDxE$Trn#EI! zdc6oTEsQ$8{IEv`l&mjWz)27)eE=MbDsVb1thREzVhn2OOPf}YOIsMwRoXqwYtVUgY6q`1?4B1<@h?-d z+F)K@YqQvA%ssGk{X6{EBPphK&#N!q^0=-d1PT|wGgna6)7}`|=9@G_M`I~YdPa~P zf1{46{)+W3)RHhXc=$Wc5*2_nJJ|zNhpM2bU$ze*2YA=nky;jGFRo~Zm(nmc_4zii zDzjtTl-YX7Jte$t+M&=zuZpikD_CCxjImCjQuiYO)(hQ2ly6A} znn~UNWiKx-KGzjH0OfMSReo(p6|OB8p{{nMF!dx3N1H;0X~0wbOLyXOr3kQ_a+hJR z8u+p(RO%q|h>=#Ss}9zR(zevn3Ta<&!|!t*oAEcw?GTWId%cd~9bsm5!&B3pcCD1= zTxzI+Rb2Ht4>@o&{cJqbNvD6bry=Ie&>diGvXmJ)vT!MY8z31r_xnBKBi2`6W`&=2 zG-@;fz)n}My`N?Z;eHc+BN;si)6Yu@A2D0|-y(rFR{+13f1`;LMf|Z;rE9M%iR-v= z{RIy{d^fWF#Rn(Mc+6P&AP)nscE8fRQ{O(k1@Mre_@zkTMB0_#()bg$|AjRCmtp1d z-f7vXBHnVx)@$G4ekzsIry3GGN+qFKEp6+=;ignkgQ^$=-7N<()WoJ&zS%C?$e`|& zY}DQY(0@CVQX~%n0@>oe`PDKKN&XvwiDzCi1(WFCFZ#LHjF1J*2wL{!&`QvTyq8II zS&60s)wBry5c<;=n#EC=B6FX2zbBZO8ck|fY^UW~5l z&tlw@CY{UHrxRsiof>XwY@T1(A$x0idoKO33sXrM5fcrQ1=Bng4QuOYDh`@mcTp+G zVq9Cw+udl;q?QCnN$hI?9x!vd=*xw+(!1Bh38qQ?5Sl7(Wb832(F>21l(Lq5GM|#X zvUCPQRY~Vi;a(;SpW_mw)hLTTygW?AE|S4}2QI+0y)DF0HedWJ0%W7irar@A4;4{m z6Ob1+6AeR5WguI@%WuQr^yPAR<0oFaT1$%s%W~G+mfAgt)f)k1FGP0_=s@@{oC~GR zQUIMd%Xfx)idcK3dB9IiLqoMCOhxGq$Vn&habl7S;t?6)!Kb>_q%*$x?OJVTthZN@ z!JbAEGaG=hK2meZ#d^*$=F>`oeysb1hyro=5zV;c2U*LSLk?7&{`>np9jv^zEwWKxt9rKQ1tUn*s z)=%|#qnkgL{FcUNL%MU8^@Orl7^apu0;ieIy8fH)RPALq4`?qG(|^p(ww`}~cuGJ8 zNHNpa1TS+7rf<)myDUhSgiW84RYad6<;`Ln+8tLgA*mk3TUM~firOXCk1RV(;u4x= z5E=yS8W-Pu%W)n!K#UGR?s?5iE?lB#WZ1%~Xx!tT7q4w5h4@UG8iX zKG??C+sm)-y*J894$GHabV?_RL~Vo(?G_sPY3ZTjncU|Rtv!-?x~R+AZ>;&>7M0j&^lDYckOE zPUo!H=Ke5vzJ9$gW&11<$3DOLWY|uqk@XC#O`08I!Ig|%#?1iZO)Uk-8mAL4!GJ5j zmY3IN;)Gd+TAyO_nN<5j)*9UoR%mNo-O{oiCVFbQt}}cj;j01rtlIc$ew*8|D2{)d z`wYeUUcCa%14ygaY4y<1nZf#`+GU$#7y1|+ya!$@@yM%fKg zsrxv7f)}2;%Cqk|X5t$_^^vF9*Szvj%<-?`8IfPiOYicuBxu>R3^P5ie|NgyTKJc# z0obHe8p_;({4{Y_J0f8OX+|dcs{}7{rA!d z{?LZk^f8?G|4hPE2`MNfm5rE#P!&s(*TE5q*B!CJ;FE65aKQnn=YYOi1qH9^Rqt22 z@MD%rJhV3}83f*#q}4Q)PpT0oEBHCRFa=Tg z*uq)_nZ4T+Z}r5XCcd&!p5fbX&Ys}}y8$IYtEC{1VHiz0vWptF7I9KZauV#bz}nYt ziBmil=(q7Pg&d(7w76DNzhO_fQP);6Es3iO2HB`IQTe&~XHn9&%ckEAi;&Ua+y=L$ znWorN>vOvnew4mXqm%>Eh%vzWL4;kY%F3F)x%+K~vTci_LDN^tQk(nw%q{t^#QDJsi7^Vl%xvzy) z5udAs8|lts3~4m|NWr3uy%o^5ZS8;V_m3zX_L&Btngh~3#Nz$1uvIH}oNu~4LT3};AfD8i5JF2`a>W`C}tLNeaSs9 zzWY7(DOb}*lcg=*tCZxuMzH!7b|G1c>EKiz*d^Od>h-=Ce;0G-lS=ob zD?@%u0}s`GLEh^WeD=F(?v8hZ_{A9c=M;2Q?)$_PSOkx>?Tf(7Z;K)7iJKp9-b!Vj z__q7JP-QyQ2gv+~HBqR0hU!1HhFnsA|DWt)!+4UKJ!#t;+cpKUgjWf#^VnMUGy(^) z{+9HvhAsrz?+j2I_2FJThu6k75i>;kj( zx>1(uX|3ihK!Ie>!&YGOc*%!NHn-neDk<06;^{2_v?TG3dB>2)Gs9*3cMI6#58ha8 z+{#-RN^hCZMMT>~ASm3HN)4diq30Y8W&t;jK{JB5Ekwu-BvS(!tH~qrN~?u&pXKYC z<}qjyOc;J4jc72c>|MX)=4*wSg}C|3xwR$ZE!X<22JI57dSb$VBTk;tzCt{!$;r;g z5-ZH6P~<+ZZ)6xtzP3nnq?FXko((XEdEh0hE-owNwJz}aV zSwF^+fKXWWWUY=-Y2J$^X2GAi*4F;F8PGh?dRhSNn$4<2G;gm`Z@%HX2Ti+{$kXuNtNkI{oI&Ez0>oF4{Y0U92YpAj*_;*iN$1fI zkQ_MrQpu>R{4r_LnatPEG>Ugy(mu4ttnN&Bj6QoyG&z6gt=6Z+sh_06O7H;eqUpATEL&`d@S{z?{s|ZxA7sr zA=WTIB+^`Npzb>vGy4;7Z*uxXn-Xa?x7bDYfNt+i*)S7h(D~DT<}nVT>A4{;S*wB`Q2K2QM#YH!a?>43 zzgoXBab-aYwK+Bu4BAq5Ox#Gm67`d;@13FLOA?AY*bY_^Pmlz9-D=I>Xv?EvEtR$f znMMnEd-j&k7oGYY|aDy9(wtaEF;Le9hVqPOq2TE@2=MAX;}F0)580DHOO*a4d`U6GF%24YVgT#N`L!r*Dg0En@!Z<& zZBMBvb&SW1&Yyp9-)K`v;$q^AOZEZ4MbVa2>cus9d2h_zL9D!>Cu4}x?%NszP6sC* zmOS<)1_H%m&vj(Hwm)s6p!oY$cD-%B#ut9w8I!qj`39TvX~j@XoT0R*%{x)koLeyI zEGa>lR*H!W=NgGOCLwh35WoCmjfj6*>BTI}K-!r&j3@RNbwvYZ45B3wj%gQ&g~0B! zr|Vw<0E?VaN+iuL+ztKe7vHSk?rRY0Xc+f-v41CTP(S35L#WY&-1Cw})zz=|k6f^Q z;lR)M;h#orUEL1g)CeDj-1B$bb18o`F%PDRt|r~4fuxf<2H3v^n&lnY6d3EPVx~}q zt6TKZExL0LH36LwG8#VURATknN#|&q!1>P3xLNCpK+NK5VEhhYsBU8KPiMC(%7B_Wp0)?vUWYEZ((og z28k`c2Y|PT>{-);Uc=ZD?;Gjw_tgGaLMh0BvJXlOAIiu)v9B78cX{Tm+#4*x_w?oc zQp>-(f&#DoHEO(sDSRKXC;t(3q<}Ox(bEH+y%Dfo%Zb_Hv?c3?B2&0DbHxP`dAy%H80!~W6=ar zzsVAto7zx?gic}=`-ffJ>%mr|%+TmRqfse*2+Z*Ojz*KXumlTD9so_^wN=y1OZZ`v z6l-fxlq_jYX@YM%&CMaJYzLql_$G!at?~0=*MF`<%X>*Lp=b{*?(f?bJ<;DguF7Pe zZ;?cO`M@R8=3Rr6K5pI3mA8JM>KhQ4*tK9xnoIGz^rI9WmUR-V$zjJVko&y&8ozCw zAzp@v9DJuC9j<7lxgp-e+9!|jZZ~p8m|}z{M*A&2!wA!;FihbBbs()!c6wLuno&fo zZoF5ae1I|3#5B?wDb`kWJz44NjjUCShtTBjf|6khV0a|n1U6UK;4?8(@hrPqBoSs4 ze&}r`JH~sg7!$Y%{fNd`bx!}P zYgRei&X>IfTxVeN24#-OnX>RMJbdQK1ao(Ywz{i7a-fq1VuP3_25_I(_#LX&;W(E^ z>~43zoPX1#^T5$*-0kXmA<6{qd5sGz2OvARF=&hNoIi`RCOjCDl_3rC2 zim9A|Ek-7Pl*v-|VXg1vtTdYPh1m4^)rKk2a;lH<# zPDvDq^QBZ%*Gj+T!tb~T`p_^B1n$C5>od$hr&suEKdG|G%K!7(z}=(0^%nbIvFE2+xpOJ{-{*r^`#pe|lxpJ}FqXAM zP1YeP7K}`!Ax#2UBIu7nrP`3Mr5xS^+A#KdcZ_=$k?FkWEy}3`SEy##+Ae&8F&`u?fKnuMyWR88G{7 z8eIvssmv+s3&~dwB#i!VVrQ{dTbUKPFXWLExm&;F{1REP*M1lF$9kjrW5ct`F(p-; ztnL7Aac{hs$XzU9zHwV#-kddweK*R!&81%;UA*GdulkAlm?ur}-R`kYD-nA~{Lp;G2l)}CSCwak3iy)8&Q+28m$ZM~6HuC}$zXiRO3 za5A4c&r+P$dugZ6en}?GpvlCE1UGT6fYp??W_jc|ayXc;RshBKVaCFi`f7z`PwK$> z3=CL3{|unO}%DiqG>QS>$iLR)h|Rn@bV{G2q|!%_qdz47J!}f zq_*M!+~>_Mr#)G<>l@ph|1>%9^w#o+0WvD~UQLpUt^#=^X>U59J&y_mX1WYD+9N zr!HVXu{Z6BZOvpTw?KRMZhkL%#b8fu!<^qYvh`i*7pHyL^ClCD^HqLVMd9OF9W2FM zA8MTCy!B)Q(hU00Yjs8{{kE%)h}f_S#0s0!PQ26qnI=5^g_WAUM*+NXl!DiA3xZqW z@%HbMXfM4%S!pEfV|&-|DeWcSYK=$E6H5CC>oe|^`0*^BFOg0E`%B$zO50ORRIzvZ zY)|47H=_x3KHxeDb1Bxc-v%Sn){F!-iAif4anFpbjp6EgAbjeT%vheKi_tLXJmjZQ zr!xeTWt*Xnq04wFNasDTd5a!ppYe5&vUt@BP6P8esULFSz#(+fbSql!M@=8Xp@FTVRVtiKK%qT9|?Yb5a8ECldxES>jUcepLT z3^mN7C0k~+yi&T)4t9Eb^2{e0+y#)eMxNO!TtXj!Ax$S7I8 z5n-yaa-1eV&nKl_@X))JhPNzID7V(uReL>$xIP~9+29&fv7mZa$pm#32Pe_D?u~9C zc2ifgQROh2{L&c7X4cI_Q0d%$S5KAmgvDXnUE3+v!o($!0|JvsojB8$n)a|Hqmjf) zgZKxC1X%oe`N?fRxli@+Eq~#8^_$lntJid&IM9xBMcu4++S$4F@uGXJ(SD!TyjXqz z5%$ZrqS5Xa21}p4gSz@F)d#ostGl7CyFl0FXVKdr$VceLjyunO-)k|Oe|TX9YS(+n&5}>V^2-T$+)7xWQsLzBh`_#3tM2JyHc8Xw=JI)coxSs0p@7B5(9Q zYcq^n<{ba~K|&VKlkFXs5>H%`YK#B>#9&a~aw|+pbL~^o)$x#c-phW&mP-Z9Khs$f z{ePSO8aX>n20_{mTu6|jDAszGm-7qs|Fw%!6FrgGb0uLC=V9K|`NDJrNM{DKh}%*a80SE> z_$&et%-3gV79Ws)<;01_?uob3oWzs2B$+RilV9Ib#?EQmv|q!^Wk|-lw{4=RrsDHw zEQ-){<(#@=EiLQuy!8{zrqo&J(}IkCpZDx>Gykxx7thfb{=P57UWQ-Y+`#22Ze{Oz9}|I2uT7sydHpJ4iXo3 zL&$Gw%?$7924AJ&+e_+HCi{r{72Fl=Ef#>d3~@Bge+a?ao6a;RIsA4>dG4lSx{Ai$ z|DyX3fON*REnlc9SIO*a0i?q@m?)Qr!{&`UVzp^0f&k#>oF}Gz#W20iDZ{b7+hzen zXJ1x$kWC~lb3beG-7nlHok!E_T-x2$MBh~^3+ZK8lmW)(U#@A-w zs|mjH+?_<%t}a(~A_|322!&7-iUT>r?jO2ikGb|~*0bjLY;zF;bO-n^(hju!Ue0+> z?f|S`w8Z{q7cVP)z-t7*XTo<`PXf^Yd)dSK$S$aZitR~ubnd}P{{cV%Y{}L( z|K2H|z6;Q4(-j-rKVvcQb{w^Px`s#moA^+yY2mdJzgf?%E61t6#LdYsWs+FHOFNE) ziLq?Ta`*sB4OMte0rn%5F?(LIE=8~}9xR3VWwjA)skWPScHhEtT2SW+&_y!>EIq$N zC(qDZ6^Z@GYdhU+wHk+yiyjB?bN0-AY+p;tiAT13*Vb05iZOKn#(UZ=D z1N$ffsRsi|e6xN=ph>r}rkBBSzFhEcK}pU;P=q`GTSxVsMoFC=4 zLJik6eT4%>v2&l$s3D$NOA(BaUIO*wY=M)(65Ie#;jp?BCv~lNo3-IokJ0zASD^Sc z5C#o{#Mw8JAsS#x#m0-N{ZRj+>1Z5x1_ zHdQGE!r%w2V-REA_!&2zOKIxCwy$qIm+jNm&`lxV(R~8Xbzi>4`RycvN0W%DnI9Q z-q3y1wEkRw_|MRQTJL``H8AQ5k>IalIaf2oAine`|L)K)`_}VDsuIzc$Y1>V@OrI( z18A~qEp6}ASY5(<=O*KEX~TJZmA7k37@{|uskgQLYuS{}t2w;oBE~y0_Br8c#jp4G z5gx((w!8<~(-I|-@{84Q&wD(4i}5z7lj4wL{NdxI5l8m{`0wc*`MZ~@U-mrT-z+i8 z*!P@ylIL&f|JQwxUnNGHi|-^>QeNc;{l5z_NisF``0o{#t)3PTcB;x$B)&SBB@u7= zwzu)6!PV+o1v=41QFde~VZHA8tXnjE0Dy@S8rm3&A)C!j&%XpMS5bga4}uAoODNcE z(gHlA1g>bn!tk1i%Po3Nz(H?o9=m#LcuSfz_Adcct&0OFdcPIIz3KXI|}Rv~qTD9W}T zMuEjefkjN0-6zKHrCY*5(3Ck7kzDPJUc4E#m5ICPBvyuEa&-)BzZrXykGEc81zVfzA?4Hem>Bll)_!5x8X2xo z&>6-h1e0C`(iJ`+3CIUfV3;4mh{B1WX5P~gCIPL>MFN=FVp)$zvD_2|8(lX^*jTb^3ly}66QfxR)S>`FxH8kNW zqbePXE)wEGy#+|WWw*5!F>*e>L+gCR;64pwjIVj_eUcDgxP1>C<~oF~R>b=#LtNf^ z4d`aXO+DAN;)z=w*4|%*AlYjG0_xFBV%FZHw3wgMdgAyKgJ2*>Vis{MMl+3Y1uG-_ z=Huo4^oWzP+sb|9Yz9Sdd(njnxu^LD0npS#x`2D$c-XknMW?Afe4N_<`+7u=ig|{f zQtBrunp7~4N#mGU7&m^_4O39HbvNd*nKl+cte0EZfi`-fg%ss@!Ff=THcA==vnvdF zvzo6;ZYlQ>o-J>sfvOxXRawobwPVwlrEzQ$6d_iG#Xj`BcOZB&e9~F)+fODO2#StV>0){c!q6?I6W4|;+Ezw zCfWU$!uFMjs&&a-auIT-S zB;v39QRgUa*WVSA6nMo2qi5NDL%6Sd#jdc6CN7VnS0yb7lh(FA`dkT|i&07#Mu?Kv zJ@w?kM#z?(Y>7YP(E85%!U;q$O!WR2ybQoBzlnoPduiF8*Y+;!ta)5?oGu)jhlFYV zSqC2yL=w*A%gLjlWg$Atm`iw10;(1gXWrVV(Q`fLtqgjWGtf#ct{6p_MZT}SmDr(W zAvSc;Rj$HdxCJE?0;^|Ph~T2rLc3 zUk2W(G8DuJp;>fIXjcz#ym2u~14PxXKkRw<95C9jvW@V!&2o@BA7>42HjBAf&zU;y z08j)836)>M{8iV>o58b+dSRXspxBv<2pxiKH#nspfg|lo4DifqB}L^V&GVLXPD-!hP{!#^EhKP=*Nprgi@+&Ja<2; zGC~)f$BD}}w#-BH-1DZIFnSsYiJ?TYxVcSxvZCD%k61AN2{yHZGos`fE*tqCP-55& z50mo(fAayw@$X)qlpXpPHQR>4fkRQvdR^k!Rwr>)4nrx{?^>&r)rO+9i>Y_qYmQ&p zm*r2E{41vd-15jv>S(DeWEMTQZfM{&fI-(Wj`QH-Ose?W*U4GOS3AX6z#9ej{0jXD zm9Qphe7eXC5knw=g+VKaUS@pMG~o1UVE{B=2Gsv{dY$IHefih|*L(GkZSG;EAEo&< z544?+x_{Xwu_yV0jQ-DT&suc&ZyOqkFaCsI^VP&VMcAF8EcBZq9G# zGjIH?Tb`4cPLgBB*^X(`%q5wM?QX|e#B~Y8JzD28AGy~vpA~1LIh!A~EjkYz;}Gz3 zJ{N``R81FMO^lM}9qd>bzu*MbulT~&3aoi8r6<{27lc){)u2tk#fe5?ZIXC)?0N0P z8BiArp#UMAPE)vMIbcTG%&OsS5<3ZfyqvTw;s%YAbIw}yVqu)Fk{^TSF*U@DxL`4G zR(h3$nwvKch0V!}>~e(XI8Lf}LenB=I2`I={j#h==dgWq;uMVlo z;4i)?AFcKJ!q$(5Hz#0){8t!$ORfrzc>XPo*PB<~5K+0AVH_5pU&B^3JDllFHsxSO z(CBFb=H={$bj-5F^S|X{ zgyPlukFpz(Z7qlL8HS!=JI2nS7fv%cb`Nr#ZG5ST&%Bb$dhTk-iuGILMoy)07U-(j z;kpX!O^P&~H$|SN6u#DN^j+R_K4{=9rk_Zm28P&KDCIz>V72aVuE|#0R=+_O_W1Br zOG2Ike<~})aAmG=RP)0Qaz($irzPIPb||JrMJ_5JgM)U#_htQj2E?hJr z&vo;C**&+5_(f?J8K+XE(|FFvyD=el-OTBRPdOFJl^xo5Dwt25dNYHc$&|7^L^cN z&*vnjdEgLYnh_VmNdC`7h3KPaviNh_mLCAQPMs2d?4xryoo>cBJ_2dx#v+p$bhT%i zXFc*TazkZFh#w*9~V10bAyQO~3DQZ8oXk%?V@~fTZIrUJO`_iMMmGRT0 zad242xGeO5)>Xk0t)Bhzy$%ECz4T;JJzF%$vV^6F=_00+MjY>ax*&$-iW5|{@?J@4 z$sj9G{#7OX6<;f!cU4foraE{QMR%Bulg)y79SpU^l!@1ty8r;q%uA84gk-GnRmgnt@!#z6IOYTx)yI^4js}xj$N=uaaV@Hx?P10c1ot<1Rfd+<(ffyLR zCy6eoI-wzL>&}Ce1RTdiO}a?3oyMrM&uq$N_E5~1IG*va9dUDRyC~L18%iP~(D7-(F&aTq0b*NxG=n4f77Jh5Way2A*uYDJ-nnp}T(5L~Q ztL(meJz6GCpIoMfDm7pa3Z#psj+3cw!5gZ9##I`-76K73;(h<{3Z@{_!mJr(KD}1W zf{Tls@4BH+?b+=>lP(TR{9-tbkzxf$a2lUW`Q<<55ZTsvd<`fZV*RSVxt_@j`jFLgkCo5geK;WvPXml|IX!`elBoS{hi z+B7%=8@bbU#5x@z`?#ql!$oc6!mQ#r8L5oGX%Opp(0NRfV;y3Oka~RQG3lh!%)rB; z3OvHb!}-c6o=e?*ebU5I(=eUn#sW`Q>F3t-G86KU3#g#F9U&{ zrVe@%;4f;S0HG2K&0;=7^y&;85B1&>7ZW3N6A(__Y!UbC23Mj*-Pg}~uVC)z0b$l8t0F9D6Ppa zIc1xkK5+!AutQ1L`Ex$!vmP;9-O#XzV~~?&ZiAnF)OIpLc~o1Av5cu(xu5f%H$VJb z_hrIGuf@nNEjsC>8Jk~E3-waSHK)8(!Qn)Uyf&Z{0z~;OZK9~Bfj#MWF=jER8V4+3MW-i-*Sr$$PjC!zW5Ply!W!CeS=X@>`7+i6lW$zyx@(2{p zr_&jz9`s=$s=z$>0LdZI55?AU<+S2#+YiW1Q0vcU zgClB}#@ZC4`bv~Tl`*%9Jb+KKjQ*^H%SQkYFgV@`}R3Iy)1sU%%YU` zdD8N4_^{RLLb&yti%8?x7-0l4wZl|YCN?t!%;tIBSB6Lj*_SUbA5 z_|H1lp`y;R-kj#OVH**pC!;|-ER0mguI_`X3gH}!9b*YMIo+)4PDYW(nDh_}@CxO{o%lS@jxXDbsk;@4oK zNM9Vf@VGj{)gVz@T>(wJBL+;p;Fvo@6}wOreTH&TVt1Y=-pg|2io?UU+v$7;#KwF? z9`}Zak>fd^d9P=xQWiChSls~BHy$C9Lj_+qB^g{UD@@y;$i6!1JjAw9EX?P;{dDou zw~*8|OV`1^e-=tt&SP3>F(XY-j|gcNgCWILHn`kBILehu@eOFR#h)G9-lGxNzrw6@ z9;^Y_#59hp%1kfcj&z&@MpKvF@OasAKi470IOLdgz0AY_!XR7IO2}Ul;=URrdgu81 zr_jKt#}pZra##%xkmtPD&v-66|Ha33$QI#{b#l(tR|Elf3A4zNt6)z1-{^XaMo?(Q zo&|g2_D+9Wxw3NCOFod`NS$Mk^4n*Q05L`bF4-o>^)ln;8~6HIW(utJ0?Y2+9*2Y} zXXJ3qKCR(JdtJl+?_gem%qE47&gg^X~H8BLB|MQFed1nQ`1T>DG z%Nw^EersGx3HcCdSLOr=A3%K*1OBc}6op~CEqCta=xS!)spFR!Q_^>2J1<BDiP* zIeOOD<-O@XA@}=)_rsI|;$x9mFvJL~_&z(K6joM-ynfkj%5DRO69*52)3j)Y$j!t7 zyJ4Pq3!&iFGi+kBy)S`WnKQqM{w0ozb54Zc33rTk9Y>%uYtz-g{4 zcF^=8m@;Lke7A#~=94ns{QpNmWk7k&-4#2ay;zCLrs1mEj9V|V8jRNcqG#WwB3 zXng|UCMh`VE5cc$b%PTxyukWY(uY^EmqeTUZf2B{7pRmQh4<`9_M;all`nji$}g|) z`HSOuxqxPK(Z{_)AZ6uY6Hxi3G_VPd9A>rQEjpEqAZ4GmtC+${@1B{;8ek^M%R-IC zeaes6HAxElsk;EpJBBchIpu~KF6V&*$HXEobKLVNcBY*zinwTrO{!p*KrBkgA#v&| zvES+RV`oAkyTV@rTNTO^#7|QVL#GSn6T^%ks~*6Ue*Q*(q=Z?g=+3d}ff4gb?`p(# zoOIIE%@uUoF3F2#r81EgoSQ5d%?yK`c?xel6YH1)~KW}ZI0Eh_<=Mjz3{-NX<;Bb!JPHX z2pg-G9#6^+FS_VF#DYerg^pZim*&J<3SU{e+AOF;nNW}I*)S8tBWtmiYD)5*!d|eD z<$D((x(AXj;$l~vVU+=mI7E6_kIy`H&i#)O&?zD8E%(*Jp~Jcre0xDEPnDDJH{ecLPwd|%V(70J?0}517QvCq7hq-gE*O~{pPn& z7q*+xltulr(32n;%s?_q0qO{Do*nU=misGtt(ixv@DRN~+MH@CLf!Tb&ceUj?ZUCY z8NnD~-lGv;R|q6oD(8aO94YhcaWTo~3c|J~f*=hjHOOXK7$4#d8;-l*yRwG&`%-@w@B~#5L|HWp$t6JSl(j-( zt3aPetUqq*Nl#IuQwpoe>4npnRcC8c)TD(RueQH>M940~v1SUm`f@}0Pq;?!Z>i1S zI1gu=KW?m$@TSjaVAy@YBm52W8rMM+ONGs2>fM4gP5IQ7|KfwK7B1tAC{fB=3N=U{ zW1i%5l(+ZtoO(RUvyOR;0~+5^;6>z(HC9Zi=OaIIUv|d%IL-ryL>;i2cK895)#h*y z;z~n4oE=Hq{+0xlCQ3m3TVDJ75oI8owEcUF3n+A->qk@hof0xJDt$yVKe%cPofu&r0;$ZVCYB08_G>=N)Xj@#_y)!JmqrsT+i-+{b@>Jmn(qgv?0u)Kf|?kJmFF0Frv(%`T@n?!5f!U@8`|;;$e6g9r#};YeIEco|NH$(fX}HX z1%z*(mGlCeoiPX2QTuy-DT258c~n4rhToBA)^mi_AqKs;YyLc1t3a5udvMs zBO9g`oxXFZ9{*tbT09M6Aoh^0*if!2h=qk5qKC`SBx_wsogV3;aI~Fy06xU;HrP~t z7ttHo$&JIx1*cL@@l%oOjG#3!CVz!x2QZO=7M(Qc0&LkaownURl`d~@g)$hdt*I;m z4_TF)@AX_*7<3)0DR6soR##_K7hjakO~cYK+#>x+rq0f=azz+mKwmL9u+MRBqUSrf zhT&p2hnQ9sL@uM3wWEz`fuq^^x%{p<1lW!%PqC@2w!+M%v|LG`k5q?g6tcCfE_uP+ zN!x1ka_>v+2cu#4g$bLpX_?OAApnidyD`lNZc3IjV=~d&y>prufINB|sa{hfZqfD7 zELyneGUcb!FV3KhzbFJF-!$*!u!~-1_~b@qNW?Gkf!mWHaoE1tyksEM))_}+=-Yse z`rNDnsN{W`wt|>0HJ+HS<%}IS9O8pQ&{{E7!eM3rVv2eSzA#8scp4$nNbW5&CF3$m zdzkSo_p)!$9Fi;wn&H6hJ{Y<1j1hQ>YxZ51N0HD;$+43rY(54juDQ zJB?=R({nxZ<~ZQ(Y{x}eQV5hYs6rMy=_VMgoX89ptQ`Dc(iyj+*4cKi``k600cvnC z!=kCdbkSM-n!dj0QT-fNV^Exo8JN$ABhnm~T3gbID%XtWJzb{utQM8Btkpp?4(hlL zrlFBc$7%bcV^x!`s|Aiol%CXVblv=e`}`TtN#`H@Oa5FT@r*__SM*kp^9{{Zv+8$k zt37mkkIGbsH3TUnlI!HV(fT@t;4qxQd;jE|AXf0EFB>w zA>N5S53J{W)-90OG_aC?n-6r+rR`Zg4y(eq^URW{e(_SP=4dc~eS6n8-Z{UM(bGJV z>7r|cw~i~v^VVjl-+NDAd0V+wqAG+^ipmD^s&3{l*QQR=-*-X$LPU@`TB&A92cT0z zRT#G)q%iMcj-vKKUrZ66amHQ~exWmnrTNNER|t*#^DX6MOdyF=?GFP+{C#O~IS_;C zJm!I8{TT=7tUb5=i36bPm^#hD;YYF*ms$>O&AvFF4zfy7Rtl6P%mqZt%m~l6OI!6=R$}sh3n> zkSU=IBJ5#V&$4m?qzIV?o)nKrMJW?u)og%xjS z*zh^#vO!x#m}i!7vDthznv2n9Lr^lr`*-3b_vK(F7XYWK#0FRIMNW$R%8>tImG<|ygap>YlECh%XTe-i$AVv z0DS}+R!hL>J_7v-&O!Sdt9d;+v$J$k_J-59d8La0p0A~W%C#iwS%8@zR`ve2!_=kr zroo)JThytv^{7G1p(r_oW~icx4 zR}GejOxjgI9=ztnGce=?E`sYUz1B3sBsu`wO}o%n@=Eiw8EqHD+keE4 z0bx@zr_&ai#p^N(bwt!*5SBQDK@4CW!}zZLH;+k|@wZrEt{QIRBxc-R&-q;U)GI-N zWBiN%Tplo6@|V#olyggsN7PlekW#?P8n;K-)`|6-(&J+nCs9xP@Rx91DQw^vh~=yAm8LOuLFIQRBwj3RHkg zHr(6jC1 zxnuQ}&%Q2aAvLt-S$bBK?&Sjjx@a8Jtb{X70(21{!`L5yVtZY59&Dj7#%3a*XBCEI zIDH0EA;fLFrs$&ihmTXw|0+VXG!`HqJ@meZC>gloQXU0ruB^W-O2l#nC5kDWw+nWGT&k4Eq9&! zXnAC-Pv8ALh~=w}3hgLL=n6j&ir-Ky=<4)fLlumOo9JoYKxH)HSGel25H@-WQMSrJ zC?v!BCSl7@_BuAWc~h;OvW#*E;>-~&IXi}{g}b&;)9_Mde_({NsgVb(^g=PRKEy=; zZI9JbRo1ZSl=gIQiV2RZ^|WCsA~=={#qAgk0CsHn@E}8C7s%r>8ZLo(@IEzVu+sXeK8Y-d6eM<-LBi z86##8DU{jqp6!JRZ`r>2U4U=7w;8V~c58=!*4LKy+579X*u8f-_WJK4E-NQF*l=&_ zy>5QwIm0tP%v3x!z7LzV7hSp~!u~E(k1n0XZcsCt9{cwoZAPOQJ76KA`gUsFU{_HM zN2A@GSs>t56w+T@!hnv<`{Zb``=e*@v&q&4LIZF0>9;a~)9_#O{}OasbBX1X zR*2?W^<8A1(n*R6c?{^QxuT$p>6tNoQ{dV&aWD5q!_AS&w zeR^B~@wykW9`}Ihb`!T#mMI~XWJj_k9d~gJ69X^1Mq=W8&QJu{i>ci!Zbo|dFrOH8 zc$qF@-MW4V%|mF=6-#_R=RKeKOz?4%9PGrlg*EMoKi)YcE6Q9i*jloHl)_B)X#IPI zSgNdR>cts{SdAt+x!*M>6S0Pm8Pi^VgA)Mweu(Pf2{B}tCHH#t8joQj?{(m?(A3{A zL#RpyDud}5x3N|pu~WGuM5g6R#I6{>?SdcHJtoHYtW2`S8~j$j)xXeLc1;0SVd%fN z>O!nEucyF0&@)c2R8e!8xHR&jgE-651yixvTLs+}yLkp!x2DHhm)O&s!QaV#ypFye zGn98cQoLpUHR&DN8gr<`h3vpow!y?vZU8jHFvYgJ`Jyuvwo;xd)k(Mw0}nf({yLj+Az%>GKzP0>`p&I-Xhlfu1B5Rnk6(L&f}sO6Wkw&{Yp2^Cu6F#9u6vs& zy8dJHg5qP*W#?-utokf=sECW>lGgun;`lVG;&gE}d4p*|4lW!&c%J2Ou3)JtHPY}V zK~(_QiqYNn2>)PHGd_$6X)trSXCQg1<)?*txSX=W&~nmcK2=}em!2cwhL!xr^x=^l0S_iJFky6t*&1B7k{+iZ&)9% zH_itK@DVS@VY1+u4IC}#)2RLJFg92QoFe}A6?9&|L2gOf(~4gs*;Z#^&u=Aq8o#!< zc5(=3HEz?N#Ae@i%bfrwuDL&C(5ynw*6Q;6?w)mDH=80fai)dTtCUfGzn-J3#7dePBar3@RxA)Pd-W654tCcurOZsQeuzCunDx2n& z->I7kw)VyP7DAq1)tLNtKqdK(gof2FiSt`3gEau9}z?oCW}&>$sF zSWB%7D0uQDjtMJMf8!-utX!F)c1Rh65OQ8=S!=r&k2n8FYa3|3Sg+gR7q989*r%Ad zvA{iLtAb@l8VNPZ#b`}~7`!Vl)i6+kXGiQx9x=a$nwn@@--h{aR-NIwhW!-G6lej| z8OacXR)7-!Ah{E(u`tI8LX~oQixA9Z6Ez|=mcl?$lm?h4F`aZ4fldl$*>=%7rfL!o zEjLJ(o+W2{jh7uM?lWuz{Fh#at#_1KtAYsUeJ-UuE|RsdtU(O-=!PF6JWY$P0>cWk z4GjOI%#NBIYXd+k2<+vF@m{6h>Gy9^DAhlgig*EMd?X%9J(R2_yRe^ zzfRi`tJ0;O(P<_;>c-#2mJg#ozRF5DH$eHe0y9I6iYGU$(|O1-=`=ph#@!i%ro}Q% zy67_hpl{aU`!c2Q)c&b*eT-w$Ntd0qdiBF;TAzPU;_B9GMahRZt9xxNxr@WzPK^hn zizc1KfSDW^J;@ukZ$9{!^_H-$*LVLS?W>gU3mXNa4aWGEZOI#-e}(VWE_C)y%OjAG zf|9>*tGPn8&F9zbex-LneILE8uB^``xKZnVdBN(8tMBENFF9?`_0F5ru6%hr(mpmq zWElyV1@F1dU%x-_(%~zbNW+8v9ozhgxB8yWX)ji(`uAktQ?UEA$?Uc8U*3LA76F3b z>epq#oWq2Vurm`!Q3f#JcEc6o&C)z=*-Q;0u(Je*GC92%GoZ>H0~J~nuZ}~=&J;AC z{bnVC$2j;9vpC8#?Ba?`OVV`+iILc}HbRvtn=%d)ir1oJQSg=W?J5}1e_bFQgI zc=(n$GI&H!mj#lXrrxP719s1%!p16QrpO0a_^Y!a4@;GVD`wFuhiwlaSgQo=qx4ri z(5QGD6;iG_iA$eB9HOGbr)dBt!ZW2X>B3=pCzH)i)2t7MS(#0A7~4gt^u!|2fGO~zML9cuIRS^-QHC!cZ z;iFXzUj+tizMdSnl1%s$e9hjT*l}XN*jh00Q`I*gF{(kd3^Y-7W}OjIdg|rFfcYB5 zj96O70^CniVCkWGrv?14*t0#KOZRC9?zlm%g7pGi;Vu{(7L~A!X3Up`HO0ulOkG#$ zag2Ueu`s^rMQaFkrsj}=9LusO`mM-7|prxuEadPzv-)j^Q>Q?gfVL_ai`w8KD@}=N_H2~5# zWDo;6cZ%?;DF^I+T10jSh<-t$&(AU|RU{rw1sW1vvlN$z>-Y)yBXKC~n&49;c;Y~4c zz6pS_Qp)=WlMxzc`5sA6^6K9fFVGEJ+U|?fYE3QYe>XKLc-b#aTxKjI`O8iP_x2@K zlr4_n|1Wfst#$XuN1gn7^Y^dP-pfb(+q+iV&U2(Itw5jEe>H_z`bTqveDVV>|GsPE z%;j(Bd}W3cEotu-_G#&FdcF?I-;!WQd3!2W{JP~`TXYLo*iVSDXaZsVvl@vvax)&$ zo2@je91DZyBhS3oBbaFy-IN$^3qe_o3i;Kk8$t}7hwd62hNtyRjGJ%1`MChQ-u85R zeI1l<9KM7I$5O>-pMvs%PYK38jKQgu^}2oc-~ep@!~)EMW}gg1XAE~2F81@1AZ<xsNS$9wyllbc)LYtYcq&TuJ06m zAl_JT^?PYww%Hi(vZbE#otUo(MKBtXN8qCE0PMI1ZXQI*3+Z#lgt5fKOh>W)FJjp2 zCpL9ZEPlZE)}?olttJW95tOPZ`2qhHZZkABm z>UYo@ruj`4CXE(O?rLDJ6m4M<8x@|9#6`lY5-#;CfZaD+E+N)-r-k8#s931FDUCEe zFx{Rm5BV;B;9*7}`yFWk9m9dc?7^3!4a1w~)+G2f{X&`FWw<^0ypfVmMGT2rdkt3p zL&?nF@^1GPJ))~(`kA^fbClL0c0imIrK8vBZ7tv>MQCjcuKLfIp&aV`+xWy$(y8sF zQ7&qb=e+sGT~FOa8h5S4vKg;4j9U=7=5iP&U3wjTm`=C|oxaUtgh%aav#&yL5H!0y zqt3vtc1ZRUfyrVpLtE2oR8m8^07_+zw$*vW9f!vECAh&t;TqZlinT)*4cqcE+*|KB zaw9gh-RrsTdGoz)ewOmgDk+)ihz~h%;1J?6{%C*SNxt80s`hW?-=(?MU~0O;;cJUM>FSC2R(Ir{T-rq;a;ap(q=r24F^M{~EQv6{Ikn_LQ*8OP(pt zpAS`zz5Ittn6T9JepC6@*VmT*$-DSlWW)y7KPQLi{q|QlDm1=U{|ZkgwpIF{C;52C z%vVSH8`*}MxAMQ0%N&(>johWLd#`VHtmy%j`vh6CY?$U zk26TD_uYFJf}KyQPbazy3eIFRMLs*vDDmea>4GwN+exXm)q6h6E|ai;0llrs?SaM$ zR(Lv6*gFrVt6V4V`1AHgh|ThvO9J$}!uAySX~{e@e!PG$;}NRYFop%Y3s1&?uD9~G%nxiza$w>k2E%c^|Mh%a-sKn22muVR z6;79W*veWM01&H6jDjxR)s>-rP)cw*RWizOm9R_q;GBhSEShF+8n5U96eG%8^%0|i ztS|$Bn)B$=y+A!!_VefftFQUa#nxhad0u!cqIh}V=$L=UwxwWa-Tf&OH~8gyHEdo) z>F`>4Zmn?c!0z7A29w0qEHidn3J}4eQsGul{(s=R!uxx`#t~7#=6hen>G!NNAt;=} zEGCx2zd-ETiRCwW2AmExM{U!*g>0VXYg1QLZLFclN%6RhkMo@s{aLxUIOT-RpAuM% znZZr+s$ax$R}_cBBZ(tM>2$BCNkgZ_Q`H@}i{>t)pr-XUI2NHfOnHul2=hx#W-7cRMT0+2(kMI%KrFkp<7o8+7%zU_J7jIAc%C>8wuQ-xp3- zH!aA^MVk>JPBX~fDuNN3KSE1hbkaNqUAT&$(c$4=rbxwzrD`2}Pp&3NDpMcktuylU z%;&^;Yu~4B#gCnn5t?%yCy52UYVW5BWXceio-b&A&OtK;OvkRVDc`3h1-?2C2KR?u zg^QruF(TKvFm1wZ9K8*FUwg&CCu=N6# zWa^+A*!+Cq+=Wd8jO919!s$Xn%t$q4_bYkt1~H0}$3}XY2eYg-;zldVZy)7&@4Ugw zbbBdpW5fY3qP|xm&fkTjx>soYvSvIuHos~9-qr$|`Nq=%;ILnB9+I9dVq_l|Jc47I zZ(MrIY1W9@DX*0aAm(AN@U{6sI;2JPCkC+6znjWirL~n$=I%Ia@r;vdi-9oAzJMxh zvm63r|I$T5TtXx-^Z3nTWUH$wXO=6=ZscUTYzMBE7~7oj6xi5|a6zkM&B_Pm=BOd- zSr#edJDG=HRH1c;V#d8(dBeq&nR6hcN!a#Qib*adCoQ@Tp+QU+VAoD)Box~rKL)uP zUC1`1(4=|b&v`)iYdXi(Qw;!I2R8#&_SSnLP3e~}pO}_-NfY0)>GVr&=%NcKQdQ;e zdV~%?r%H2SR#=+}nLi~pppQT`4b<;W9|ee&q~+GTj_B3v{ePx9y-itrsx$GURr|Hc zJn!V?zrTJbX(bbyzm?$4JI5R3{P7ELy7;bL8b7IZF`R?+7XI^8A^5JtbBz51v2JW} zc`&Z0`=^#(HARj8YXHHjex0v!pi6jtA7cE>KphYFj z={0r#hDG3u9mKoc9#B+vDN3OfFNUhr>b<=DQu<^AxQ>(N;d?43V7`x$UO2NqUUZdx zcGLZb;nT^`y02MdVt=!MeotI}{TBOwm4joJE{|q$l;UglMV{?&v|FDRU38V}gHh%c zm%N-%M15Su@s?f|~@oWeAvF&4+cGO@3To;_8CvB0Ql0H1=- zD8Idpm0tZOl4nmHI>$oNf|Y7jKm|yfuSpQAfS+xP`3L81!O`X`G&SN;u}%#-ix%*l zH{aJijQNWR0BokBVRIoBJ$VwscMk=EE51lkAxA0EN;S$Xzp_Fpzd74(R#A!XU0Bu7 z?<;Km0|4UeMsX1-%npmDiG=Gp?dDgda7;?!u0jL*dgt)LETx1EZYi_Ud5reI!aK4= z(w%KB1?BxX0_<(!RRJQ+SlqrQr5N~3O$^m?yDeR7k?qX}Y2jjwBE)Q~QyNuZ)K%lP z_I49aG1!y}&Y*|0D4MI4l#PF+I=K3J1P}sMIY2Dy;_`cC&#bTTld6NVYJEPg4EWC0 z;A&EWTO)`Q0C1MeWJd8O4l*erv21+#)R>>+q?5Rb_uR&fO*NIksAD9(knHo+dcnYp zlP$wU;31T4M1N&du`RV215I5Gw!rRkoLE9%%7i?{(BLPsdNDyDZzk86;!AT*0xn9AHQ-X zOQnk)8!y>QQ}($_-EYfxxjXkywKImIXf&1@F2c^Lmm1`Uj8PGg0k^o0X~d? z?0tz_zjp`%`e-k|2i#xE^gCjuMwcBtNM=kMS0GG4TUN=c=lU!Lh-+8ZUO=Pzy3|D%n)8CYkD z&3TC(HGfG>0bD@r&^c|24e{B#3SZKrX*Yl5oc`Eg%BMjKaQmBk{ZE6P5Eq| zpJDTD;I}cfo&@n6f9aJ&ED87P+nYQC2*FnpzAA24RpVt4%GRIJr25tR8wbb?Vz3!KPP>>`50UX1c|CGp75U?f8~KQ zr1VMb0Ho#g(XRDKI9B``<&6|yOX7un?ZvyWMsM^MC6BkoKrnu>fB6%WDHic2{5U;R z75Fq`)o2Rg1#u&f9t1*cbYR-f<58{XG%)ErCY^K@k&21UKQrk%*zN#htUB|XlArJ+ zMS15TkPzilVC06qiVNQ6Ut++`eX}TpwcJ0aT3ft%hh1sve6|Ec*1W-6w6<6=T-pQB z#=Z-AZ}|EOMo}Tss3I>BOLF$5-!Dd0N|?R??ORE{$%D9>P71B}UPILqns8&R#5bq8 z%Z(P@8~F%E%%9~~Ltw>@SFMG^|G1E|iv28HHT@lI7u8?H=D9?!+09>M*x6dh9$C&U zUM2GfuF~(iziA#<=@iD4zR^rk@Lq5J)8+MtTqWjZH?G#quxT{*dI!xKW*Z^rgBZY~ z)6XXjNkMM>jCYog4s4MKZ@9(s?-rM5n#V@(PsrO&5<9ov)UaHsnlH0hKtCD~!*}wX3 zd25q@&Gjv1zVfR=C_tOCtK}K-wgR zW)FQ^rXX;$SL;knOK=gZGzuS@~HenO$YRtjrhc{W5Wbp@&SIcA<<1FAX=1 z#M!nJ07%1rP^{|}qY=2C;!)j;*+xi;3Bm=hVExZ97t|M|7Q zY3~*q-Kjo(^Aa+RXL+!^?#BcNH5hhG&uL*{r=QYXLieWyoKN3bLanBD;EW4hD4eLOxxu&3+m|kX`=C{439w5F^r6#o0_4yG`g9bxS zTx`zo@9cFq!7|=jdkbRf6B{%Ozpu(sYLO@(@fm@eXF-k|nl74#!SfKirfLG1kNk|E^JhG4r??9c6lKzdb(~mWxM5-( z;~0O|0qUN_Jvlx$T>O$dX4|&XMqsH)8oiyuYY>tqZ+ZBYf3y&6z@?MFa?+RuwM}{D zSg3Pmg^wy*dX>{R&uS9b4n0Gu%P3_kw1S5DLNfColr~c(P_;yHI-j^EsTavBCQ9lxEyXpXW)i<`XTe(-Aikl0-z@q=T zHXwT9_SbV{PAmPqzmy!|myk!oKR7U-6=9e2aWN4^K!C^Sn=NSq&XU|hRZh*@`tfq3 zGJK-ASgKxzv)o)XfFj0RbZNx6TEW$h=$5n$q`;(QP_3|y+d?6JiE5+3!$lN+56IoX>iu9w=i%@4$4=<}pnfyi`pBT$qM{bx)-2n$%Gm z#5RJ9SiLCs^++5ZG1G!qt^nWr&*k6u5&_WFs@=lZ?J6c}W~sRa_&B2#z=G&z2<3{y zQN+@21r~2Jp?oPgwRhaq$@u#Ux2R=WP5-%#Y^}hC3s0^D zN{D9YmvUzAXHn zOFlOW44O!+x`OTabTtUXp9Y>%<7Y6LyoJUZ_F3NWD|ZU4StDoZ*FiXpeKjI_7WD<) zGWZ)Ei8z5Uazu+ti637da3!G_Z4wd*VO~nVyf-j7Byi5Q7Tlv=g|x8+T1O24k{fUc z?-F@}FaH`yC}DC=u3o7Zyp{A&mlhUz-8pwp-zla!N)PI4lUk=F@>t$xu5G+=19(x5I8N^k3|W7>?{NTy8V)kG&M;(rU(Le@ zj!BayBrc`6>|}RXWSAR}l^Vbgha)~D4xHg=x2IL!FQk+u@0r*8mbRHL;$s}=0g~fXpGDN63N>h%SMLRbPu;s$Z%c-Q8u zS*kJOC3FHQ3T6Mr6x&h7AmYNNB?QTUPme*j%MH#5jvMA1b+4a!GtN(il+8SJ(M6}m7#tdow`@n!RnLJ{f%g%h7z_ZA8R6{u8o(8UVZAXG zB78SZ3@*4EaHXevDx9AbTHfr|qx5OD9uD-m8$FL-`F{2AJA`<-wXMG;e*MP0m)Bp9 z>T0TO_EXCEcso)I7t83m&s~_l&ZhD%TgFKH!6pPQ5X`$h>`LwM)J5O?9-51VBXcC8angg@h zFNSj=uHNb`4kTb{_%L5?Qz0{Vr{hw4ZnS2$=!a28Zuzk(!NJoIz~Qw^p2*gZBy($pj?g-W-C@WYj;r%OuC5`(68n9o}+m(D9xI= z?U`#|GX3Qsm_2#Ev41EveAm@WBor&YGco=9Dw$-j>TUTfdH*TJ!UIa6f;PQMco80f z?(vcPQm+#RmWpk}Ks$sWRl}UN0-w@@7|9GrBhe#z<#}rEVYx~o|HAP!fIXZEoK`^8 zsm}5LZx)!^nnP;u{t~_rzup#`-~0sSFqP3lPUI1cdp^sKlsUsw%NF!?(RrLC>Ls+1 z8%9nTup{{AA;)ZoWJ*+ zABx$hKbgamuqE{jbAeWgjYYQfgtG(f(DO} z1^jKLy3)5dM0h6`FCBtjitZ1#3+la0;zinAiuq%ElGy@E-Q+gtGLz^8Kl7HK@ytgK z4Q*s{bAzOs@bW(!R?CbM^u&c8X?W+1QChAV7OI}>LxK)T7^9?^zlPtH8v6*tG*W2e zOw+r0-$x0jMaU0YVkk878J|@8C@$KA%)2xi0<~kLWfYslBLGDmH zT%YMB@s$?ry8xBDb&p`oFllXYnVViCmsT=KS#6n9+MC}!T3o*Mb~-fK+P7TsN_)iA z#&@OH@)7s3wen&1|H$>?GkcPccWCgvTP=BBaK148OZm^Qe@SAc7^n*2Dp>+Fiptw0 zu6e)GfGgD-Q2G)VMULSGq|WX!&qW{D*bKmLM>V!%zstd8Uop_xS@Mr4KGUN zoB?&F?ZzH?jq$M3z9!&d+O|5|+%85oK@}CCf*+m4bkf9l9^(8_xff5j})Q2LfYv?Zrbp<7KS>lsSE$nhW0Dqf9ls% zD7fvHGnm7`5cQOGq5j#5k2n2BEynrT}|+@ng*FA}QrF%lGyd>{Q+_ zcGKmGAmi~22rU}NIB-n5fakpNGw$`AbROrQ>%cMH=wRy7X`WQAf9XECSiOPAvRVZQ z^+K$tw&$J}swdVTBpn-24n3dCj?l}oNSa=MIn0-baaKvD_O1jVud;cE zb5Cxhp&r#kXdZ)@t|A*iY3nU|=oc11%XTGRk%t2}ds4g^6b4rhjvZThnJ|m3HfCq2vR9F78|FLx!qUWlGsam?%{H(>K$^NH0aVm z4;4KP8UfQfq@JQ0me)noLr}c+Vzq4htfFs%6oon_1Ye0i@-w6g8074v*Epg)fG!Bu zcX6sM*ZiA3t-}-Plc?~E3}l|sU}3GhDRa`;N5Yb|2x8};S;#H#?!N(Ckc7{c;)$GWVJ@0vjVge?pY`X^=#)>cU zv$Z1f7&e+Xo&FI9Y;+||cEi@M!APeaF?C^K-1C-u{fO-z%-%QzMg^3s%#dn?tNNy#lQJe zMh(%dN;t9Q)?5OFIc-I>k+|;~44*R$guxTd)G1gPP}9V6+!MdlUFR9jz4TPuVFnRv z?YW*o*MZ{%Ki7TTjEQ+nJ1bKu6aTDRp7{*A4k7t-9w1#mZfFL842MUJe}dfe#=UOF zG~rN+3Y9pN&2t36#9JNB5$fgTcMkXS1p?FUx)5UZ6w(!*P8i+tvzG(s#HMa&%KU6$ z<4TXuW0D+lOuFnG*G1D;l#XibFw~Iq|-dj}#KG@%_7%Cf@Q5 zy!-kk6-)lyXd~V-lC?ETTdhTd0`)==;xZ$?@ju$rYR{Qp@E@i}9(mRykI;EcbFe@; zhQj4=Zi%g(3U7tUN=4~mrCyNHzmvh$?qsvFp-;Z5@o%x;ZkU&EMu6Z*!89Vyy_5=2 z!U3vj`AwaT0S!?5#JKqbiF?)Gg z61{g4>f-{;lNDazCbn^5UPZkrmSamLNUOXJn7-0S?hp6_s!b{6G8;JFucxlyGB2L_ z$g_T~XFbDqq~SRqQSYIKJJUj;C;?Q6J;(qp2EcEMRfMEv5H;I!?{%!cm%p`Af|pr` zv&Ux0;G&Z7CW=Rmn+MlLG=vv0=3J}`@uR%x%ALN7(IOP0^$&m^0%7W)?hBhY-|Lxi zc5rPOa1m}<;27)KgP1Bzt7$0|!|D4HF;i_1JbVASYz*Llk;7!T@{RRlyo2Am+6ebN zi>+t|3Jt5+GyD}!(me{E1iz+jPRQ%G*To;+!OGM2auV<)oJkEN^IEXJ)-Zsg1k{li zr^U_CpkLOfxUEo2r9=o-E(Br8vdVYD3(r9F0oC79Xwiq^fF@vJj3A~97qKgi#l&f2 z9HW1g2o|4n2)|guFk&~Y8#gXohqfcMu&|!<88_ej3}Tvp*0Bx&p7Xwb&Y$xJ2Kbm* z7&mSPj`c76U+1ytBnIH8A)jHyxP0D19=Z8hrc_S40^2JT<|eGRXoWQYPBZro54i7` zhf+fExe%kOO5s_;VkOt-rf#r#hwIq*y8+g*Ii{aDa4?-lgE(jrf(~gvyngXIK9n@{ z8EH5b;>KSVQ2NV}U5d$bMIS*$Vj#x8&fids?P-~+D@9?EUg$&Jt2wsZKo}D7o$Hgn zNUY+u89x$YFXs{ycle0WxpHrTBadUQr?Fwj6K{Y2Dd9^|IT3(uIZJ`tTp5U$DTh?;qdt>6ME=es%SG0Q7JE$ncW8m7tnjEKT6I_8k$017Gca-e{!0ui~u>2M; zUIhk0Fo&iNiq$ltzVn{XW&1eKvda*5N}w21a8eJL+Cdl1LuedKx*sY3U&L5VoSlBg z*@NfIUWFLFeAj5eC0mbQsqXZ#X9kKIIH-{JMy|?Re88=uV zMbuq^k$f7(dRcoMeT~*_O%fyKEY)6j?3W%P$KHh-giymZZyd?7%Aqb$pN9HT=jMAVbRWYD*HP}ACa;fe90Rh!@_ z-cD2ZIB8$>ZR0eBKNvTt_HUor<|Q7X8bpci`wnRHZ&1gCpqQfqC#4k$!{GFYoZ52$ zuDFV;vZ}O&GQ@h!+jBjF=>}uPV#Fj_yJ27uz)T}BpW$VuAJ}Zn_J^&8)6BsO7hN`! z&*#EY*#Nlba{)B}@Nv?x86ws$AkTU84Wm&)>yQKLQS{^M?68%kC!3n8iu75Ol`r}C z6$y21N|n;BX)8h^2CJA|Vl@j^_s_*xC|ww3X6tMig%`O*kt5w?9z7#gr7F6J?U*UW zz(v;<&?}9DAxt*$J$k&{w?o_wKFxGdamTMWfn8e%?vlXgoJ>)A6e#NNw^uM=*`Qh_ zUvHzq7nEd_dC+ov@L81vPCYQtC>*EBE2FZ0jamW_JgUFxiR0h8();%gc_@m+A9?rW z?P(3A{%v(g|Ne1@9~W38=`GbBN>gsR8Vu4*iJXNiP3+GEjH|iFKOhRz-dkqR9;Gq3 zVzs3Y`BMKKpV-V1dDUb|%Q5|XlvgW1qEGg-DBD<^G$=ZHiTR=MM{v|3y>t*q%3dXfM^@XI)NeR?YS8ZHy8<1tSb0EAy@LJo-$q zQC-c?QgX!JKmnTVAMxBFs&H`;8VyQd6X3lDC%Fh_7By8%;!DZVvRPNQ&k2zWS8t6NL)TYY3W5Nwi{(NG2;wnPk=UihDA0=j6R<9Atn@B*DN9gLO5Exy?V2CJMQ?y z;D9o}R)ojbB!LxYs9x{zaxoUt)IqITy&P9j!GLk|&${s}EWph_+C!ZURnc$*TU=u! z0gv>R0??$Bn1+dfOe%=FcfwWhL2f`Q`;T7XiCn3fnU`R@^aKb|{-PNkLc)yg>{6@+b zz$Lm}KId|bn9Bx(7@I_ipceLR*ze3W0N@?Vwc~>rE~EdNwmPilqIv~P@oPm}wY{~q z-)e|f#@0N4LD4yPH8kXNw4yXNzcZBc44S>EP#h(s;S*wd&9dl#$Vpv_Ew%qmj&&)| z=T@lu68~ly&K&eV@lILAhfP1(zBo%R4fa|jO0Kvf+BYOm7YrW3C;?o97z<8~H>70-;!iB5F&6WqA{-ar_EQFz%%?y&HTnJ1UM26881Q>|HM>&78==yc#j87VDV zxU8J^gtb4!$N@F{IXf$UDfs@&N1p4R&-JXEpLt(6pGDUp$9XLCR-SZKi_{|~qMc^h z8v1Xth$0fK#$MBUE`CO%+7bc7&9vJHLtHYUg>x5&?Wi0mZ968S$Kw(6%)Ch@wrRR2 z+4A(s%+U`n-wgtrJp+|^jqld%yVk_Jidg-<<=Yp$6>j$Sh}&-fpUg*IUV*ncrq-R` zHOEX3;jcj^yXO#{@MXTO{wiK3tSQ*xMK|Q*T}`~bE#I5Ex?bdeJ=cw&abFNDU7>*b z_>^&*sNo{U$dF|-e9W88SIvO!!IG*r6k}2Lz@f7>9QO59j6w3DTyvwUf?Sm|@sbH7zs`TL`wG*;06g-@ z%@047+9aRCLj40jfGaV}l}V_h+u(>b(5$pIFW>&V+)| z*h41UdU8nkLq+*YQS)zp!lbU%BpHJO**0ay(AZ2TW-yqQ4_J*=l2tess`>Dv2QL@|O-3LyJE=mbMWl3S;R`*>{??q< zVDSLhdne1rcF*U0uKQBooga4bszb6RV+k2_v_cuFHnZVJDywj(@%9DL*D)jZem&Pc zpLGk)r?vk|`kslQtm@K3kZG!I%XS?oEJX;?H{~#zVd@^Rx_uh}o^kUtpGlXUUzQ|w z-t*`D;s25wIJI^oqmxdxupKa!uq@FMy0z?$l*P76+X5A~@+$)>oD}BZB}VKYS}w9d zurTPNaj=Eo##9B$6jK}m$m0OYmBGKwvk|{Xd#^2LVuO!O{fjsHm13?CzwGW-zNwMr zn*|gt#OT_4ww$9Yc)z4oemh3K@~@_faqFemQX%zx6((361$dk(P^JFx2<8#)M5E&w0;#{harDE`Uxy{)n!y&M~UExgNoJ z&wD&k^jhVUuX8OYherZezPFj-k|7t;Dt)$HYVcsHZLy z2X(350U&1~12M}2f5b*BJd9-#gLMbQE#3dw)!O{s_E!#96Nur=z6GS)jw?}$X{xY5 zz zCcd=hp3l?DS^*1h6&sTgIYz0g_R4GDThA72?Rr$lX$rt12Efgvg9fF~?fUO5HwZVe zHrt^M!p>X$Yxh_*lU17Yk~YOmj6HS|Flx_7sJu&e42PjP*Ox=afQAs$1txyP4qI_d z47!d%a)?2@IzMmynY4iC{E;8|k(=wa4aO&#E|Nd^Gmmjd9uNW01b}Bg{BzxSE@E7C z0cMJIeCeD%57uL7wppYQ%Z6*GiDDPPkC?^7Fnc}&r;VjbVs0!MW=0!j{(7I4A1!Mm zJ-LXSoi5iT0d1^oTjx{5MvoJhod>Oop{s-|c<-RvUhBCjaI1Z(IbUV+cQMWN=Xd@u zPULSpw|2-@d6vd4-%u@d-v71YYrE_Hr5TWwBMoApm}?#4L0mZP-i}l_$-YJ%{2^=o zUH-{*HKhjKES~8bPw#vs?<%jP)?u^5%QN1iH(G^W(AJdX%X)V zQ}gk(cEkQ}f-P2fU6zKWJOU`)j9c5>gTlp>Q(K96wzPc zJaM(Ueu*0jb8uNnC8`C5N`Tn?aTfQZyx)kU*d(o1YwNjwO>C^Kb$i-3Z2(Bvxn_QsE$dFawz^HIVCn+UsYr^|3(*HPG?*v`L48f{p($B}7rijE8QB*r8ULc){jj;l|SH z;rJ^-Ibd9W2Huv(7ri*h&^n0iiHVIbDGg$Pmg_vE?wGaF^88gIGBG?}%4L`C4548m zqrqTCFqw};1wHKyr?m+Q0)!|yB3N|7#ndXQO5AOyL}ewB$e41P1tunji;fZisR|>{ zBqea`=6iUU)GtOu7zL44JNt z^>UfO$si1>$Mz&v>%g-@ojy%08ZHExc;|$F8q1yAeNw6AqLo9LLh>t zR;cJpC#6|ir>TEVyuFuNXGsMj$297mlBZ{-_1`Js<%9{2~{O4>3)OpKh;vPf``( z48%94`#63xFsQBnlGawXkS6+f=mO#*%>1T@)hcYwDu3?)WTbqbbNhzUm zps72^+Ev{EyRwr3cpr?sV5_loX<=N7n-P87@v+R`?nb9!3MzsbI5w`Xwh>#sObiQO zGicrO#WZl)Ve$a5{6Kn~5tDHqOlK$*0^J%gf;J61>dQyj#X+_w9c^uO5<8Vp>S#|F zUFkE_lTQzhYlbZ8oUwR<(Pl0}Wt%p}tHuLF>h$JCablKnsKPV6uobJC9Jd317u7i4 ztoDB3JEiK&+S(`06Q^8Z_kIf#y&bK0WO%J(Q3DdeCgQDHCi-LQzXxE4*|XLzRc(*4 zk|Or&FGR0aa2Ch=*ygF0)WlGVWyW0d2}3yzO&baK5ud~~foIy;oM&9dK4_}{DQ4+> z%xkJqE_9kDUuYac67v&f&PzQ_5Ce?y%xBv560^uM2>6#8MgFWm*B>09Ni#gb9fP`6 zgd<%t}QLoArr%FvG4=hy^t<-FOPfrU;Q4K#a4hhlkNlK|z-$mbA zK44QvT20$lf%ZMH!3DZrgC;J&M~7)o>1r@YcYgQZPE=aYF(w8o+>wg|u2Sw31}@T3 z$xB}l=XZ%;%UwcSS|4rg5Ulo&8!lJyS}xHwvcq9($KI#^F<@k`BaB(~>nf{r`j}b1 z;oL3c%|X5+YgFI{ILpZS{RY?J=IK8Nxvahb%Wiw2-~c$y`5 zgiBQ7roj=cSem`9h%10wv%@L`Q|OGlwsY829z?bCtuE z-yw5*Z(&jVXQ-qOy%T2Za}jvh>yCXg@gFX7k!1$d-g_I&#*_t4TqO4?6_zZOHOB0h(u#mXG zFCr3%;R+T5#p1!~pV@X6qCAzBNK1`)stbEGGX&#Pas1VL_gm|Mfsbzj?pUfVl^#hc zsZ-C8%Fgkhu1*k>d4xF0tUP@#_3ZR+`#Om2H+hF#s3P0Lzjy?lmKPR=Qk=X-< zF&V%%Lr9^k^+~WZHs46?H-}AK=lmcBXY>TQWpEi&P_cMEr=34{xi8X$u~SV>(`Wz|Z{BW3z&+GHhjUo5M zY3B%QPenyIV)Lh6U}8Jba~g$Un}=+np5pV)RLOY^5?Z>uN6oPsu4!=f(5@9@EzkL! z_q?I921Zgf>%mAr7|EaO&-Dj~n7lBOh9@0*2If8EZdHZwqp6I+K@;J1fYY!THCkrCiQM+~I!+|b zaGG^FMFL{oU&X+URy(#i{G~38%=uEQ%a`DltNf}tzb7ugG zhxw`NRh=}``IVI1Hc0(@HI#6f6n*OQ_N>2F3UJj;%AJp07Z81VyoJ zpqxdRadhNhxNWoS^Jr*Vj{wj4To2!T^W#WCkUX>1zq}-EQilEca#lsGdDLEBqJG&u z5c+Qrz7PQvMn2-5)8aS~(FNGmQ}WE?40gs*WY zMfvQu!&AMv#iQX<{4(W}DXl2W1ijOo*0b~lFTqJZ*q9W7+~0EiTk|fYvQm?`1pgbh zjt=GRqq=-PYwX|T*Ouk=hD(VgKLRw7(tFkFDYH4l`cfTS-~Ol; z%rY87_b<_pmN%6`F<4!#mxRTktYKHE3cdtnczH&=el)tGEoaE4uFz%mI*hB}z;L8{ zh3U{?7Mm@q-kw5-*aP;p)jl>FfxVlOT9OT zkQ}0FA5B12Z9QTtj{g$3c9StG-PLR(xrkFqFoAUC`tK{RDJT9#N({ur&`?EMMZA0~ zeL5OigR7$jH=7_;lYfzT^Ob9-%t;RgeZ4oYbfKE4dG>bHoLDvhC>|?Ll9R6{^m9OA z`)h+E#8|-B+iW{xWbaknUCDY3OEtKfojS|;s19N?#tuNe**BX?xT%M-*EvCZx2s!?S8ye_5 zeI%G@lJx|$m5lV9meeow^i{8>FX7L{OWLQQd5zvyu_&n;^QqDR|wpj{6k^2oQyYaX5 zGPvt?3}U)maZL~5{6I%d(w>%8S`n1KgDCR|PTS3Qn#g#@K8Bn~rA@!J1F#?!31uNg zszEb0h0hq)5w=7IY#$+RwnHPNiE!RSu{XO~sIUyX!wBN)SuHIACZC*4-V zuw91Z&p4pRvIq90VaA241;h>x6NSpLEHHNF@R@Be#AB;FHf*mlzLGFM9wI7!9Y+Z_ znRX*%Fp)A2*Kw*3wF^=-0J}H@!L;OM{}QD56SHXZ1(sgCzf$&QXxrp0i#MMQUT-)? z$62@6$w)>vfbb7~kr$5E_f{R^0{x2=|6&m{>*~I;x)5hf4t>t}YUxVbM=C1b0E!}v zjrG;E%z{-+G}|bayj#G8*U;+y2)kG%_Xq3|R*XKE&4lCvki@(j_w|HT+*aZsHb21` zbSYa<`<(}Q+FGvTx+}dq)im4ts$*z$vv?T)iVM)rdIp%Ld3V_AaHXG#!|%gEbDYdC zQ=>A>4p`^>PaMAl@HiF-5XME1N*SAqx!1tuoz!>56o9`A zY+a!56hqg32_M?qlR{Co{+>$^lqI~>XI%CHK!uQ(SAMkMup$-}FQLR}3;;ftG9GAt z9~JiuocFx>hVE8-#Ebwdl&)F0bv4+m5T}N21$pFAxT422i4P$;Ko=O(qFk;IBL@RM zTO+uAq^O1Q=uW;t%Wf1Dno9HB(;tdB(*Qr?;h*s{v}aF_i6>vRuHG!VXde6r2R@WL z9f#>2-Dlpco<2Gc{26}jRjlot3pN+kSAVa2{MY;$4~Oppw#qK2_GX^Z(q5xH3-cDc z0b0V7K2UiH>nbA`V>Q*DcZrW}Op|V|Fs+E8BY2!ts@U|FYWCc)i&nMOSgR*q?tW*^++a z&$l&gIjBF6k2<~*BWTv13T5Qh;v1Gs(sGv4AC8KRf|pkFx#k|kO>%MxNN%}tkDH%i z*U4PzI5AoSGkKnQihnb=tR~ABNx^*h3Ewif+#lIuN}T(9S(9;wDX7tG5(g<( zWLF4B{95h30PpE;1l3uWBg%oe{9@;bz&9i~5efEd?bU#!*f(M%xHE{#zyl^@|91HV z_nkq(IMII_BCHN$g(;G#ZSOKgbrliJg^kq|2Ci;dp9YO64VqXQYc<>Csw$tEhr|ZfQNecbfPOx3QdShTlad{?s7{kI7}QCyUU38GAH&>QO(Hq zf)V=CpTKSrf5pD7W>d7k1*(#-l=50%7&EA=YUk|_FGc>n{%*qF?nd{oFZHsuZ*2|x zE5_<974o})Sl*sHA*?f2wo)kmOnj2`g-}D9*R>`tzXfQ@jgt`|$;=PR+7e=*Npu&Rn+^qo>5k(WBWGIU*Ohy^yDFGZCB42Z3QdVIwy;zjMn0(!6v zq@BWuh8YQ{7vQ0syC}CM26l^|f6-lYMR-Db0i6kz-2q-EMM;=<@pN%9l(v;YqClG8 z!VI86rqhCMy{Cc|7x@f_^;tC3U}V%{91`MxGgko^cD2vdSbpM#tp|?tv8`@5jYwd5 z?&_G@%I%%6k^#|e1s)(sj8#^d{~hRSpr&|FTwiZqiuSFg*G&Ixv$nlZF*v@fRcKV1 zEBnb1ym#el-)IIGYBwFm5~x|I3r*messO0a0XT$JgnrI@KI@TZnnJj|k2LARqAU8h zia|m{O2da}YH9ey!;n#8hP3rif4Q3Wh-3Dp+1aHM<(w<8hj$fB4~l6UDdyurr%=D8 zZAlrqN`a=>rZGeNd>+G(uqj?=#{oPH_Zg3RJmolPTJ8y?x}N-^j3zLj^X8xNBlj@F zyi+sBis79&jK#1O;Xb91zyiJ;x9r!jHYE&83e_kf`ckNt&IlikLDP2yZYxYoSzmT0 z>3|BlgV@4LbBhQ5+7`$reR-bP(`83-4i}6O{-xYY){QSXxW9D{#7$x8Jf2FG6HW&duuzb2AA!JZfUTRTylU|DV;i5e60lKsFcp5@K$L_oa>>_BwwMv7Tk;? z){8~68d&sbN;NZDw9W;F+}FK+&ilf&K<$f8`gfwDCuu+|)WuGZNVn}p>oyJManhve_^h|9JoCApdC%waQKAdi!7jc5#&km@AZFJt_CrxsEUT+ zjX@+#R?1hF=9FS9?PZ)qI>}8qMh3~Xkh48SADTVBq%iylfK}D7$RVzNZGs>076l!S ze}E+f5b=OMnww1@D##*P9oMqatC9YyHk(cjHv_^B=R%iz$FFCUT0Ag~gW@i?J;8cHN7IBJ2qzq=1THKb%Vb^zPif9I40Wf`aV~`C0k_~NdwzgS``JRVgBP&)xB-?H#}H~r5?fp;;@VR z%tg#JAy!`Lsz(zA*_3zAU|+l`4F?3v@DU zunT9<4{tYvu@+cYk@t~Q|Z zZKGg)Y2P7Oha5OSOw;JPJmZ;qV)F zi^~NiU#+E5KF!HiZj{eFy(Nh z^4S%PHXRvtAUx$@Mty~O1C`Bjvu`ODl7Nh?lym}cw=EIFiPSh?N9K=P?(uWpayzlT z?7Y%>3{%0DPQB{0j6RE75s*xO5M$AGjN>FZPPk}0TzxiNO1Z&l>GAL(IuESWv3`J8y z95U{(RsU`C{zwbHcYOB5M)=Fn-zp@#JvoM0sfSLIE z&nb*bqRiZaik1prfC^MDIDYs?QT&6W_*Y%&oA(6?U-kszd{VLfQPX$BJu42Z;eelu ziWT;86A1@oJK-0$GGR<(lrffyt4%GpHa#SdI7C>OXG8E-A_lRE)vMAkV(hcG3tdiq z*?ymf3sJGrV*`%q7wA0lGj6#VVxsuclSDBA_pcZyI3WOCBx>$bwbDJC)&g4(FR#PK zF!9YaFwGP&VnnU)@wBfO0gNIyZq@vwHpVfMFz?ffScq)J@%5RAKZL)U1f6cwm0l@D z?UA?5Q3J}Tl{rq1MF5*Z2q3&_QdR6S2N?J;ukGtTfvj)_d=R!HPwbE|Nq3tmCAS-nN1HXFP%l4^0>FB%8$aW|ZnmJ_VZ9iCPLecc)3TF}ePuY@ zNv%*7HlUc8;FK(QI&UiB`c%8b<(OtXcKitX%1@k(O>)4?bUyHFBCE$Z371cTujDXj zPI<=G^DkMg&fN05_>-M0F(Lxt9&-v|$(>F>8dI#C8b}3G zw_8|&8chKhZS;1M9<;V|wCk6gS1(9=GPo=MOp_r8kCA8G{4@WHe>^B0V7hE~Gxm^C zsefxg)9W|P&gdn83_?0RZq<$;FhL@S!AM_F5&lcfNI@loTNX`PxQL^(9v$mJBr_bc zXWTONl?GB+s?&%qs7%6E@v=JXhdmQ_hG9zQ0;{NKH5JmCAu4)SAT5ac329o`5=zjm zE7^l4%|GM7A-I5eKT|QRMB8AqzABV-9<2KYz!2YAr9`#t0kK{Xn-h6CYgS7o4a{TG zNla(#{9GlB*RjgLW_{-V`?Yr>!*_h9tjI4`Y7`5xrm@vsM3GNoIm)kiB8D$C_8t(s zpXGy1d3{d!4bmdU10xpp0-8BuL|K@Cv8+zVfs`E4YT1#QY1a60e5c>O3pT=St&gS{ zmehQ_D}Hq`G=!CxYR{;cd&9R;0Kg0%11Rc2s=qEAAiQ*=crSe(sr6qVc74nRn1%lg z^VPJqJDyXouED^tFt-z;0+X%=3zlAhwq_ym;?yYCqtTLLKwE~_(J}KrLY{aNkQF1V z+y|#P;ZTtm*h)+ffC^z-F(jtsWmSX?^GY<4&EMmHJWtqP*q8%cX%BXO7xVQ*L zO#5uG-UshnPN&0TH#VbL&Jk6dSL4|Nq))NLLz!ti{EI1jj7Aex*qpXy#O9Yr*BBH6 z&^X4Q>kzt%FX?R<*pud~Gi%xNdGn9_7ysZMjZ+Q)SMZr>Dw$KAtE@(aa;QXN&$%M2 zkxYu3gfO~zP@o%URzgV(#A;$<^9Coc6&tY$6nr?WWu6xQvD3h1aUDzyn5S^*{!pbL z=KCFiF#%)Hd7LzlNoQYEYGmJB7cR$e;5D)0645BV?A*|jU$<2$7sRg`v-}LWK_TMS z9CG|(9`*J#4bSKQ7MMoM`A!PKu-wx|IcGsFKbk41_~F4pw_74c$DX$nzbU`g7P)P-hKVSL}$hqJmZoRGaIGzhcwZA=$(rQW^HMhE}&U@at$Io)}2r*s9`RDvI|17%d zDWm>N+T^Vj>XLA^AxWq;>*~~VK1%ZEJvCe)b{9BGR#N~48xt#uYk;V=!zQ|2u?I#B z4l%#sC`$?Lghqrtm=)%x?6URW#l%-?`^Y$ij16YgDIp=8vBklsl92?1O|Uv?mM|4S z?3~v5oWQ{($07!+{oOEndX#X8avpZ&_SXx8rXTID%tmi&y01cHqeWlzrVw(mF9=Ti z!g@{-T^Wi-mtT_E72xl!!EN&^iaJ<+g;Fk+KvYs-mbFnWa4+&G!29OKo~#z8qR^U= zVFt-}Jd~QdN0_{S@0<<#0$CShr4fa_{4Qk5Yo(4?iAeOSZSz`&5SIRgLRcho7rcoHY3}#&C;>g%`BY(;hLY{VD8z(ofv=>?E~>xs z{IQ#VI*+caQz`3Yn~AT)PjqbGZbt9ZVqw5-DrV+S)XQ^ zuOy#umemj7Wgzmz+b^#v>0z$8@yb^r8^}2)aIr4y*Ah&DO{?7@^1q(LdF*(m?Wh|r zAXL62uHCzG=k_<7UtuhGj0E%*)oqMHiiBo;uAteG+T<<{?dC*~eJg z+dAVHe71{5_T1Ocyoa8v5HS>mZxQQyG*$a@72I13%$e-4pGry-NN>EUf74f2qtVh} zMq&^MUtg{`4mEEx>C!A7z!`Jf>tw*NeVK%^XaUc8%d?(&&$1aw^Lt;l9v#B?gR2WXP~ z83!K|S4K?~)AM+S&iLR4t0>be)<>N^0Wkolh|}&Ul~T^B*`nn2vAzq(jvfUD6bq~M zPfCjw3jm7grEKc+xn5j3pnp4Akk@pPj|r-v`qil`Y;VC>pp4*r&U^W;Gz-TP?VNi< z3PF)}f-;9z;+SP`GBm9Xj7ffxW*>|WgZgDU8+RJy7iqj(?uSy4LdU0AO^!fF!^ zysSwsUa^sfi!S0v+`eiz`l;A;nJ;1%4EMe$(;e>9c%Sn|OiaVVUKVi#-@t`goN*9D zV}XDY9}CXq5WN?C+$I>y0$9(3<2vWdY`p7}?RND$mviPCZyp9iVW7*$P zewXwT&NFfNPWWS_{Z}P3K@;A!Y@ijJkFH2pcjO(hoizm+2KcCImr~+(X zEL(ukBsXe_d&S~PXjKU>4^#D>N`M_Obdt749qy%_f{8P!-lkczmJaDmU)TFzMs(HtS1`!{{-X}CaP^mEu-)IcA+=1$=jKI4b6`7bJqqOx*8 zU1z<#N|drisaTO)T8>^%Z{MDRW@(1NC|M(pC=^23`jFwD@xG_5z&jv`rAB_MY1atKBv;9EfBIl;SuP$yA1Pa98f z!)mV-N}rVD>38dAm`q)+k!RWVm`go_&-t9TA8`fDBUaO-OO+oCtcOE!1PxrrX{IMR zCY>gL#!4V|1Bi{NepS2*8b5JmLa)X6Sr6Zr34-wczeb1sAU#?z6LzfQ0XK&++~3S@ zf(mZJ`%DjG-S82J)+PR30Nkv#z%F>Zc^-hz0mZ{g+3*pF$(EY5E-jyPW|?e)P~fMH ztk!ad%)1;j#Nmq^09`cc1mpC*(!@oe_Tc8>MzAa!PLw8h7L1L;y|ouA{k_wwCS#KV zXOjVHpV5p@13QJ>(q_Wt>Q6+SDapwqkCn4|Neo}33Y)JQoqvuU&N|gk+Q9LrQQr{n zGgeD)Ip0#EZ9aMAQKRn(!NSrVrPaIFxL>|e3gYpIDO-%^x^b_c^<2Oqf7Y>%nQ=Pl z8*lKXRI~ze>w|GM0HoilddP2w%eLMW@~lrvJ_f3Bvy}rt|Kob0l2OEv7@`q?!<3NA zo$msfj_);%Nm@l5?VZI;a+qA*<1jZinA;SiFOZ`3`BjiCF2Kg*J<-7X%& zP&w>-sj;$O4iQV?ltwt@LzL)ka`~I$oAvCO#8{>VUUU&3r>cgS#Kp!dxZKZ*9V5q5R5p*E4!iVvctMLTHFMhu%4wrdeQYQM(viImtx3{w+x!x z!YBI#j-5Ux+SzY;e>!v!vX_9eza4m8)ObF#rBZxx5~`#y`Zt}4h?tM91gzl=2- z1jAc-ks_GlhZ34Az+0L5n8P&*s_<3c7RL24Qa%3yUOp4pI|(y{*Etb;I9%1L{)Aew zh5d~<@THFI(iQRe1ryVbd7sbqTtS-_fIcom>6cwp&%`t_%28TQfZcQAI9R{T#Dpr~ z-*Di);bvDJp$?9?#+hlt`@Eu@t;RTGv(SqoTCUguo_Lgiu8KV<-q!&E)cbb`E{GqR z|C|_p6B;qepY;a^A2X>?2?ZNzUbAR}VgoO9jW>ge7%vI@AcUbNU<@lWY&A&XJFBSS zA$A8%N+qKT1y1t?UrAcN^0t}~d#GKsD&9KqmlgAB?WHtkSuSrEQt*&2)P4B^G6Kjw zxF!;f$|u|iVykSHxs^3BTq%#a2EfE#npMTOTe@8EGTuu`W3y)i$nwS4Y~RDLn3Qxi zlY{ehE+!VoVxvpMCA6vJ2$nh;@i;(u6ZR{mR#NlMPvUY{uKh{ZR(gqhgsVXfD4XV`qyAO;81zqt)8w!

=@l&lHRiMQy~h5HP!pJ}%RY!N4ue%xIO`?B-)ozN*2rjWpL9z+Ju;}{ug^RvDucc2rO{H%fl7hbgkC=eEa&v zfcXVEMI6S%7YXl$*i zE4_#40#-}%{=MaG^}vOmB8oNQ;crJ(3@G2S{mwEwWTU@JAvQu@y^a)|b|^W2M&514 zHd~s%7wFvNLbPFGnZp`lM4C{Vue`Q_m)|14ivRn|*vQxKU-L>Kg#t8hnzK0&!^Jfw zw%lk6{xYM9?<^jcZv-v#Q${lkU1{A&%0#nWKm)%_$1KI(7lC7RU_N!lc|Dp}A()%W z`H^gdxu_-jJV@7>(vDg7Fr;t^yDM(ldxYSgQ4v;?mJR`j$=4L%M`i_YUbgif=l zE@Bm{RK0#q@G%b@AQNh7V$rln1@#;ZnkL+??yU>Fj0(rLKG^B43UFOCFg*Hv2G%3b z`OIfMOMRQvJs(;lSGwm=G;u6b!_#LTY56$*`nv3i@lVu5-)Bhyb~79BTB-S!w>P!r z_}#=W3I4AhpVPc~9<-OUN?MCu^yqB}*c4ATp-Sx5#IqdCz*VILfbeysaxwzuW#hO~ zSt{bNmt7%-a`cq#e=*U0AY}$Oa8^8wprTBSrS&|^%pp>hAONuz<9r3fL8yeKzwW!% zyF2}if%Gmxv>vERctudGLnQ#@nKypcEjK?L%1^elCk8fAT%~}EP7FOhAIRW8I|W*T zF2l+%;#TVjYT|~3h2+eFKEsEd7BS04A@Mz{Q2L@hUM^2LCS9&;hk7un1jO+9&SoYz zSupmPFEyTnOKK4gEzec!Sg1+d zPwc_Qs-bLADN-@NDZc(|WWUOLDNd{gjpu$D=UUk;L;oJ_qXA!-i&io0SzH%=rn%hD z=kzH@m)ZLmc;u017;KXI$`BlVt?f2M`x3(f-9KhGL|;o zju`sBfJ2yIV;$#4TvnS50&F`wq%thq{jk!vVz6IDP%}nkJBW<1nkDfnfgDUphAS~i zJfQhd+}jbod-(&iFZ!=PXaC^u_qWB*mZ|1o>}sjM1rSV9-$^je0fZ3AZ3Wfxdpj+4 zaVEoLTbWu)Hps@iP7`If6e{09u873407X>{)@rG?(jn-ut}Pb|?)?qt_iAz}d23w; zTd?L(A?SR0`tot!i!86-JZRv!3hWd-@*3(nhXrH-e29 zen!}|k){<(Be22t#5vEX(I)^1w#LUvXAwAD7(pAgWmIQ5K6;JcL z=g+v;bJ6^>{wx|MOl1j$hOL(y=08?xK)TV}^0k=+Mk!N&?yFmUlFpp3HD%G>HJvq)8Wzw*B9tE(cuw}a+KLW6A(yT8KfAAZ;0M#UEpD-PiS zT-2zxKKU#Ij7P4-ogJCxA$|)XTxwqupu&e=hlqsN^zp4MXZH!d z{HkJDU*1ELi_W~9%97$(DN2r^&vV_mblrTfpK%Yn7-QidQ{yQNAEDs&<$UGkqmhB} zfw^KFHdqA`XC25Ue+e>igw-z0pp};e`hlihiFKL0xzK02!r_V+VoosHOwoK`uo=B| z{6Yo;p7AX670xJ?<=Nt{HM-arYDs|$+g(}GXLu0EZe)V0tBf5$+z;Rmw%G}Y&2 zEsK+~K%SSYn!KaDvW+15%|$x^3y2C|ppn>VAs91>lb5mJoMlVd7z-9)j_Z?7n#VkZ zuJF;0^zv-`OhPy=3f?RjaMsC|-$~4hu|3U0IOEER%l(^$%nVbPR`$NO&_pz6tfLTB zX{ua8jxsN5zv6GVQ~|&*i)&*D?a6lXX8ntpBRN+(92M^GFJ2Q;pxu@CO}LYUz2vr}VIxf?6QHgl;=%;Y zyHGqc67yjC&RUq@9dtv?TI=GNvv*h-F5=EoORw&Ky5+u{L2DoN#Kao7UJi;F1*2@D z(@0NUVh+KalV$|bUeSWh023f4T`D@hf)r7NE@?ZA3}zpd%?~JUp*B%9BRc$h_QZvW zMHd#G$2iVI=z5l(>i1k05HG~XAURx=tX5)JB*ZjJwz5x&(cguV#Ad>>> z;MhyZ^}8L_BXD_f9)qq>y1bR594nnKaTduRt(T8rglYRUqqg=Z;o~r-O`Gr2!%ACh zPtLidY?uDK8xFFZ@gkRbt;OiQ#47AjJDv-+GBb!c<+Nb|u>))|cMHKE%Ls5aq!~un zW(ZGQ{2qMC?YAKKtwB9rD+xgGdxZJZ)B0#1s1#~J_Z`pYdX}A|e_?)jG~4e_g^=wF z7uk*tf))8Hd=<_evXF7N*oj%A3T&2VspL8wZ+=VCe!uex@L^Q_OO5pJ-#=<)0&I3Y zm}+R0i!pYTJ*_7OO z|3I9INK;jO2*2^Ot0Syry!54rrqH1t#Dz?<1z241-)W1HE#Na0?Kt`(gWA~l8<>rL z(4;e_voY2ny$;2SK1zA={SXMevDuguLyNAy%t9Gvz{vOM3SlUP2o;MfgKbu6#bIak zKoN3^n4h>th+XmgQVzx8w0Z|>;*#_Io})xOx2Lb=rzu*YQj@anvnLbnET)KG@ia`#9>|_++Y2b_;HS_SYMJNXy*BzyUNw~GB>P1nJ&F6@!mHE#2hCT-D6}d(#4d)95a8Zxg-b}g z#xk6T95^QCbKbbek0`CCMdyKI9iXfE45n=bSj7y6^lp?3g_e&`#i^}`=?DLi_Qn4K zPJi%=npTBlJrnPQz-XDWY71m!)OEo2f5-TGDdit|^yg+q{Ge68d2(KY%+`XYwmuUM zscpQ-US?v2|hB)3(u zsEVA~pB!mzH@H%8%P<5suH}>#ua(mK;A^VFl>%j1M@AjzGcfMUj&)N{-We*j;l$Pj zhk4f57c_>7coR*t-xkC3tef>xc0JtBy}9^IVKB^EnK%s2;V>rJCameNfT z@l*q1bH?R90Bb;$zmv4}YJoT=2%fvdR;+OApwNyP%JD3w2S(C|Oio`VR$8_OYiD;$ zps3P_tuuS#vLbrg-^*qs;iBN}(o%fiLNg5pgNp%kuHYjsBcEk6sxE!&8bm82K={&E zqq1**bu-7@&U#x;HB^D61Vn6vXyM_Hy<`o`HGGbH<5d3o%Ka z>*i;;fS1)1DbhWGAgS^kwJ;(R797M*tH)&s3ue%qs|`ve9At zeVq8{TLJ7~a8n33OEm*U&0QA!nu6^L#>U0w(wBZ`_c~6QS3Z=&4?(B~Kbml+d9)73 zp5)^f`@fZpbj7ehzoO;=6g7uS9#CyL+1ARozk(`CS^oSMZ)rKnjv8~t0Wj>Pmsv{7 z%YU`!Errrj@ayjjDUDj{S`K*hiKAD0e_kzzg8cCf8fuekQJShdA4zS%aB^_Ejb#$!Nj~>4voQ*f1&F z9JLLjp&H^kBX1W}y)L%3v22sTqUrY(f`R9J*p9eAtLIgZVmSDibm@vr|85CX5}+yc z4)cg>HFkW7;gxd)02PB2W8GX?%1Rmtp83q5@pInmxd6II{;WUepLLwJFVb6kC#f}; zmC|$6T+(YR8#4A*q`l+JUEh2r+16o!wEX@Ow)k!TcaL4{?HeiTwDsk+31VeYijhtS zUTqdX9UD~XB^;?V!Dr0`0KYxY1o1^2La3U7P^TSxj-N26y6N~B<|!0n@FuJdB&OAacRrUI=0p7) zc8rZFGzfm7TX{MbOdTAD96}dtMa62L_k8%iR6@WaHgmtj)9X^~ROMJcP3{8pncY^1 zLO6paoX4~+MvVidG0Z(ws1zHYRq1 z7hSkwKjCG$he^LHf;r1r6q8HJya3WHrnE9H-f(_3-HRGLgNcKxD?U`ZUYO>M)_Kiz zVbLsjbpFFZW9{FvifZmg<199ektxR8M|G2$vpz1fF~5FEa`~<0E=C^Co|M_SC}A5& z-dGa@pY!=M6|-yAsFqPr`}&;sgwRFf;9q^2|7l92ib6U*YGQ1?A}(Jv4FI73AxP$c zUpnqp<+ogV%U`L`85U`AF*mNT)t1=EpvTNBfe_*d|6q&=OYum152{$eX5Z7c&4~|` zfv{c?oBdZX9w~VO_LkpSh{G?DW3?&z7d#PTLp1pl+jI`$otQ2Mw3V3pJPh^4|HsVJ}l@us#sw!C92S;!$VY$jQmfgtzQKI&^s+5Jb5ZDqkd$BV>nWzg^VEJA3l zQOZ2FoCZ69JPg|Uq%)j>@G2~y3mcP&{zYDYSMu!M!+_C4HfO`x(mZzLlG%ZqJ#9o^ z8BAt(ZRVpCaHI&(!^DG6TcYD-qZXb9DP0)HG;4F>$LjH&z!A_iyf;-DCyw=cWtd)g z;8C6ta!>0J-sU{wdbtn+J+|LGpE zOm(KkIE51G%0tQvZG9WfbiN26)K31EXNn4yoT^Z`FhfY3nSMnWcPha6SRWJ4pGKLn zOr}^>2})ai)Ceoj38&L%+r5b3p))+9ljfgstQH!_+l*59B_yL|H=&sVs`rS-5r6AB zi`jTt`4OhzlO|n&N@{fJY2tK&Z9JzLYJz^mTuT!GTMoD(+Gye`bG7Dd{b=J?W}2P& zIFFNfR|<Y%kxGZ4NjD0@O zH(G(>g1I7|W9s_p6fq*l0Ha{Z7mF0dMf&o*$yfh_^c}Rin-+GHVh@c4T6_TzGD-Pe z;8TNtIbL!#X|Mclax7xB#ANbC$u)O*@~iCz1%N^WyF<<(>m`QI?2==6k4nw~(Epf7 zR@p0)kKy6?RtD22Pqj8}dk7@07)A_65j~X1p0?Xisyg`+kV9m0>kHuGTVYC>yRfV# zCJ*?+%J-}Fe^(T__Iblcr0{9L;2t$EG1wMr3YG}wH0iP!=y9+un(R-V%KC0J)(kAV zh@TZ_xh7QJmMsufKMUj5B|4q0(X>cx9Thf@;Vga&HtP?9Sxxf`&LRYIC`-di4Pdh~ zRjjU9N;NUwNq|B!m)G)?r|NC|^fA5Dc)v=xDdEUpqY&zRE0#+>2)(Bj@Ctl(crGn9 znkNpi(zuLhTECJNOJGO;22-lo`A*>>FECm^Yx4KfR!8q&h4|x3?ddOdC0m!`p%ft5 z+Kb4cer-wI#AB3YtJQ?goKMa`C(sDL1(Ukx+u!9%EVlmia#bk#O3aXFMNJXrA-Necf{NBdz~1)sqi7 zSa%oJ%WtZB(|ntfr3-~%>zM#XlddArCm&0JqvA3#D$jcSE?xECvfBQE=X&_YJ)aXo z*CEICuk%>M#L+%BT7Zg`Ev@pqe6M+vW909>_Wg#^_Pzd=Qopt>WzD;K;NtI!(&MXD z;2A*d51h7^-MArT-UlbQatpArxX7b;Tqs=urz9*%UlI!SC7FkMhG?hBS0tO)Mh&P% zrYLHr!{p^X7yAqgOi0KOzP4f58^n=EdtrgpcJjfcUS>Nxl+%T0o~N1x6UAu85Bc^MmFpqu^B0=l@=# zLkOcoOczb^eqZVVwbQ7FeLrcMfos^VaqqqNu$oMeYtMX*p0|O68-W$PbwvC$@}sJq4tV5>;s~)F+doo)3EiWjIxz-NVCliCO0NE zMVj|qFV%KWwGTTKnTL^Fk>5Gro=N)m87~A#R1D2{Ljo@fWwJyxki`1G%rnTxqIn2SBm7*2#wYuPs;qcO4K1u9^unzFgB-VEMUA>7&N)2oNi65ZimE0#jkB3uwVO(t7q* zl)OBOWiwgH4iS*lBJ2D$717jl@|MM0SoXAPjjg-@u~ws=J)AX!kV^EWn5inf3#jHK zUy((4v>BV;^iU#TYE{5qSonCxGjSHd0C1b^qKO=#Zngk%r@_gB0}jNvjOgmCH_O#mkx5TsbZ^T7S{Jn)f=T{M{hn3ZTM{Qs029PS>P!ARW*uMX|o*Y(* zEgd=%m+uUG`Dg+*Yn`c^CT1975bJJ+xuYnOHkXIdPpJaG^^$ED&_dv|cp2XYOKmeD z_3yK&Tgz}{7@+cfKWqvM$RNr`aE4@}oLoZHc3!E%rz($^mNT4QG>;iU%&cnRFYz|c z<6*o;!kBg5^O+tTX)9qq=Q9ylDkg{ljzjP`MdJvC9eJRfE7jqT^j|YZQp%i0QJ5Sj zHA(M9c{2)U;xwlb+u<-5SjQl+o)le@8#XDf;&1`B1?N1b&0qlL`nV9`kzQ1117_fTGM zTB@GdElvv`zekhq(41-|KmCuHXH5EGwt?K7Bo$0fV1MbhraD1Kvii8Ur2uCD>Xeu? zFazMU3|eTJEg=q=oj@+Ll$*6pp?%~zafUT)x28+ec#PXd*Urx z+Qccd)-xE(#I}>JW737|IL+?3;~&jdItwh#9$H(`$*?I<8j5q4{Z0eGxaZAk9XGNH zvpQYoGng3)8nz>}Wy9z>T!c|3J;K;9hyi0Fpf`3)dRcx7<8o5JsUA6!_P42w^;d`y zJ+&pq&G&$`sDCMSRPq3O|MtF+=4FG4;lf*0^0ekf++6)5PmZ*|BS<_p^~uG#($DXp zi%+9Sg_%)t%db4!o1%*L#O1e?dNrSw+Pw7oc0{?=@~q;elQI`V5b1FVKnN}%-u!bb z)H{W1JawI{!i*J~^Jm>d)d)5o&o+qsuNyPcxV^%IIirA44wr%Pg-}+h;uU#?8;v{(xy6LP96x zzJ9L%n)kw_@n`;D=bw29UD2MD%3DiO`<9Al;49}|$Lmcws)lQi8i(pFYYiXf&R?%< zW)waZrQ92sP>;C3R$}#Ya(dWIq$>{s3W2!vy!s8=I&iu*;B+lb9^*@q1DY|7iJLT{ z&s=C^CRZ$&7*#3uJnWQ!IJn=6F{lu`-@PXRLmsjFO+)Gdn*ZI}uzn0P4J-bpVQGZu zC>E`wWk(4Psr7RF)Vq$cu`6s+jiaqb+psD6&RcHY7gGf~h z-P9DdJG`PYnZ}1X*2`MuM@W$9EPirSyhyuq56SzxHpK8~2eApt)-VHk*8Ahj~%c^vl-DJ9*JI ze`0i5gWnR(XOx^$|4Vn$(j-=|Xj`A@cuDilARVJgvv^2joVrmhTGclWmrE5Za`(Qi zT}g9u@pIntgPZC4vyOGl>X$DVVz^wr!_wP%C(BL$_hr+Ip=64~HrPpItzI1X7ZUP| z7#o;j_2G><&u7^QO_r=913u|SZsu*ob3P{)Es{eHq0>b)Vj7BUD^=OJ`C33}#{|8DmNq zSIi90`Hu@7aIq!+t}<`Y)UIWCl2+DhuZdspxR6jTedsU_Vyq2n0KZyp^IL~B7`ZX- z(=IWsM2FwGoAo!Wt)8hU!o2tG_w=ja*ww|X&+F!RFaH?!P|M5fTa+z=oY%hMJ*@9t za9vSCObtByx-VpYMn0eO(^#jA6$}?6T26y#SexUy?(0V$UEvq;BRHP{&_&Zu01i)` z)0XC&UAn5CVXIW%qCs}%=5!SrJ($ZpY)i#c7v1P`5vHgSX;QMjb^iKqk&$#Xt(FIBW|wu3cbV$P)VU?lD6Oe`pS?6q+C6#v6Srb!o4RB355RC&#U zDnf+H@r-Bb`HOy*HJvo=o;MSx%#wS$aP+~zGjV(R&cz>mlz&z7Ua4M7@oMo6i&ICq z;srFEOM`QR%VvqUH^638%ssJQqcq58QBvS^EO;}Dl4_|&&=@uMT&@HP5JsNb17Ew2 zN#}VSZ5N58_({D_O5Yiw`h0j^{R_to+h)CuOVSF*nX5snMEmY8Hvihv^5F$?Z_7n9 z41ZaR$x}d>SSD8RS@ikSsp^Xci}|k3`+DX*AHMmyp6eDL@i36ttRNHN=^~~H#A&=K z)3JHEPC5^!tN2jT8Q=zag^r_-OLsFqXBpk>0hjreZ}Tf+264U)NyL-QUw3zOU-e50 zSiVo%>Q@TpG@Gj_Lg|&=kmz)aqjyVBEe#)|gu!dLvK|;wXms^3T^c2JoNZ-#okPbc z%zZNqchh9rqUgttY!JuXNi0oOKVzjXGbdiE@L9_#{jQLi5%nx{Ce0W1s?fV>Dmc6MkLGh2(pG`8aXHo6*jKNo!N^{W!-d{D1=aNT$p2U26@?z5qZ`v?f7L0xU-+* z15Yd8dvBwhAPBEbqE*W66MmSGvouV=;_-c9GdL|TD$u2x)gY4kyV#()Za=SZG!?Ig z5*CHfM78lK;W`83eDHw-B*!$Y&lD0u$sO>;9^Q)LV@>!6ariEos)C5fT@!D<+Jb=) zbOdNS%LG0SQfB6Qm6sUNy;XyT{Jle{5MzURb!U8#052`@MVRdf;L43At6}7=lN2Kw z?X8wuN>EM)t1Ms_wLTJWe}7GT$TW@WYiMKRYj5p(ovlg1k>si~ z1!(m3RYi)ozX2YMwk(K?0jEac8F47M`RYm_YMXQ3Zxb;?G!7IBW0DcSPOTYlEPo~33$TA0LkH{nTF&?JtLS~05Gb3_=8tIpHB5WKCo z5wk|WuYHF~ow-d|mgl^$8#nH0=e?b3^osBu&)LTT@r^&fN-5dDe$_MHugLOMwWLSV zR1@keb|PwM+aRPt;4rRYQgLXOwKq(JRIJ-~6-?}$U9VX}Rn-}}XaeDtn{hOpNd@i! z{s>-Pg^pLV7CV&1crr}v?rGRx6>sN8_g2Ri?ZW+Do9MgBfn&Ra*D=y(q`aSYPK}@- z-ghqkE8{Ee>Hp*GU$bV{c{DNbfp-4w<#Ju@F1x&zhJvABC>#ohf}v<2c|!dIcSh_l z)6KKaenS#P0w9SyspuQ+p2kWg&h;&A+`h~H4aKW~ul4(V>}#PPGYSQB{u(Sy3`ysc z+IzY28-KW)qrSL`n2YGuV>VWt?SS|zgM!Cw?s|cjmP(x5*dM9WdHGe>3HlX? zeGE5kx<{JeCU*BV1I;9qmkY;NDhWN)rBf3MN6X;4Uc;dabMztC4Qo#zad-d53N0fc z*T&9M>6dGGYNOH2Ee4<-Fu7wZm#?`N1q>Z6jbiu==AdNita#PAFdL*~bz&T?{`Oxb zo3zP;l%&%sJzRj=+C$a&+jm^s8=AklujJuU(Dsf)lw%z_mg&Z6nB%!_S{~coP4iAD z$$uSL3Z(&sey(1dfv@~btv0CNuiuh}I}_vN4E>!6QQiH&*VhZ`zoVi8e~Al3op=MG zhKGXC2HC(nMG%JXrUM)b!tlJLe|^vWa6bfQDmZ2UW?SlKYl%a+x+>qy2x%0FDeLXq z-(hqV7VOvxFwz6ch}d3_>G53xE%a{jw{f60iiL*}edIBF`^2z!tgSVs!!GvYdj*1} zgyK-*G1bA(diRI@n3?rQu6@Cy1G{{sfAQ6eV-bbN1eZpOSfEw+SFyGj|4IOAs`Y*~ ziq6`@fNSMANeU>@_zT9_OJOos$9Ggxr$OZqOP;?gP2t(}dxflF4yv0i%WHHG7-!F# z3LGCTO8dRf`$3bbjwXbLqaw%Q#1d^4jX6_wXUY4`u#3668;7aGRJNKyh?$+hUw`b_Tbd`g~bgRlFpBTRr{?CW3Bjd7zlQv_y_;&58m}YEi>r52ywQfRDE?ULF|z&m_bO7 z>qA=GdHVZ}FNL!!*JgM8G<^~XwRr=88#mgZmf_$HJ{_$wkfvF39H-W9`aq6MOS)f~ zwYB|?lCUOE?j}pj3E~oIICsLFxcz_^ySpvE-shU*VrwM$ul3CykMg9zdGr>E(QwfZ}30dMDD z4&gUHuDiWn%EV|1b-z7ljB)Iy^{-46`d@W3RGR=y0b8)2^**m;8dCW>6mYS^DL@ji zi1Hom7Rl(=7r>#Wt$E}waYYFtoxvBmEkKSyv%kAyN&3QEp6wl;kP>RS2>z;;RylBN z-iz%E#+dmOv{SX=jE?PuI=nFqI8)%ZJBQ(dq~#xLj~?Z8-s#I+B>tVA6gJa4UTn6~ z7cz%Bgu1-XXMK!;DKV`Jf3|)9$Z(Wa}^IT-KWYO&~Xli2R@JBO{Iqx;7 zoo}yUykQuOX!;?e&gq}{HtT17)Dz%cPp5}gY(k>MAL_@Q-H{^PKCh$} zo(PwUQ+@UPGmBb=RzQsBx?|Ru%rZ<8zSY*uZd@ifQM6;w$)HvnVJoTYRN2MZ5ZVg= zbiApzCQT#dL$AuVo6g3GJ&>!%T^wdttoOE%i66wc(~*RDzi|SLmQOe}dsml|=(YYD z18rsSHg3K0c!pyOg1!6ySVcXIHM|?^!~e)X_~@&UOD_4dFCifTJ6YuPivf+WzCe93 zMa>PwQf&z5(?c93%8b5^LgC97e`LhFMQ%D;wLkAc)k?_V!#c#&K#LS z{X-Ldrjmy$74@|({fCwdj`bx@&9sx|H(KWX1R9Hy);}kN#U9nEyv?gI(qncrABr|` zHtdm%55N53cYC4>U;C0ja>2cSRv0JSRlmcuij)ff?7F7&;;#8|un8f*4)2<$7fj`%gl5gm zU}HS6T=&W%nBL{}*^Xs%YnJUivKZ_0~+!xgk{&qgaAp{$5 z)JBs`Da(1Ef4yWJkkT{@x@CgtZgD1~leTd@4eOd4NF2jOC0Ro}_UZ;~%gl&+J8Sl` z(?wcOwqI@UoAI|I*#??)$sQ=)bbi%h!T8}6;RAxd^;l11wrf`d+6vRJXjt?V9Ho5x z+~RifKCgau#=il$n<(YSgBTC89M#-4$x0Q??Pfn<*cc9!OJ?1&)ytetb(@k?4 z)jYU)qYX-~t>mJm9G3RN5wn>(0|g)7ZL9^eFmdMx3BhzBrfh8qwwhGp7DwfJ+Un?# zRXXI-{oe2TEZAsSj8JcE^w9EiW3iIg8 zamuJxm!^zP`Ivj4u5s`jb7H_87yuC8r7le{TiE1p((>Pn1WXUz#4UX?oQ>Mna2kNf zHSD<>lHC5zghPp-fI}789MVkO=VH2P(XyUC>^TefCG^1DakuT^es3`Gb(_nKmM6j+ zebX`b-nc&wJ3X`MJ6+1CKYXMJrP6xqG|xVthe4uXE-_yY3!~nY=E>Q*w+LW%1yr*2?2b z&&MN^jF0E%b#Jrx$76-XU%`+W0R1m@5VW&TAbssSt@f^J84$bG>Tv>VLBdoa9xYBZ zI6xWw?lf2K^l)bk(D%RynZYGp&>4%%RMRyTKNPt&2e1fIMcQvh8?tF{ySOd&CEsZ( zhk9znPK9J=5`wrob*BeDR70l~Jy^s|;KqWhzB-&C(DpD3yVq3TU1z|xiCtnF4k5q# zt`C3U$z<>-pDWW69=Q4@Qxjz-uv(+4@iFC>%aRf zZfrsa)@R{)IRp$G(tPVbX$;Q#>MhvajOv+Z)K$>*S-K-7#LryELOiMP)++es2JY>R zHRo?g<7}F4mvF}*ee6#v-Z7#v%9~O#@I*0Nc?K^J8gRSj#4QC?%!m}$0=OYo{3O~? zJ6rj^D@yX-ufEUc@o#8pV!q3e0~}_6A(6Zu34_g*2lw|IO;$UCUOjdgSgF%-dh`XT z0}xyRYy2>yCjotkGhaUyjRE~h45_`Nlshu2G z+wWiUKi0695t|jV*}T&5DEzzpUMT@r8@1i~<0JOa;W}f4*7EYapvs^!^W$!LRkxq{ zyT4{U18w~J{c?2)*0o6g2o)Zt`bZJ8nNY9(zwXy z#En8ioDTWzhMLn2(#FZS5h%tfk|DQ6;7qq|k*9z}fN^@JI(AxnYTt$JTbGcA)AESw z_A@W;oU`oIc>Lo%5BR;`^31<;55}{@Gs9ysnIuc zW=BMEY&dw_RcR}cuNx0-n?ukv6Bne#6l><-fMCJpU#zs^2@#vcciA~+M>r#n32QPH zE)^Jix)1V3b6OS+qT{f_#S5?L#vWhkCi5up_63ng&b*E&%viP3m_wF+Jb- zRk;89&VuM6fEg={U-Z9L13>>8h}5qxHNbW8pDa0s;b2k>SBu)Im~+Ug4{hv8n~=TB zOJr?Q8WPStE5fJ|smmCk<}Vi!xn8B;WHBYKi7<<8mOLJdP z;34n*S-1h>*lR-z;THH&!j+J!V#InQ%gjstL+M?gyW)lkx_4EltevYEKJ7j&tuNUzjhDNQr5x{tOaK-{qdFz zddYw&%Xf6VW7+C#GzR;X##5|JI{U^sT6)G?yP4^^2rEyvLx>J%upL$=t{GQjV@FKq zK{BPx*t^@J@556lGoIMjb^4E^U1|KU%$W7lwC7CbEgEQY(HNth0?e^%^B~#^bCAcb zoT2CePFYhE^s^z*T%JOh@K6v@DOlyQ9K9&Dt<=hI1nmN_v?x{;#g;l$Oy%J<^~HyIqp%KgbKt9Iat{+M}n z$9_qV>JVeoeXYw=N59=*20=4?cZ(pNCEl_&_8Z?zRs_%AjoRUT-{jPi#d)_ES}9<3-!B_*PK11+@t@sfixN>7&|UnWDkf1CcqIa z*PJR0q+&J2MS;27Jq5Q|%JQ?C?o0|uDAGHwg-)^h$yAHQH%~-H9hx$^b3z2xslGNv zJcLiQ$_*bqa09U`el;X3^V-WFwBbvD9^b3Oz^k@B8RFkq3=<8BSM(TZ9mxV4cI<5h zOJ|@7rHxb-9J*}_w_-1}%^drz_lZ3twCKK+)}_~?n;vYq0c7Wuy$c}yBiFvP?>GH1 z-EG_=cYP6i{oU(ioCaT_uTk46UiW1xS;W02fKnvw?{01bWNca*t(gQ5af?UQ4DPM( zma5gUp1p&L=}D^6NTH!wu{_^WtCZLv51d;hE$1Q~Y|QJH`thG*Mg3~B8*R{?g*zL| zK1}6M`&2GHHJWqvHJ&1`njiI}?gZjW5G}QCa2oNy%RPncVrtuIpS~&+!)DAz5>YBl zp|Swsx*Ca52zNoFSH;Gw4|NJmmrkgsj(Zzl?Nq=a)SPC5MNc|}RwSxB$1L^8sD7V* z%@oZ%XLB>@*+*z2Mh}QFyo1N2Dkt?`jB8s_hM_4ID=EPA&_nmAkHm1N`cttW1%~TD z+vs5Mn&8*BZ~E8F!zlVm8_8by$TaxrB+Z!e*SoFgaYs-1#+o|Y`qkp-zeP@rm+FT< z`mw|2S982Fw((bb@Q~0$;;{$-$J`Rc40l&TeHNfjEljx7k4Z6%qJ-=e+kC4rOz$HM*%&Lq|UWQ8zkfYiO;MZh%>fIxp4O0#FnNTD)QRj3{D6q>zJa zeMdwi&C)ni9Yhz}hP%NRwmX4f`(EjGDQwVnY>G1VtLU-^m)#k;y*>K2UVWd>%F$Mb zlz+6Y?M|&Hwi!wf-TBEKcEdPI#}WxcIN^HBJR*`dr{rTFhF9#ln{jRr`g*&{CQjE7 z8f`-UN8~5mQ!&DfO`9H4KUdP+);HAgwUxEK7oko$LFh}jzvEFm-{1rYJ-%*YUG;TS zJaM=5e2yqwW|<*R*?{Suot7vKps1orSp~{Xof#<$KE7WuadQvuoQWGPiQgst4xaMO zbJML-Tqm$ith2ZFUaN85_0n(*{C9gSc}K>xs0(5sBqXs*s7%BEstI$*1@=*^xu15< zpda&*4lXmFHr+0`hS!h26Cxh9q+fl>Hg2sN6mAe=dh2Y6rsz$k3Xf`)KL#jr>Ol2m zx=fwTqanJJA%qXITJ*n7?NMN`Go+FobqB*6p3G?~1|g)tY*Ug4HA39fU^Aw&&!~lR z`dtBbPvdGwei(bXkEGG{Z^1W? z8s_@j+1)UH%3Bu{*+7c|Ssj67LJAXODmk5TaDn|H(+xa*FEOM|H?3>AB<9v{FZ_cu z1Vu7|v@%<4PhmRd^m=SAZ0J@n9qUQ9PV9tFRlK>HjJ8x?9b zHmXabtHehe9OeZ9-a9`fly6q9Lmuto8zE@Zx-^3ag;B@^?p%H34~f$xMS9WTEcRtC)C`G#FXMpkU#yK0p~9~t$y{!K4b2h1;gJRj;_^a zEIie7(?gN))P1K~iq?@B=J?F$$q;CoSi1s}oKPGOA$GOJIEB#T0?yjaWK}nBHO@*6 zXlb|ET&Ymo%rwW8b3DaAr?D!rG;o_=ok}`yKR2-MxC@b(o|Y;aH5fR#h(niuUHLwH zLe^kcQQ~hJ+=r8Kl;DbgdtTh^sG~q~aHQJC|Hx;5Bobr+Aa+V!LVDqkkeUktXd@jh zD=^d@+p`-xhomBGrKI) z%UVx`r_Ifsv)$(|&|NT-4q2j)(h8*NImAMoP8%LtMqnIIjb1!dz^M?5ue_2J>O1}J z`aA;#U*B|JD`UP1yJH~(lU*8>E5qW3M(A<^c)I*L&mQo(9+>T;_0Vbr$jFr$1ezH| z+*SAPtZ(;ez6-(;`)~mXL76hN7VeD_3Ka?H(hR^?%?3D zhuR~U8&9R1M!$}oj;cXM|Ii~FbV)qT09`_5bp_B*oB{CY$CmzJ^i3^5OOI9JD=Utx_$$9{g zCm*uG{oXJCvp;SG(?bi7f5Lv9W8MUXrurN-`$gIB`~CTeXD=FkCicl1T+&|~D^OeAS`_T%!MY#$~uGXNQZ(!#n!qqu(URwTNt3YJV4ert< z4DK(g7{vOf!O%6R%+grl;xD7|XvZMXRHNfBQvR`jf?$zcis-bt*La0YJW;a*K8*RMrLxjJ&1P7wN%64>3?O%P0yUBAC03igW;h7CpYs z1TKp*>RYSq_W2T=!|2Iw0-N5_gBb#x)&S6Ef3zMH$x%98VyJOzS2G3msHdY|D_`z7 zulW~Ui1S}LBZa+C)vKcAHtMtOXvJXYx?R`k!g0}Cw)+lKf8s4mn_EX|`oV<%|DFm_ z#3(e)FwU;OQQVniV3DK^1=B-J_qE)A-6nkIb$8>QGUvutgZ^|9B^Q3rZyHk}!NzKn|73270+Su!@<23Ye&NhO*)V6y+Ui z0zuP!YRamgA1Dz2l=6KjYm$9#bW^pBS%K9AwyR@aQ~W%_r{}h3_@7;E)c5|Zm){#g zH{BQemiN`vKO`MXjhXCO658)lu=3CQ``bVB!`l*d!+{X(nJ@zY+cWXJ3eZD(sry?) zK@qC)lvrtOmj$eFJ_zuZ3y%sJ;JHk6m=3V^Y1Zo8ilro_NF*mK~!17>H! zaHj!#;z-dm`kRWe_H}=xvwo)=GWtmAm-nek)$ABZGm_#-hX%xGW?zeImC&jh4`M{D zjo>8KhY)8^QFcL^t2wWMp)xtXUr8GQt9E+0C-ojj2iaKJN*s9EztqG5u`3-t*=MDn zI>feirOEei2$1fhovE}!%G)SrA@-J9`SJZuVDdisa_%_|Z zEpzlu?ApSn`?84P{-Q^5=ei?EX7qzy`dYdRqdlOc4S$^4?i=)WX+QH_^VI z>|QIO>%3P6+!#-@1~CRV_%6|Iwerh7G-_L0>b;@ z#Zle=6x(dkSah%-ahQU6l}h3*wduN#DdgyRZo_uynZggX6Cmc)x=U>XG46e!djZHF%l%^6-S8>lt2lXaUN6yv2G=#RN00HRg)DEUY?#*oU;if2ZyQ|qx_nzh7ZtVU?HIOT?sn0r< zDc~^r3Ksuev99hMfi_plIX|R9V*Es@RN136NHl76XA$5^Wc49Hi?DRFbcff~Ro$IY zJ8iML&O5@$WuI%N0*B$V+&fsamdnSR%Fj;j9lX`lg@_@R38;~>l(Z`@uEVXMNe>S3 z6tN?#;mND9!ai_q#uj?MC+K|DF47#9&N?1&;4`DSUp_khn|Z$p`+*268KcrsPj}j@WOh#R_=q0Zq`sAmgs-P|Q^E#Lu0sZY@>! z(2X2diZ|zcZ^rrY1wT&w7)V~V24;%XFPhgcCDPtxFs z5h>ZVTtm&2UPl&E|{T&%!1G+6*>NG?JkR~ zI`&M{Y#i&(1v@HY9D2ne=b+~hk?}|AtoqNO+ZoKYyZ|#cyr=lwmL*W{cwho4j#zF13 zKi$JNpQ>xb6V+SMJXuN`b9NTpZSFRNJJGOb&B#n+ts&glh(Rp*yD^z;FY!4n?|^sv zGp;bcFOZc=96LLjmDQPo0$A5gdfk7Rl%6qJ zwBfr$9dU7+EGa#A4?T2eZu{+@`nWw;J^jumXh<);#2Gmk6LGvB>_$6D<#p2noH2s9 zFD(2jfzAGYG}Lx(i*6s!HI4gFlnrA5F7OoaM6(<4!3RK1n5yAVGPOB?@Exg- z3!^I1+QAQ2sR-I?vHrtY?N`eameoG=Mqv#iF z{f4WaPgv0Sl<_(y9!(kSjtM3yT5diV`8qUoTu+!|5RDHN;gI)w^s#rj{D4ZOcH5vGFhuL4IC)M5E zMUGLnA8&7`Q*T*Np${#D=ll7oi?1gj)wYdToEq zDdr`Q+->#3FWE%UuLb{?YY$rY5`JZFPxe_WJ?c2_0yumQQODt}bEjVau1xLtGUK9o zM zM=YLPUqYObC1i?9S2}VD1HsTeA(n^lO-x;&rwo=6xz>Z?JSjiRmv<^~&ijK3d&Ea1 zm5fp|(Bemq9|Z)`;8CE>;ayRPqxNo4psB=XvAZ>9(M#YV=$vDt+zw}Zy?;(Ysp(Sa z{?;%9?@GUV)uUd^zTTMv7}zZp+>E4Hi_`0C@XrLDFg4}X^y`a%hcT414M6jog*vba z8maGk*$v066MJRSFtoO#rD{tC#dh7Nl}E|c({S$j+#&OH1DWyQ&XcLu02uL!60>pD z9RoqZc#ppv5Yck9p?^^_w4Tj0@oYus3nkwMM4Hi}*7tB_)LLbrL0qu)-ZYIF1!r{Z zj(O@IO^{~LMC^Li9{y%#S&<_W)}9c%nJ&j$>d@{~EhUMOTrH9FU~i z-yxil%r0jdes#|!Z|MQ4b$lg*8c4?fPiZ;N zXm}sa4(rkQnc{&qlYXwDo~aUT6s0tyxI0bKi2Uid8(yghE|$Drq0c&xwn5eMskz4xo{{sc7v;JKItVgVLJlB!7%5Rcex zjcPBB%W&C*v*^p+dhUEKYIoE6jWKfW;+a=Bv1>Gl<8V?B>=ZpqaovQ~;P~RSbDb83 z)qux9Smpt^HA^)9=W`koJaT&)gh2zv;$JI+Q(k_Wk2c z`rqs|O>YiIMC6j|zHq619d7`&k0`?a?&v-5Dv%M98zB~!KU4e2fM;t9!x+#k_*>e-i$aM%o9tycnk05Lr-HCS<+K0o>I1w z3PQ^h2b?O--d3D!1rrGR3f4#6%f;yf7mHO*O70>t4iQ`C=Acg4{n06-6({19`zF-LM! z@j7-jsoWAlrJ`CNaSgh;+Xz4?>cJ_$B1RiA8l_e?JPg-nv_xGU4nJ;2ci<1NIcH0@ zKjLoZ^PDmNkt-r)q<%@tSYL{og$`$Lv%zgRB=5`L1&M6|*r_=I`eO74FTG}`6v~lV^ zIKci=%be20NNMH{ENzmr!+~<2AJIW+W5-RMRs1g8epmE-wH;{)4Jx#hh}_ZRM#38I zxWdee&`P(k&P<+TzVpv0cxePNf81=z6Ava|*y;Y8IP28m0AQYNYX)cT3+!)mTptpK zOEQh*T>a<=FME3CLr*yJ8dV-zKQtx7Kh}xk9@~IQw*sJk-i7tMiCwo-cl0`i!;$N6 z+i1Cj0L&@(J|FlORhPSW5xSgz88+aJT%s~e8(-^uY5umq#~lt@l*}J|ZTp>_Uk@+s z({vhf?Wz)0Ba1+Y(}nb1HpI>2U(SqH0`gJ%J^2soOzkG-CI5P_ozEU+=iO?d_{D7Z z7aS!uwEY_}T02eG`7(gJmZIo*@szkJ1HWr#q5rb%leUm-+LU3r?n^U|xjM%P*-s@c zy$I8O9}4pHr|tAY<+KVzuBDh@pghp3IMhf!u$tDFCR|C1|3Gm?^x$u4icq9Dxaqza z4*+=||Lg)hUdAPG+NTz5(eL(Q?TVB!5TW}xhQeFh2~88M9{1r(NiHR($Ij795xrro z6vSqRkyEgxaqMJe$moj<|EaO@=en_JHx-+KOlkyRX5YJUfA)vp$NU*A0b?t4NM_ z2r_nX%=wp)>5sWFR~o|( zlH<>Unm*Tq4i!}d%xQe7ZLXP^0&qtOJ-LH##}_9(0=Y)6M{E%i5-fw`ecqkhIvfkn zG)s!V^6q~gOAo)4ZftTH;5vr5o>vBGtV#julW%`7N7RkmBTnj-*6%gHC|%hpE8Y(G zU`t=!J}36UxxY(2la$(Dg-&R#{=cgGU2C_V{$psY==VKhr0#Y=Hn}yYW^=Y$x-Sk# z#wjHnrXw-GV3B$6$2y+%|A*wqCl`hBu+zh|zeL|g9SswLd<_pwf?NK#Hw>cP)Y8KY z$?3Dhh)ZH*sV&&87jd@1MWRqldoVjoKxuV%D%R0QT-RcPCr&XjB$6bs?PN+9Y6?)K z4m3!-O?l-MRwNkYHS$bbF9y{-F zyT4CH)IRa9=!;d-YsIPLI{dF4r}1C|?%xa69qoI>)ITDx2%#?djVv!IPGfbfiq`nY zeoZ_sUt=p1dscpGmWko4gQh9SHkaF{cw??D?3=)RQnVywQ-+Un)J z>f_NV9oX(hXN2Qt7;qBiGZkjB0%yFyd;TS~juAZz|GYPA=G9M_88KJBSTh~hLw${s zD$4uhk^M1Oo=Y!Wiy|XHoP&zf-rAZ`VgH-4n(g-uPEB50IE@)__;3Y?9YDeJh;vP= z3-IT?KbmQ#8VTZd_=+n$?5WH>ZA^n>oKaqPM%~xlO@Sj>T;KiP@BVP|6s~-VSJSY3 zQYq#9ymHn%%U@!1p`^oMo29PhOIjoqzf3^^jyva@_{9Z9Juv!jD9+*HL}3nM?G7e&`n zq6JN!x{|m0Js2Na?n+*%X9T1hn}P>%_7R1N1MWj#GDtR+2|Qr^nzwZ#X>AU&b^9b& zdC>V?;L2aE1&oDvn7rz7Z%GPqqqG9U^`a-XuvKus_h;LOCfrH&7>~=H{t5sF^`)fO z8nk27Jah~Zoa~{M^M>7}?s`O=Tbi3u?hF9jB(J>BD{jOiS_5!@^nJh=lU_<@{t4jv zXSi82l*VMede_B5si4tv5*O0#JFpzm(_%iaW z4gFUx^HwtokG~2)>3^O+p$!}%(4&kr6G?Lnjm^9I+s>&>e+#j*b)jWogmMu`$JG5w z_tA!|9?B!v-D5yIWZ#Ug4SI_ra!av`nBDg2`$W3rN8kNEa{%(V;MK+NyTR?FvO{?y zf##{KD047kI1M`n%=35YwdipsdwYy&4--?0!c7$nvA~|t=Iq*&+=6{C|MJ)R!`Dqh z&y1x6rn#Ktvp)Fe{ygsk&~wS3{bygmQ*j_qLa`E5o@^(c&d~{Fm&cRtRZgE##x^9M zF|>Is!x^#Clx0u(U2$5Q3p}Ko!c9o`_TB!Sv!Fnj;|`VgrC(~+dM)8p-$fQ_x#`Jh zhuZN%z8=aHL@E_%Dq~P$t_Eq2jGbzx_LC(uDO>2dl$0L7?=2pyi7}bzZr8nMI2&5s z3;&u+gMALlPKJtAd{U>1hMMB5DE1;* zO5p+kcTwH+5YtKy97crZt{j6(A_7F5Jb`^CGJ+#AFutp)Es+dIe=8vE#$`3~;kT0uO46 zp!cH%(|00TVe6dwq3x?^XPjERCJ$ca{k1s|y-7L}gMTXU!}U`C9?E9pVPJM_+X z!_I(sJdVki(hYW(-t*q?!s8hnw>kG*gK}sMw#dw(2keg5XY2zsa}nVVDs9lrPop9k z2i@<&?Ix79#`F+}W1G)0Fjj5tWD00}W4_%Fo=7+ob*pN%ivsmmgXre?9H@drfVB&$hpH1iOCn`slfRUe8x7@o=Lk z(6mub{YTo|KUY=h51cuJzsm+1H7AT6pM-Z5rT@7H5;OR}v4T|$Fa0it{I2(Y*Jr)k z6rtf?GSMFD11QDHo5F$^2w_T(Pmf(pu@WNuJ#FBpVD#E2l0k-m$5}%YSJ7H6qJFnn zNVE`a(_l&yGt)7m75MW!wG-O<@_#)E>M^#B*ys;pJoH#pAtIo({r3X9QK+<1X%hs) zzB$Im3LhSR?ETBO$mnw=1mb;`x3zmPaACW4At!_i?8{6k<}34Kj7H9(3Y%hlk>iuS3?$)7+^o z#E0qwAY#v9rjXYzQAw%7wASotwG#BH=xw=m{EdPR$SB7iikr13DF%CAv+Q~jD*uS< z^Vb^^@8u2@p}sP$&d#_`?8Y7>)3OM=GhExw*AvO z3{y7@&inJ=2E27^cZa_?m0PdhQi=m}Ma;4dq@w|bFLAqSZ#5{zo(=UksMOz308h0% z{?K!+f4Ir z0@WShYi1nMj6LajQ{&cOSu6bw@tQ&m)I2A~Mqkfoe>GkBDN)30pr8t&DX7A&>XL9}6!2Y2YDsOTYftCSn%-etxD|j=EFt`jR&( zRW)mBwym!Kkr4)@QOqb?h`a2euCWL(+{%2w&rL2J7!Ans`dBM=_y79@7{q`Hl)rgW z>{a(ft@nQQvtRx!+w2<4YXGSO2pvi!;7l?VkcZY`HzT7D)t(JJdpN!P^s~g`Xt+$a z(hZz;zuzmCEkJ&Qr?HasqSeISZ4kOA?_^i0Bs-Ioi~RtHUD^Kzw=3f0 zEy5vV+zN`U)$cVo0^9{sVsSfMJAGRQkFUKRFu1=n3Qti*8TsX5S|_p+i8N!tzc-N1MtT5Nf`|3CS9J zO;aqB&jVt>+%>pggY1UV1}Bf=m`yzuE4-O4mFKUJ&ln`&amB}0neU+#r@X$;hd(QF z2uLrb$CB9GZMfYbChrYEt~<^aJ^5)u3;@!;yCmCU9ScaOvGuU~4C0l+qQIuV3ghIyq{<#MMg~ zTMkLoU?62%VG%qGsXyp&gp@?ygA=(5beJ6OZ+{miw#a#t*fJrX^^b&7PFJ5r$i#NOciqOn8+m(T`f>+g^|oTa zn!Ba8<89allvx}vU!~Sha4BNv>pR~ld7|U2WhU{I!5=fKQMwi4?%)_W&hbO?%sIQ! z=2@+YbVt<4Q_L(74=I7xM9lEkGswNDPH%^rxEftEqykc^$nR^)v? z`rag1ZhG%Hx3?KPr#$bl18A9ZbJpbVW(@Y{ILx5L+y91C%yQ%vm8(cwUADK=RjTjC z{VLSg`r~c@L@p2DQgMtP1+>*aQ(PZd7~aH<=X}@Y?D51_Q)=(CefmEQyn2aKnFNpq zp>^zR_`P5AhVPH&xtN-M?G3<}Rpc z&JQI9Uekz?leRO8tfeW8%8MUrpqo-de1O=aEz91Dbkp?3+C*DkUuY`a0y^Vp`z61(od3i$T4(}?(f>lp^i4hFujGPNdsxr2V zibw=9I0EAO%ZF-QEuh#va45x^W0cQ_+N({)@)_2|0TX{kaCWwJv;*Y4uQ^?i^rFZ8 ztb1fPl*$KhUsBi%7&^7R45!gZ{PpKRy2I{_tuK#M2mq!q`MIJRDMB?U@AFJX8|qjY zTr&EVVFoZ_#g}RuShBl19#46?Nx-gBKQp3XFf=dwO4X=wKZAfcr2-S@{ea7$a=wu1 z!#11bv)^T^fFCil=)TsanN;NoRf8fLf{q?@KQ$8+zN!twmRf#qbDxbSi6&_m-GfoJGG>wrK*XL%n@-LSM-x zEsA@0Xt}MilA7WUgrZwVI##oXB$Ioi?N!RHGOpk~x|1-H0mw4w=P^oEi75Pe0v z+Nh(4v-->p|aPx{gic~ zO+xFsT_bwvX--ZZ`gP%l0T;mv2md)y`Q3hRMNZh8^`JLjucJ)f}Iw z&%mMS@Ttv@ya13tIYulgKQ1Nx!o;y?S^m(sRlN&Hg_eR7+CT_Z6fo=N&ciy+)kp7j-$Xei|o41sWZ z4Z1jcH4_qL(8>C#fy@9hNqT{H4)W@rX?pAo&TG+~1Gs@WG`>wIqnl#W#&azazbllu zIy*5YxDF-4e|5$4jN@j&Up;+n10;0&xrm$}(CBQsl4T^vAZVy#+DcND|H4X+DWBuh zeaYg>(w5zFh~et2^&!NEx@*E!0kEl?36F`7QWm6opHO&e&MGx)c!hTMWozTkgdMxb z9y`9TW#5_eD6qAmK$)Nh!P%?T@F+{%i3pBiE3UXZ2E>VFD)eEHdt&!(YPK=`zc^61 ziLiy7&#`V=yK$w)bcnmh`-foHAGGs650?N3R!91WW^3wfdh}zxWc)zeSgb=8!GI;& z_LYz(p`@QXD1zW3$k=%$Fw&r-slcZi8TLa>hIsj=lSWB*Efb-sm;^>;sQWZ0l1M$VdJoE1}ss8a@V(gmxFY-50IleS7USCzJj@C3!i9*J)eSISvM@?mDC%xt*;%CzchUVYVoz znEI5PkumdhI9vWqgdbMwnSUusLG^aZ30O3#m|C@?@AKi8-y85fuIzuGq@MGrq#NU> z>EnI%*5MEfdTG7?K!xqSBRg`I;du3yc14V1RK^;vUpw2`hsITq4gTI_h8T2KDdDRY zfoTTDS~Cf`d;Q>Sm}{yP`kzn!OwdB{MhU%Ot>JmL!$>OSaJ5YF5MThXF1_TBc^uFb zs!&JaXUwe`A8AbJESy3Y`Ikd%I})Wl!ei||ep;vRHjJcEdORg1VoHqD)1Yy}R8uKBh9er->F*4UX*ZMC zdUyX4ZI3HFEsg~s(_>2@gl|^|WJL)@F=lRbLBWac&xmM~DRFA?(Q0d$TyT1*Q<&X+ z;rvddQ!H659AY!^t=$r#0P<^@yUnLoFOqEh!vY4$rsWFk^WH{TdPuJ2WB4S+pNstj zvYl(5^Ux{hGi0SUz(7-}e?Z;bBON<#b6%2eI~DttoS^8Da=O8U z#(##xDvbC@nMN4i&JiUI1LGe$aR77EbZ)y`+S9Iv_Kb zkmmkw{e`<^dmoI>OU3(sVzinu;>BquuVT4tB(bHSNKxV}iaw*iISGD91+FRhK$m^QZqTI$8`~e;{VpC{i7|E=lzCt4 zVtTMVnw!P>^g^Hgo1P}scbX#!88IHJRl1Xxm7ccJ0A!!}aF~&Kc-%PYK@ThCGHSnS z?Q+&uz>K6Zj`s&2{xB4r+bbSom(JCPYiRk+&+g2yBG^+>nOWx-x&GRkO&lg}%@mBk z8@+bCA#IHXzCtkyY;9$a){Z~%`=j5XCv&bno-@)iUb=&E9IM9(oArT~&%?R?6c}z5 z$Ia$v@Ot{6oymKNNK$7>DS9=VT(2F28%nIHN6%$Ke4Fuct^VonWZ~-U)_2~xppUY> zZrtHH#ftrtxDbDm1SKYNL8t!~9_7;VxWGTKR>7hGvqpf~7w5gUxGan-6>{QlVqNOlNGd9$|*7H%LipLM& z5iaR)CT)UE3N1Vr4`9qO>|qwMAhBh%vWMUx5AC zAEw(G+NuPRHcB>^?l1)N%(Ph;vDn!~3YwGQNt3YZa#CjJJlXI4*%0ux=uY*c`*;&3 z;E0-aRk0LTD^}0uON~7rYETOKp|+uYsWbZGrbKbTHlWla))@Q>ZN^$WHI!i`pV6%i zO=m&ke5?VVseTUa6KByich$b`JUqtBE0!4ZE|2TqF^62~yrF=(gy;^_ukQoPzQuQE zn_0N8o9=KARU(qRa_$xrv?uq6-{+M+`)DPvY9foe@ZjPkGz@^E|1i|G&y*ZhspzR2 za2pKuJCdfhI+tJ08Eu_Mdm(|02Qpdz1xuqCFt||{rwY9Hhd=Vq{m=b*pcWaV2mIc^ zwf;QUzEBa*uX&5vS4N;U&uDgdXEXxSybk!g0&Q3*Qe_^TZOt{%piqNj#AobTHwX8S zXyPBRUMUUBrlYauJljmiNrh5Q!}A2HjM@<2*oF5Jp=8yOOCIw9H| zG|`4v{(g76-&SOcL8-s7%lSPInu+$(U7h3K4A6usb#Ha?w zZKph-jkHORS52pLU1O*A6>hO@&cxKAQ+N{5UU z$zPzlJ2e?Ou8%_h;@P3$TWfnfa~@^Sk62q>F>%hD6Ply{a%BZit?#;jx&7w5lCV~9 zjK(8QjE=mDsxQ_lT#uSKh&9KH%^qtW@{| z8J&L1^iZ5{hQLBYv6R0MnF3x0{q1`xv9Mzvjv;QC~m$!TZp<^t$b1BIFo0<{FHDBi5ldhO!H~g0MMe za=uW5)7z^)cD6Ag{4O$|5j#ZU2Q^hc?xLZU`&1l{N;fyi+TBzcJRy0da=^q6`SzVU z&u6{+y#cI?&B&wW3he@bcX{&Y4%5AyRK%}h`w=VB&sK9TC7oe6oMIjZt-A9o;=STL zOj03MPZ?dZS>N%~>HWHs!qQ z*h{zDY7kO?>o z(B*SCBhGPQ3iCH3$ip~L^qI}gh?sONysfw0BELzGmMLT-As*Y)G&p%WyVIOei(L4r z=Dlbt{F2Gy8t^nj_lrgjmr#vJQo|}FZ5F!OC(gMZQ_Y8yE@p}*l>kjW3y!@eH3It@ z<6Z9uMKS{AcPXWNy1nJgb?L1P$GybQA9ZKZ!Qo2oe6`<7$wRK^Uu&iAE!P}kyjrC9 zOU`}w#sOQpK5fpKv-T<1z8Qz^*b*%JY!vX`uf97|MZJ$Wry(4-0E8AXE!SS;71tOM zjuIP*9JBa~IO(7n05(>g+a+J3ZC}hL*WrwFQE-;+rh7o_8 zU}_?#GZ0Qc`9>dTZpm4T9(suLQ@ZW#k+@}|^Kte536E133eA}6g}d^R>eii!OD@n% z8OWy_k{R0`O&P7OvE&RY$HfiXopoz|1XB`cfh*htCCJO{JEKG;%88w2SOSF*$4qgE z;@u}xDN)|$Awnc#9M+~C3zOe(iw%%;#)N={(5%)EEX{reN z!-v{c!c%eEj^S#C>sg|9^uY&=_DqO>ygSh{)zw3RMdPQEum#uK z2X|BVJFiSO4W+!|gmAYbAE8SHe<2=g^4|pVHmCrbpx8|B^(nix*uobn^1MWEp?@i0TJ?1M}QXQ%h(7-Cw zz$EvThcYB-0tJatVCIkLl>21Cj~Ga$9K z7r&-t2Ap5`Oxlhg6Mx6XfKZkX40fMA{)S2S)ijRYVqnJJR!Bx2c><~Y$6RU#^Md%C zDH=LHh*fn!l5xzHMxpZ@67Z0bfg7hhmTvUWLUDhNwePNzwz7#UzaeJ(fYo9DB|(8^ zQm?iPvhWQFx}7T(M8O*zcBJg^B(Sr@(PRC)k)TGav*&4Ln&8nF} zVKSfT_NN7tw_#ZnH9|SNw&xn%xt|)~+@i$TUL)eh3QbNGZJl#h5i4ZlyXe7#;&+7v z0F=YHT#B#Ov3}v=dKHLsRB1K4BIP5k;fS=S4m~w228@A4zy6O)tJNZN+Mf0{zgyOE zm%HnAv>5+NTNgOSpw_+SQzm;N(E=4`^mn>76~KJ0q;D)h{~KEZ58bpbA+AKBL}dV< zX3#K&xw6v-Nl@ zX#li=+~IHhsSt-KeaxE#(26)gWpo1}PDdXBMPQG-SL)8)K6?+SF~8S7y2{}>e(y|7 zl(C0;t zTf`L1KX$t@aXK8-SEUfg_lHQ*jDtfRO74>iv$|OlGrh%&fs7459tB}w#C-zZ89_*K z@D+Qpw{|MMJ88>bj62~5a@@a|^_O3ctInu@Mjfnmt!F`HL~5U8tTX3$2riGGNXucU zyaH)Qs}Hq}ERGd)2;Rfppz55>Twl##m?beMo+8Ir4eYZKepCI)aw@4S9>`t88N*Jm zZ>d0`8thP$d`1);ft9#CSQ)JYLfmBTMvq3gqeI^ZN>83r9E#7m=s-1GUn!TCYs|By^_tQ^fZ@-2tfo|L?cK6c}5lE!~u-#BC>YOKOZ z;(U|SDaFLiX<3d*1|asE4Xq&_cD3VLY_3A;l`NikaXgkn`VJni49MTcN4bJIff)ieO_ zCRJkuW7PYo^}<<0(&cHevd3I3{wU!ts9Z4PA@9ch*{^SOarg`d&H21Dhb*{WamV{z zZVuk`pw<)IIE0&@Aqgbih>3af7;$Pn!-dF{Zx+ReTeX#uHVTY!nc8>LY9#mBJ62HB zeDfRQlG3@>mF?>4(nZ4Flk>OxavSX|b0~v5{d>8p~uiE-+jUR1EzN20vIcDn1 zwYJW8qNGmq!}?;MlY@^C!D{=D$MSA+FlZ zCm?C4pq5nvq$u{Kp8BL;#5aWYxc)-vNMs2WMQa(BA3U*}LN?fFC^h>aT&cc_6a;pI zN=00*Qka<>B7ieZz=fkviruC>XX{!^KKyiUbPE79@v%b$da9wm_q!rvybPaWl^IQt zv1ro!W;mFai#Xy6v|*z3xo*0N2ed#f0Zsqx9HR}i5e^p1uE4Wn?Xk-|vsYpu-31U*&mRp)?Gc7-(y;XeSWBcNaWA4$$BED`+4&CovdQ(yQ*-sarme4E{Zg z#hA}c1)!7-KjX$0dpw|vFCFjr{WJk2v6FU8VsL*wMOiTNd zborQ=eS8k&OrJ`Pe+RHQ5cimL(x;DE+?7+2uK1T@X%JbBmAf(E-_?9<_o1oqV0x&l zKTV-D5uLD<5MH4QnNPb`Ucza7Rvc4_kJVsdeb&36bdz2yIpX?g?!>&4gK)G|VlN_< zk<@f8JCWHq>FGT!b3WJBPRcp7nSi>-UNpm*lgdZ~8{kxO?)vr0vB#I2SYGK0Sn1E6 zjth)Lo@iyaHN}TQUNnNgm-YO5$0Jt?tL2muBe6AGYOa*|rsf;_ln1lj|;&$sMNPk^0)GSjyBe(}I2*NE0!rrS5jw z9>E;Ek4paU8tX3wwm~qqc62E!X%aH{h~o9}?=vX4G5mVFpEQ* z(M!KN3Ztld)F}n_XrUYt^9?=v1aP?C0le})|Lo8G&xX*t#2uueL)BZwy=~W@R!crF zeqpHS(xmh8T5asEznzEgm799kRv_B%2HTgpe|&`xlu`ujvV+O{eHHSWlWfy{-3WKH z95Y~-TPyfG;60U`2+6~;k-_~#ozs#893!LH?FH+bV1f^@6vz&S&d@ihI-eZNN>u! z(RQs3(7d0(hL~~#Aw`ufzk-H)idu`HaI%S=Js^dhei?L8;$2QCr%~F?m!lBRDF<}r z5bQoSj-^Qgq)An2TI#;Zc1p4{ZwaRBbg;h+*mi7B$_xk` zj*&DJv!>H4`W&&0+|RUMP5+dhR11^POn zm=inbG@c=AD^B+8Ek&(n{(!h*!a%;M$(gE69HQ51%7EU)m1d$JfDwPLk#>X7mD@Gb zSS`j+iyo&}&`}zM$T403y2#CT68q_H18}UG;*I5I@_9Gn*{&kI_5DFH4BSh+5;_?? z&S^#5My7t1@?BXW_{znQ_xU`QkPW`pg-e1u@zvTqjn^@%xz;%pJ@gRM6Nf)FrB|Ek zo%D;`h)4!ccZpC4&0TzY`dhOVpI=AHouqnQo_9s9!Y-7(H}5G|;GuL@tYD83D5I`B zkLHR)y;)_>|F@oNNVJv+f49^%#Ef%nSIWXD0#{}{kuzMj?DFerCU#OVB}hu!22ITv z1T8Ybvh(Qewba${GB8i`D%uX-Zk)Hpt&b9O1H^Xae-RXH978)z4?V%3MVlruYi(dR zR{%$ghip0}W9c={Npp{@BprLvgt;5q!O^$(=%txO1&Kp3qq(X1;h@fcRM_7&(?jZB z`}-S<^QofKh2!2UeyRn3_N(s=x#W^RLK={}WRjG%lG!oVMzuOyx2K#f6jbW9Vx}|T zd*37tKBNoTG$noIT_4kI*3s8=mTY2=ni>2R%NH_^{m?_5<7q+TSKFPtnhqV?B^a$T zg}%P0Iq1K)&(y&FNBMFN=zN~*_I~e=Q3v!uTLz)JeudCLusuUSvyKqECcWutanHaZ z=oj?|H=nMZuRzhP!Fg93UJeB?Lksw+v7DtSo53*vE!z%9>v316jYZXKK~{Kka)@|) z)(qHohqGzOb?4*``JDdS?i~kE!Q`?#9UU@?703N(rx;jANdcd<(wM4F^7VK|DGH?w zbp~=Ev_g;*mq2cP8$6mSOhB`63e&R)Knqjdlv;h84GI(4ZlXRH%1(d?)+q zUcsTbd#XYjf=vk_wkv~*7$qD8;&f&htCU;{LR?CTx3M$fH6jALJ#T#$JMx~?MU$kk z3&9M`Sre4d9uF<`0{EKVr?2sx>iAiUt5#DRCB?f(5;6fyUh$31PtprOvMgH>Hl~}> zBmrW!rISL_*M(+y-tX;S{o37!9nlbaS|W&iu@)htOtKH+R60ua;HGx0wimLSf=|UA z-}@u~e*gRW01FJJ=UNy4!Jmhe9w2v2?DArcB2IZ8G)UUz=x~bZRrE~0vJrECf8Fb9 zYEeDJm*Si)ij#dK*4f;z8A?&mh#0X>MoBNFK;A`*)`d&yNi#K}56FiZvD*hz6llh! zj&4T8>PyYDw>f`RvM_QEWW9I4&-;AVyWR(iMV!W9R)H&!`_P#)jqbV_W2rV zZy$JtvX%TM_`&Tma;H*hGqz%sJ6m#EzZSQE&k}C=FsmGMKs%a8n)zgHR^&pR+L*Zm z)9ulCe^ImwH;gsqzb1|MaHQ2+?7h~y;r2YCJIj}IQ&BogLX$IjXWNqA{FOB|s8NZ{ z*%D}_t7J8kwL=-d3)*4K`#A0~hN%3IBl7?~o*hpbM`NIkQ?;qzalE2LyK8smES_b& z)1EhNkz8kb?|<~4_0RqB%mG?|_zz!vEtgRO_yJon^Y5?txT3}``i}dF@r2N4VQ@6h z%Njdgce;YqvQ0kwea09|i|nkt>iQ)U*E-C#cEmvu^H`iNw{cY0Y*!hS$(fK`V^G-P z4IZOo9)D3Ha{#_6r9jiMY`cQ^9&ne^x&M-}uN#lKx~U|3!>(-xKsn2MuO8fx5g(iy zx?S(lvf)u!b^OHp{=WCS?4PwZ&NAEJ*1sUQkgtL7cX!H7|7#(O;T+%$cO=t{nn5N)t zZP!As$kyR8mto;EPC{}VwPIqNJmyzRX_$nOtRTeL-v8ZpI=FChbz%dlYV@>1DZ%gQ zVHtbukPSB67!0T%|J)ybZxXs` z{oxDOO?Ue5PUbHGm}AaKo4=HdQ%Ex(41byGmVxVvB@Y<;bgy7B#6?J-#JB6$1vzC%6XFa9QT+w=X#W!vqq7+-=Ak$9?7{w z4UBo<8}bdVMk;`E<8PjSO3GX%WHI!ptVI{>y=YaXV)8zd>r8?Vql z%HNzP75L(HaQDz2?vEJL-HaF1T0?M6D_q$dr}y|QzBcbn8&BgTWe+U>R&vMb`rYIa{+JB3YUy3PM*r(Dy7H@37!shI@d}96?T(HgE*aA(R)(@$NSco z)oc;Mddsv1$5xQzPBD_JC;YWkHv!Tqs8e65m`6_$bNB)e5T82O;MJ#w52@blQ3!IUs$z1cl$bI%!1H% zBcDi+Nt?jX-=?Fd z?jC8jI7X;fUtXIAQ^rliqmIjV9EDnyv{EwFq_MgqC3+R50H;7$zk%GVv)%o$-4D-h z#8+~P+W$W1thOmAAX6}yI1F;9o;sa2#TxXe!55oBjsn+8_^H4aH2aH7l&To}vw8wy z(#s>75AosBX_N{>7^hIE7+plN${dY(U^BFd+u|th4G!3LCU7}crOcz$JCqupb=i|n zS3+t^#A?aHXikk%N>Vt+%y1R5`t{k@%xsOB2hH`Rt^zoB4<*a}xKbzN5d+SPp6*`j zX->JiIgRF17|XZ{j3tka(wf&Jo})umubG{k&)=FRrN4RH3FqWhd=s7Ul8P!XU=onAj0hs!mtxaQ4QzmNi6;740nFQ~~^0m_8 zMN^Vfa)#3c=)F*IYm0$AJ|e*#)yL`iS|x&#-ZG%ohC332HYR>mbdEdtF@A|_QE8~R zRf<=M_s)kh*xfojs5xLrTIW?l$z#R}*)yTwy*qo8rU(ZPLzWF!D!$?=ySc?fEBn^9 zs_3hb6N`YwdNP?pe8g6`^ZIJ9{jQ#7oxU=n{z>y$@@|i_dlKfI-_=9SgEKz@8krEe z-Xt77QLQ`gTCX~v{tPh`E`8N3pk-cI4dbyDMJ(KeIGtBajG~%DKr2&0tsF7RxSOe^ z;*PN!pq+aU8EE{*`?lYCce{&n`&ti4)#3dv3>*?uef!Uy2Mda6wLsl{A3MhD0xjB2 z>S8!XlNS+3en(QZJ5iJ3!?`Sg@lKlX&nvI>_h-M~OP*Zs{(6%g+A2Gra&x4i;oSNg zQN!TBLV=@9{MuUAziReJ`C*I2sQw?)yg&lDA9|(HRZWkj_XFwZtSKn%TJcRNMi7A8 zo!(=#XkJdlkpVHG!#u5JH^{?7Wa@Vhjf4edLB?%xi~+EpF37H~uAv&Ff;P6t`yLHq zAQ|qum~L0a7`~vwyON(F*kj>FU(FOAalaasfP19hBrXls#Fm>c(u*mst~m}O$(A~M!l~XfvNbP^JPHi?antMb|J?c15jh)zBEe}+$7hcb=`R) z>2?z!1$vl|KO)u)J=j$kAChMuN=C`XiaMg=9p4y>O(8V4|G(_ej*IERlb<&U_Adk9 zIm%_?3uTSgGx|DcDRP)~+V2@H8Be?Vm&U5PhgQ=x@HRYQSd4D(^9r%0%6u!25vwq_ zg7;@???KHp&TrrGp*=R7G4{t(vvs|SQx&^+F`bvLY3>l1)3&p=KP6P z0yi8r$`lBmr@`SG49GbaiP7vZn&J<~5*`!3yUFcFH@njGc#L=#1OU3*;eh{#QKU_c z57Xl|#UR#5d{)90w-{w96^S^VCbR8U$`J3ADwTN(NXmA;J{=Kbp+K&2cDJ9!Syvgx z*;Zdo@ZNU7lRd{BEjiTWs;221=Jz*uoNsF5Lc6&s0|IX~)t7@hZAW0nmdse_Z6`-x zwCTaVZa>tgq>MOKm#J;x{_C!+Bi7oYcx05^4Z@&RnQlf46b_9l%^Y$JQts#OEykRe z4mH_*NxE@^pqF@8#~lHhVa7=h{D>!YX}e~|(NNtsl1>@P(z(7YzCYmG&FNe77LAis z2|k&wa0;j$k4$);5?TZ6te+*vahAFxDIN6HSFzrmoM`sm-%EO0+dgNW_j!dLTC~33 zAx^jB?kAleuBblJWki3E^KPS~KWgkAmCfj5aY99xK)bc<{n>gsWHQIBAWl~aQ^k4<|&bJHtsHs?&C9iHjr+ue}H>vSs4N>0lb*CZqlf&N`CEUO( z5Qy`75Yrte7DPM5t_jB&qG=WhNpnb|$=a#=By5M%2J`CBz)l=TnQs5DjFaB43};H` zS$UUweKf8xjmBmz^gN;nNfZ=6V}ECkbIju1DY!Qnrqg?-(zPeG7#z7AXBB{q6*Ha<)JL11K&=pAv2t#R#Jsj)ZnW$qlbP6XOkl!8)&Mw5rM9o(L zr_y%EiOo4PJK=TNg}fQ453#w(z7$cZ4`J9?MUtm+(h<91=Mz6gv|)76ONP>>VLZ2d zfT9+|#xqQ3`1QhaYWmA6N|CT9pExfjl!W2AJtClBB4PU*OYf()Q}gLTN|_TEtb zy>9TKv1(U3HKd)BxxeImcVrs91d)`+bYFMEw1J2xq%yYsNjGi-zoKtI0BV$s_%Lwr zrJ5D9$Pwk3zXPwJmi25`jKQ;p66PiQJNuQoPkp7&y4v3U9WCrd`$dk&s_Yx;sizo+ zl#F-l?r?^p&7$qU!r?A4<<*#Fd!pZ-E$ds4XzQzCKgI0~YOnj}!nt-bvr3 z-_6}xu9SB|awNuN8{Q&qu!s27k5CZA6c)h!Hjf|La=Q|YFjHc`mw(1L=%E!a1Y2d{D%0|qUx($r;`=ouPj7^9^GJaLP&XzmtE^lv@cu!ID zsf+ua=0gcCdnY`hs~d9uhM827=j5FXSqfT@DXli=HJP)1L6hqtjGy=rG=3GMMWfE3 z0FSxP#2&XY7nIbdPa=`138Cz`hl{j_r8ZIvK)$ThD~)dL@T4MzQZxZ zZia+|{F}w2Jt1d zd{u@S5bHzpm>B6FCO*_Dj)?N`#|nk`>=egPGn}?g3q_w#0`yrn>W2DI#jRpu)Ck@kmK!!AOpL7R=$~MKYwt0D$GsdQ9{5f$eycqB}beB$?&aU1|8ftK^ z*&-Z{zblPQJ`>&iioKwfdlIcQIXum0VsOSJ?u$u?X*Jo@<1j)t)3&eQ22Efc4vX17 zjCa)BHvF3oK>W8^(2Rt(KEkbexZtXNp6;@QP+I@-MAFS=@Uqv;S(|EUa_UA=rZgLe zN>}_cFzIW%=Vt+3;bl5`i2{cn6(&o-96hAG@+)MGG98Ap-7rJLu9xaP+Df_(hwN=K z@YQ$yBOlPd_z(XPx~t8vY#Do^*-t&wa*_{uy}w}7Ce_w^e-`dnniH$Z?O!go9}(P| zQMDGMzQgLeT!W^KPqY!Ey2+(=O6F`cHzXA=@*cZcnY=Wh85(<^S1mhAAQ_)S(lYAp zkemL&uXVtXoUlmF}zC_Q_qYV4QpPszFy(#2>TN~_9JcrHmUDWkE#knGSk*}xGE(7%6O4#Y~ z;x5+Ey2HU*{Aw!2Fy<@G>W`W_c2w2E{Uv0r-|p3!21GAU})T5MghJ=}C2cFph5lj2i9fWI%fh`Id&ll9Ay% zwwe>?{0vtC{3;YsPl%w07Cp2^go$&sr9r4bo^ckfhH%j~+gJAiW?Gc*&uU4c+VHhr z`S9a=jfy&+^<0I2=&@uwy6FzzJ)SfUIirff68VRi%av@f)~M!uQ^1Xi`R4AXbN#|M zi(z}W&T(~Bnt%A`TnM&)qU@xGt%e;uB{@pKQ(_OHw0oN39eW9LCkY4ZjkTJoanE)8 zM}Z}Jf2BDd3d<7ZL7#Z0C~m~zwQX^EyI;ai&v#SwDMHE66ox!#RQyn#&XHbktMti4 zpysy7?~=KZhUN2Uhk_O!^!J9Q3EkaVJ|}(F{jF$G=C^1p3GqX~Y5wAXyFG2IsyP}v z=Zk0hb!3dcIWi~K_#jX~RggyC@%OxM&-RSMw<3`~Wn}bPal(qUCRMf@gi1osP)S#)`EClVOl!AywYg*slW8VnsF#6F(1QPB zBxwC?CSR_&ZYI8JGq|gzY3QEq&g<4Qaa>a=@40r%XZh0SbrxEZsw!cZ&A`3 z4+YXORFHFfyXv+o@!7Kgd_6L4x<^<;vpric zq18ludWUoWmxCt1fxEG3`KpuTD9g@o_fZqbs}!2-dI3l}V>ld2nuB;T^)3l15Q0bW}tWqvr< zdTs0Zzuw=Sk+FEm^qM)@YXtL^>N79@tLF zhGSCHDd4fX9RlPy$BT)9@Mh+rGUfld69N+rEjktKia5Q;0EVf+#A7*ym_VvejNPbP z0yan9KRdkh?8Y+j9%mRD-YqvxzWBN?b?>#*Xt?2IaA}R+s~`PG|B?5$m2>F@TJ*qF3m$51MX1!(Y^KLn-!VUW zsiSd%j*?U~N@`R6Q%#RP5i5|JINx!s5fF2HtR^u7vwaiGp84#{w53p80z*NFA@yIo z`G}(q7fpV+2L4_gt8V^vUNWLa@Vo$bWZ5@c^O-b9QW=9u9bj;T5UVq~WlvxSdr{z| zsZwdhslFU^JW{ky6-Eb5_=p$kph48qCL@Bw>Jb%R-5eM(wBWHolLO}Vqnp+xcvA6J zZ2vSz%7o0DaXg5BxNJ4~Tl8f#?gk5vdD=L|ciam&93y2$34*7U0UGUjIW(APr58?a zR$4$f6PY+Dm3+%unp}*3-ig0W;{0V?25)WdR($cwGUy}6V`MNI_SyU+Q7_?doPviYb*ie2@3Dy4Zu9Sf#y-2&O7*qt_Zu?7HJQ}d z)Sp}3l^;r%)Mz%)R+2sAW;%VtzTf-ZpXHy$Fs>Oe6;9m8a>(h_gXqC?Ezc!eZH+`5 z8Z9~W+Z}e>YxaB}bEL;CdSaVK9hEmEVRIW)gfQ1XJI)1*w)<%Wwyiwkn+#yw7)~2z&G!}e5r-T9-YB()7!#6;S z_~JkNx-G4Xc8Y|KCk6!y`Yc0%n{Y{cvVR)+QIEa-5~59v`u$zk4S%6Y-xN86a-7hW zAG{aM0|e0KCg%*T1Z`w%fQvzT=pnh-j#$a)S%o9;`ml_EL# z;il}Tf0c?Bwny3ntKHOFpwG1ZYJQd>*1r&^GX_Dsm*4x<_K9|NV`u}kogY~b!JWD< z_x4-?cLqY;<-;i%wx|3&>3ArWTV2?;Q96FbJeE>@P->2%D;!ngd6j8u>EM7#t2hGv z6wXfZf>HxE>zTMRLDJa6h^%$U;qY>=cOGnEkfuoIc#AmNqPUlEj%dqt8SM5OE(0{5 zxLWp6-0z*SWo_$8`rOXAI!KbwtjGW%&MgjH*t{(dCgOtchFT)=q23of0%VC(6 z3x&rUfyG~HG?&OO!)HcR5IiM%+Kg&D~6|uG|};fT-T`l22;RNIpp% zS|pkC&IYNmo>R!x}@#SJ|NeUL^elDN_?Cz?o`%mu0{Vkuri7u zIW^P9LtOM7^ob%4_2e4!m&So@`FV!kg0_Em+;n-AnMJfPDT|0azWOdVnj}>A7TsdN zv@kcV1xSZFLeKl~XT3LI2M``mF@pNiC>0n)d)wn^w(|BDhPzQ~`xtCW!!ey?`dUio zohIWQj!0+=HfTED2jw#lOd7vrni}*5x=G_7B-#M>hd=o6dlTcKZBmAq*m4f?Ewrei zwDC~ryFG`AiNmSyI+DlE$l53h$-j&S_G{~V-pT&6X3h9kx9dw^5cHP1ZB%pu;PWTl z_wSAq54_DyH$4{@k2&?ne^E|Yvi@=UDmO?00QBGQqXBJf7u~Nvz8!=V#(w9pvAcgR zrOH@+C%nLyBCJRq!Ax@l5R7(wK^C&Xai53vt%)fG!GVfyrhN@}9R z-_64vWyb?Ff)Qt!twExJp z|LjWvXTm}p@oN|7i#WZ5>s&)tjz|Otyk8zA9&mGxZ&UBn?^x>kFVWVq-x;&sxq^7m z%1Vgit|p)DGO?{NR|d3_c~d2_8M0`*TS3ChN()G#mxly>*1O%*$_Vj$zbg)2F&bY& z>st1?41|%J>y)1KG6TpNY`B@a9oR^&kTK$eW>d9LJEavGoa-=V0S?vLReXK#S3mo` z0eW2YZ?u(^2haTKZ@@BnG+?G>8m?lTk(O7Fu`OO!D_Pty;layLHS)-&9qRuZHN)%= zxOj+S9@MMSjMU=Wl_AnF8#D3n@$+yTV0dq}I7fTCjp)&8z%Gr_-1>H&U--JKqc~>u zUA?0n@3fM?SrIU;$a|qXoq#n9f|^cEeecY+K4u@ZaB{E&S}Ku zjBExWyWHRA!Ufvl%>b|@b@a8o0m~_=0LyZ3f?;{VEoiZenn12RJsQ>~y+uKxe(Z zYG<8)j8=l8t-U8mn$}Kbr-C^C?0FgYwS4U{ilbO-rGM;$x_#z|yJcfXAd_4xH)jO* z#E2ck+ut#Yxf&OM;O12hjWZk>&{sa|gU^2VD~=0Z%`bO9O}CZME-54?-W6Xi%+HM& z6%5Li)I!iEuXs=skN4RXs`M#??yt= zyq}>Y}Vs7xKKYajom4tnMUap zYKoEJ7RK7D^rT4ieZ`I=2h-#6(O$}+aBAg&RIwsBsBOH@%~9hGI#kX3-T%mEe;(kv zNiU`S(U1J|{3EaU$7nV^B-iqze}p(xC_hy*jC+AADCv_`9|LJTWVe_H^6gT~)T6Jf z3*_eI4dsUu|JIgYOFLv`yX)r}YY8p$V*B6U{2MWXV+4??^$~YOTTPPtLu_}*?Gaw% z#@jb7tXJO0O&uk6IO4q1K4VZbb0+e^Isb$)-DyHGAF8g^lu(({a93|ph+C?Y)wcE; zeXPGhFNd#<+K)E^j)bVVFM#0uY5d*ov(PwV4%=hX=1A$HXa)TdPu(< zIXW)z&q)dJXfC?r9Pl1k!5pq(gJ~89rS6F3n4g&-)Z@Qci2M`pXH{OvVl? zp9bNGd~rAvn_6$RIk@BaUTwn)XuML0#}hSt4fA2vW=S^|C)L{10|##pz|NDN_kf7A z&3cEOcFR*w(PFKEF96`Z-}TuLY?mg6-Ol5tBxp8fb)!e#m3)e69(Kd7%8%7D?tW>f z;pCQD!y^Kl?|F_1i18e9fBo1#0-pt~Czn^t-T#_Y4E(xo$)4vlIX}~OBj};0&#Gn& zG5~#V8&7)r-n!JgZ$Hj$nfu}rQr9Om1i6cra8#2neU?FkRnCd(xYm2>sK9onNjvvLT|1x zL30RT%;DbZY)639j=8-hy>iY+n=h{Ae)yxG{m;ft{O9?jfAEKi&r_&>1ifO_TIsMc z@Ix2p(atFJ-=4-Ev_%99sc5jg2vGzz5Ic<`ZQkOeG}CTA(>9HNv1mT(xyn<O-VNev?>RiMiAfi}Vy%>a^OZ;vHNQ%*NqT%ghQ}R<5ffep z>;ml@t{GZm{e-oh?D6X3i_glmLD%3amn72001rI)TVs)r5^3l+@?*;0C}@^Spg_`Yf|`qf15FJsD$N zLRzNEpZfxZ5Dz*8+@l;R9FwM;3QBXBL?TWa{W9XhNOy*n)jsNQcPeH=PcYA>mSo)G z@uZgfTGvi-$3fdjmxLOniNo7n=66%-orY;91XT=w-yP}^LV2Ih^AS6E#i-($)hHCzU}}NqSM03&I4?CGCOKP-BB^*Cs`Pi^`ggbh z4XcQzfjH?5SB$tR_O11i%$mYDef<0*D?pExg~k&(uNsH1w8&Qwdp)}uq^Onbchc}L z4}R;f#lE)9)LdWBSHy1?^_3oOo`-BK!}JRkxwx>uk#X@f7@F3gMcK~Nh;ueE;BBly zQNw>7<8YK(FUi-kW^yzR*cyk+IZ)%D3UbxAvh(fH7IB}U=rqj;UR<|%vYW}!SNts6 zesjZ5+i^z$zs$PC-gcB@zP!PY;@yf)acWFEKF3AtNA$Eq=KYMQp`i3mgD1koX@z86Bh&lk?VXR2cOUW=l*BEVT3-{x^RK?QV+@S!AH8~ zRrmf?nC%C{E%`{p8HQI!_E#qXP^t|_4fZ<@+%W7sBC6S|ygUPAbqweV;^bM-)wkpq^%Z3hIfs z7Hvvvqe<%ajm_ozcO+<>@>IVv7j!}nHWydzAf(3}rb+cZWrnSbmUM=6u@nVreW=5o z7&Nt8J9WO}fz@8@UOhQ)s#v=z^ljwOv<7Neq=_|)%-)GHO}UasA!RwQ&9xyZw_sntd?< zF?VC_5TgmHq`YFFX%hu&{u-{xC<@G)KWp`(4!_k=H!@-cZy;BB=Mx(}pF+E1JUN7j(Z?(SSkR}(>~asfV*UFx?6;<@aZ%FI996nzAR`EZZvFc|MK-i%VC*r zWJOz<8L>nfBE>;BP#hruNZir(R;F}&@6Yqm55Fc6H*Fg!dWiG1*}!CD(UY#l;8I+A zwP|>N(Rd7!pskF1*+=8sI#>6B%Y5sUj=(9gz6%dmp0%Gn^>$wI444G~v`i}jfNl^{ zf6snP!u+FrOTD9JT?ulI(SLsuu{}&epfjO;B2j;Trbg)3TiS!uA$AY{cw-g^@CJpy z)eJ&Mh`I`p*mzsp1xbi2XW|}?2Qd63+YLuf;S%1K=Z%D>jh&})vZdk;g~$B=lHT|2 z@xc(Fn#bCqydrLW)RWy54oPDW6&Fdh&D4thdH#`){G*@e z-8I@S01O8Fz>OU`26E2c_KmblXwiKwS{JSrjvh<@c_05a=pB0mNv}n6vCl3msSX5| zr8tFBZA#JmU_;V$SMSG!Zz+*`I#Aw?J5N_!!^>Y(?fCMzm(=ej-B5n@Jk!k#o~bSr ze=)H`lWT7O8}~}UcgRYW9i~}9O~I8`Q*=&d-u)rw&vsFQVFyh?dhX7DH7%pdp=3Yt4SOCSDhxi$HzQE!)=X43wzKo zB@l5n-FScOuU=u2IiG5IEUCn$ zsI4O6HV1b-9&&!CNii^q$e6#Kd_VDSp?MXOPe1#9b0vw*SA((&Eg$ z@0FB}_jm5?d-BM?otZ+9x2gDEO`I;sO)JN%=Q-(nzl$EaFQs+eFlp7Za~h{_9CSC= zhtrHWXUlGHoui;>(l)K&z2E)b?{+O&Jtg-cxs=wm=xH*74L0If!yf;&R#)k$6n#!e z&LWtrLfW&q_m`3UvTXj|%MVC8&lqkG@9!(Btw3ymA@;H2Jj^M$OBDGr^Fylq6-N4g zBIgbIua8yx$&yoq9Nx}e9>9HI@m7ChBNub?!^GWF1^RA1#B?zxdpDCS!cUj*MP2sg zvgjdpL5@^ia}rQa5?7I)p~UI?eE7pJf0my(xlBdL&k2_?1yl7c`3>sk*^?2sozQLi z9^!_38cpgM?W;Z8lRV!B9t42E^2-&$fH)aQ8IVE?XkuyS(qxaf2_>{S1_5us0xZvr-E^w)dD#nSk9J7M> z!k_);`Lq5!e>N>DWu6lv9q*s4Z9Z%7SW-;7K-8J|PW2If95;XVKl`&j&&QOBp6sPx zbNj?;UYo(e^jMlRDXj~a{E2#$ABchER(jB5dh27(RO#ok zRrJFnij^x+-c9EdLf@;(+AZE$Dv2e#N*E=x_edbmL^_47O>0$Pt7@jzRc-lF{cy}a z9`I?KbH#gwN>nqIxmet`tAM5WN;3wuDQ6%)W?Vx4DlAef?+x}nh)CYWUPljy7@X5D za=Orh41<+X>;2As-`nXCFzrlL#Y`FXKC-`-sGJUwN`6K;4(yHH8i_Wz{af_k?q2IV z$z4kPa;~J)AUJGNao z{QwAleMp;XnO*k9TLmklX7V}XfRHWUDptw9s*!UZ(;Xqx@EJw|Q*dFg(V*nkQ_w5k zRFq-#UA(>FZqhWEV?Lx7M61EoVD&k};d)B9xK!J@!}o4l)dsLV3qvoZ+qo7~O7BX1 z?D&33n*JM21$4bI$kelKLZh~E18fwmxL=6pvTV(cp^8g@uDc=^FbQ-HqA?&Dl2u|f_;tw z7tW3MiEqJK#I!DfV1FLmKqQS`%RjhgZHcu?*>dQU9tquOs}|@m{NO?SckSiNNc&Hf zBcdJe^^q5#aSg9ey?(B7yKByrS8;!y&+{@s>jLdpu_1mG;KDFl2Q2>){1yE@TKHAy zzruj(0FDWxUUMIB1~`#~8SUut=C)ty21&!&c1-bsU(#$6kfYVi8mHP zs9KYjQQ|G3^57tq$)q0*6_gmI0&pb-HClA#m8%^9$CCZVV|+KTk2H6&e30dvl1t2dw9lTyy4Fq+~rb~)7^rXe=vOTjH#1V;PS_j&oB=Yx0W zE~pXbK`3DrXVZLE>@Vq%_rlv7Wk3N~e25-e7yrS(-GBJn89n1zGkqmB=ZgGjRT@-n zJ$KPJ4hdl>_UC!m2Os|IkN)$#{5}zRX|ZB7s%)I1wTdP!*v*i~z0pkfwJyHokC2QV zM%vmHuN{K)j3h*KXist1D8LTK{#s#aMrCxgdl=V9In0)`uT9ROUNs}{h_f%iU0V-5 zxe^8h7Jafg;eDvbIh&Et%!P!b2~8gJLV=8xi`Qkq)sl91nt|#2-fboaHog8nRzhLY z>sr~4pG5MSCre7NlquWWm8V4yw&!qEbV&o(Bc7uz)+Pfo$#1U;IHQCCv9_6nG*D_j z#Om0`?i3^(1reVrZtjF!9$%F*E93iW4tMXZ$0zpSO55)W#vCUGJodS0(_@EEHAPTc z{epL1MUO4tqT(;pEdAnK{a(lf>$Ax~DymDto~z+-VG{#m)#-Rc;um!(VsAwc@jD=o zx24n0-;$vT)&0(21_ZO;k$yVj6XiBcm0KG&jENZ!)s>@ecTzN7ho%jcOan01XC(OF zSlBs7p2)NEPru)pzoLL;rd)dA5|=z*R71@xK6DM>T%S=pa6W5fAr3`5N}E~PN@}9h zNc4Z3s}`u7tGi|9NNhpCZc(+JJ!+HpB{8B_j0Bek33yj83*YqmL&2zG?_iyrB5ZfVNtLxgkIM zvp)=6xP%_)E7%0jMXI$k`~C^}2*^c|UZcDB?8=~}|61x&Et6SxHkV#0GK7S<87rmY z#V-X8Wkk^j^Y6vl_xRUnr{Jt>w8;w--LCj$zsi_1MH8XK3%U^#uz0WL_j%XH;)8b`4Buu)Kr&3@ zgF219ea-uB$YId@x$aB;Jb&&#`?~X(6DA(!b*WuV9dVjv!k+yd&*X&I)o6u@u=UUW z=lRG7pXG(YxaxfVFhgBe^B_c@#* z(|BqI?W^?_zTjuJZd-JK^Pro6Ehx`pW&v%b5s$U$$lIYDbUsI^IDH+q6m*$byE{p08@<(n zO5@<4+#?$5nDN=*TI$dTb~DNmJ?x|cevW`RcX;R~x%66S@{+h42>v%(mwr`pp(!TX z`reIJjs5_-YzR2_LGLp!2<@51STqGn9Me5&OZ=eJVyN28IZ8Zm`di^N?-~vW4$axZ z&c1mVi=6{+zt(&bMS(9l!A&Z-8>1`F-%bdhnJXMB&6!c0P7bFvpzrryZR_jSG{Fiwi&^mRRwkH5FvAHvbly3O~e|6x+ zU->Z;@fXvP+j{5xWo5tXU7z(nulRhB2tm-Ih)$?OZ?CWv<7)fkwLWiD~bl@IKyv;BaP z-Weuti7fLxFKgfPdG9)LGNyX&ErMgY6I*!9W@imwy7U;!X#0HjaV}NBvtD_{mEGME zT{mE=sxt!iE1!Li)S7$d&3|9)1F2-z9JpS_gPs1u>T9PVgn1YHO3e~?@ zV(dFsOzHC8hEc+p!XWTNpM_cTEbOr2OMR@`kxa89HN#CokN39I4MSa8*ttA$C^gZe z30(0{g{-m5CsZMWXTO>v#>zRCfy3VlyTQJLwYoCA@LY>-Vow=a7hC4OZ|`0t?|z>T zfAI20p=Kh`gY8M43yO8EOD-je?-{n0vytn_71H~&KJxGDpY`wh^7~XYW6T`2YvRB8 z=LYuQHP~a`4=F9WFTM1-Fa9I6z|j0k9e~&wOb@ZAOtZHl(UXl8V7oWcLThC4|{(j}XiY#v=>?@#**lb2-FwG$&m9jA)v_orYFMbmh~`#{`Rze~s+5qWM& zctIuP$`6R*9#i`lr>e=AMZcqAjdFa7vLi(hF!gR%bSa{PZRZ?9k<0WmF2~Y50wxr* zRKm;!7dWhhI%2V14HDvJ0Atc;%C{Ds+UT@ar%~q__c*Kci}K!*B#+nNswYj1^OpLR z+|H%x;H7$)!Rq&GL=DbO&!wL5Cpo^H@16WKYUJ~;{ExH7FFnBv@i3k?vCoEF(K8J* zi($$%~Bh-(wc_Up06A?cWKB*;n+@Aqe^PtA9plJfUS2#X+2yZOCz4P89`y z#-Z$e`(N{a;LIGz7#TI4AzR+f9O{Xqn;yC?B@qnZ{_saXFo*Y4uj#Q2j!WrrT_%k5 z!o7NhL)`!3cp7y1ReDR`Z|``LD|M8=xQ%i?n{zE^F9TiG&(?PFuaI@!nV1J=J#=5U zxgft>a}4-iX>~JdH%YRSYiuC(3_o>(9{&J5o8}s=26pdy_1&NK-k#oTQA8n*e3e&$ z>=CVv-6LSlYS@l{^boj){J%dN_basVc%BYt-rREDou!#`@OjkN%XV1o3fNd5P5qs} zsL7$-_?|rAFz4d%K)};eA8IBjML%tB<-JawtTE!2!-dr_5o^aJlf%C;By$E!cFbGK zR$HIit`%pgSy`)U76GsU`Y#V)KfgRQXOlwg%E5Mnq?9heMOO1lcTjQM^V0kltJ~-y zsD~}90MIry8>HNYm?w$`4q}KCV}DP(6c8M$tS98G`s!7%sq*kNp|HB3Xlj`_y^tp9 zO%FXrAx}aT2r3O}w`Y=GJ_f@00H8i(?1%ma6hl-wMRr>da}sh2K7@LD#0O)slp1Pp z6#k`F=t!};GsoU)g+J?9NW?&RqUgy!#YZjGIN;k@i2K>vXnE8Vlz0v?8m&creKiwY zB@-<|$uE)F=s_d>?-b3MV7lX==@g|ca^pRR(pSaE;#IBZ9nVmCO+J&|8N{S6ML8Cb@ zIQ)^sb~WOI%biCH5k=n4sfq(c%N6(Dw5A`FxMD&xNel>u!|VF}5jzX9tL{!Hr9g8< zDsissprfWs0tb}R)l)Ns6MriKUr-Fd*P5#0@FnCRKH_4W^M8zOH?NWLO^e*c?ztSD z-Hg>eyr>GKvqw`2+f9k^Hw)UKcCEu-EGRj-yCUZOXsX$g*bn8t^-|ax40%;_iPSzs z>iQmgl$}2B9ckKeY)*IBmFv*jjy=llXbDML1w4eslJl^gxUYz8!=a72d=n_Ej>x_rM9aWPDJF>t2>IlTjm|s(#I*X4=*o z>P6eNAas<@6_Ap;-p%$?-#Cj7gHgVox7G~ij9*K4xDy|5sJkZ2DYHMzHqE%45R3c0 zYI5LRA9lsO=?~`iz2v$tkcI(dv^X+TX73%lFD!mx<~Vq3zhz9LV#!i8IiqMMjjw0U zcgvW|KJ)C?UizHbjf<|#<*N0z2mG4trUR^Pe-hVeOAFC(9=$i#M{xtq9CZ1W*eKu| zpYDJloG*Z9)Cq!Pbd9j*=Wv^m=*}+VC|#5Bn6#bHjgW+n#dde~<2WTn5qw05fkE9p z*BKDO2q>5yFqVJtZKdOLmk|nfJ$9#7wmnF*=!rVSh|D(YKdE3CAAip$d4IYmeNFJ3 zJ%Ce4!#$FJ+4Wh@`b7s$IbKEapDp#C#x?zy9rrTW$j2*UfH^TY;DPCtEYt-=9{xQ6 z9E*(v$YbG4m}bccMGBQNGusX&r0}0c*Cy{f&b{i4rX8hVEzH=BsLE0JVOMc(JbpY=X~i;CsuH2{xA)($3XQ7D)y6ZaUJQ%mLoZAMEk)`|pe zyyazalOrsQJ^BmJ+r)SdJzPyA^T|nZCj_ z9C15^g2BE@ZhDC6hJ5&+`=ib{Q#m3D5kU(8XydtVS{MJ3ODigSzt4yNk$?1` z=O28yof(>rA!OoB3S0h(2ji<4NBW^E+WNY$b?ra*zwsZwK(}wh*287Qk(dH_+^o?t z6IyoiwsJB=#MQ3UN5XJ2ZehD-o*7Ma3g5Z2#9B@t;TMl>v*AcwirI2dta-50o$&V9 zm&S~rDeb^1(A&S8n))^idH-h`)=-Y;djXx`dz7@Xljbt({csH?i|h zZpPh!!JIKBm)_!u9L_^3mB!(s1y{Q&>F%$oNKtf*UP_5Wt%s{@_dKd-K)wC1)ODVB zx<|V>IG5Tx8Ie%0DXrbf)2-({VsRm5o;ZA|sYZwhU6Xhd>g<8I$430kro>Ugk*0sN z*xO~)%-5y|005@~t-ZE8#cJJl=yddnbSiL-f0NzcS{?HYZLP%qDGf#%>f>4^9~7U^ki4U`t%K#kg>P6bpPU65j#55?gI&C>som z9iErXov9_9PZtn)YN9KrZikO4S0T1723 zm=1S`9)Evvj_*4!^1J_ed$!gt#e4&O+M^I#PVkp#2pTeXl#WXLw*M$&D` z2VlS_PpD_?zR|lpq5@#=xY*+HVWCq>1LL@`2_Az_Vf-#L5>EmRo(Pk8${*6>hcdDt}5^RH= z9gDjaQ`PP;o(tMWv5E{LsaZmCldBv}Rps_?ycKWS+n|C<3LH~V4_}hMF~plT)gzd@ zsZ)0-RO0}90L`;MS!j0D``9m@?Q#{I!RPh|Bc^Y88eCZUJql7Hhr>(8b;=Ou&R``sV>gMaQ1%#G5_ zlI1FjLwk5?3%ic_SNS&hjg`_v@(2H>|E7N)(%Jyu{N|c5wkJRRs}arfc&em19?l}9 zFcu0f2WJsg~GM@$B{kg&& zI%1no?JJ7GF)?PX0`3UyPEj#fQkB;+j_fqq*@_GdIP*CAy5n*6T{OqSTR9TfQlg=a z3NbkL^S5|_Ov?6N(AC;)^F8=RL}6S87k1lYNFHta;xU>q)u?O$uumC$^nKnNN+Cg- zyL@PVn(;#Z@N2O!xo)~`C!y_2C8I-MX`E2Ryyb)Pzv%Gmem(uxF8VVy=>4|_zQC7h z=<6_`GI|%xxsj+GevHyw>@9(rk`~nunHwBfobzM4H4mrdoF`poh<|WGZ1>=`{QS5Zume$z9G5P#i6u2+pWjl&b;xt2TQqVN&+$kmaYLNK`E zaUh_T?JJK{w%Y)1!Rlj}-*}*`i?(8kmy+eR7BDWL=f7ZvVF9Q4C8X@|K-$Q@`jzGIV0du?fEX zReq1ykrOIkilC;A&@$C;?935ojM}`B>ZuRUj21M)SkK~AFkDtHd3DomH%bZ3CE64l zKch0P8@=M+n7~cJL@2&k6#XoE+H~U-B;|j=m$@K7XT2?k6l`b{_xFQEC$u3%`+g~3 z?9DV{AY141Vi6-C+z4|`j^)E`*(bdViw4~(Ag%A6N`|$@v0%RZCt7|~7oc(CTta#+kEq4rCDsn-9=H2i7f7!~Pi$%T&CZ*+*5-SE@Y(-t-?<zyh}t?2*1 zi_?H<d;U)JiZ`>U(}r@c472IEu?BziLD#mF~V8XaO5^8qYoKs zV94-*KIukfNG(^Qr(_L?W|Ur!m@n7bY_$K4**QnFaRo^QvSWwDFZ**7b`OQJ)Z|*f zoGk%J^LlqTGKK_92qu(S`Vkb1fSz{zqfp1ou_XK5dh%KWgT#jsnPojNBl%vzD}Tl5uZ8$a67Tj~CoOv~ z#K6eUs5bxW_K8+v!(NQW6fq<*VZBj9jaK)uxzTWs6qb>a zm@3-NIz`(k8!0AfRSYd?ID`o2`#ii0(H=J=!G$JqM(!da*n;9N7=o_ls5pSra@ueWeg3T@Iqav1~V&-3B;0kG-0ggAY|{EB^!ZxO~dO2L-f=PqjPN?%LBa1VIPX<}(Ra0OrLVYCx5;$LEaS3g6X zJNNveLA0sge6P%g#Ft%3e|~HjP^ZOCg`z3C$J!OgTL6y}iBV+=M{*ACGo;i5bB8^4 zZfnnvaO!Xg?Lil5>zdCQ-D>Nhgheh&$-n67YZZ@#d>v-6qL~13=bP$n+egL9*y?Ak z=?36j6+$IB7LYMhwDAHkWEz^H1gBHoFY{pe9&Pp7JIn}T#F$RUp5|dsHrA(;IC_!k zU?k3&U3v}BXLrY55sA(99N)A4N8y2OlvwNM+O76C8Km3aHW<$gpFSf?Rwkw93_G}l zW<)aHX2HddS&ZF{ZF383`T}jbXKe9Hr#bM5@|9^& zmW~DP|rb7;~tE3e6{#0445M*_qxJLe&?Jr9nAnwa3EQ*iec#Y6|q> zPydL)-VBG8A3?@gI%xEASo7!uX#%?l&#q|hV*ovFYUw%)3oe^}hd2Wvu$y@I5WwzNXpWEZF0J;c?4)o?;rYFiqz|^|)i7)i|y^pEk(+S$XFY zw<;1pqOI27?v5DLh@3^zU|#eP+o^CD{_Jc2VNy?+NU>~@r=Dy|8|=7mAD4yw-k<%h z4}0p#0zS{X-u-!;cH#nVj$lB#6T149s!P8H+!U1S`8WP^U-zYS``q_*@-*LKg>q9w zyTc^u(^fAsS)3i)&e@*NdLQ?OJEX&*8*ka++q0G}lDVk99cV0Hm3|%1zmP-%-S)8k{k# zNNs2&oa;GbDCTi9>-l`VAQoW*d znkY~jzz^&l;{!$>vK2U#@m%9y2CthI(v))a_S0o4z+u!OAQ&@OQPL&GrXca&spBF3 ztRP6|Ztn2pFH@ky&?V+94C9#EuB(W@llsbm-;_6nMa+TDTkGlad@^gD`@v=9dPq5- zV+Jvhxl?}cQ1tCy%=4P7eNFHDAkuR!+r}}Z$co*YfaUkl-LmR5SD~b){LrGEJb$6B z7pIv7^vAjZ&<-r)c2^@2lekeh^x@H$~%0>aX#SfEemQLabGj7mX4lg`EGRssI^cn|D`SBn(<~30!W{d zck(6tnS`EHg@hPL+kbuZy=^)kX)Lxp*ggC=J=mopqJ%lxA>2U(xY$>ZZESE$ywrRr z;57wyeX9uZJX&r(VFpGpH{b$sQ6-OXUkGMAhrttD0K|!K<=HMsdk@;_nvt7_mQz(* z%H0Oar_?+&1Pq3ky#Xo^+cB+|iy4>0 zT{JQ?i2v_21VMZxVQa3u5_14F^Mbd49M=(8q@f`10-HWEsboG>ZCkUae}6C(zcWR% zGp*cb%(mmAD3Qyadw=7-w%ncRRN3gye0ksD3jGGvge&38&(NO3XF61?zt-pYOh3yp zkxa>K{~T1AhK|}n8sWqD`91IDUl^SAxsLSqD~)zx`RvOK+(!{n6W8as{1(ycuk}&0 zFOMtPT+B%boyeI6*|Vi7aLM#i*(bpU%S}^D+vv`*AkwWyfAOdSvZ7w!V*etN=8TOZpyn zknz7e0isRs(0lT>!pPQC zUNysbehE;3ypw(j@jXuND+&^}S9DU}hp)t+?0gK4C}auTpaWJ~8QQI|RUhvl_eg)Ml`L zFJfNZZ&p3wq}wx#L5~{4W+c=WjI+dlJ4F7M{KxrOA6t*&?C$a#z+$f+NxGG+m%1r? z^LyUwEBEO^)MuWnWC3n%DE9sc;QoXZvWDRL$T-F^&JX@te_emAp&u!>8)6R`YGP#8 z9Zbna)UBAqMr!7f^1jN0l8|_F&wJgPpEx+jHLg^l#cbVaT?=?mg>V<|`mDuV3OY+6 zYL((Y3D*?4~M*AN#{CiMfhb9M@u zDd9jiUQy~!|D`@Jkt$oJVSs@EtmLrGqY8HkP=Q|Xz=1!z^!*>bhUT8y3L1b>A_)3-dZd&NTV3iOz)?5F$4YE)$e+E2u=9gjE-nE&BWo?&bJQSw z-FdK(>$#*ij{>?}0kTw5xs2H^TSA&|LMa3p4l4x~ful5f0qOzpinP%SSv!c=N7bb*^d%lA${_I8)p>Z)5D|clpN`j@%DNen4D_U2%`N zSCWds8Uh*DkTE~{IsTH*`fK_rN3LgdX>Q`oDpQRsM;HQ-?`FCY#z%b%JyD!vnkCnX zdI(G!0gB@q@;TZac>qXY1!%Q}2Wp6$fZ$9>HbD{%8gj&WoR;?lNxj%;HOUYZBgFwo zJziptD1*p(5s+4T_KG?o=hJ9pyt8q~U`2pesMSB)Y0Y4-;w>uKm6iHWc)0O#V!C*> z42pA@HH3G}W{!i40+&<6<)dWdw%n&~IL&QWH4%gUZe!C_=y@|OM=Ij62D)cSH)^2W zFxO%=CgIikNA%8q%~6KYqaL&r*6|#r zi1WWrLx@()A+dV{Ue3&pPhc?{Z!KkCf)0X{(!Y4T{h9Yvfw$u)|No@7_Wp;wvVtPx zGFDz?570Kbr-#;^E^G;KD-L%~8ObeIvy8>|4?ikV; zH~W0G%**XaOWk(Am7Ua0Tkj+{}87FgXefrN>RCqKLCWTF8~Qo zv0i5M%G;2CASCORiLdnZx+x>uFDPp5-*zzchV--?(ODN zyQ-ejrm!$Enh3%^-NKjc4e_#bNUnlwTw{*obNnTr`H327(imdayK$v>#Vk{ET`c!r zuMAB(@=WD%AE7|Gukfh5Ss(X)24fLdLCNd(nIH9gP|wdfF2_TYG3>0c3(uN2$JRcL z@e}`=(}`MLE<}#YeDy<$A%j<}*?O;!jH^M=^qH(&e?||3YAT9tAVmKy#7b!|EpJgD zD}eiiCe-oRYIxk;G~dMPaLkh*uzj4U7oGJ+ zJBzt!*V#(-yGUbfUF<1-5YK0?y%#=M(G?y5|Sesg}k)ub~NEz^SAX9);B z8UpxXv=kCYmBk?xm7YG5ft9$RIMa-K`9Xp(Srk4gEVUD{nR*~I6lxzGz)XSIT3q8q z)K=eLXYj$~a5Hy*m}fvX`KV>%P{D6AK3G7e8Sn9MMbKg7tkWtD)eQcd)?8C1l)BBo zfj0Lf>}t-BM6Z%N)ryLGZ3ag-2kgRKtj&0eHq5VM?Q856gin<@K^k*nKV`q?5b8%z z5TuL|<@Ub;4iCQ>ColkYJT{jqUA!x3g>D=BpPxO_IgPu}RpQHXh zm7YjELb4fHqEwB7-t#{1_4oR&uYPr`&;0B9>-rqyYQ|5l9V1a~FfRV)8U2M^dB0X8 z#n4Tcma@;(8}29LxbMnJ#YuoRLJm*xmXzL1>Yuc~T>c%-znd2Qo^J()@SE{J&L})w zG$Ce=^yCFnGet=|OE6QN%O&cO%FCNM$8@pp7&1;583KXDR^euavtQy?%=6*+EU{4P ziD6m&WD7;Ix#XKAUbAi&oWDA&)Qy=Qx|Jb%5vEsT#%VbAB|fxg zUS)yf27#eOBGxd6^?Q;q(pne-lsYml3nBUFM?X5$14VuPmO$4(!y3YZLtgnLk1-BA zexy*A%_@G}%THchW0{x8SCh9iF8{po)x>2Xk#c!{jY(qG#4SbcP`b{E>Vz8Byh*kK zjM3(~K*wU)=r+&5w0lHN1%Y}1l%IzqKk?~7cs4SV#;jCdVG82o%ZX@1+K*GzBbG=s zfqbNB0&=kb7WS3jH1cN?4M}*=fI=g#05K600H%NH_;4|4Wl&%2vB^;t8=cqYV^bc- z=4%U%V|CB5#`&2;#+4={sy`;qkL0xhXLexv-fz94J_&#>nyCNHQoOtU_e|n5vN!1GvfwUD6>mhamlAic;`q7qu;cNkB0C7<_ zS91FJ&$>v44X>NW#h|7Ucm&U4?it27j_Tkfy&s)sp+>X#WAvTU=1J*S^A$xB%Bfn&29Wlq0D9M|$I>d)n(h>S$u1Y38Ybhp#-TZDO&!{LE*~T5w~eudzHPl$6q7-OJquR~$o1RrSQC_6`Bu9fNVA z)`)C-B(U!>CIO{tDT2qUkB@%w_wj%9dwk_yAN@=IU&mj^=P2{jZX#{)3Wt@yv{8@7 z_lkmLoe2y{PHjE5G=U}=r!v{3524YJz$u_SjH5Ol(w`gloT7}pzWt}9Rc{Et_;=U; zPnmN9<9{f#clR;G!j6jjQa^oSq?Q7!S?Q;dvBo*1E~xPBR7|3hwIyvq3ibsEuD22I zfRLd?h)rERiFPZvmD;1QsAyyQIIL13(+{(TxCIt%Nh@t=l+b)1c?z<6Sa^qpX}K_I zW8rKzd}fXC%P5hcvn0(jor&=>7oLMAa?H43Ic!-&Vk|~iM~HE)r>iFb?-T93Oqq4zD4w6RwT zW9O3FzKWQG_W2)i7>j5oojn0DZlg=SV>PgD%sR0{t~pfI{U}FcmByIx(~WS>ali_cu$52bCl#d>bvN$mQxgs zep=n?Gn)-8Eg(&Jc$OGDUADeD{~(-$c81Ak`W}}1xL1C+69DG8#yMo1ZnUiVTR?n$ zu!l*;^Uy?Tc``+Ee1z>s5U-ulVSH+cr28UA8UzZr?gF4QnG@Ryh zZmQSb&v~D>-s|u6J^seudY_Qb@gL`Z;4k@LEG%0+WSrm~C<%4)7!Yz~>vY})81SgBrCA+n7IFZXxX7l-(?DHz$eK`_ zn3C$Csbx}cTvQKxo>DHlnmGhz!AfsNT27a3*t|XgfP0*dP%xUnp>8XcJkJp}4jI8{ z)(}|x+El1WLMq-#K@K%S7@IQJ!n100luo}aAn%Eqr6^+MMFfFCVUq#0_Ts_T&TY=; z$Tb3>tu65UFqVoEhE{)v5FWU;-kM>|-MFFm#Q9Ym-^+c16e!9n#H|#@6k^JA{k)`H z#f&j0efiCB?f2|9DeB>L=rcTiE}k9sdHK&V^m9Of`}oRNzbABDW9GFj2d?zSXfu6G zUzxSG(uzhHhgl`|T?LAr8K_pbN@k#Beje80n1^Xhoa1Vwec?~LS%Dtb^CF0=d(3hy zZD)*gT;m+`bCNd?4nfjyNMUuc^HL!_zUM99%iEjeKV&Ai{C}bstq(Pp@vnq9dh?uc z66$G`oP7F+R8*kY+u9-=HpUyi-Tpa`3{avu9Y&ij|8EsmRoDo%!H9Mzm$vrkv|n*{ui1=-TE->Ebf$lt1ia1Ij+gwP zUh(PpYh2@UJk_DiFa|&AD&tQ{(pbduBjX^zR&V;>mCwuZTU(9kEqjVlYaDh@1wDQd zgod96M4}D6!zHQVNmp^58Brm=mm8&TF)WL*E*DJVG*SCEd(@1HIZH;9M@V$FXCpo% z(*po#xdkJSXDPvv1jUZTByok` z_3c!i{YY31hGm^|_Z~$T?9-g$s+C#QVos$uYWO_ENFez`LK31<8YKm9I zP@1JA|4nNX2wNrQ_GGT1UWmh);~|bDoJfKa#SlUYG0bWZ9C9CGYT9PlsR4v%cqjxG!p^Fel+#cm$_N z3Z0oY0ZwzQf<=@Vo%Epv>ab^``T+N#p5yq~$O(siRH?qmAGte@?%`$&v==#dNDfxCH1fnvT5r^eP}!SShL^6}Mc2%?rGqB5@OaIBp^NZW-gE+^`vz#IUOadQSu`Vy-z2gS`{OdSOs4 zt+tvLn0gDX)#{%DgoE#^A+DZZ%}iMsoayt6dq9J|2=~QP7!I5GPSz zTRWxA+JoWD6#is``k>S=D!(G=J*T-B6kl$PmLnzOK z0_61rKt_?kwARZUQX&0TC@@B-fJi*0(b%_?vg18*e6R2Ioqx~2=e=O+>GjwAOa8h( za~uhY+f6!0^h23Og4F$*?GYvli5);vGhsXARk@N{UEarOvVN!58>IG}c+Ka^qxJfS zM62@+a>-+}^1wSFQgNut*|B_o7i|L=|5-MsU@&1BngEFrtBDW{c0qFsE5L(B9oafC zo*jzKGjGjyCliGs;6V!!Lw!)g>S4Ic-U11{%def7_XJ#7DrVBiZGGPb>K`UpNMh;2 z<^;CDO56&)4?oY8XN=7M;s1#XkIV>UTy+v77mMW8D?Cmy29u)P;m{qjXqDe~rp`FSNiHr0EDnA|+CYilF=!o3FfWms?4wv(dG2dwP>V||hDDnx=c zt`9!*BOe_Xrho12`kwc7AHcXi=SMyoHxB}SEeFiMO`lAe5K|T(>d;IjcLBLiYIn2A zW#+~CG=n~SbMuul+!!-o6GKQ@IXO&^f)s-(lPZt~03PXT?{p98=Khz}nx5n}H`1b><~WhiHrsxf-XDUxiwU{vyT8yO`TkscJco~OQKOZ9w@{bC zrj>_*w)asB=Vl1j>|O3QHdCb0bNT6|)5FiH&voN4?N=c#1<#p7Xq1AdX}{bg&0~wi zJkZ*lzIT*@ORE~5agVc)F4|wEER^6`}7*L34lH|@?2GW z*+<%;Jv=9j;cP`$z<=>P+TJfCI+6z?Bg+)IT4~jlh; z8q&riBt^}g8E`a5aG`?3#aCcYy=`gp8_-&6jsm^seSGDM@BCihg?a1e_{_hK&-`fJ z{?M+0*(iA#U6c0Y-fK8W`bcFzsxbt=6iSD<`b*e7KZ@izm61oWB7Yvb;20n1UwY0l}l%O>F6V%c|Cs?+HwoA zfFn35?F1R-$Dt^SM&frblM|2XMOd3kSaB~eB!w-BP- zUVX)_h20uWb~jB+b%e{U=Fvcj4^6q{K9LAYLaGcwsfHI z@x2;zQSX8G-p9Z`w-G(4nW0tv!z0*}^$6KpZDyP3>14ZR8l9ZFv44+*Mag>63(%gJ zNee@4L5>nBOzF9SUv3cU{mdS$Ho~66tGUV%7Ue-?fkFV{Aah7YIS84>n7#uIVg<>lHfK{Llxeb+BHGw% z7WcB6&EQ7nQy}PyjZjjwEp#o%V2dMK@rqXa^0ML_vX8GpRDfcMny6;j(0&q!_M2XC zlK!imRNqVRn4Xxo`azaSqV^CW?P~U>`!av*v`B2u>hU6P zrML8J=SlXYpJAku)O-%h5zz1vPusRt}xEUvnIFuGwLi zBxLw&8kTTSwhN~aZDe8My7BS*G; zp}%|S9j3m^)%mYfoo$!Br)PUI{&T$qH|5haTq_!78`+DvlisELy9hlcB~z2VYEu-1 zZ3u6P*=S-`gl7(^_qWmp4UCJ_ud>9M?JU1KlLDDXW?LI!e~DINd(S*csEkdjHnZU$ zjB$-K)j&zRu<&Rh*prr8lXp*rKO+)(2!nw5M?BK5PBeRb{tdnJ51B`2fnz!rNE|-U|0*siKfPRi^$YZ^$u*GSrbDH6RG2S5tQs3IjjnO>JUrv`r zC_-W9u@oRzmI7pmICGt^z&VATnL9A%W0}zA$MVAq7*JzPP2ZP`j59Tt8o%y|^H%yc z6)VsbaKc`MtxuKetpYTnD?^NCijGJY8X|WIybGPtcuQH@TBsGQNfnjoLSfl@kp5m< zA8vAb1Us(4G0h~7*|YupI0%o+8np_!*K5RV2+_v#tM~e@FTOhFI6nHBA05Xy9x)9u zy2!Ecb~j%qYG;><&B}J;9g&23P$^5c;H@sSXZaUj%uX2%_fVrJnO;mun?sDk#5%M+ z(EV%Nb`z1J40Ghf*l{~^rt3?iU?UZ&1O%ar^+PD+S03XQzch^%HENm{X)GEYPCQZz zNU0?tXANro38g`7GEibi6wvxeRQg2>>^qfO96@E}MTpDR8lTq7pZm2KD z5sHot37MgOxzR*M2+U`Wnp5P)H>*8$x?hqF&%vY-c=+=&MNehRpLnK4`1VrI9BSKB zV72g;)c%BL6~}#!(*vcaOA1j+DgFomsXQ{+Fr(QruVt2Vgn3}A15)ZL38qtYG`@L$Q5*U{5CpkS#@k$Y&c=|o;2Bu1Ox zzL{)C>^qJ19ZLBNhZd%xxj)`}SUoSpZ0~=R;r!`XtbDHZY-zR9J+1as_;Cx*Hv25a ztp8`SY|^tAP*$)Zz+(XcaKV~{0CxKiNv7Tfq%Ov+3cN|wNa!g%kmBwTwvF?0PYi_w zGjXy0=(lqgys_ z>|V5S;TjT)Du_1V_q!oZ>*RR0a4ra>JWWuu96}-PE4j+%oAbR!5SA3seC#V#zlCzG z&6OLQ*)oM!5G?ZSkN{IKr7^=2za$0ONh4_Eciq=FKBx`~cPiY~aJBm1>%Q)TLZ50g zy$OiiZx>?6HI8xGsZp%9ijlVXd3PaW1wunxCQWtt3pO8kLo8`&*n?8{Sl`&XIW8!E`p4h z$C_U}b_fN<5nIqyPm=tVf5p~_c}jh@C$&U0%6HTnC*Eci?K_7tPivecPWi00`hr@~ zk~BB%!TFSHjLbSb4wVAFS`VqUs^RZbs8#Zx1`NCiWWHN+%~vnuy|_TB*?wQQXSNR@ zXC(t+AHh{5T2Z~|kK-^pmS_}$cGSancG7BGfmgxv z_D|xE2qRrYR5<(vl=Q~ti3<2@@TF0v<=APgsv)^6xug^xd!Gs8z~@(*?P+cAx=<}X zvzdnMGruRpETz@syYBP8?lrEDTOavF+bJe#`$>4(H~Z=J_;O+F&cwpG|d%-e8l*I|Psc zWNt=-neb+sjwD7Qjb#96ZldlQ8`a$U@xDj7?7}~$cB#Ea-p=4Jti#EZq-m$WA$ zH>EM8fS>)lC*-T4IA!t{Z#B=2`&48|s;BNFEYdqp_N_dW>0oXl(E38ncSyF#=Redp z_)6j$q&CwKgldLljxx$u={;)SMTFavBZ+%N0-NbXCB&C(FGo9+>&woWJ?AaA=)@xp z+20v1jmawIOs_Ft?JX=0=M;>}DU|#$e_TU8_~3J<@6fz}r>|gf z^-yWV=}VJ-nofpcWh-`)cU@C8m}C->@*jNFJ}eRC%PV^?9Iou{V~7?!wgP?&*AgWA z;(ueLBl)N%+#K5pj1y~I{-KSl&iQ#-$)=VC-BGx*2EHylCDV4Hk$wD`T0phDyhBqw zVpMz;jbL^@HUPm2oK~X*^{e_5!t_iwU7~EZNpT*1jcd@ITZfbqzR{&p9}F`W8;20B zL73mFykqxNN*+6DhsJtO z@v9JmtiG~$7pS{=UD&GU6W8WtuwEHOC?zB2K&mtu57eGJM{G(31>2D?fkm^4*nuZu zqh9~XxvGTj5>84l8tpd^Mc^J~i*xG3uUGw*?b%cMICiLr5lo0o6}fi|OB)VRR+FmD zRC-P_OkA{dN%?ooX|y5PL!sQ)eiLRBzy<81{^%2E z)=+!MVyeF9eSQ}(&LN)}A9#pWXFlxj1;5^JoSTs_`?d8x`k{_f(Gp&7#8e&H0o|1U|K}{j-t=xJ@qLrAQmidi))Yq%yN}Ta*G>G;qn~_}J)uhRQjbE6> zGBzp9@=ZUPBSyN*6urmRUhDUZfr&<<_xt4(@cul#rI*$_v<)+Pfz@HIy^TL60mgq# z6vL=&ZYDveU(OL6f)HAOJly$ke=B0MIHRdRkWhni;AXQzS$oewCh>r~^IkY@L2O-{ zNG9;GerOB3QIPnQcEucz3?-cPW)C7^n{O$-@Ws!z@C>SSVv)9zRpVe|o15ayIDcbl zfd>Lg&Q=!$miurT>mI^{2eSx1x0{e-$t(TKOE5Q4_tCX2UrE~U*;iTG*cY1GT7R>2 zv4L3-Qh}TKL6H#b4hpRbtNJpp1E0e4*`K7w#NBv>)AHtWl($hyvJ+5~{Ro1XbnGQX z^imGB3mtC%wfw`s8ztlV#Mzz8|I=aWj`ilaukQ(COqZRh^1$cl#d5<&?9P}=Sg34_ zV4Vt#Pj3Uh;?KPFW!=XuelgD(L8PWs=HBAo7g4RhIh!Pm%Gi~F1rUp?RG}d=dz!n= zh2@LI?HuQ17iMuv+vj->of=zV?FuXd6#$5lo==d9B&_lR6c z6QKaEGM;G)nus7?K&AORAi3=-;Z-pOs+Po0V;JY>_LD+HT>2nh>{iF97GQ-^=f072~sygFSV; zWMNYL7mqoB=X^JHW<5Ny@m3ORNe<{G%AEH??W9TNLU&*3zDI z&)7J6ub033A-Ttn*i1%$X#cg$X0lR%bs1YJ+DpS6R#mm5w3!F12Ahwdjh_boiR*H59sim498<091lNV>K zv}_2msaoU$keEvzRxsHQD6FK~(>03h27x|9%&psxvmUMLZk<&;DU?0y|Bn&*$%<*GV_$Q`Td4&sFeY2W}AiGQi+kzAo za4Wfohq^CjnNQaPo9@zq?@n+wEc7Ds-6gePqT-!uqW{exsojC^2qP< zg#x!MP}IXak8uT4@WA0CRUU*t^gizER=etH9CoJAy%Sbu=BGC>gaVJ@=7b{xQ@0x_ zVIvkwK?)OR`eAuql&QcFA6IFNB$ADRq&Qsh3&ji(L3Pd^3|}25DaTh*sOh7p*@AkF z1@Kh~L-O=hFj7qds&KZu-A@^q7M4AjGODzmixnkR z&UfE51(HJh!!UGW^%cZExL9?xQfHs1e%jIh#+WYhW1{im*+fn@0~W7A@{MTGprCPd z8%Zizf)x4ZBQ&+}UO7U29lngkBcxoS{Tcyo*O4%jCfZhf6iO8 zj?afpx%venEk2W(9}tu*>J4`nJPl%{nInIuH);Vn0};x4epg=hGW$nLOaz4h>B|Zg z^l}E&$?CjM67hjp=uMEyd zyhDf@XRX+F6WAJOvc(A*GA>-7@Ry!7ONpteuIeyyK1_h#gL9m=#7NLgXL#_8(R79) zRPscKZL2}uQ;tgR1@+cJJLg%-T8a^4fykj&e?ws}OJFlsMQ`h0Wm-fnl!70v%o0nz zJygsH^`N{x1}WeMEJ5;=Az00?lo*albgS3z^g*+C$<@L`Gt`ycWQY3NX5?_mcZeE_9v`h3=U(Gh9ACW;oHo`z+7rNP>yy;zfbUYu zOeJJ(D}0htmpBh`W%X=Q3$m|La>?Mj@}gWJYUJDM*}@;wtFJ7c0w<-t{!Gfh*<`aB z6xNWG1)`NnoLF4_c3NuX!o(M6uE+7gM~Aq~l;QJD4G!F&J=1I#K*)PT-d1no3FD59 z_Z0rXuWU4W6_r&WD##_L|e>Shf zG(j}Q9|cIkm2TUX4=1bS(Gq|IhJ%*5Ai{ zjrEbgM?qc07|5%-G#$u&5!<`zLzE$V=xw6{2W4NC+s60+<}Y= zEHbVt++qQ=%~bH78oSrTekq_S@}RNr{4X)B_FnQYxS51{5tOFX!(P9h3Ev@)Hmoe? zb!tAhnz3@MWqp;EvW@Xs|5Ia|u@8Vb8E)Zjg zAuipZ`bcI@6u03%ZH8-5Un*Zx*2wf+L(MB(NvoGzuDq?9SAR~-t~`YQ9j5=fmj#O& z6&)uZJ7D-3({loOlXXZ)!A%gx9`-wtG!>X9d^WWJmOyF0Y-*PPCcD0=w^drBpz;1Ub+tn!u z%_x!H$4aH`?q0%#l4qm*t~6u9w>E*!dd-#@^(86~oV;h7cgbuI@A>-jJ@a87}KoB%?>_uOlph*w~pBi_*tV`thx)>lzkh6=}(GM4k}Ih zZTWMY8>1_hwt4{Iefq7HhpY1;N_An1d!tQ=3>?IRzr1t?CiOa+*A7YjPL(BuC#xY; z+c6kxTw{*oBOZ`^dT@<<1?yh2yzlcIm#rdaLLme!9|iGo67NspynyCeDnE6s^5Z_f zY5}Mp8Hb0=hS&i&mK+uE=5R?V@Uj``M#;w^0-eb!Hr+FN*%vFJc|Et#Jcye{=7?3h zNgBX(`L{C-Y;!PMJT`W=1mc!La%h2JxkBK6Bw z-^D$&-PO|sXjfFq@?JA=T^nBkahi!YVe*kEL{c*_xOSoaJ1g{e7U~oefWWd<(z7@+Z=*l9`^x^({2-YtT+S`yI(igf8SGcoI5^=arGrkLm1)` zS09t&P+eM(ZKZ6Zuqo0Sm_F1@@cTz9ANq9at6sO-Q=pXqjTWFL2ZUoGd&wN9dS&jFGZ&RT|eJ=GB^hD<+2)T zkW5s`&2s=+9{}IjnL3TF!r{V`*0eDEo`s)X4V~$7Zn@9oI~h*6Za#o zi!pc}M&#Fjps5fMMz?rbb{jG9kM>Khvzn0qD3N(Fo@h%neu<0Rho6K2UH8ANe$C4e z__oHpk5pb}{O{jL%kw!!cG+(kic4<%68hjmf2nHbUo7@l>r);7mcpa=U`9$z@uIMu z`MqxW%J;-^ABoL1PXD^ma)sv{VrLe*F`$jS_aN^@Jfr0tAy@7y=+VzXxG?RiUHFUi z)_Z-;COjQy$!`Q|hN3QL3f*0B(T3aPGfo*!Q#L~(MtP=(_6&0bd4kSym4Ma6QXl81 zI6xkmz({JpZ6jEihj4BqHUR{4B70g0ROOZHtEC6#pbIc~^}~1CCoQj%#=qpz|LcsZR z3BfxJM7785Q_^mTrI}b(WBjiRu|Z>DtrzF1Vjt$UH5tF6GlISp0ulZ#&C<4hz|({` zv8VOO;p8wlm?Y!DuUqSDY$HU8RkcP2qya8um8cc)io7moK%Tr`NYQyRE6$~|)ygFH zmV$qj!UJae0;IP?EgQ=?J~FQTcF_FIuhsw}e3m_erQl5RoCuHs!)H4? z7(Ee$k{~=D9>MNrg8K+NZy=LuQ;s)eEL_8Sv+^kZIhJ1pv-Z{H{RWM_<%D>prQF`v zeSO7_x_h8y!R47dZ@ZaveZ@Z;e~oJ#hF1!?XWn|-1*P`PV=^XAx2j$vL=C-7?zKT9;=b;~4M;wd<1zz7^|F*nvNb=v25(A2Q{k(u0{8KizmLDwe1y3E znxFNT456bIaKC?yt?j6JpF@(1$VSn5W;Mf06CheYMi`tO{Ng78m%DkE^ zC5V}rj$dKy?G-*|N`^unaZOQjAGh9$5s`{t8ZMBr3Y*ts4Z=#*l3Ykh+L)v zRGTG|mj?7L&%?(pfczY>&uxAu0Nj^JWoe$5A%2>7x?B-!1Hju#^rs0VT@V0BX9MFV zjbA56BLfYrE^0>RNS;~03QD7i^?SCFSX+)fcFv4)x#NY5NYn#wHSmU)XeoJ&{Z((# z`0tf{@zG2UpCwzrY)9?*W07{I~IUR9|{R88lV%2)+Ar-j-&qwK^EUW!KS(zA%$|L)~N&y4jR^H}YiQh^k zJ1*9r)qWea#BMXr*uOKpST^U-q*Idns<3B)Gd?JyVbG~DX;|@Cq}o2L&vQsG`QQbM zy#uXecq%0?amke#to=Uj>nmR=|J;x~gZ=ltL^|2qDfL&ur07Sv2!91gn8?i|+;+PTtpZmd4(-n!V+^o222 zW6!Soj6TmX8r)l?5LIs$tL=}OuhuxN{57|niJ0x>^OyKF5qO6+%S`bq3sfBm?0uge zv2Mqt72h~}B%)d!@9DqGJ10S7|57Bohq40+vU(wrMWi%x>|;Z&lRx(QXPuj zW97P;VTV?fXAa3#8SiI6Bvq>8*P-e5B#(Bf)%v|fd3f5CbDD!rdF?57v6aa_%!X}m zTY(2B2ihJlIK{n4woPcQmHEBl`h$Wj0rnbD_9|5LlZgo6TXMxMbs-3i!nQDxwDozfy;NE(U0e$+(U{%| zCVSJ*pRJC|i^XZ5?Ft**Z7TMA1}SGt6>1x+FD@mkDSIBe$k0ZT)iKKs`$dE}=4Bu& zL?RN3QxIt6!^XIJGKZu9JiRS$J4A^HzT#O5T#*5c=eSIz^RtXt(3;- zHGlEn6jI%PElKf&$AP?Tpj9p;)+Yunz>>!-FnqyiGrq|7q>nu9ziOAnZoLnbktl-9 zBuyWovZXMd0(bU0@0-0M;GA};j1uE#jRkvh2hmGQ!~2lxA0A=Ey6-)|FyI;&);Lir zv6`vmt6$u5&mkXt&T;s7t~QTBLl<}k0{@01t+W!1Rv0kytz8*|zm3rzpYwyy`s?~@ zjw44Y+A819zM{%NH@$7EAwv)XnwF54XW2C=*jXox(<|w{8PxCXmY*Qo!JKf>kXJvF##+6D+$~q{Cdbjgi__N%#87eco3o*Eq*D3B>Ju*x%& z(L%8iq!Lx@!>?jG^2WoJ;gXWqwHzNC%L~^y>m=+rjkP7lfxDyvcs3@MsR<_4W3;l; zU_R(rdWGQux>-KdVxZY7UxX@vymEWem!1`_;<^MV%2;7aiRW?EDcaU6Zbgvmi04j2QyANfM zpTk&x-hg&fy3?h?Xr=D9z;smrVEoHV?ad8Akh2XvMyRp*omc|9lGh>elkUG;;F7d= zN`fw^EI2p4BS(^r<^2irwqeI&VWi!%Si%{JlZ!l5YY3PC^Y|$-ZshV8%pqpI8$v`* zdJVETqXJr;j4r-@rbs0BB*|a49r&ksqx5zsyW%SCIs18r|SS?FXJpWGcL%uTIdHS%~?CXr(Fl* zSBHjJ^E(|| z68|F&R?``_L9o^*y<63P=7X{Sd}RqzU`!f{5~wToC(UX|OjiW^dYczhZ$}fpQ zVgy=f;x+==*ZLPkU6iFCii2r;!3}g6;xsnS z{psbZUB<#v=eNbz)Z3X93jx{6lLf26R?3#CxI4Oe%;nMfNCX?I;mrrM99MQPTgW1$~uu zIX3A-VHI=pL$`SJNSg1$GVge@D&zT*{-*OKa45z}$;QbdqV>CAPCKsx5W|>FK`pVy zTl3RZX~#6hq{4Z89?xWNA>5wbYF6V)c6`UUeLg#nxwt;(xFApu1r3ak?}dqJic_ki-kL!g;v-l};__qa zSB6m7=xeQ2A8Cc&(?1{&_b$d5$`HUVjkym@V$SyO9MO{n+-KfOYKq(RnV;jNGc_7& zt1ETO1LGD2G{;EIXe$FvgXpgk!0=28$2a0>BDkz7N99CCV;N>_=J)3Zzy4EMGz5FJN|&y=uy2d3a}v9Ui9KJ`!Z( zc-htV+jq;F!qli>6F{iPvHFyDp2p6%E1pP2n5`d+6npDam5`3c^=}v;%@@4m48qyDW>NJ=+l(Pl99Ht=IhDF2>cdOe+S;jr zCt+nU@dnT&7tjJ-ZXdQ**eTA3K^^YuD^)3NYG>Q7+Ju8U|paIblk zhkHUnvhYcH;!l$0`7gkI=(|CU-ajZr;XU)GmFO5g@qNO0ThYC4ah&9i zKOjXitX4iPDy2z#?eAnwCVOA4=Tpfu_M)`7@!uEYF#?gfA-GzeIJmt;DPW$$;unjj z7FP3wRnl&o%J&nU6ja${qIX9?Uf1Ulu%3Yk0C+>bv9kUa3@q&%X}ZtL_cIeD*kK>9MX;fwK~4+EW9MNxcH) zJk!c|i?bh|cP($uZrgpp)UeMf+tna+p+boSdoEq@Bq(Zj-f^<` z3^jk8VKHtVgo2JxyUY)~DU&hVk!NkrP|tHVn>f_e6`}J=ec$d$yU!~HDJ9Wq4k0lx z@Q<~t$x~KqZTbH)qVCF)e1?p$dWiS3{iXC8d!q3#jk%JaXeH0EUJGC+$bs}hNyQZe z6>5B>+M9S=tutv?lqu}Demvq@p5>JC!!zO~tPS=idX3fg)f)2ffJD3Ux|`(%S9QoI zk0C;Qa_G5w65eo1H&{wAQA+~bgpX$8lTm9{$(Un`=o}|qBf@DFeM#y%1CJMard$2$ zw*n83Kekwv>222eKLse%a|Xod4fQJ84>J%(wVkartm-|j^>!-&W4UStgGat#O2Vlb z+0xrS{aJ1^tUv3&?OLj7xD08ct6?FDgXhL-e0tr`c}&M+0uQOK zb|jU3l#yND!&?$y>r2rQ0GnLh*7O!VKy`kl7UkIS`LS0BzM}Y0w6iv^rD3$lkU44m z7h_0fYK(uqH!k!q5F~qFn3}_)TiLbw(^fY99IDlxG|TNmmBbS*qy_l)EG47VniQLo ziDymoU4?i|Oz0PTYZxa}C;4IE z4)>w>8Z2@5al_`b#=ikLXf(r2<<_Y9= zE5-EfZ-nDqBSu(KJXu4$mGTxZvMQeA+LX&;=JP&py)T>5F~j3nOwWC=97vdb9%m?j zX$xIVB(RZ7l1irpN`Q=#@{4taG&ZuHsD`ZdOm zt+$xOw%#v1*hs_E*UUX_-LW6F=u~`?rCQAW8^)Mufp%yX)+WR`JoEPg;QgnajJ_A4kusj8kFkl5vZr+KO=a6U@HHWD(k|}8oYQL~<{!1BR2Am3MefRG) zBTdJ6!~daDMO(_ELH0-U-zYl~a~{|Fujh4NU{R3p2m?X)NI!7?Axv~{Oc#*13rqGM z2^%7ThnYgj*EWMgJ=O`R1kY~6mN>IIPf#Q`BXMVudB?FCMLA=ng~ltSl4YN_84{)Z z6llYvX3Y$L0MIC@>}A8yjZ!YNxU-hwgTo5H)&2R#4XSob;oWV5zqD7 zU|Onf$n@6p)az;2p$y3jkD`2-$`v)eg*xE|d<0J2-oxrtWLYP|8tyaNW+jFbeToLcE zG=7aLIhzESH0xz`?szi%{WoaE{7kJxLDrV1FTLVDLA6NsSxH%zzn?{a@_IOHeKn)` zWcOWOa7bkeGn7zonv>%8f%x*2s?B=CKqS4FH?PHu8lw{c7W*l{1)4 zN!FXdW>u(kX2?w>3Ahn(25>*J)v!Q@|BUUX*sT$_!p}drI(X6PK*4Mmo&c-)H4NSa z#>&%b3q?Z2$EnJf03}US0>p1KLM!5piv{U;%BO)G(x>T)w3FB z?)!i#Ed+~`q^C5V>wS(8GyEU^!Ro~X_^!S!Y5k&LI&E)Pt^v&*Sz)>|giqCGP2EMU z6f?C83oo{0?g^5vcRKE=1$rjLX7k?RJ(I$@Zx5Ix%&E08AI1mJl}(x@HG|$sM9on? zfwF$n=_>IaugnF|Od$wv0+=eEJj3d<7}iIiWQOOM1YP$0H@=SK*rYmR^DrPydb_8D z<%OaQXnh3W7s|}Xe94bIS*FRkhyl^LObBh9YDjNFCQ&VFx$xb%h;%a}y#bYR ze73jsG!e9!b6UwERStc1Fr|>T=H(~7(Txi`bsh=5;jqXvxWB9N44rbv#Fa{{f{j1; zqxvsdqsj&bK~<}r*5-!c+Hx1w`2RgcyYK6@lE#i@OehM4WsTXSz>vl#5D^H@%O&K) zAq;Vd)5(9Go;wqbg-QSK@BTUMot*UBP+wjyN-MD0LWAMzMn?4!=DEmv2;iwicK;u0 zNhv_~=xROQH8>RH7>jF;=%=7BnAuv!=pvW|Hukw;cG$K}x%C6ZX5tNtCPF?gr2Spm zXsm!sZO$QixxssiFfYTvAj894X3bRd3n53$a%aK;rYTo(_OMc_ot@YVU{?10fWl$- zjV5G-z#YAITl{S7L^iRp!akNOV4;d8;ZJYc{+oQ6TO)HBTjb_I!j^jjk()0^q!luio-q z_u*=)ICjkTe8h(&!S%7kmRteRerxATsB}*=f35TqZ6$`}ep>*AQd!dUKd%rPo9H8n zZ?4!EPf)Jn?4jiq+tkf(ge~YB?cE{;Uy2nBaZlP&US`{iLr`=@y29$U6e%2fON-C9 zbn7Qs7GoTwVVJ+O7g1^sd6~1QWp8HYTQ8PTnIS;XtKl$O^heS7IL9SL8PZ^8xSZ}& zy@FZ|qpJa_aM-XNl5bZl`BpB|!x(AUu4ZiMT&3v18iM6H)qkV2)yhKgHN<>nKsg%s zc;~&^#-DMq=h?=csJP`>D;t-fH-zV;mxW34^4jlSe`4cB0d-HlVyWHklR?{S30Hy6 zbLhx|;hl#bv`3p^ zbcS({c4xG*B{o+BU3MU27`P?B9BmG99w5%s&aZsM?(81NIEFYUH*QQ^&`n9e+lb89 zT=Z2qdY@j(_qyo2Iv_1F+sSY!&1d|WV^UZRilzjrx3BlQMrOmIU~2ojr?n}Fv%P&n z6va(^Vkf}m5a4GUYb*j9H^jTaTKR(QQzz(0(x z0It6n7KGzJ=rSE&PSw&|J^aArnRe*&rOYbXGU$z;g+ zii6=V08k2HbxQJguc$w|_m*F8<5$3o(h4+XugWUCt4Ri})b#f&$#> zgAhU1r{C;(!Y7t*iN_C1mtr4_d`qEWUMj^dO?L3!z6{A+CHCQOD-;Bv?JU087YjB8 ziPhud87B2Ngu2_QArngU@#MQ!hKdY?kslu(KQ_kslu@wtWr65ckSMY~x^ zLg5(#x`~Ry>$H)TMK~|JIOVJMEkP)Ya4EzERu-x{eUl#dabMrdRKhV$wTr%ah@OmN zoS$>d&ly;^FIKL!+o+9Sj27Lzy2cTAaWZSV4;*&gNT?dyx>$De=i{_TlI&Q)_l(c8 z%^VI>)3Z;^m zmHV{Q8pfRs+Ua9P-qc(N1Y}%(TR^}P#_eSf1P%hUuhnVxq-cDM#d**BaL$vk?uGN# z`}&%faKb#40LJyv4^jcF{_hmV(o9YyZB6&|s8@a4d)WkG$=*e(-yIJGl?j!3yErB5 zlL3nW!!zYo``dPFrlh2K;KZhJlOSm=C(%(Sij zy{(Q3Hj2tgqx?qSxFpl*UR$WuZI2{u?a~Qh4U~+yD$flTtCeRi4g4v_#9%N+&!p)T zSt(y&2oUlHUGRs}j>7pd5-vq5Mz84YADlm~5K5rKsQpSmwxYhkDHQBUsqE$vbRuv6 zR^Z2Mkf*^(TM`KrFN3VjD*9|W2_ms~#@gQHl62O#T1#`YX)+8!Y!FG@No4eA#>7?B zJVCVGQuhcyg7>Bb5^t@sH|f7TFDv{m5{k+$GI0kxWpHtz~th-V*pF=i5&gz)0 z-x%FW$aQIk+!|p%Kz=e}#hHa+Hg=m8b%=4%%*hJhIDOo#w`)jVfe8MBftB1y^Fg!CsINkg_6Pj zVCD&&-p2|J5I^5>`UfkPufP>;`1}EIY-XM)x#$)?_134C_tv{VVKBj-!1k^gB$&B| zPXV|u7X!1E7sjPcs8YaJe$3nHjVRs-t`e+=XD@Ne)YQ73tG(nY)g;?b`P@JbU& zDGZCGjd?3{oN20iwA73r`qIubaZf0DgawidSoFwwpY~Cu{9>)`P5I1m+Gn52DBu`H z{nY z`D(w%zs?^FG71F^fJ;urE`Cw;my50G!P0qkTc#xTvH8 za*3Bdl#mo5RC}=@04bk>F0u&W9xQXE>JpffP9ngDudww|w3%33lhXt6> ztv7+d?BK0NDHti=i7qLrcub#O-S$7y$zMxUrYrQ7Zv zWsC6X^J*g#r+@@|(nlUWKk34Y7=#q3H?p*Q8)~#M?=wzQy0AHW%Se&#*MaZH`nO*l zzg}|zZ|Y#~$VFHb+$gD8JNEyKlZ3-&d8Kz5a51&^$`Ei51KRSE>aADol|9>NRJ79SE|lN_T21QxUl0oTTpK>qVKzLsfJu8dx0(e>DOc?I*5oMX zJ@4b55h0PrGTGPAE8h#)Z||rSo%u_jCmS{2{DNC_fB>CND|^zgDXt#t=}fCR zas^Dp`ogehX}!|?cL6|38PlldE+pl@x{br556`q23>;#5g&Xy`6=4iDE>Mz97&P=S$5Z`!!K5{@cST&u`QY3)}wghnqcy})Jy(+z>O0SVxztli_YO3H~ zB7bkIP`29fJc-842R8L<_I9WYHpQM~tAx>2!3KZZ@vn)veyjBd1xI&?ou}>Pe$d!| zKdoG$b2D!}sRR-?dn2s}Wdw67ksLHW`8C07a5-h5ehfj$`dgy$uD6PP{HpxFuJ*jz zlh(h@pC>=bU|+O`=nj8}`Zk1;80gEwA(Cd^G_nm^?PzsDiD0AxX@PbKaym!A%jDvw z(Dmy;Eq}DUgnm1@9i7m4opk#Kmc37l$Kh6#59r8K7BYmaN};Gs60rfK;gJ|i&Vzdfj(b5vsBIx3vzO00>GMq$ zL=98Gv0S5IEbK`g(gV7uo;T4gUA8~b&zfa^h@FLzhRB`*Vty|iZNH{5U@$>nLx{x|JPt3j@zJ8GbOW9ONdaOP=)5qPq}u^d#3W?yEqjDt*>)B zf3Om3TTkvCYy1Xu7F(J>L&O6?Lb6?|NDu)IfPmbd;`zIykWz} z(&iH>P8?9Duc$N5WhPdd@K>1R#`)24)M1HNWl3_(CjM!4i9cCwuDO6s9U(#ZxB2g< ztlBW4NCu*nB@H#7=WE)^{wWz8-EuZKO`%cnjIW)B3@%G^Fs!ACltTCQF{D4!49OpQ zAt=cvbBeEwCV9NJw72`FD_GL4PVfJ!G9-xzIW440{Gs)h(%`(M2bYNFwdGHi=TqLr zh|!@p_lclbEGrs)3+R*d5tC0twx1a_II*vmj(_1Uo2nRZWc;hEF5E4p-(`#S(nA`c zJqZ&68pMDSOrY9Klo}j{FPS4w;3o7w+_QFOh-on1nIuJE;@krK(+G>o0W+^?pMi7# zh{1!Yv<VcwX&Wxr2EvBR-(>UX-o2xS`gCi&QYiKL2 zXuX~I)5LiKC6M{8ZP9maL~~Q9LaCyKspe(FM2z1WrPRjN8h-7**@_MFSmPM-nM1~v z!a)kw(7L)i?3VU<`fV=E0kW&A7-z2vd8Wmb6@5VOd&43@Ys^Q_KR&zD}1knVF>6hmFm8f!iQO zRx=qTilEMSfyHfEQR$MpXd(niYT(NDEUky)sv>d7oh+> zL}|ky$Kl05VkH-er>hB!O0DZXEh+`-hRrK2{R_w)y6)q?zUM7=n%lkQ=Xf43c;Bnm zZhWs%M%z>QYocU}z#zDr4gu0I>2Gr7!+G`LEA9B4eup-5`$|guh1a0u2>mH(&>vQ{ z+8T9u8=w0Pm+HKlW!68y5UR&B|04}=Yg1zXw7Vyj+e__j(R;5kQIe*RH`C$$Nu?z^ zD(!i1Pk^rQ4I$~VOD}=fJNBBm{!XvBQoho^Pdxt&{RvyS@0%?DjB*k%)n0g8Q_RUi ze~NZBJn{K9Ok$R!9>H}0am)$;((y1y{2Ov_&5mKL1{ERI;o(NGeq@dDuinY(Y<2Q? zn(axz!!PvjFX)OIu-;OKT!~7#ugQq4lK(0ez9ga06wWt4SL)b7Kzo2=59`RSVu>!( zsJ?0`d|Rw=@44kopjnfvhav1OhdEh<#{@_)79d6c3bzYP&OT~c$68FWvN4a9B^&;J zoyu_^_tlo)N#DxLu?x)7_I7?1hkyAoN~MStLURPCwBXYN*Dn_ zRiv#}6X*}vd><~z%L5iNql|UFS|h2_3ZBy4N;BTu8&uT7;6BQ;#t_7gi;_yE zXZ&h#0#|+JH5e~-ew3^hU(zT{ekI9rktV<4UnYgV(yxs+KGbne(_3t~B?gk?IRTOs z8PB<`N_pY^8$~O#_8W!SPTTb)==23YJYJq!98lu-z@#WtZ*_wfF#8ov&sM2rhD0>n zs9O37k#K(qIc=hIT!@eYga_0O!5qgpKIgdL;)Y#>ol4A1 zc4KJ*?rl8$WL+wz0Fot-1#$0)#OP+YZio#wUJ{~f1%|pk_solLT)gF9+o_br>t&p& z;8@%CCBPFHXR520*^_-X0+RKq_E+St*Wb}XV|yMLWH}zxM8f$T(pxhOUK!^~RcJ*^ z)*pgVSl8ZifdT4sE!599xhnnu0HuobQZ0|aUtMs1x^NlpPHJ;ffP!goy*6^)|lZw6W_(* zRD?%T2e*`~3rNdNa)*D`M>kcNq0D+8^glwY?*qtHSTuT8Puo~{3yG_XV`K=55|ir3 z7=m_&1ZDJ9XF+3^2kP?(oD^x3W8x6!!OfYYIsGj0NxYaLqXEtG=II73)*1N`WxO&oc2-y zPQ>B`d?r2Ogt-MyDR%M>_1s`~jWcLym22?<_6AZ2!z;$g3&oXoVLm=ZC=jZerGZ#)*0TXlbbikFM-5M%k9|3TD^*+otJtRfTNRmRt493*P#YSd6 zyirXVWg8n^t`hkA*!V@@LaaN3FI~)DO;!DohJeFrBs{Vm!|=YiAQSi*_L;V|Ae)#` zrL5)orV8~Hzt=ZX_iF#jwSgG7l~_iJ8s(~)ZEe5fJ&ObK^TnD8n7#yZ8^U;HZoGphzrdHuB@81wK1~G*FwYe&| z$JSa53dX&qnjtx>khRiCEp0WsrlQtUlyJ%9zKP0vojM-Zf@7n@QF)=kX zL%mn@eh8HYuu)>d*QsPGE!qhC36+@~pU<`2Z_ zrb5DQ_Y`C_kfKJ_pq;5etcVq%nFzcEHHPsQtpxSVTZ%md1ig}gWH(wjV_%ijGO#A^ zJw3!U_J~%vGO4or3Xt|UY33totl3==d_?ZpzGg45L?CNPLbk_HO6*D%xH#dP^uw}G zjo7a=w_a|^aOnoN@eAI}U+X2W*Rm(29F2O(5B}U<;G_hP=cd)#Wa|xLqZm|zY6;rT z9nzr8hGmqMO<5DF8_$t9qZQsWWBu?~&gSc7+*X#-UGW<7Qc`K90ol_bj}=kyRzYdy z3P#Od!vG+hto?~Hdi$G)hZ3CM+EFlNx08Y<$DUyva#~=HhSO!?>AYqQNqmt|k(fm* z)yLO90S{M#@4aWpXh;5cVx;6}ss6qoGVH{l`T5X3m}~h;8a!>${c;5ohZbx(BI~c5 zl<=a3VsN%|A;B)gj|3JeDcHx_e;bSaO-z&2`%KJ=l!TBZ#O_!QAtKWdV$GAbv;fC^-Q?32~Va2Bd;#?fF@c`}oSe?&*HR<>Ks$ zvvn>~=)Lk1@`(_jBt-?ob+w=Yf2<1gJt8BSGuD!j;mCAbwy*d9jyR3}kbAGf3qJl_FUR#23!By)7r399LFZ!LU zpkw*R6oN+zhsFZNi4ZRg4hzf*GW#?;msa^lOKYE~FqBSH1p}Tzm_u@;)vfhP`#7*aI};tt z19lyk7XyNn2#KHp0NR*l9*JZ#Eud>iU^Q>*hRNa;yX35SD^*BAE* z9mhw9x*GQ?jFFK;>E9@$tsPHEMONCm4aR)TaMTgToQ!md^^489>N~WTp0~al=<&SLroWuBs)S4Xvy!Zury};gRUA&vg3n~dfFumjuFozAJj@1{R zZRNhX5xWBWuatR~_uZil#*x!(Q3eT>qBVf&w`Ch_=V;->!1^5H7(zTno!_ENJvlZD z*iDN+leQKw!V{mhX^;@P~2d7JikL zMyuymXG>XoBITQS>NROsOUpqo+o*}4_g=Oq!f-F%?XvXn)nZ4 zUu9cNNl(%rhQ6|@TKmH{z8m?sJ_r{dUF~GeRKLt!es@v=v+lZ3up@#e#_=3C8x0AsM zm64a9LCVGM0WfDHWNXOF!myl~{!43D6C>-xo8QxFD?p5R?I#vl#N5P;2{a)}(Zw*c z-C3VbFR|N6WhDMD-Vp)PzAG=-*Yo~XqC}{&0N|myC*bx)Q}4__dyaF+M{%MqwwCOp zl?#z~k}X$T=;wNz$j}$ODwL}SaZ+j?;S*>fUGtt5{Aa6Xh`}QQN}|E5$Qys7?R+l3 zY{%!#xAzd@&@BGdR}KMHQpVWl0`e3WbC3) zjEh3HH~a`rjk@8xy0AU!9Z>X4Z_Q9O_14d^eAI;nvS$B{-;v(%iZ!Clsk)NIQ3j1| z2mCVsWlronKBv|MpTfm+HpQym{Y*js2v@Gp@6_9zS(Zw=3gN5Wub5eRI^0ZYXDR{{ zNY1P7qpWszpQXl!C&I9myHv))a_W1Q!79vl4pC^I5KScmF!#B0x*V6cc!`#$g}vi| z8sCS&oxlB*`*ih|M~7);fb;|+gK=KHx$gC>TRW{g-7mcZ-d^=UVc34w(!8!athz7rG)G1$|1>s z`8y#~4Ts&~M!mN-e^8{o>=r>V1Cz{~4@w#3@_9(hw{_FtF>WubOTct(gZ9#TB;B6h z%KRw*dWgZ&dTswT(6({1~ORwL^&r2&xA4lb&w#f32^ct{$a=_u1zTYK~DD^@@hPjvU1eYN_^f) zMA*+w6W2H~Bh2nT zEDRK-+O7;?T{PyajmAt|%PT_4+?H0_Fjrb^SJs_F$o5=i763adGshVrMu<(0QoXE) zsd7qFudF2bRby1M2Mz1(LWG}b6bV*Zg>k+N?fZvUXXgcd1jaivX2`UVO#QiFKSB+i^m7h%+Ls6I}oGRe{?d7JdD$TPoYjph-?ILfM(q$6NN)(_E z-0x$iTSnkJf%IrDAWu(Kiz#9iRQ6#Yg_j|;Czu*|neh-sQiT)}=NgqrsvxW%xox#)uQEj+R^|&hDV4P6MR}Ibt?V@=aLqZM+Z>?z z-jr~%5j&fQ?AcDVww@x1e>!U3*L~PI>M{U&fFk^(=*vf!dz-j~5;?@M8uEiPNcKwp z4UsYMMBtim3WsU$?4!}X7Z=vJXe~`|ZCTxRX$z-r{0O7)yyM1kmo7}HOgw^1`ty*4 z1)j@W?bguya4*G#0v4P$#N!!=4rIV5$XF|Rq(Nakz4)B^n8aVh=0!53r0q#w%eM|6YQ1qh+3*z9ZufR*FJnZ0H&Z1>s8z;mc2?^{`g39S z!$`50kc8c#R7=q+Q^*NC)o{Y@Ev-k+zIu?qRx3piUOrf*;8a-sXgPI><)Whqn$#ATY3BaI zvSbB;;)-wKj(^3#bnzmJCX%%g9+pY|U8xC`uQsYgj{ZIsC$T|jU8-%V*t_kxmLP9h zva`3g#kRCEcmhL;tPY{lYp;~vJKp||*+YagSetdJjh}YKYY1{_a-MlI$(rC8&>cjM z%}-_C%1qAk)*>%zO%VB^4HW9&j5ou3;@=MgOjJIcjbH61x57ZY74>hgOu)rzMX=P< z8R$eK1e@rgv{e-Vn(Bt!0#;^uo~DokSFIOv{nvLKp(Z@X-JmGKiMDRBGo|L_XM8)^ zQO}8f!u!}}zETpKNtw|n0ML7W&wG6(KRbWfJdSI~nB#g;nfGTVa=hY_I&5KP5-n4y zg!AKkfj%(M{5?_YP`Tv8pi~e$ybeRf`y<*bCf}W1$!BV zCAX<1B1efbXEml$>_~yK@pI@$FoX1th>x+s2@Lwa`oT0w6qX#3XX4`W&+LD?9kjeAuY_g_onX)@jPeJ_hkW(ej0g=<0IpOVEs}P!r;=* z%sKoPB-gMAST&!HjZwuN<6rf5YCeBp`Igsep^ZxU;Vdt|0qM^^i&l3bu8~m)Rug9m zy0ySuv_z;_*&!;~9mkqy&aamfrtU>y@Tan32%mYPCpa-K1u|>Lo69$v)KBRO#rC0`ZkIk6|#r zcZ~}tg3iWR86BE9$Muo+tx?IZKhJTdlKs|x(vOk(WYiD5BF+3IU5b3bta1ijw_ef&#cGPq}cTfiuj~n|W6p(j56~YhPcWJ#caK4z&9 zYJ#+Gi497=3#JgA2H6L^?BmQ%z)`#j4WKEZ#4y?hUJZ49)UWsPW~C^viHp1lrJ9q5 zD)*xJa%&Zsau(#)j|tSGL2aRGquOqDHtYy)1l8SqsYD5admEm%P*j6+vK4Gt8&z)s zZS-Z3;G5raG)8a&ejT{MaZxI3TV03Xm%OYYSTqK4)&qVa%Ni%(7WkR>z83N6q!K`! zYpFT(PeUxcrx2|*Vzt=-lEK1#mG~%)V7xNK#%WY3PLqBMu#wpvAFGif6xEvaq`-^X zg6(;IuP?sydqVwgRxt}OuCY8sXpL)JL#o#=a1^MoVU`FBK$=ga+&q5<=n`*W6}hSv>BXoA>%m>O(15EXbnX;AkTpU{dCCI2fg*X?lVrNC8ULGT;p^GT!Ka6#c)>gx4j2}v+&q^{TO-e z<$epkOv`^nnY6YV2LF_Fg9@j7%`ogGdsuAbB3BW&DQGbXo5-sOY8{^gGBcozWfvz9 zlrGB1==26vGI+?4PCs7*Ya6x?X`qts7$a79wBzf~zHjTk!8tDbT!h0&rD=$t(iMNB z`I#k9U^#$h!H@HllRZFmT2H(elKpM{i=G5ci7)bE`e(0KUg0M8onaG2vBr5T^gf1+ z<8yp8R8}(a0P=Dz6Q@OnoWe*qFL}U1TUlr{OAF}Bnth|ZfkC91&-2BM(y)8U57@2k zM|pqT*scw-V?zA&jrvNyZS~osSVr4Dr8zSUo6(2!t6$u5 z&#{Jn)aA077%Ax@PA$5*p!;#NPXI`cJ(G%4>T#UCE@IW__;2MGZUf{l3ca5h)4y`b zyjkmLxejS|W&pdL{7<29k5t3^MqYNMa3WY)GPH-AUo7vz0s;0*Ryc%ai>oOh+LXT{ z*^`zwXt`FMN7{&b>!Nh-W_%C2xx?K$_45=+@3n z(6)T>r-=|p7@v6dKD;Q7v*EK|aI6w6a_yU0snp)Sq9>uh>hLGzN1bek=RCb)MMP`W zOaOGnLJt9iq~GkxwpT$STnGy`O=O9cEzDlJQl~8=F#equ&@NFh#xkwugHkcgnABTw z4C9IF{ejvo|C)ooRf~ z>cH-;=R$RMCDm3zuSH_-o_cWIKgQRzmLJrymu=6RDN+CuBQ!?O!-*DHd6*@*a63F= zv3d)3Q?TZJ-TGaomJA^ZvBstdwpfGr9yA)>IH){FFmaSVh8vwdehGvCdBe1uF`Ah;ggCAKH2lR!ts(#t`$yo|-cQ;7=6uGUj%#}`?}<6iag62O z+KDrFt_=Cik2XHO7}uXhix{7sHxZ|KEB#pc){3@*x*4};(itF>fVb$VxhZD-Ew>?2 z-qQllP^W|pWdbkCrppy?#=!==OBdXZG+rl^JHcbi?NRz^E_yQx@;ITe@b-D7B_1yZ zM#g_V@JomfKV5J<@h9-#JzV$kwL)q&TvV+EeOY;mw-Ic3u|%A{wTfezFMZLB90<(A z1U6xmbn(t0<6-qs@Z{7~r{NkVD=EiO6==r9$bZ1N*ZA)<0hX6+mB60BPIQYjHAX3# z?ZQW}_nn9ZTFOX2-)v+<>F)yCWHl_ht-q;na7I>bJ$o`AimsA?1ArDpdAPjv2v5Rl z3tR9cCDUtETeH2d+|_zj_)xk_9A70`mj5%^PxsYeXDge>RJfcAqZsBLwGYl~9GJCR z%KS=f8E0Ow(FLMOy{mDJo-Y7%gwtLlplMurmLYmuNg@7uVnGo)iN5VY$7Jz60bra> z&=im;NUGhUH#uemUs9AejAoYR-Ur5S`$+Pw_&S=8GGL$2AN+-_%^!kg)zEE}#B+5k z&F>|}*E8vZ{kv$6edM|_A9lVM@fmHk>XQvL0T6wC61GoKkV@`je=lg>-KoGhLF#-f zsa`+KEb{PXL|UzmB5mfagg)`wf+npbQ5^esQx3$$qam9G9&dJh+h4=-hob*>H~EH3lIusAFLse2nGyQ&*d5sA&dAaXQVd^ieKxm>&GhKJ;GqabNd!&mrR);#dH1e{)+0LjrFf;ltuoTpBn-b#&f6=__Ti z6Eyi$&4~CYQ42JBTC;U4j<32`Q*E4vpm?(5I?f?ue$H`@GuLcVyisNvG?SgRszAMc zs!zL<09$&g$7#{;;`aeXH&fOGNjAwb3fuY>N$9Wh^HhN}>t3%o3SQC>Dp}aiP%nlg zEGZbxqsREqgt@ml`!k!fIVbA|MBl|jnvuATSb5?e+*&GbDj^3{g!G1eYz)_xI4eqC z0PxCrsw9Ai+4(hX&Z4*ZewJUUzOQytKiVz_OV^94g_R-I>ZX|K>frQ`=Ull@ruD&M zl7f<8`4wHO?y(<&R~aTbZ|6CWKL61mJ+a3Ba}9Y2)|MoZ1Z^yCMv1v-Bf5QQ&>|)x z(bB7TYkT*=u&~h_lBZ}=Y)SKD1cCK5VPy-4=^mL9h^<}n`gNZmYO9_JN z-y)ijB{5m#1w2E&c$)$GLZsCVtU}aF#ybSG%^KzgGgu?J+25GYAc#qN1W866eu;6L zFKa~E7{%&!Pb=#pYso0$yw?b5oFBi}hSMaHTV+@H{vNn60e?$un81)`FPlw?nB(N@ zRScrvhq;FAvGKiy&97bSaCr|RcV@lSyo}afy{%F&>EZiE)pfkXw6v>PdmqaDWO&x4 zPKI+B36xA=6;FWLF>z@`SA!`O&)^H04I;17j$Q46^_Kf5A$hO)YG5b~m#-A-!|MIG ztFHRLtMM936D|n%E1NdF+Q{9%Yy(Fht4;#Fuc&cRt zES7Kz3!agBv&pIf@7X><+S5}_3k-weI7|ggmbCMz&1$9Iyrd1ACvI~Dyl5RmhG%acmu+%Z8tQ*1eYOEWF~8TXd0rSm?(ALml085( z*OT7Icl|xT06yxT_Os2T)Q)Y2ZswT+8f88Zk+gQk)rM|#*L+*pXsZ=DVmStL{5Q5i zCPwh|P3!?{J-np3k*m8sdAH*J%}3{+HvKJao}Or*gHiHVUWQG|5Pc^t24oVJwV3R_ zu;pYvm+p{8{N|AC*G#f8cVANCY$d9X!V~@I>>v9Hq~Ht|ry{(MeT3PzZvGgdwiRF> zL-x7ws@@nj0hhcz5DFAck%R2TlWf;s=C0Y;(&mJZxl^~cV5~7e`q3fd7;PrqBZb%P zVHTSMfRO86TMxOf`heJBYSs7|=28$R@RM0k87uJ!e3>t`R$xn`v!Zu)A>)J8H;|A$c9N(}OLvDw6PDVpD== z(qB1UfPJBQN}{K?_Tm@9z4FlbRi7vs_5CyerZ~q(5xj?zw!jdMk@pZ1)|T_Iw@=4O zJ!j&1Fkc#;0dsA_Rzjz?tBg`!FvX4$uZ3tMy?b23@vw5uuoecq7O^0uh%`I{k2xfJ zw(jeTulT8}HIB5#mNC#EUgcr2+M`;S`7}4$6eaZaca?Y6TT`-a>L0^_&QO%pPTI$RigExKF=i{qivE1z^RO zwW9ylt08zX61~{8dyc`F^vnA6G;HPQ{4j%QdIitkIZU=#(0kq|;B=JR zpFrarJxw=K8t$J@yZ^hx)v)Rm^MYG&9dIE%(kI9f!ZWpzoZj?zS-DRCQuFo9$ zImh`qa1GgMjg1nF%dd)_2v1`a9Rjqv9R|>NCe`T*mSjoA+vt@60-bJ?WGeaYSzjOW zq`b*u4`rTvx*l?I@rT#Mr8h3ibtM0tjh|!7u{tsmqaSI~PT>|>8s46bevcXhXl?TR z6vdf=#hWK;aE-zW2p*G^_J<#90eDIa9saZJ^#W-=-fBU%72zMgIcV3cAy}`z@q)og zp!e~;Tpc{dXF=oL!R)9xHDA z_hAGwh?2jUBgkGhF#Ojx04sm>B<*QZt|GP+|KHOS-tUwckeH)o-+Pp-7Tk89L{^JH zq%Tq2E?^kS%a*NP#b!A-5vWMz1lr~>}U`Lx5XdEeR##8Vkr19kJc!m>(TtOxk*6XD0xxJnKQp6rOwHgp6w(j^%Qq za1s$&c~a{1aSLo~3=J@>&sr)q2dEF*#IeSS~#q1dQf9UDgoOiYV&p%aIT zplci-i3`bnbZqQr?YkjqIFTPcRWi^_6cd}|bFBVEvVMiHu=*<5hTuJE8-XV1T6$@5 zQW<5x;OIMr(?Zaa<38^5d)XJ%u*UH@hADcu*L~dbw|?#3*9rKUsjC>T1?CO?oG#!2 zAfe&CpA-9tMNbvj?TJ05fn;Ynd(u`K5APNWNeW4oPxYxbeA+JMlfQJ8nRuQ+rx525jbNraSil zh02<76Y_wi;M**NO0VSNu|m>BXuW%WLj48Tg_>)td5-wTV2BxZF6~56c1@aWH0LNQ zyM2VA-ynL&1!?62^IrWSy}Yfp{4mu>D=1lC_q?KuDLu^ly!AeZjNHkjfCxzh5-n}B z6u;-Y5isKkQr<|+Bmz=nW&3C8CzTAd$NcD!j1y=c`82EvP;(Pk)!f&u_qq>Y93TD6 zkB%9pslvC*R^Bgqzx8&4M_u#R+H+dmO{DkQoA4Nfr@ z9V45$ewM>ntdv4{ruMn&(U917%d6>py(`a%IrZo9-xIJ4QSU26+w6ElqJw$_nJ&&9 zzeJ}U{io3O_9osc_OzNX5qaTp!t+~e`M$(Ba(1^VVR6ZpzEZw+Mb@{5kac#$_u;u| zxLJvOt9YcJ#B0%V+LO7G{e(hVZKIv{G)*wYX{FknMmWxN;o$1yVk5~4Wd^f2CK=u) z5NC71`91$W{?@-u=sAb}wfGoN+-d!$D7AT;mvrvvS3<^DFK< zxM&U&7A4uNW|<4P@DO7o_$|gr!kYB_R;EP4J%3McZ7~kgi0J%Y_jRB5<-WlxZRvP~Li!j-=Etzm>Z)Y2`z#~_mmy4P!3GACi|DTf3zv}<4wpVm39nZzy z*K2FbJqFqYmM2DWW=#>qm|6`1_ddSz#rOF8y2ZQ*6_kc~D~@}{7cj>)&JRBGgU>mx z6zdDqCk{`A5P?PWFF$Gy0UkbR-G6ONQ^Ab6CwahV0syO&?YUw$JcYf~*PNgWC^mlr zq|$M~X_B8kj!SJOenN(K25^${b~J?zN- z6Rd}YzFO4=%X<`3KyjaHJ=OROO}RYI~zVUx}ui{nrRiz0 z>UgV}Cck=L_m!sZ8Qjf1gy%;;@DIw9c<1e(n+N?&74*1|uYBb`p=18F{=x_2!sVrB zjeSyY#lr+Qq|xq|)rYQpDL6^iB%D#JL5Q1>de@Y&s`aLj=(F?|_KC(b>W#I#u&2Pn z>MX}J+M9UmRpobFa=fy1*5Af?`VYySBioyJUsUp$>TR{j_2n(E{=WDMpYR_NPBWX> zU)|~{=f4DCdifUv=M%eMG}6-56!}Ocp%mu?Sn-l=XU5ykX%%QYcDb21LPa~)-m`~d zx+kIEy_5pf-@tUwUEzfsXZjtb&m{ni|9#z;S@7<8U*GdSZ^1Pdu8)53(IMl+zPzUY z%@4HNZ@YBa1W3PN+1OkR*Yqzvo$fMSz}rT&FYyh*d&<@$qlF-3DOU>TucJ`26v^6kCH1!;=xHmuSm=r;yH;);WT8++cAo~P;qDF_}gG`D^-n! zUA;jDV@@apD?hP)cEZy}2f#Q9mk?}LVeLsA!$}IWdanX|t}U5YZEh;W2&VT`Lux%p zVyn)O)LG#i5?2@$mb)j0`mT}x8kWLIU-HDEwded>`K#3zyhHnAdW~c^EB9sl`)lW9 zFfJFJosz)|%d!sMR=?yA}WHC#6w-IZUpx|`vnB@Mf zIdn)vBEY6@RUKu3S+25AGBYgqd>pst^;aw;BSS^wdExpTyF>sXua6Lc@!e;?|aEeihV^s`j*(w9IHnd zB8Qa^i;THQx)7VuLA~;Km1bgHIs20Zo*Ew(Ow9P?P0+P6#@)w#_~ka&=NxBx_B>7} z6~S2P>X98Gjm`VA+4^2spYyZ+x;{!bK_{In>h13YZzC_+>y~;P2H*UcWhcSV`~~0Z z`}=A^vCp+^P!y?*dTRmefoFrtWl9ox)=_|r?33P9+1+Qou-5c5WC++h8*_D%YYIK(VTS#t4MYoL66X# zV2U1dzZ{>k{)T{{exH~;80Uldrd2u8-m}Od%Yeah>?|!`DB(r4Qs)p09ZWkpWAm1z z1uSi%$(WFYE!o$6hY%@*0&vi`6kMEX10~y3}&E0$wwE zE1#|PQJnG4ijCS$q1sQ%3*012L@gi$Kgm*v+_%2MR{8l*ieg65KJ@$~m`dXMOp)90 z=^_AQqyX(heKim4qDu*$mw$b1VcKWCEu0r>-zWO$IQCnBUvQ8YVOO`ZTKPmZvQ+cg zt;bja=pw=oJHJ7h*w^k#R>Vdh+OH|9MH>MjgRxEVG5B-|lwbjNUMoF+VbX$s0$A?@ znx``juaiz?Ex^yt3fXU!MrBr1MJQn7GAc$NC3=W+Z`6{wN4yn)-~v=Pw>c^{|8-r2DDBlg-v z>-RGIn}RQh89%-*5Pptv3<-Il8S;c6$U_*;r_!#=QzF1NDG|iFR$8&wpyPyKa0Hd zxLSH7hsh=6rPwoRwi!=VZxS4RVw^QK+y33A8oVuzq(hz$rMB_hO-&2D3jT7c9CFy3 zL?-R`#FNWSJQ86%DQBO>!YJ~0SlRjz>}l5f&HTuT^X0McAMNxMtv;bAKJ%4#Ew813 z8yxMOR2}?reLh@E_8f-9iJ25#vfaQ*fu!@@WI?I%}>JT3^?v5v3aD}pZXqS6GT%BKbN@++xV ziYUBb1@OFc978_(nJILDtdA1Hkz4Rzu%0GB-XS6N<4+hy(qftZtJNH`+oID?40?VO z2X-rIFG1x0w{MfYLKd8L-lq$lCYG5xvodBPj7N*EbqeSVxms-PwG@<7+umGRTYXiX ziS43(Ow?||USeBAR3i6CwV?U@fCrv0-)$ML7v{F;MFizdiax=RagqPSAnAR5+MP+}GR*k{5W)A+!qSCQQ``af)EXpl>~pGr3Tsi*TsX zf&?tpE}ne;~H~_N0=nl-n^eueIU$s zJPj9V*j3Gyd6lZ{1Ze+rFA7SBtoQt$w|p1YxU2^dz|Q_4)*yzRiJwDu9z6;kL!Pl# zfsIe<58;=ht)k?;()2z7`nbvjAQbJf`jL&mrCp72I*bz&>L!94Q*|8Y(U6e8|6e z;lLTKwZ<;f2Bb<4+&hk=YRnx1y?aaZWftEsPW8I+aw)6FfO<@F+6)wHULY9)#1w*H=;^m(uN9;tOx7aM|%siN+DLL5u?;jqq_XYpVy8@-u0WXoGx)jH1wW0iH6tHA30jB%g7p3Z3d%>p!p zMkkV3kK7Li8*RJWE(30C4X?cBM% zkQaFJf)C@RRL=fdu6@N!0ax1UM5fp-3*|JqHHGe@6sdrw`N z`;N?ALit;IaI-n2l{G9DYVG)(q-1R?Z2>MfJFM|*cQdS>#K!G4yq~b&RMUE+RhG&i z=DM}ie*aPipXnt?_C3A7QS6f0+TLm?hZ*Xw+x&*VZq;YdVjQ-Z2Bm+O@o~DElUr_b zk^N=kX$GWgL967^FN~!OmVtGzktwLKWRCYFdn1&O@xRXW9BEi&ojqP2X11L8ogW!W zUvoOX^2L3AF9*lMgA9p_JVH5ye9jL(7&2-m0^Jc|7h9`o3-Hq^07$R^okkI0EMQ#Y{A>NShPrwjeG7#T zLp*1;u&NxL7oS|Qv*-!;C&W`tg|Lxem{>Ofw>VzDnAYeUO!Ai|)Y*j2^2%iY>s>1A zg#QrhY4+R&3hp7TugZ`9!FqNdx7t_8D4@6cYa_Ep%=Y4QTgh4v7sMwrVCre#Zf8q) zH1hWkD_8z@m>QPGd$~Dfhh@(T#pDZuv@!vIZv(!=|HMF|Mcr^JOymVKtrmYo>nnJb zJxX@FYx|y7Z~-K>E`Nw~PnKUKG~eA0Zr;dzC^o^r!T4 ztX6t%jL`B|tZfgkU+MP%`g-2YDy>Y>g;?)&JLBnQ!r&pqL6{QnhJhjAomRqKy^q>a z23dAbN`{%4;$Y$KP!gu1o16rX)wyxno*rds^d+%T^H}9q`+!+KDxUwrD+(`o`R~jJ zKoJPKmm{B_;+YepQVVi3d$K+5}wGeeOiOy6APd_S>6k=TC{G8*8sDWSDJ_LWE=h#h~3Xq(T z`q0(0#%T+7kBr`I2C5zl^@{TNeiPhE*t%71IY zW^TrQN+S~hGV)8S(P316PmAPg0F4H-v0vRIU+T2Ii+zfesw8d}b9@j!yh)tVTJbszWoip9FyIRPLpuYltGoa2O4 zsHuN}%{yM^`_1*X`W+5yw++G?EN3kr-g1IQxULWWZxGJ!Ex zggbc%G+?8|soK2~2AHJ9C#|B`#p0uB)M*hrJ2%q^iwdjtUU}3=s8jFD@{qgpLHDlc zQH*GVA}>2Cep8Dr{}&A@Wxz^@;6L*n)g&c-YvYF?{r?qqNt=AEW8UuLK5zgYRkK>F zl5;69R7S9i;9K(5oAL;{ip!-UE#N`zo@sfnk^(gD(To@^6pU5w`c+9|W$q&Su)Ot; zm~jdi`N0mB2U2pWr)6i5u>$Ym*kz1V@D;|Gc7)&1yTV-mD<*+pS3kL=8s2a9fZy|N zWrjFhE+wR5=W~vjt=XOoRD@`la#s)Qnd?1hxHP6Z{;>MsnJjQ;XgEp{SDsA zpmsyxA)Ca2zD^#n7VyejS2xWx2;SZh!iM21cFr8QPAV%vqp^?C+ETQT!Jp`BNV%n3 zF~6s~3t-2Ff=6fF2L`~JMSx}cbY}lPRHCQ)>z{l^K*`M z&-?mb_QkEw9Ovgu#j+3)BBn7&i@P5kNk<4wA7T71@(0iInF;4LDWufJm$>(6vVq17i z3fi2v-{)3es%j_AWGqvr;v7YKd65d@+5A@u&?<5!=(UtA?1&x@@m^amWta*-Kl(Yx z`I(MI^n96UI0RxV{AG%v)(OY}}%xn77`a>+Il6cy7}3=M1DBXSF|SCZvmf_X6^B zJ7MCW-AN;2^fvbV`6jfrJ5p{WdAB9aCN;3_#%lX5DO2lb zYJpvXnxohBrbZVJ8D$tiwO9i3OLRmEF(IT5dTRl4mYczmv)%_Cv9e567)s?ZhfWiZ zZUP(g4Q)A|>t^F+Twuh$7u47-)P4?p(07N;y%9ez3z1{SH!fgO8}rA4zyeyTtjRi2B|F% z_L;+SFDODKm}Ln}Y-@<8*(zNn@+}j~jl30WUv8c>d?JBierYZJjHZ^|hhGZ~f)fK8 zmbV#<-a|;w&pGsS+TDToq)T_EsizRk?}eFPDO=-A3x>@BAU-pZZ#W2Db09zO4+1R$ zpesg*(e>6@WJCBR2@;QAh+s_U%7)fNF}s=7@Bgz4ujA%4g*2P|$}ya%q(E*#1&K}c zUfrHLTi73llz)&teH7v=nB$61FqlvKGcABLONd_rB1EEAv`ix-^!%Qbt%GcHfuf_C4MR zdcSlN*8{2vNOM~=lv-|#p<-7wI8A^~>}Zj23IXxF{QBz}@&`3g!Q-T)>}Z=Y4I^49 z5AI7_+c;|zit{&JLLp)FaglgIo>u3L;TOfplX+==TYhDjU{UFr(HsOPRdEF>Fm${# zL>b9+iP7$T@ZN7{(u70qY_wiia8NOy&0C8b=BUi%A~O{ODG0aHwDq=aD8N4G`m8fx(Rm(@La#6)Tma@)6BcDk-Y6^>0f)} zWld(i6zdn*kPuRWD@AEYvl42v>-1kz80*wsU&mB{f23=KE$`8LnRI|WGdQkcJZX|* zBng|~)Ki%&I4U4MxNYHbz7jDczhbzay-1jv9INMV=^@R@Hb(U{$!pt_X8ZHaeDI82 zfi!Hy;iHw4wv$>4I89O{i0`YeFnY*by}S%be1z%+h60{TX}cOljb4H__av=#eY}$& z`IYp2ULWP;h4=4}&<&}%7NzYDNsKb?6S2G*qU zL=8k;=TWDZUZc_G@xLX4gl-*cocS?uASa4~H;Bl%*KOjw)fLIpMQ1+{P3YH_5Jz;8 zbzs>7iAA4FDA;cyzkJ#R0Pf?K`?_bGc`MQlPTHUHnu0FIsk8B5NKOv!PzxjQ)J7nb zXq$RdSx>*OivzO+@Bq{!uZ33keogXw2By+9_UV_=Ous5cZ47dp1+j&?rQf}5BgBJ( zZ^>t32$Rh1YeP2T$TC9z7A$SKg@FR(|4Pr9+iKSLys!Jf@vuI>F)^AYpCcE*I9x3) z*XP3n8a;xK#q2$FD5nProR~w#`H{co2Ok~Bkh;J-1h*J5$2VA>wsFdS0TjF!#IVZ8 z=qcABFH$u6f<8*wX4-$Pq;$M@YyyUR`0FYRxan$f zn?VCdAFD7wXtN;C;p!;Sv@m692yriG4g;?lm^TV2O<|GUJ4g_CmRHism9PzZsC@Q* z4U4x4+H^RC!;OE}m>+GTxMct1$$Ex_zMS_&KP$+-X7ahSs#p7)=pC{hwe#3txfhn7 zwZ8m|hl{w)1V|W(w7_Y;x5ys9zTV$dyA8`9eK3J7y?WB%tB=o?YZu?u;Q;5Lk~Pve z+l18}!wsLNI8}{j-JpZI(KS#%&Qy~J8ydg4B_BQ7CMfGQB0sSPOtCxI)l*>z7 zf@i80)G<}mSo;yLq&Vskf*O)mQdw>A1t9v3e46OvmbHW9Msf10D)yaMvRb6YT>P## zD_dfExU#)TQbpi0X@zO_2Eb-^2qUSwlpH=ugpOiH0~~5jFs~?IqD&sD&l0#<|j}HZHDcD$v4g%I|ZgZ7|B5Z8r zRheQ*;csvOMRSzcr=ou5wf2HNGP|J5>S^o8Z zBZX?d{-GRxSZ8x=a{M*-L$!fXE5*{<#1Hiy*lX@B`g2!Blh~iz*FJ#~lLZJvQxm0` z#Y^sdw5t|0F|?;>K{k=u#$ww|C}pSA$ZUDY=K1F&(L*xpEfg!ylx7YED~~oBN16$X zX`!7(jM|{Mgrtl4yNym0wWCMUPLTize-qhsSHHQK!e3{$Rb!zg6jh*TeJ%n;#W#E? zAWyuF5XWjj0yZZG=Yo{QER=`Pr-}wU6t!e9W#Tm@RpSPw6H*7AityNeWr6n ztp0Y)k=RVfFLD1#S0f44Hi&+AD|D=J;Toqy@-R}!IEJ_k)0HO1X#tIwhEaRE40GIb zti0#^{DJMB3VWfPg`mDbpB)Jtci2q9hP?XxI2pHC32iT<2&#rG!+6FX@|>NDy#|g3 z4UDw1yf&&Tjy1y+pd`t}0RZ8Ft^6a=4hy=_>q`5jKc0TKx}`^G`WtTA^IHG71??Ox zYo!!7Y}d|&)Z2NsUG!Nq5+g7^gBSb9?9PNJ6g7xc;Iq|gr|oqA`=$;f*To|mX*KmO zl&~2wZGBB;q}wvQsGmsZOv%V5_nyhc(mC=H_0tj`SaFJX+*ifUSo9{=SoeaC|GJWC z6+wGQ z6k{^$IV8ethGdweL6A0P>9LC?-~wKqlk8(HNXr3vOKAqii4+rivYGWZ3j3L&W3-^y z2xC(XP^B~sUod$jq5W% z_#8t&8fsx&Hd8@SzW%utOY4!tcp&fq(kC;s2a;u0uN8akRyU2DX*!6&0w+>I*=$eXaD%(m!N_qL0=524|%QZfzK7Bh^I* z_I7$wAXg9?<*0cPFQZH&%i5M<N);q*{3pTq_jg(C(2yE9bNcy}S z_ld*jbd^Uk|DF%w&9Qg*2@f-vIwXT@IFWgwZnO@5LH8W5#MxS$VYCa{l9+J0j-&PM z{KK_U;yBx1AXF>HTJYQv>i@DBk`V{4mRw_}T4=khx9}%VMoXLE{e~S3UPK*izKO&n zbFl3tPU;6hCJ>@jL~EELd|&qgb#>7AEYL*PQTWAfi6>SFAEg`RqPJvx2av+9QIw2X zx%EEn{)TaVbj)!%*e6!ILO4^&4-ShVxIW^7;0ZQdRhrZt0LH0?htY%Fv^(RWq zEzb=~)_uWlwdZ&E@{fMC{g<`A8>EXLVB5_bL^8|#g8q^o{xPNB9W(KNb$j#HhQhha z2*qUKIa=1%dZ`~+trsu*4&_O`1R$4>r=%IP3JyW`dAp@xQ7xoQxsvN^7)&70TfY{6 z7Y>z+qPx~GiBpC=IOIgGH-_5VhJ}@!IE8pb|Fq$TjKIt_9wpzD5I`;ghn{|6{cPK{-usHIS4gIO;;S;?^KDO64>6M;@Ih2 z->5WNN3f_Bl#Ng%_P%8vQy1dPYZV$GpHMAd(rzUymcUeNW&!kN?c4S6e~BBFwcY}l zV$TW?YlBN}NzVyT=EXfe&iYN@7<}e{YaEa>YfZ%-KnW*dumMoqL;r{KklIVUzU62F zu7Gy;T57J*)KSjSuzvaP^a2^!EdtEp0DxAHKZm^~JdC!|Cf<_vx`TMB3Pl~8Rx0k5 ze8b+5nmRAUSay~ilAeufDJ{1YoUPxfdA&8~>Hxy)%D{_(i&cP!?_jo-F{*E+s1GB4 zb%P|#I}IdnpVid*OSm>iol!4PWSG7!uXL;?v3%@eRLWTLFImv`0RFCJnsVj{z? zliB0mvr)WwSx4scXZ?QNJ^FO-4VO0g^?P@MZLfNIF<11GCFk}T3@QKqvE=jVzDw{_ zU+dkZVe^`La(U{{>$87Kn#NpZ(=bZM zc7GvprAb`-F6Xc?$_3mD<8$HsTu{UKz?DZZnmkxV^3`@$pwHZ&134z8w;7f6kjsup zRz{N)e1V655#`zHwU?L5W;uuDBvxa8NIwQpy_*u6h10v@zQ9k|K;+RS5N=5V z2ct*}p>I)+x8bfBHYJ2ZDa*;8MV)bV!n9KlZ}Zotw;>!r0Hze3%lsM3LRe-@pbvnx zzQdG9nAiEaZvOBG_A!TLLwzN4vA3)Igwc^v8fwZ0VvDi zvH{9vb)OAP=pwDEBoeTtH>7%WfmCVD-X7MShqVWAfKox~uF(3Hmp0aqr4=J(QQhkQ zjLkXkfwNds{E(Rw%LIb74>&u7pSOQXSHk9HDds=7Vf3PTXoKm|qKn4YIL6nq@X*u1 z+#vtAF)y>5r#FbfG-6%$73pKZF@8Sg61hBotP|AaUj?0UTCT6RvZD$cONAdmw+b7B zi}?irryF}30c0g}nSVY(&#&ptE2#Z{&9UcRes33k5g?)zxGKi*-7nZh4Y&Fw#ae`U z%1cgu(8L>yb}B%*S{8Bf<9D^gf9>@m_+37AL(=q-a86P_UJrmq9wU{a7gZ!lp>T%6 zXz2m&Rs*V|S%lw`=g##u{3Am^@jCPGvrmiw0eJw-Em-|rv{3Iy3hFdDSU~z|Uux4` z;2e;nmkTK7gXJSj9JF>yM;e5n{78z?3c4==o&OqiY#z1MW_=Z_vi4|VQRZqR6#42( zlnyPl7xx65XVR{E%S*jI49$CzQY_~?`iZ5+pthztYI9)n){A~ew!d{ZcAW97P44r< zAHGkTjZ^>cq=?qhQ#;Wy3Lqc3Sx+H9gydj1fqSAZ74*p}EesU?Rn~9KQzdQ<^NzmU z9vUh%anS`}>Wdb{xQ8LvmdOnjbhe}pi(waEJ17h z0y^+7`D_2=*&1=7Vf?;+sr=sRczI&E$98+gFMsk|txiFvKd!gO`MtGV%LnY(<-N8| zot0YZkBaU?&Xj%jCW8<=_^n2%HI(}+emU|&L1BruEd2w;DS=BHHEu^K@RRwC_b%HD z4BY2E@9T4wP{_;QjdMxk2<;yCg`qi${80&Oe4ku;2^Q~9rZPX;*8M!Dyc?}pWW&5b=37^2HSX*!+O7>jsehr7p*8jM2Rg`hoUMw zaaSigJEupf;NW9@onPyiAw-jq6|lCv=4kv57C z=NFS49M<|#;PtxCdwm2G6S%L>_*pl{YB}8>M+?_+;_Ce*wz1CfknrueGBW_P3Hv5p zHcS2mDc>_A6dII$VE0%H&G1<;?z)F|)jdW$|s7Thl)%ucZ zj+eZzWq|BQX-(|D?u)V4{nEbH;p|qzLZ&%i;qyauiJ7Q!$6W2mYOCq-w$VmiqjVv5 zR{;jDh6gmi1rkTl3pp=R6;Ee;5qmzPWT3;+Rp>~nxhg8e<=HL-otKR^-=p+a0(2Iz zYq^+GQEB`!{`GMfzCTJEYZq^Eyhf++2~e^YgevDA6MxD;G3D@PX@%`FI7oN7;HxRZ zv_->t4yKdlF>q}LiZno=8UDB^Q;`a6b4p};ibA3PwqGr8a+IxFf~11LT@_8NU!53q`58$9>-GJ^|yp*PqQ&FDd&p0DA*l3Y`*%)8iN< zha8ixKpIR*Rp1f?zImo!BO#)W%_uwI`*GMOCzuv3m3>%%8N+=C_^ga*$LMhs=UB^3 zB$pdLh>N)`dz7^CJLRqzJ`8&A{q(0=v$*-C@t{gUP0Rj{829Bys^&*qPc>~j&TOYm zVpsW7MqjtTrKstT;n2MQdgS)@`F&siL3NV1`pdIgdzUTxRLYLuX?t(6)E_DhC;pGJ z#ve9kOs#n+V)L8jFdj6n3d7ZvZVsieMqD^%1z?|^laIRWtj#m%Jrd8EsJ*!*ybfzC_P&ICTD8ZH3?blh;=^NnDX*1omx>Fp5ZTP( z*aDD5dda=+iADcoS~b&&ilDjFFJ0A&PMC5u>=U&(=7D1zlg_5F<$!R}^fU0ql{gg5 zd;zA(iUBY#Hg{KA#9op<@AH;>-KVm5D+f~&F4L)XITR0%+4`%x<--RXs2a{AF;O+R zO6(Zt9@;rjmqkn#OtY#VRMwe3L4Gi>Dx#30r-+eN#>+;xh0wXlV3-8Sv&E@K#X%h#e6@7 zE2DFz!UXrClTNjg)0HzCs#U4^B@Lj@xtA}sWLmX%y`o}mmhHI-)Gp)-42YazAvJ%$ z)w8-6E&W|oLCPxLf6#RdVl@f1b?<+7@vn5eiGJAxcK6DQwR@S-s`9^S@e5;vGOMa= z-KH5S1xkPBfUD5?kOF;f;+}ESQO*Eu?Gd+jdn#1iDGjnc>re4r4HNAtrtz+N=Q)5j z*CRd`#OhbEAYTpv-<0|&9kkxVluCm)W+>HQo~dS&KG#Uj=KZo6+*VHQwac63tFXpO zpa61Wq=_bbz?E{!TG9LCPi{d-_@G4P!3ti$j~Qg(=?AcyNA#vYllNLwZ?WmFhRDjG}TH;evA#3ps|EcY%u^ay*n?y9>b8!`1qjvI&E@zx_TxjG2;oU-(L`uO?rW3w2bq zW~GBST!7LV|Dr&6iwrajl|~Q7zV51*DX#ypMv&{ODVLcTbsZ$t^-j6^lM4ANzqF)m zxN1)+S$Ml||5n3Jc~(`ksQoQs5Dc7UsQREro#7{}n zqM2^AmKA}oBla&t?o(gFN>$NlEy`!)2Bb>~ns82Hlq)^-KhFZ-8PDy#fa1`!;w2=( zVeg_D&&+>is#w50h|_id(m0oXYHrgv4gp~ ze4UcvYBXIXWRTl7*Da;Nx&SaYHS(=*){=U04U~@iysr;`=6%wji!K_+`8DlZeZ$5o zSJB=l+e|UGBbeDDAAQdOo2m1@?)h12x3&5&^MJC|I_~qc?1=l*{S3rr5IEhUYPHUf z+!w?w&GUeLT;kF->;?(_95$VFg=g2dpn!>e3;rq&pODDLG}7`n(e6ncAS|Bxj|$;@ z!|V7J(+BS@rB?*d^cX((;%-kz;c5fpix4`1B&i`z!uuZ4`?ifUW9}X zjzP8d5z^7q@MoUy`PT5J00@!;@N*~a>ViEVabrC=@WP%RISha)Cmn#90^nzi;)+$W z=#9toFY^_x-x6?r84XOWOyg3%f<2GTB5~y7hK3yUi6S22W7y|S7G?nH2Aa!Na*lUZ zC)?SdzNfWa3y_A87vGng^}QW$Wwm*I;J^GyUb@m(N|TrKzkTdlsz!wum4{pAewB2s zU+!mJZzW?UHj2b@^Ipq?1HIMc{PLZuo?cpSQneZ=W~>;_8Fb!2UaA`D9`qB#B|%HY zkRHE+7J=O7M=-IFX8vK4Z``B3{-H*!Mbk_@(G%3!t8z}Fhe=hXZ^a&5aj*5tTQ#1t zk9*#eP-^R6iJAveA`Kv1WV0#U9Q36Tx$bE{q!IDr1snCFLBi>02?-^Y*j!9>E zuLq{mTsX@|D;7;+d(B+M=d^HAd#}y%4u9h+Yz6{_1SeezqE)!m4=T74a);f{M_M-) zT}Ajzvwll`Q_d!e5%4BvCC6}RS-NbDXW6dkEU#|rxa*@nqF(pT4fA9P5NFR_ z0RU-aviXh{$}VlKHjAee`k01+Da77RMqK;FrG042x-zfqSRQv7!1i=6tp z-m-)=3wCf|t*4lSdLQcDlE0MTt&ZB(+Gp~1N*&>_)i*X;(fnU_uczWtPLG>0)6qp2%|pJ%0h+PjlPf-rKrfuT zqK4Oqhe&m$K1;oZZhC2cX?aWC0|bO|M3Ty=O`1yNCU357HebD%PqD`E3^IxWJ-nqi zG*gqbt6s;j`TuiiZdXB3PsHu6*D3IIoS4aZb~pFQM(LioP%@rMI3<-kCY8A7*JAj6 zL5eb>v@Xa!?)6ze>*jkZraq>AgIm#4C`sJSz1^r1X>Y$%+>p)Y|Eo#wR?hBf+$u`% z*g~*dvbzg?9Z0$6xF<-i!kb~+Dbm(f=xhQM!yq@`>1--wU$^4_)|XcRfc_^mG^lsP zS)F?#h=CBjO4^>A4G>Q(cVn85p~AH6o(}}SZ2JKeKqwBH>{?ZXQQs(o_`7Gs_RiB~ zfWd(GectEI_ej`C7o8->q$>tzm>P85@>w`9kOy%B-PZB*Zei zikKKpi;55%oov9vM-h3sJ$cj98_!yiBW zRnt^&=W%|mV;xYXT2r%3<}Zn{)^{!n1C6gcOxU5oEfTk3NFVbIOa50KpnaNAvr~oQN zT92ptYylYhhH>%GaOB7|N>31~l*^EPZ#j)X3Vk$4z%H{5Tv*L_u8wG{(TwdW2X(8+ zBBZi~rZUw!UuD@UWqMkTE9MOQUqMeC_vF`Hyyf(iX9b%CGULH{Ul23(GF^3Sfla}& z8q3bIegGSXFUqx=u;a7_H`Iy0EU~}W4Y^O?7{~ZpB!*W&y5Hu*V3nhwW5N$W`UdK` zKiO3JEtPV;W~oM~NNaERmzv&_=NP44-b*Hq_{Vx5l>Op`-t%)j`?E?nfbIEs$@QO? zk{$15S$WNCEw}M-tnk`jVyjUKXV~lJG`-i`ZLSxvJ?Egfl`6@pQgfjm9CgtoIcAYi zHJZ)Mr}HEF#libj32OUwrbjHzfKQdINK2>UjYert#qO@hnvrr|N5?dRA~CZ@e+^)B z&*Yxp~BUjQ4pE17NyTiJ;+|E_^7)_CFU+^D|b^8J6)}4CUQQ-tE3= z1&9~PIs68Tx-zA+gIre4H`td~Eg=PF=7_H{I_@cDp!pVk?m%p{N&(&}u60B`7ZmhZ zfSh}6N5HV8Z8S)_C)WCYQyUx(0Wjl21fVVtKP`EH3bc~7tor;O_x0fqK8%4y=P^~O zG!OH@0Q6f=s_f~J>8g&U!6I6b(rPOG(PFtHRmm?jkARQf>*0Wq7=@(+55JJpQAeP+ z{ih_Yj?#}-UJxt6m9&R_Q-E!bt%R{o*=U4AvEo5-h8KpWw=f{%2=hU+*TO)HXaJy) zzU4-?lcJ@*_8xhYQFE!m4ITnCu)Mb%Z56R$rV^*6SUP>CSD|*5F?z3k!G)IQl3NP= z7?wv|h;4JhD4fX-eps&7Mo>O?d-$<^;k9Al5zRN~tl(sE@ zg9&WoR?Dw20}Lb>^VXzD!8(FQ9dURU5p|-~>Z-1U@eP3SA(wed^Qu1Ckjv=)(sC_t z{Qbp1S9A3wFFD2^d<6VhPb+F+FI@mNem!tyd$xPrA1T{i<=$#J*2aT$m5(sK01~T> z9|k}B`!H6apbQvd8TWb54?g(lEM8yZ>pF1D&40_!^%Y?iNf? zsTQLy^VpsHy!mH*a9@DF5Os)Q`#KKUYM}^@1iZbxkCMB}&8F8hnvfDGq*nFu*13Qy zf)R1(`!K2Q?Sb}a-C}QLmFclJ8BFoA;a+r^FHm*!1Rv>(K=A|# zP@pJUkWvo#sx}T{z-^89EN_cVb3re&o=cqjHuTg_T6Bfa2jBuW0J+zFg8hpoq3BIw z8mdp{##DvR!gmsGPXD}X8F;sR3o@ii-?HI|ZO+A79q0i@%(kJU!e!!Y8mnWN1}<{A zR3MKhvU{7#vyZt^pvty;9|3<+*ZcrXD^8wVHaE)-x!^(_kaG!OV{h8N=aTYV%&MVR z*-K}<&(MeL(>6w>z1DANVCVc?E?}BK9j0pe5fp4|WdzLB>lJT#ou7GM_l0GO;iOCV z0xe8CyxiC3qCuAndV`K#{HtT7e-(~a? zBcZzXWyYONCO)H1R4 zK-*X``+3lKj={2hxt{7r@x6Qf3n;T+Ta7v+4g+C`@$&4{lzUE{wch;wS(~@U=@sp? z8tntWr{L9A&!diiObiqr-bz1=jvhr}C0*%{bqyrv2h8mF>Wye?vOX_CT-Z2j%lD`& zrLOkGISZ3p*T<_-DGk7{EI4b$-vUMMBPJxcQ4mA}j#8Ix}(WvGvS) zYm~UL`58j`N97|@I}oO~FP#!EU*NU#lnE5oP}8WphZr31h1S+<8zD7lHeX4u*f$D)hBqCFidX;C8sWes${ZJvgiWt z@tO91*|K-bPMUfs0MHZ>rP%G-%NK~Ln02XE#+-{~<`E|dac+aoHUSR$#}iyo9XD4U z;$L@!*Hh2{?BeoixqYS;zm#$@3fb$aRM!vza`rlEJ!Z#Sk43mAiFXoDK)(cO0mh|0 z2TYIGtoa{c50?!jWmNcOGmb{5ywv7?9Jk9d>|i6MUXz6;OZ05mx>?qnMb|M&j+4$} z|F1OcEZUO*;q8Eyi{rOz3EQ1p@IqBBR)vuiz_QfiB~jYjsap7L+_zk@8myLEDrNoZ zZRDSJyxopwZxMqy_WF}D-hVYn8k`M2UT>;J=~d)QSjyI)VIFS*++!Byw0?F~$vK!A znFh@FUceR`tzU{5UH@F;~p1uQ$Y6=Au#! zz&xhI4J;8N6ZGadRL@|*l29?#>=)(@sXg3$h5F3uLb(wCOV%v0W?m0r2dYvPxCHTuc(NbK2XNqO7>QuWA{ zlXY<1m&!qcUaqw@|2o1LCI+a7omGFSi{M_LNqn4N*MT^fAyyV;kI?q}z@ApSO1dTP zG4|xO^s-!Pz(e6n%!!=0O@%3WTTIO|6TvDR%4?TRQMXpgHxe7aiPDU>(dhk>q^r3C z((um4l?$f7&znE^T%XGjVarm9frJ#1h-rl-X&`kjgPfmuCOY5p7{CV;Mf4ax1 zjObCBr$-&^lB`5XdSWjXtvIJ~N{ccY(%bbbAZ6Alro{&qp#PTdQVe4dS|Jm^EYIx5 zm9JgvG|-H9NKnKU$A*-c{8?b#%wJgcO6f%x7>G9o&HD9G+ggCR5?lzi>={7vw{Msj z*aW`YCt=H-0qF551LrNY4jlZYUN=NAX0Xc#^vJ-G=_7SZeE!i`Sol_T8n9V3y*@z- z%zTR2VjMKBcCy{ZAcGxxP0L%4>+uko;(s@0p7o8gB{)DD1MO`fug%)m9y}DCRlEa( z*h9j874!)&P;&UaUDQ-a)b4Ca+V<2O9@c;c&h)Mr<@z}QxvyJjS_tS;D34gtx$41s zFnw6F^uG6X0L?e%rQYby`z!oSsN+?X#1&Ue2~EAu*81+p!ImCAOcW6yq{?+gu5>Oge_a=7BzftH61ERsm42VUJe|S9p^~FQcY|rg&uXK91WVg6~TpjY?Wdu+80L&ktg7 z=X0k@%H5EkF91E0J{x(1 za%G_1^)$R(sC>0n{w4pO*7H{LZe@K^e&f;Jqo&Q$t}tn~*eBk7l%?zn{@vDy%X|2q z9Ck9d{Hzc3D~gAFt%K?MtRMVbpYfTd3Ti!z&O?s#5E=*Dn?Dl(%7=+AI%(bG!#~&G z>+kiqd=MX>2Lk35Bn1GVh3hyE9OLWyx{i|u8H%$QuWJi?+j-wJG4P9=t?JqSY){?_ z$Cc-n$6Z(yOsaiSFrN^sW5VORyymvo`*A$$bN$F?d?tj(!N7fjX?|T_gHCIC9+Tt{ zI?2uz=!vrKMl0}?&?VIpzm(o`OV+p>Q;3_{Vntn+r>f0eoqOSf8y%b1JNY0d#%pwT zw3(^aDdpL4gJ~#*Fx)&W=4ajW!#95>hI4Y7bRMUFbCXMuFsS>yz#ZQaXUt?0x?4@lcKs^c4*?1I^n;cSb&*#IVF_g zpZ_{E=!=*%qrQVD#8#*XWV8v@DondvKpC19hlk_Ex8>c19;2bmFuskp^uUieGxEfF z&)Z+L7WdZKM;!$41@sG~jIM^>J>3fN1f%*M_RGj^`KMx&988g<6nu|r9Bn56^1fDkPw(nh*Y2XIwr|6 zbZrnfWsKnpB{pH}Pb&q5o(@5qH7Q2`z&30AGTYK&=hpUHJ@&gWu1aBVA3<4Z&28dY zTk+(tUN%Da{Hap7*eQo-KoJjA~W(+)8IWB^6>EdAyh0R$=cYH?Q=F2c?!x z>{mJloL#TBMuzo8YZ^40KwHT9$b7X`L^#R2#??*J3F51}hfPoMcY|G(>J>X&hWM|4Tjog9lzO+0DF$Cg;X!+Y@8AUVW#NRH{segPYI z9$u>_;+FeApDLA}*y{{`27s4+ih}17nhz_p+78z!iFpt8+WyE7KIhN*gAeZO9uT?? z{^DaCr+sp2Icg~9sWUjjpgCR|D&z22x7-xA(i;G(4rTAx*3X*iz2DkfdGDe4$Q$Rw zjvsz+AH=ab_#M$TMO9+Qbl#0{o$O<^%z)_b9p{$Az7U{0WSSAa_w5b&_>)z7B}u_c z8cs>+MWGph+;YnYKjX9Rb+3a-_t0!-*6y^07Z7;*Vm+ziRy>5&LBkso6fa{Rnu6(K zY4Y5uN$JaBgl&j`{);Mv7x5-Q7EQWv`37sgC@JJ{FvNihpf!iuWfc;y1}V6 zn76>zdhaD?&&oTtJ#ky3)uT2VA9M@l2p+hSqDl{(XI5d^=fp=_y!}+v;suF!8}?{E zQUJwEluDkr&+U2SDR=5KGzDI=XFvm5#gssdoUtrf6*S3~sOHT4nOF0#=1t#|mssUF z*YzBrgqx`vesHNF`E1?elgi8sP`ihV+1Atl(@;xLNAry_AKv+{mbpTk58r1_nMznaF(c= zabNem2Q4~D$W_5w8v7n7GT@mk=Pw@C-w}%KL=Dvb!1ngC;MGh0`6b#VYhxISA-&K3 zq{xJBmJ^SvDRpU0gBWEm+4s7KxtgYxSv1RTw%#-qT$M+*y*B_b{l`q}I;l{Kju6`s zD;J$uBxOlEJobiCF9opsIn1lY7QmoG<8S|{KL*5L^#rO9#$K~N4G^s@EK#C*2?ozV zs2D2-QDTyFaP?01S%-{?vFJL61@~R%iHq^D=^XIwDnO`EQWro0ul3n@h7Q~U51&e5 z8*}1^Q|mi+#B}912D=*Z^p83%fT__8Y>Iu&CHL|pIkc6q<5v->S1P`y(X*nRRpy1g z-U_^lQI+-dF?ZO5W)-ZIjt&f4TbnSk(gT(KS}!X5yia{^Ky%V{OgibLYlGH;Uy`+o1t+J)@%Npw|)XR*Q9ra8Cv=t_gJ1Bc_D;GIE zkQ=6A;VJT3c*oGY<(r<$bC#{0MOTByy-j|ItCe2;-n}+H{h1rPyr*LIR%NF5dF#1f z#|ZRS&qsuOaD>XNZRO3z{i+(`zqr)I*8VcH)My4PEmuWF1rm|#mwq{QtKC84&)M@u zspKpKCnj!SedIwdo`{2nwE>$ldtGhYdSJPgyCT(y3`Jc?FdHTB4j`Dg)US%siacEX zenv9~{gpC!Uy@c`>9H0TbpR`+{;WZ3aaz#+Dqpy;r$Wei;r=D7<^wh|BRpUS^6=gc-W zR;iV|`!GE+4P3`b^Wg9Kz5W_s^El%G3MR(8x!^yY`Wf@ZQ^AnOp2NfMToV_{vo94{ zNaiGNXk(N*1UaYcK0o}CADx}0CjVPL_>7+wr&Ubz0@^;M>o~v8FFr&u74a8;@fRNg zeqFZ}rPW>)dLO3Vr&^;u)Vl-DJG6Hkc~P7czUDTiSW6HCk8!D^p8eo$?4LXP*Js<; zRQH{>CovqqiV0IPYNa0Z6h2$26;xw$j@ib~GdZmSU6Dz<=3l^z$w_<%e)u!*>pqEH zn$T3vsMwJ%{F%R6&CT2WPAQ5Ksg#fH@d8Rjq9xN_hT<%1OW^W@A2MJ7U}F0BMGOv0 zGZU8ram;~zaqiln`-*AN`;uVUy1;-r+vhRWwN6~X-RaKz4?cV^`&jFMm{OM(JMp1; z(Wre8xLm~IFH4LqV*a9uOSNgb=%RHG4MC+v=Yiv-aZFm6g{gXfq*?nnm>f^W9vtvk znk&^kC?L3qu?ol#{bJs6>X-BiV;Qd@6g~Ntsy(m}CBNHZp#3PH>3!QgV(BGfUlAGd zbi0c`N6L~~{7Z-#Z-8QvgSBN$bpbFSL)P1@*ZS@E){8Ye1!De{@>DQB*R4W(*{PqG zL1bSOBU$C{=X&$^Isj z+n#Ser$>&bm(k(UePZdGED`Y0YhJ((%YvE|icI|Q*}sERgm@U{my0^1rrIw{rEZQZ z+aDgtXC-58Z6lUR5DeSi8JxCanYvKt3K2?R1XT|AIbeqX5fr~#ZwCO-w6~8r zM2Hyocf(GlyMUbX4npY_LbWLedDUdJD&HXX)tGPFwZm;Rc`wAS;NY)~)k5I{&9K0p zBULxGgc_wx%4?n}YvHH|Kry{#uD)0MGv4^r3jWWVL~wIQUC;c8ZpbuV+RXX)yUD8; zR+uj2HE-c)51!S`hgJR%qjDg;n8l)QpvvzpZ{7ddpImu*FIw4h_OjVlCJEz%wEJn2 zkSgioI4EzCdnteI#Euga`)t+qnfJQq=ei}rKLx((7babY9Ou`v_s}j{9tQVWH~*{; z?rA4^GhOzD;)QizpDTPNYO=L66*eOgHtw}k?)_QmJ)rZDV|~x>@qK<>hZuYueeM5Z zK=x-_R7i`xs;lNXXYz0xaF(bNwx$hv3I3Eg%Rgnf-5-_9+~dAJ=V$y}KjSmuC&Qn| zN%A$0b>Nux`Va?w`Epd}60b90JZRyurZ-$G{%`a$Bhkhd9;`K{>2Hv?%n{EuXd=0B z-8l*EcAt`DlXl_=$up_9jI4!6)QpsoYOVbtAYC3~fwIC- z6h*7|K5x-ajTw@;4mrj#=%fWSdBar|i!|V}AkmEXE)sQIv-anf!{zROS*-lXd__EZbCr0usQ7sdn#aJ3^MGcY zKDU0>&-rtHuKO~`rq=&ywaL@E*WmzAuDDPH!1QK)It&n<$0RvUTs|lUh$dYJ)39?# zYl@*X%V0n{qiL1z3uTFcEfJUSkI`R`j)UBpSifMW-(;b>K$zHiYxGq`p)_RgLjdqu zh5h!rf~gRfe&hlS(C_fSeO{Y^_@0>u`I;5S=qxlxe*HCwk#?2w3**Vqq*q~7)8qY> zXA%e4C4xE+Ct8}x0)T>EpU+=a?wVpocJW;3-+LGFv}3VN;-z*Gk-;0M0fm zwY7z4{XwYYzrTbcMf!)g$)pJ~rzA@_^B+OC6f0rfK?`uR6|vp(zfn4#C@li1d-7#Y zHc(M4tContSayo?L{lP*c`9pJLDR~86h1UJ0I_q}$B`Zql&%n!X@!*yba4WyCIY9a zh0zaJY3!q&+wgE_CuQwD%D_&-*+l)x_ZPR>`ur zy4Pcsujpk3`X=^6U1pS>6VO?#$B`)WNl`YuM-sn+Zc2JGwUWDu@af~EBC;b%S(T_Q z1{~Y(KcGqc@|_~sQ_}G?i#RWK+PEr*ZDbo=k0jW~u|MPXZ{B~)|9oEHUqcEHaM)B1 z9jDclbFF{8^ngu*YgE(uk$Zjk!+OGj@3(6D^%Fx6IZhe}2d270UWk1MInE@v`c=sk z`%uO5?|D96ckyfdDyK>!hM_k)5Ba*ju5bC4uj{z%_!>ulTqMfyp0{>nTYq~R_IKHc zFma%j@+`~;T8r4#o}~R+!PyyA0Yc#fd-=yMC*F3p^H+Oa2^WgGv})7XaKRPBXa|Q+}z*DAb@q>PE{@MrsM4k#qxs+xi2-4?7lUIK(4wE3Fo@y-Ql@($-c9X^9-z6@%L6x~+^2)?BdxeJ#22 zA3dj;s%imH+ejqD0uZe2VTSNplfs@j8ARLSk>AgR4C=Vvk6MO4dwh;#qq@Ddln_ZG zPtH2WU&*|8$x&==Ny+a?-k_ML;1((j1`@VL$7-8RBqde%ckzIA? z6z)`4Z_@3pBG+Ezse8@o)PSGiL328$#AW}pi*#DFa1op82eR1;K32!-Wslo_2vCvp zYS&_vd%&pI7x`N9w(96C)VWX!$1mkPN7T?vBALr+uO&R!Gp+KJ-}XYQ*V|*@mtziI zc+}>x@iEh1TqFpiuHjzyc%i!Pjy;z@c~E0C*?sa>7;Pq6`KZHq%i316l?^3D%U38W zmTzdA8>b#o!oFmC&ztYFd>J{Qs2{dRi^efY9RJvCNb**Fk4-f99ehl>3{0}+kut9%N_s0fB3#yS{sKkmP~PA? zlwS9am*~FnoTo6oMc$@9q3{5d96qOu_>Y*X&>nX<{zdHQ70M@Q?t&yUzAQId;JpmY^dsPWxXJ;C{zCU!w8XcU^w!_-|E)Xk$=G(}l^N>hIs-aM!X?UQq)_ce? z>9o2>E4>1RxJ>jJkV0YMqev7nT;jRxvgpP0j#qBX%yb187}c+;wdwne_re+Gm@7V+ z#~%E@si}fN|Mu=WhpY z8WB7<<2BHeznWy0A<{ci2;@kW#a3 zo|$K=IbNSO#bv8oX+;;kwailt*TlfCSVx&qL#m)Gsff7EUbe%jY`v#scca~v^mIM^ zeZ9ugjsvsQ+=x_42{7K|1 zf#ik$hj|7K+({{yEv`l#=l(SH%imi9F_i0Y%=!Sw;a>Y_Zt1bJYY1J6x=O8B%*Sbg zOEbpAMw{4ZUiKopa?hYH1h;H{O{P?R*nl=7RNZM%iiUs0qnWD!6#kF4s-mp5kaaB} zJ|yJytPrjvxOI-&u(4YE=;pkoxA(W8IHxX>1ci(uc>$oyso%ZpH=lEr%psACd267fgkz4}Y7Y&>9T%~dg#=U4@(IXt+Oc2XCnV&Liob)tf zKurq*#yC#p?7Q+p)<-y>tb77})uYU43+WZh;pMo^cGgz!j2E+zms!mqE$FhaM~w(# zXQ5(`TE{3e<1OSW$(p;Q&m<+sFKDfx-1j8@Z3LRc`#$#kDci%MNHhL(fGFEfafZA-nY_5*w8WC1O>FEmD+D zJ1XWhf81<`hb(Mtm%2$=T-j+3T-<{}rtrj|$r zXB!2P=U%pya&Zcf_LyKh1*Yh;djR^Cw~c+^0cd=_q-@Cw^0}xGUWe9aJt7seT)&bP z&~&1*p%E-*Q15Gq{Kl&Fo-c1ER)!rE%H}5&tmCXlMgLjERX_8CQRKDL6T%{ZDS-U7 z*`T_j#HFR*+QG}yN3vGD)OxYBH}IutQwTeRYZScrI4w`Hk(tRS=4W6AY-7t`Lq4`t zj-Ftfqs!=iXY~jb!08$&V?}KDvZTE9A@eZ0xa@7k-z`k7)E9HW(eLH;6JrN=o@yVY ztri&5U)0p$TO68V0(1rVROmU>%ttdkoqM3^&M|VQdx^(b-2(jaO=;G<$-4nRo~PuF z8od4%wg-Q=m~v-3-H38xzTFw4tADfiT4uC*GF2mB3s_9{_ zk*<^~7cbWS=v^GoF(U-1b1b%h>%C`M$hr0szo$`P-pF4_8SAaJS~l+1_{c+cdkyQI zgkF=Dt7hRQ1FW?Nv|mUr(?je;)xmmRINo^gc-%=WvJ`IF8Grf3OEC#G=%O<`3340Q zkCQ>w&p7Rk#1U@>uAuV8jQ8+i%y_bp&pOS1pK0Ja#tzVR(zI1q->mUMp?pgD+L zUcRR=!koDiiPPv?P+8LCYOm*?X0RHifAWOaFXpu9rN509oX&e>byW`4c(U&PRYts0 z;k9^a{mYazG%)YH;8k*z@_?%tWxmIKedN9%{st-y4FkQh9U-}d%Xnz5L*rd2No=9VC*V1bTlD`&{ozwBA28#7SUhebD zJt%@L_#{hL<(6Bi1-SPwO8FX;*au9E0o>!BKj+``bAIMMXn^S?rWpa|3sB>z2B6br zk&maEo|^YTRD6P*JH}O^<|Sch<#v+9emQjMb|Ot+(3c%a{-9$`wte}NdA^id@AF- z7pD6}O!d_t5t5|;pEx#o}WlS%i?@{NUYRfubu7CA!%Jb^&U7f<}*CsZB zSa(gN=8+lzo~Im_@=R}lm#O5veU<8Jo&a2s#!P4a0=w!nj#iDPt;LlzwYPVD7woyb zJt_azb(ixc=3@$Pd`VnHI2Rfbftdr-Kh0a-j+O3J471U4%2C#71S`j(H`L+G@SeqS zG$@8BqpFO7l(HQ4iH+Cv18s8mSD}t7c{&Y%mRvM-9IQU)x)S9RvUpu<>r<~6rqI~u z$pe^Zi7E$U&f{sTjb^N79gA#b0VOcjpUWn=xp3C1(%bGJhlQn7&|!beAwcp%sg&GX z4<2xv3uNG5%p^ccguRW<2VPT{EnA7E!Z9k4%o;Uyv&urQ43}_TlZ1&gR!x(c5{Dl3 z0rVaV;WQrYi5=-Mn@=4kjST-3e1Ux5UeC4;WB6HvLWAqu$h=BN?10<*-Aw9_QpC8v z&TssM@Aa)GDJ(l=Dh{IxxerM3t)~IEh5}X|C&t5;`B`V~J-TA|d2bd_-VS5qOAI&Z z3?MnFb0^|xGm#TQaYgf%I%STfih0s&h4Y+jIj;xt!v~qaXK;d%&JJsJnwdV8U`EFk zUW=b4F#wEv>Oq3EJgTnyj2fm`>_4Y}wRu~uCQkyLH!jzE-LYCWN~>%hcuT9b1MI73|JCzwJk|EIe}K=7pdVsn zHD;hWnf3&}uh00K|DK<9pCwM`DyAC->>ChgXgu2bBtFEB#blu3-&ps{R$Msr{3QNfDY0*j_r-7@@gyO8m9Glr=rvrKxPGC!34oZDXA|q=@!b4vj0h z2Ps;W3TrKQ3%zczUgCKjD7g#?wn$~MS1gM}4eH}A$Rd@~ki9-WU;R(F0 z2%u0Wp_b{Inwto=7K#Ehh_}_C6c=g}S~y(nzUmkFRA+8bU3K^+zxTv*#H!V>+PE*) zUncGV_*Z`*mw%?|sK(0>QdZHJ{MLu$iA9{=v`nGj6tXhnML=i$6YG8xdBw$I+h{533Ygchu`>TZ}GT%76j$3C_*00N@dvl2=XrsGh(AzHOLd z*tl&nP12^@)TXc-o@P575G>dHX;>)jRaB#-jjgUO@T~su6|PYDYFWwrQ7=Lzj6AN~ zd0XKTnE8FTd<%5*sTfsJ($fmM8_OZa1_rY>z*dvDGNNqBvxT-5CTI@x9rj`KUIn`> zG_4nprIcUX%Hu!b)00P@JqN7)P8;^CSC)}fDrHaq(Z=X?#>wyPEV8%Wt?;%+vTU_# zhF7`QTWjCyj$AG_&kSI;+#KH%)WE;fzy4%jJtv~kPTWb=K*kC}Ms|yNg*68$JzTL-$@G>rMxc2R~lyin2lcz(%&O?F%ttthJ-Sm zYF_CqXU}YIhOPWrPARXb0mKb2XWy3q1u8tw589w zB|Ol4Od7<2TbGXe=ejRqI!O+u3v=7IT4Ne93S}ur_wzg>uzb{UA#S}Z&mO%-;d`w_ zsbj?bm8x~28QuIBV=Qe?dfv5_>0{XuMx`?Eij~f*tRegGO!5!+_?JA9HqMfzLbh#6 z;$BjpWK@sNr$zk$^$r9~tRL*U&wGBLUopkRJkGE4n~z1Ow_SE8mvJmyB@7v-#cj?H z(|??;6Kuz>C`6eS5TwrrDFfuN&yIkHgVcItil9cd0&u&)xFtSJun&t8G|GugD~Oh& zM9t&G0%Mxc=f~8w)P)(asfcUOU!HmL5Hu!!Q8~r>hfoFe?g}M$SwP4Fl=&V)>kwj^ zPj~yn&ll~q4LZod$6F7iY>Pp4BrDA$)Ib4(o$(g7fyQL_)<(C=l?|vAO7zLgjt<8H z^5I1(+!E>diz$K*@xJJ|6+9fyN1 zI0v4IfdX;c{bspmVDl{PNr1k49!eqHo)jO%7|lvsSF7EL72$OucE*fPI4gaFX7-Sb zU1_dzkTQ<*7>CeFSEGIa0R~`pzDz6qqmjE)^m~$hl^5%yUAn2>h7C3^a$Y@dF>x{?^kfG754cekgXqy9r=zTyYTzjr zf6WtS!dIV?1jUGFX|@^FGW}*zu5m2Q%Bgi{JL#_&<;#)zye7#$0YGcD_FK{l9|oyj zW95q#Xby6!AN<7PN%U3!6_6K&zozGwiLIR-NO?;jr)X@{bL4_^(+A&ey(tDaX+CFp z=H1%TB-zd%ftly7HA{l2x>tO47u6yahgIr8*@4Ama4$F>k@Bmk_{4?U+8;*Bjz!b{ zUto;Sy7@i~cjZ#K68= znop{#BA&$lawt-5jpWn3uEE8`VIbEQ2V5qtd*1TF&-jd=_2GNnC-|5L4$-(UVmrS8 zc3PybAucUz>qbpGW-Co01FYr+puH%S5W#PtaX?7PJ!%gwALBeu514Pf0C5;w*X(0f`VAZK6fFFS9z~i+F2d~G_P;% zj{x_$uMd9k86Vsifa}25_092B#7J#*x7R%Y$StO&*r~?!9~>p!c0Bft9yx}+3%72@ zc@HoRTzYohaoPzsDcp7PSzn#DKv4xL5!&RI-x4quOqSR@gO`cP7>owgY8pVX@%dF9 z%9^D7_U8k>y}f>k=k;zY-`Sgo82C4epyPHC(>P}IWo*jexCLhby<8Te&?)?Pqb#@; z6CFKr^`p+@*5kS;wDlFsuP#UHcff$i-%8ay`*Up27zJw9`niY=YrlX@Ec&%6j3ZNEo$c<`^~QmEccFjB=$_Udp|cZ#{j>%gOL}@69cGOmrx%o>yYj1yFHM z>~HTz?M$4a?+N)Ih9jTjrRCyVW+T6wN)y+?)$?j%Nfk$KW6Wi@O1a;lihXcr*wF`t zOLQR}L2x`GxloYHOjTE&VqKv-lGoWhg3&&sYP0n=+ZnTICU(;eUw&>bM z3J{A98Mk0SupFud&y~V+*aY(^wtYM~x9&t5@d^UUIP~5}qB@eb$6fj`65h8&d1ut} zc7N2CdrU&T4$hpE?R4c#;xV`B!Kt1SDe!U?ldL2teFL|yka_G*dI=L#kgv+x;}O3F z0Q){NpU(N2_vPj={SbwIlPpf$O`E?Pj%)u{A2;|40Wm!Ei)~$9ep2?s+qLtrq!)C4 z@xSK3t79P?^|JjaH<$P&{;#Xd13a+q-^_p0XSND0JAHMm+A5Nf+c`sUaqP@^O z2C=+zBxjUFJ@MQGjja^&Qo5Umwwvj4>2{+?P7A zAoO2OAi_6oQINAC6Y%%a_u=TmgqSvIB&e;~Ism8()*a+I_|F0l`#xX3@l+A>_} zcbiZ7Bx^#Md5`z1^dqA&X8P*dTg}+i7@v2&GPz~abry_cT+c#8ulyz^Fs9np;#QK*OA zIfwa?Voec8Ya`V|N@?NG-}Y^$*AcN4`7-{Y`RATV%4qUyy%MzL?AyI!#1?ygHf4;7 z{g;<4!NAu?;@sHto;W|sd#_`X9C8SqVFJ9sh5!Kh%nv?H*VpwukLzGeh#B3^;+ORG z29=_>ggI$yIH5Ta>=g{d-lNRhu(DU6e1140Jn=Sb(c`jtIlPUj#t z6GNpr>*{?P*3aaBfJ=|o-&;KL3iFoFh4XX8XDBAETfKyQ&-(<^f4$N=tr_$nQxj03 zeAtpw--WWpnaxJLD`?Sqj6*v+Km5^Gt-Tup0T+MYu-1i`&iZys3;4{Th+%1qje$Qx zM$r;QV0oQ;-g+(D-{&*Vy5qk1`-8-E73H?qVCt}142fYc8CzX0_-b1Vu*sbTSSF0q zG|1<2hEk8H*A-8sZOiP~RyxO_pDI>p7l%aAC*D{+10lvWHM<-!x}s^n8e@VBqJIeY z8t4LH6aiH?PQ6=cZ8VfTmX!4r;XeGm5DNvCXhj+RpbG=OH@Lxe0RV`7d{{o~o^hza zd7pLw`GA1Q=g#}?Fk~EZoCgk}Gr`s47$8qNGLk(kNeUa0)?a63qwRj~^V3;*fh&T_ z(xWX`#Ei$x^3>_B6f#mk@V37rJqqR^tUORQSo^RN4DnVL#56#Ne5zz1rgxaM5)z4VuR|gyfhOX0#|_ zT=Y6PLw!=(fA43&V;cz_n=o&=m@n;E8+*-KpZH}pKkWV}y+DuUFI7~coKAI-wH3+`5)wpotH=@LzC`)G;nJDXo5H4{ENR%x|9b6{_wHVR#!PlhBt6I+U|?q6)FZXR)nQ*3SpD# zdRJb|b$eQyVQxLMbiZ)BzicHv>U^}8aDG`HDawJQ={0j_udHW+#68Pk`1JPf>of0@ z*1=!vIOzfaQ}t%9W}3=3=xR!u!1rtQZQDo6&C4U^C(Kp>U|ya0pGA}}_5Q4<>$;G#A=Kfy-fQU}I|P2Dq&ba zRp#zxumLAk@Y1s}96*%QF0)UpOTV5x)&UO?mRc82J?+;?g_rgmb<1Vhf#1@zW{Izy z4LcTPB-Qp(|{Aqrj}% z;x0LdNgO=sWtkY`sDdkwC&Ak@$8je z%XU>k+NQ=5qnj->Uw~5ph@IwhG%Vl3mNrNWH7%H8z-Em{J8)}Jg45N+mfR~ohkX`B zYQ%@YEGn}5f5Y*>(E41Pn!NCT@$fq3p-DWDo~ zT(wAYsq$MyKPC2!_z09Rxhy^WY4j!U)+hX?98#7;mxT4n;}qlfv{4o+r`IG=$BJu7 zDOtFR#5;ICl3u@f$sS4?s>W!_pxrf7$CG+CGp%1$F2xoRW&fG!dVb{Q4{JnFgbJgr zF>%mt{%46mG8-2KfK=w87;p9 zG_8Z5hN3MA&`u4IFk)Ff_j#|sug@szp8n^CKU8a0?AwlzzpjA8Mzo2f4*^V+&4TYe|RL(<;Nz z6F;B)NmLR4@_@@E|B}BQX}l0yz{>wtvx*se=f*+=n7G)pwWLB9S2#v^1Vfa?EaW2f zwuc4#>az>1+dkeQ@{-ARoHi9wAa0iRx=4-IU=5ONHG_MbC|0mC!qSRjXCCFC7mo4s zaH`=w+}=lsvE15r#*+C5K-2nwYJ;XAQG5tQ(P3Zs8Z@?djuA4oVA>R?rR){DYmtbX%r z8}-d{TH6u)E@B$|c?tjZ);X!)Lc_8q?maBMixT1etSr^2A_khJa#zco&S59;aR}44 zq8s(A@s%{t%z#lEs*cZ9cC)FdPu=xeO$B3Lxo8?dc4KZ9!XdbhG6Pxhyv22M7M`}JPZrO%&-NF-< zzCpniQIv(KX>ESa02a~edRa+9tZ5TyvaFXcB zWv^)H*i_(IQia#?m$x1~<2O1y`l0b}yF;tGRnK?%E#^~R|sQb9WTPcbh`h3=p9YpT30_x6%qp=n8 zh;z=Pyx=vF)0J(l`uwh~jFv-!8;zQS_{j}7&gSTR80M_)uU<2S42k-^ zLn>mea&VNgUWx(8Xa1Z&@-yy*g>}d=zNWnv#%3bc*iH%b{r{j$Pc&LiJz_iKY5~AK zVo9nBs7q+k@`YZ$W)s>cU<+~{C$SM=_si1avhY}2DAr`p<-;g-*c$zvrSpR=33iaN z)zcij$m?xMXCIn?-JXa+?4@5@44eM4O~1%!Ne6I3tMCS*v@^6sUB1L7I5gj=L2RB! z!x((-3ade=gSvn-@g8%$KV8+}8S7E8N3?i@C(X|(hXA&f=Dg4dUkAWOxv7-w7 zAoD?s&O=BJIi|h8R(jC^uNg4h{yjX<$kx%jN?#@GB>`8{8t`D9S`RraVVVT_m6 z*P-xZzm?g`(hoQn%ExF_#Dx(@ zf-c#V1IGmNcH{^#EYyzXfdgNs9>{c0J^1}|=}D4th)~d#AD!3~&)&w4WA+i^g2m)T zaSA@0hSY-(kQ@Sr1P+^@d0!Ybzs6tkjjut|WO|~mrfP>7kY6YqZG^brc_!thuPb0P zEo}bc+_`qI`T$?}{60Q0*FLwUEv=aWh`upzEiT$X`P<5&+qNvLG= z9`>C|0TgMQxp&#|jpA`b{@u!uGne{#-0+9<9;Viv#+MdEJ%4l+^qvmw6ywh6_k}<_OBGg(v@by3KoDxv1L^8e> cG}%7+Ur$5&AvhR~cK`qY07*qoM6N<$f`!ap!T)@Qn2h&i5n$1a#l;*2Y-xAM~$4rM__ zZbafy5e%^PLeO`OQC-HUWwa@^smE5V&tCRlYpEhHj zqrHOlJ8+E|#d0%Sz05zwdu4xz-tFte-XnKCF`xSJQ-x~ezoK`q;p!S-F8=@r))>KS zTXvLa|Dw}blnAGF4 zclE+N66;5?ShM5j!G1kn+^Yh*`F$Pvb6Q?GxIWA9|BIABN4zyE7YwXV(PU9g46qn&$?YN@_Q z*|qPVdKw;I*$F)(Rz03OZOxH=@Lu~SM(cetR&i`IR_n}q7-yg7={xaUd2ZLPVGa2S zapBh$?RG;Rls?*&`a_#B-xq%+puhQ@zmp31+X;)7hi_ zXFERGROf&niRXXjm&J83>)DBO?kgnp3HHl&9mTJIhu87}6}5ytQrYzL0_Mf4jycu+ zlwN(!^Woa8S5MSBn5BBvvc_X~w!faT?|QyFU&Arbk1_5+acM>@QtLh&cUcox` zWY~`47vh>xFpr%4K9Ao=NbWS~hzoI%!tYfT{)m&m13kcho0eVhn>J|y#id91?fc2P ztgQnQVnoLswe@qA`?))yg6G!Z`F0Zg8GNmTUO#F!6u$*EBx&Uno=a0N1+IguETzUpRLG2;m*>dBKH z)xO%N{vEwrgZUBZ|H3u;VSJtX!a2+6nGs*-r+(|#)hk-i`-B7aLf5tYm9jQc5EIJD ze?=4KJz=y)?BA)0JtJT556saX)w~k(xv^jCxT^k%etOSwpJnj6G4?8)|7vgSJ2C5b zf%lj1CHkIwYJYN8T)TwVW0s%AV}JD3$2e`N;;x>feMdr#!+Q6lt$R)JC-<4x*2!lR zkJEmKjQ&q>V9rm_@OhGGE%#-wRDIIB+Et#pvO(IYSuFxp5{^sju#R@DSN>J4m>J245ydaq5#^-W z>KVHhJUIrT%S3!+v2!lPBUWM{nF=2V~+6^hvjMSu$sr*&J5M~HC6}L z8)G{pd!{(5_q^LrYT0+;nGkVXF=@=v7pl2a%}}3o)Ti98j%M7&8je}0R(@3GJx2di z=ookBvHlaatset*L`IyP{QH;wmV#K@gte-7oBi^4l=U-?uX%3N!}<{!aVOSzj&==P z@EzvqQ)letS>xzCW8rta4OmAK@Hy4yGACF|PN478*?v!Gu*C)2(QhNJ6~C*96SlMZ zU75MkX;5+n6pvbZ-vQmh_cXH$_C6!oZ%2MUWLKOglED#ZIL{8xy-2XdiPuZ;wIlfk z)t$Xpvs{a<=QeBHk2dpnu%|8HxnMr3dUURGKX=q>c8oJO;zInp(KWzpZ~U9 z9Cbvz2BO~v*OSL~?2H*B)H}+}Vh3gkUiU&Vs$#CQxYz911#3pw7h~=xCyLcS2xS6U)r$u6|7@k_42)^^mFh#sPBum_7vluOQ_rB#PgjewX7d8 zo8h&p@tADoqLzNljME+|V??v4)%Ho7^7OB{El*l>U2C(k82-0s=i(InRh+S_PbO2q+avbqdwX4ugN)AUFrFms_s?gT<3s4 z;>0r@#;I;ArXJ;YwPIdGy^PDADQk8~sQq1dch6UUHa60>$g!UJS7HAqmCR%t+(kad3S7l?AD=sWmGD}E0UCv2hLVxkuO9^*9rKGV6NGgLz@#sNFx zdD*Jb{c4BmV~;-1&y0*5IShXtN`HM5RQR=|Ys@wGYyG&nzSVQt^{F*xTKYby zj@SFVk?pm#eNME`NuF`x=T0`y>4=8-hzs#|;q|~7x-Ldq`?Y`e>z>z`uD1qTT(C*T zv!SQd$up-Dalsn5={w53sy^#oJ7aGR~KBMAyr!+WNKU z{oI55D_i~2%xibSVL94DJ?=-FHHuMrFG+P@t+b#%;`K4N@>8EBUDbX^?(FX)((hP< zGgtGKTPbIVRoz|g6EjBFj>n?SJ;m57ezt4=&TgnBcXfC9<7Nien4#Is%ce%(bDy-2 zu4Yfq|@^|$@?+&~3zN;?x?7g!^GYLo5>O;;fag-gj%f zzkdDg_2YTkT36Zz$E8^AIjQ1NnICZ=PMDKD@;$d}=RVhUw^wy1pNn-0)JgCOV^Z3? zuxG?>|GRY!>kG|_zT(m}+4G6#bx*12nW0&;e17;W^6JlwT2RI{YqS<~qqt(szk;u; ze#R7+{i-%Q&e06zN4dBk)-X1zjj?9y&g?R&iSY|{j`lFxpV!fD0ne{;UPoKCipzH` zs3VNYexe8ZMx6GnPkE1xY}PR@^%{@4?KQ91^}+K-)}~JEQ+%bd=ZU|X>$%IG$BpdP z^VAXQ8Rd3kn(?Y~ol!62BQoO7=ySp>mcP&oF8sY+(U4yedo}kRtzUopd55t$)9X&; zBMG1NodREYd}Xsf<{4|oG5CI_?{2Oa>KxDIKI4EoidV!(Tw&(Q_wTQ=OaESc^1CX~ z_v##e4`}`Og9|GBw{yi*mqY&ne}>kcY3X}RP#s@97rCFms7E-T^7;Dlbrh-F)QGMt zzusi$eqCO;Peg+qQI4vfsG7Ob^XsqIdE9ICSs!B~zu38OyCF_ImV)m&)(V~LsCjZN zSBqb;XYe`@s*ztT=P|~~+2OUQYqY_RT!;}}<25{wI^plaf0j-EJe#ZHJIb}Z*Vk5D zHJax&J9Z7n_{2{Sdq?}xaY#osuf*c{knhU5vvupuv86Zi>ZQJluGKqjgX=uTGHTxC zz>I%N@I7O8JUmyU9OElSJs$J-DaKcO*vb3snVtKwP-V|*J^r+IR?m3k7{p=?{W_>) zOmW$+dC;?pd9UnM{VJ#a9liQ-jA~c8QT;5&IkQ=__-s~;v;TKt=RLOIz4*J}`tQ!x z3@PWi?Y`pFPw?vN7_W)%@Uz{|KA&;=Yok<`_F#+(zM zrRlrUCiRAl{D=#m^WR0^6GmF>I|lRakL&|AggMzGjo+VM@vD6Yb9*+6H9|G?WxFa) zy~F!nwYi3RdA?*zXEB{y*2#{l8K2P7_bJ_(saol*C+6wL7<+Ymv_~lKI;#8bSn2zh zYCZbtO|FFdzEhziGUDQZJwkccYJG|`=3MY2o|m0NpW-uR{fGncchIxBm2y7wJL_cB zI_#lOeFA%S`h4F7dyJ$WqrSq4XH+}X#WOwjGcQ#w{SnMFPL)kRvdXcR`576x-<7!M z**-5km-;kZ%V%nBkNs|T!+H2l3g(k+9Ac>(o)x32b< zuy5iuiYe~CV#Ru5KKj?>xgF#61zzpT_B@d5x=*|G&&SkcTwmB{Cqtidphv{sI+*P- zw=b(@ocBKoe>U|&Gr!z#Xi3B{tao_#+B*CX18 z_IL8TBG7l(g733VgHNs-R98Yj{Ue|?r3LGg)X`om_Dgc-fQr^%2dXIDrp`#nMNV8- z5x>r8=fIV4PK*Y7hU(%B_f;cRu73ToHZ^iujJ6YxG5Y1A)nHJQaz^~1W>$G()DADg|6~C#86Skfw6+G`7!93&C5zING znpKQ@7!i9$H6E*WTuz`QCQyg7-V;9QcY+Bc<2J_{dkSYYOYh zr;5^Rl#~BS)okgL+3a}=U)NdB>^1lLSxpW14eMfze(rJV6OI?^cY0X!3JsqVUxA-7 z??}Cd`joSO?xpTfQMW@zox>jY-M;Wyeuh5lM=kw30Cksx-x(gF?;|TM;d_&O>WFg^ z_u7P-Pvi4fw$@$MjB;lb zKk>Xvv1rr3qoQU8dzDMD?_lj+P0X{$QQl*2EB}i69NL^QiZQ+-_Q?01QOxtQFFYHQ zeWzYa+x=0m=T*PbhM7;up+5RgX0ew2$AA;h%E%}eW4m>+Z{*Kr<;7lo&TQX4*KPHx zFKV%8hC6mb{fGLL}f9dfUu%HHW?O~z+wq0b%E^XnP>+wVQ}Bg*?avXxt@YPCQ2 zM|QSjdt#4fd5p7~alsbJzFVEJ4}70<%FTB{)v~t^;C!O>q|NrA7*+M0bsAhJ?xi7i zb#njiU7?W^F~@%^aJz)xD!j+-YHQ@aE|q)Xo=E8&P#y=5hop`lGtb3{jJR`jpX#uN zzF&*QuIB{Xxf1r;sn9!AJ5rzPcKQ0-`zgjw)R4G$#a;b)qn-PCacvfh^)tU=FA>$| zevYZ61}^wSuP?Q1aH2-=TG92Q*h*DT&7B6lXxlY9+qH-t+W@KJU9PTdEqV;;zcARQ02pGWhPnI>vt$T3wdP>S#uc`7Gr;_O8vDK7+o;qVKh@+L}q_4EJ~K_CtN#AJM+#zoKXQ z>$zUd;CjqUSG6&>yAJC_obisYnNc}2#-cx4H`cjdaU5h6Q(QH!tapEAv+gIE?St{O z!G&jSgtf9isc{dxvm5s0WccrJSU+utbLG2z;Ai z-yTTqDqpI4t&{t@2UKuR+*|5!zub2QhoR%6mH`Pd!PlD9uJWTYAIXS`E8&=p2Kx!# zOIveyw0`~V-WWT-8J$Dd)tX-S#4h$4HrO-#8lcVVLR8q~G8D_bPQGe*ozd3y#%oWU zunDhCUaPHhL3N#<{Jjm&*$QaJ1v~%We}%CfIEQuA5v!cn7xd3KkXxbS8r3K#Wesgn z%~tHwI@*`-LwT+%dsj1#=Z@W3s{8cu&*l{zZ$z~*zVZ|1h*jMxcZTbLwT!KLm`g9f zd-5q<=V{NU>vgO?_ikV6d7@?Gynm`j``VqS`jz@D*17))duJwVegYlO-Jb69nsZ{` z5xp-_V~)P(W#|4kgBnpy)L7?!tflYy2465Pn;L!3UC0xU(N^4b6lZKiM*NNgH8bLQ zW?hV}{4%)i)5bOF?=5auyl3u^QMcki-f8_a6JxYRf&B^zea^x8;Kb*)J1S}-iJ$i- zsCJfD{Nncun?e66v(LCs;~8r|Q@QzE6!{?kS@=qnPFv+tc~=xBFwBwLT+bzZG2k z9?0*b9se#&J9iXU-AaqiV;%hjxE9wW85|GLu}am;ITc($>uTSP-{I{T^1xQjg`61u zE}#`X7iYJrEBu_vUezn7+L0D}9b%rj6{;WAH1rBx1JZ2QhwO@&xL^}rPcwMEk#mBs zQECPaT!7bm1t;L|dZp8#7vQtN3eEwH&&bF*oOK7+bPD_(*c0m*i}C2I=RoaGpmY50 zS{eI&mo}I&+Sh2EQQfOCo|AZ9MlsG;d{*!IXcw%rA|X#!HJ;n4J^QhFA8q!Ena}tP z_3o!t;vVsrM8+E|1e@PI=kVWO>D0nHL^+`$P?4O!i2bpTTjczv$-Q(S4h7UOlnD$m>wobPq?V@!GFT$LLs>pV_> zL`Gc1yyDVXUUAhuQT0mIE1$}k`zxEZqk6?hxw<$9%sChQh_2Nbr?1>oEnn~5^H0~i ztJi+Kj3*KYP44|bVbPwiY#NAV|`!#sVTEBL-gv9iQM|*dT$9UqHXT)0i$*A74(u8P<)j0TCbDsdD=SXRK5nH2Alqh3wfc}mp*N$1mAPA zS>v4W39qFHuQAbJ&$y6tbnWswK08PMEx7TwKEU4{D>wmv!z`T!eTsTr6Xpo6;c(qg z!}(8VX7oIP=S}Bs)pu93J7=}VlUyB~nb(1C!2ZMQJGuiV?BNK{0hCWe#G~|ocqxp zNa)6=v=n&y5#OtM;tv) zouR+aBQ5ybEn;5rWS>(?a%#>ouEwJGB1!$dxuJrnK)!sJY|B zXS*lV#plP|bJQnX`26aq*JGcuTmKCHsd<|79di@TqvlH$qdLqhr&`xl&g-7ot)EL* z^?PjBR&P}HD@J?dD?Za4_DcVE;J`kfGTQfO2J1#~#m=Y=hmN?R}{7^6*%wqiS)&@V3ho=jUBZTD3}jcDFTi|u)oe>cYY zs~OA-)kU9i#iE|b5sxdTxchFG-sdse$Jgh{m?=i{m5a(+#oShGN7?t^11HXXq#(9K zLY+8q5Bv%$))l$i)F)hgW&@t|%xbIcXJ(IjXSE(5^~IR#W8D4KvFcp7dhf1X?L4T5 zG1{K{gw2^d_%XTN@Y?TCP!~~OjLSdJ^Jz{#>v}*%?Ykt*h+KSrhB#HWYHF{i3g#52 zJ@OTg>T9?i>*&Y5s6J}LzDH!l#Q}T8iD!hwzXS3+qx-Zke$U|d39}WODQipTggS~p zQSYbCUag(6w>z(yWxBH1^Bx_wdVuzH;S|8StAl`!5I$clH56#PMB^Y{gy=o*b$`}KW7{T?s<_sfgFQ`&E%Cw~*| zzs)xO*7~3RguYXH9W?>=zDn(Ti2Xj7wosjXuT##|^VN>`&dyfr_Nb5f9oTzUcUQyL zZI{tndfi94Q7o=Cs!{C8?7BD?qnyWRJ12bMakukgJ-_L(&4D$p4gTuIr~} zzdI-6c*Sg%@}JV9uPNnR+9UYB7vK9vbuqTnllz*~6>;{+Xa6Y97;~@C(C1w6S0vmU zE2`aZs$-0{=M-c7j@5pcYZEf!kpnSDuRXN{68v}Yzq$GwywZxl(JOM#Q^f&$hlbA# zuTU1BRbnooHnLL_d+49|>@=g^d=AUV3)aeZz3_Q5`iu$XcIcKT1! z?)y;wnrkLA`Q9Ul3Dm&YZPuT}%-f^aJjpEcDR_nz6f|)a7t^8TN;;M61 z?iKyi9Il;=*iTpcN{ga;6%%}5;y{??={uBG1-qAX%d9&Qv_fK8H>t6ZQ zJ&;lN2}WyhZj8^?igi0aXR(ed=zki{-kEu(KhHVsvOz-44)Nacd%*1z_vNRln0d#} znw+syx2jp4tJuf&X2e?hs`Z>=Pc>uSCyDEyi*w0y6!WqFRX=YY*M;YOBU*UISo*U> zk1O__df4;G6VJ-rAK9vrvPL%bJE)i&;W{z)Nk3sex%#tAX`4JFWIR8h;ydb7GI}Dj z87IF32NJ&P@4!CIz3}~`bqbUmT^YXsYuUrurPH9!f!{(hsNmx7BKRG}VT>@J0|)#a zv>x}60QW?=zue(|3HR2rp|xu4|3yj^zI9nI4Dr}Fh6 zEpWki_!_QwQahSc`?cbFA|`Zg=~{DbxbApO@*1t33%Y~u@q+vPr9K9ha!Ke5|`-c>DgyW|J8n#rDb`qUrwz}ywQ-yGF?Otx|(Wz8tAm}V5z zJt1w>v*sx~?@O$y_8vxiMn>)(4)lw6=CHTk;PYp-7iPT5)7N5DU;69LoZF+?QS8M2 zXVeGhC!kltyIKdI4`#81+K7KXY2mZZ z6JK+7dotDKK=Vt2-c99dO!6$8GUbiH&z~e zx16n|8Ps=3=ofL0;=6h#{JkAe5hJ6V$5bcvoZHOPr{0lK(b~l#r*d*vMAyv_8dV^@@)) zVa~gZ=8j^kbuK=y<8wRgNA|AP-aD&jenduG9I%ByCuGchw+o&bj=1n0o7ArIpK8MI z2u`uzA5PdKSfe;?kzf<;D`Riu=+Lg<0<2@-2@d?uLiGDegH0~Of9I+8n;&afn~ekZ z46aAGwn(rE_eHqJY#gvfv+q-iv9=nTr}@-#z-W(t95XxiX|~o&;dn0F8~1tu9!KdU z==t?mD*QXtqdNMsSDH~Lu7q$!g-=O{OdDYw%6@K{WTX}EF*7o9BM!v> zslekK9Sh?^dHGbuqI%7at^Oyq>e4nc2#Pf=|Hv23>JLc$nP4stjUUz2~tVv>>;;ylVzVf4-VppF}{SJH` z&1Xz@)K!kP{yILjwHEu_9?eny-8D74+V!j0tLl3j?Jwqk+UIfoBgS<}CEH{P;(9n>;ULycn_0bJ32qGyk&J#_cD^>3y`-8{<#> zhB=X9=i*cLftfp;_RR2IyS^}sJ?fJkHKFEHTzGaT8lU4mAftB0fxI~J9PkOV-yBx) zi=W-?H<6RSWf=PX#bYzO*qmsqNAsxJXs|CszXz^XX*5`5MZds1Z6AYUwnn!*e4PXKm_e7F98+=VUAA zT5N6F?S>0^qSsIUNL!dSs+q+!KWfD}IRURLUSAFP^+-E|W?ZlZ?^E5&Cv2i;4|*=q z`tuX1;#9^ipl4ouE_QbK%umn%8tjO2sXsGRUsTTg4sw4^m!O2lamSO|yIDK=*3WU& z%ySM7L$BXTSs(MGnAi9kD?9f+t+Z{_tonCzM?WvIein~)yMDtuWVe2|?&$m9QGczX zU2s2-NQm#E_X=tqkP%zqJ*&D`bH`wP#suFJNAUgfRYvm|8_k>5YJbvLr#RJf9`~H$ zUi-u@cAjX@NXUH({_PU0nW~AZ9>!_M{LHVYe@fyS2K(LrY1=<5=(B~#ezmPVNx2`{ zPnEqZ{M_%pF1vL;TYK;rZO$Sw?m5*^&jEM&QO}duf&0T@FMZc1^PbjLJ7(IBW_gUZ z;&-a!Sk-afCrFqhe4b)=8m}SwyV~Bkk793J)AP)YeD#cS9&?*L@@e0}TGmMK>b+Jy zRN2fE#XhYWv&G%)irPCg>m5C?PK5Jf>=nP^J9eb>m{fW9X}^o+QaQ_2Hsd1_;yauP z?>(Igjby}#;%+PE8uMqLy;AR&oo!9Uq|7NVJ8E6@znkM)@60&SzhdWg^BT=!Og7bP z-1eF~yS{m>>c3lOl=nTmUB3Q2Hrn!K&$OX-hgtu3$CDk`j(vvfNIfzB->YBywKv*7 zbBs$>BcHlMLLFhP>?eBR8PyIgJTn{h#G08u>h;)Y|2#g2HS%dYqggRVpFJLb*KXk) zML#1iV$OZl%=Ewro!^v@Tb9TxPgc>2un!t*;(VLv6`JRcn84|`@fvm5_C zr1%cDOGPcYItjlG5&CY^_d3-p&Rp&_QpPTzIR$gY30oYK-xTBTjI1N-b1gR8?Swet zJ_z?jGB`R7-@inG?Iind7H!ov>(@*w*Km~c811?VYY)TMmCd|tX|ZEC@p+w8kUQ7c z`0sVGIPE)Hs5#)1GmY{5s80Ermaty*8}e6&$I4?~aU$=GYI8qlcRK22?7Z2-tdH>= z7}a{sD9_kwB)c|vornh8uQgtK)wp2uzS6yR!X~^wGic!IBs_aqQIQ{UB2ErGOCzPz zpyUD>&-zFWobVmybBBGJ@hh~1_34x22Az*jXbtB)!5^X5P|DZ!DH%P&dljR4oc0xn zd-J?<9{bg{_Vj6;9arn;OUgRhyJXa^IFKKq`(9f5`v7%DLhdPiztMY+lry5Ox{+3U z|B{^%6GyP=KY?>%J!3yX#X6rvd*)2qQpKO>fqtSM`MVnb7VY+p>NJZwqfBy)f11>1 zM%6luk5HaE%Fkl4??Auf`DnMk*BF<7msi~<^yvG3GD|(wyS|2+8CS@;Py5rfK9jmb zL0#lP>?v`E`Z{ z8^xTyFWqTFuX9&_mmlp%J#mjUoEzqMV&|YP#yCsVP_H;O=HBsr?#Pa_M>UGERyLLX zh*^!tXSQCKUDX`c({|mR!MgSax%ax#ewEgHsj_!w9jIS%CcN(j3j9~#oF{$hVf~ZY zvs$f5l|8FHQ4>*ZjJwbC?f%GKy(XW?W$b})mX!7mXE<*5jAEJcr>a%8c#xWDmYdw$c+N<8U zj?YxzNDI!*QxbXz`(!`W6VIh~@VQ)%C?;ERYU?%B6|9|+kR!~?c4d4A@42(@&PB!f za#-*FC+ysx6|VHcGd|{BX*&)6h+=Ws)X0I@h?CFekfV;Mi0_a>9sLW>^a$;zsMq@o zev3If2Yy2%^kq}?0FAms*`z;`KE_?@d zDtwXyieJ56{Z!72c;4+;H}X}lwY2s~RpZLK43192=ke0f=k)Un=i*H&`bCEATnXpZ z@Z7Fl`}`B?Fz&wF%n8ML%sT!Co9Nu|JiUr$j8yB)bkq}L>@7y>HZ{UJob zpRx9Uj2Q78d)Q0T2Z{URY{jIC&DK>NwGPOLIXpi)N3vJi!s|f47zb>@xNIt4QwhV@0<2jemJil6$i{%W1=UHkIaI@%TQK~9E$$A!;z zBlvkwjPkRXdOo4W_7LYW_X-F4eh2hj!qxZX@mK9S`0m149^bLqQ$9q(9P#SR^Ub}m z{|WxOjBL&t#YeH+uRrOiW;eDvhTR!IS*QJSP1)t)YpFh}_U^Gy*qSR{%`6Z0pHS;O z>HT!A@5AjY{kg|z)8E18(cbB)m^~vSH-hz}xW`yC<3es1Kc-R7XbrFP{#R_xn)NcT zoN8yvI@Ql=l-JCt>Qh|xF|PbfRj>Y8-6&qZ{`k8z%8g>GpQ-oEYCP`sD_cEh->-aE z<)oe)*}HY#o3OsK%6Z*Wdo|Z{GyCa!tmmw!eJAc2QM*muC8Kr)=dAL|U3~u3QOvd4 zo)~vu_SxgEvQ^`nCwpTJb6)EvIqM4w;P#dxh5?s3nZao~hQ; z`pNdVvQPY$K&mNikztby-_KX5PJL3vqbh%fbz_};t=+JXNEyUf!#FuQCqK(~DwL$a zD8?Gb9mcM&KWVU?41a{zMEkONZnMt)naw?s5>Q+k`xR&3+2J|MhR&f>xrUk*syq5? z;HtH#j7Rla*{ms@1dYc+pK;clfPD>IAmLhzFdlQYUy~>Fz4@+;N6cXhSKC|B(|c01O3?svB{j>*w| zOKpJy|BeGSgmtptRldIvzTbQb&7(S;@BS+`k2m(|9F)ml?2Ddq36x>YTeiKlkV0SmQa3zV~pgNI^^_*zcmh{WD$7_s zr}ccT9lPT5jg#Tu;q|+AoOxiKr!;(Cdj)>hV;^+|Yj)~5BmFQZKDUlo?PFB`D`MQ^ zuUL0y&DzhaEA5f5_&b_0Tf8%8)~nglS)JluN1JiQKCM2R_ES#ukX4^*Mmo9<@RS2=3^X0(RZ+*wbxpVXaopVTCt?+*KDQ)i6w=c0Z!hqEHF{v6DXJ?^vL{gHho zd_SMiHsY+A)y?L5e6*+Cn9rfjz8G`A_2ZFx&h1ftXHWHOe7+fXc4p0Le9p{PZ_F!B zjkz8FM4yQDic4eu75{D)^E-1@r+HC*#$9dfeWiXhV<%QOk9SnJGylM6BeGLFsu``j ziX|M+9aq1vS9Pp;N=46#IM3HFU-;|}JT|lAoaooSj~I`>>OP?vYtA^3bG**uPwX~$ zUbuQrILal=h@6AxgtO;?F`mP-L;81IH_r%X`DV`uNA{?1w3kuc$=`k!ROCd09Z`;Y z1`hl#87YW4F@NX#8oqYwd%N&k32B`Sy+gGlz3Ywn1MBS&`$m48bK-Y~6$N>bV2cay z$xem8Lq;7r2DC4nW22buarV0Z)aKkf?q>S_c|4BqkLz$2W2~v23+m{+QcK{3FJdkY zUK6gt*{-q~7mSh281^W*Ux#tloPg`|8q&4Z`aN+$@6i23_51AX_c-rU-Ny~KbHOJW z&x=Hb?VNm;)Jb@TMM|eZXB>D&=4AM!b}q9Si@suvN0=Lt5g&0NF1TN}XZu%NwNF&N z)M6yqMCUVVwR6uLeW`Nv9mS;y`4M`p{q@g$?O(uWxP3Q^wXE@;na#Rg4%9~c`j5F? zKieL>XBPJw+S)_Gy|^Ovzll=6=NWVV6}#enWJb349ok37vKLp8d3`vg!XM=ZDtg9P!TnQ$H6g)$8z7YrlrPPqC=1iLqTj zqjv=RNAahz19NuZ+?~3H&krm3`65kNSLU~ayzQefl>LHP%+umUqR+)(ihi`y|2z`57xg>>#&zT^%dAVVm6Dlv)p&Yb6=18 z3J3a}GvVIsFfRL(T5OFcbJ;f{P2!pzdl-)~`i$?O^CI2pVU6mkBE$YPnoS+e_PpEN zBVkPH`IVi~6N&XP|E?eBkNllA+Fm2M>AQLz zRW|!(Hgk7m)R0(r^|@5#JoePq4A-3*tRK}WPIbI?R^#=mVV~k|EB3Bt%ohi2((qm& z+m&(0bJ5P2vrFuK;`@G9cER3XK{Hpq39~=N$!B_D9%GS;*ocgHrgs_S=pB9;2EQztzOwETf<8H_(|Kd*rj8 zHdQu#u1mC!1pC5o9d+^h3Ui)6tX4Vd)$2Uww(?ZLeC`}(GsfHwtbLUOy`+WTaSD9G z-%xfr`)?f?dVT#tgN{)Csm56a^Th#sgz`Ju2K&7b6Fj!snH?`{RmU1KJBK{SZ4=fy zslPT_BWkt1u59*k7H2o0Ya(7#wO@}HljK(O{En!KxtLn68;^cJNOLEmgc^u>umozHYw}ervD0@Aqs4AJcO^w zE_@9m39*P?dn$A8%WnNTk!JX>q8Te4%|5W^cQRU&vDKQow? zlOMJA_YK#Kn8RH38B<)gYYsip_u7$tq5qTUJnU#0oPWl4I8isl`y1-S>e|`KY0gMv zKWk!)e!jVeM?Epd8LK(IzhdVz!F~$+)pO!n`6RVp+plU~T>BUPhL7Oi_D@nUPaOQ* z(BbC^+SD2K=CeuadFngpbK6W;v(>YziMeX~c01NR^|hCkw!vrDC$Y@Wt-k>W_V6w| z4xd5aX~Fm0$Uefnl|Skq#TsV5%Vjg-8i(zfIrm5QnQ*@X&nYG?y@vXPgnH-dzspEH zcW0M}YqS1trswbGd+igOb6GE)^`8CQC{ORV~CbJ$?7(DlF?*-v#fU%6s?a$9-V zo%QqAU+?*uKbz(Cvznc_@B1CQgx4vq?Xy=ouiLfby!?GZ_YpS?AfUs)v))DSNmpjIgh!~+}+rPe!_Yw?Z_y{ z*mrUFxiYR>Cwo@iD8GuSPpbXM-%->1)K71uoRTE%uW_kO8*=sS$Nf0$2uhlYFn6X-qT zRo&SGk7uWjHKSwqc(!vTd(`utG2c&I$B#4SX1?Z0XLYN19-Nmav5I~%>pM{M)A;$^ zwM)1+=sO91pdm$Gvc66an~}ayNjt`b@EqwCA`i?h5eoQS|eL)D<}V{=5QSJXB^0pm>2q*E)*H|Fo7bT%!wC>#h3EK9l-M{O`Q%JLUTAA*Ju1%3k+(?9_Xt zJ3X-`_O?Egy5jD9d}lr{KL4%OF*&UO~~DG;_u;# ziu_YDdRAQc%rs;4S!xt3gU@2C*wO2$EA)AJRl}U>W?J#NdqqK>qz$hA?=an|{fC{zS1p+e7Tka2?CAMV!?U0ZBs`B2)t-r+u*revXQJ5iwFY|!?~(0&SD!<@ zgU|j{Lt7};`m6$V4)~6a?Wy{VPuto{9lQpNDKGW9XeZPQzQzt;cam>7kau|f(ARY& zE&ckECiqX$HSB$g?P#_8BHR5!`?z6re~w~4$M03G=Xu(UdrUL$wBUW<9Y*tgA0wMR zqqt(ZACFXVs$x`+)7IS4o>sB4aSjq{cHsND*ZKRqzwgV=s2BP>GSY^>F;0O`j(}py zzp5>)=l+G<3>}NK_P>Sa3xDVQJi4t}9;2-|Rer3I@3rsPwIAQBnz+{&n4N#c+v4?$ zxs2LZIM8v+&Dkp&zPs<@bJW9_?5L&x#t=0jKI6pq|4&dchvcrb z&u&W@i!k>|;(YhxI-aAQ2m51ehJF{3&gx!`*TH^15j!zg#OoqnJM^33*W}F3{r*y3 z{+-s|$2!`={R{DT;7s;YKTXEWC+x0&Vy?6Ew}JILb+M*={q5gPkHz|*_I)q18}_`z z=rco%t>&dgfwm>ap<=K3662h?KkakN(?srw(6j%L7Rl}YHF8rp{IvFYyqh1}>&tQ%B%Rv`Mv7GdjqMR3?&C>Uzjx^Ax_TpCl$8Hup`&x{%nZ9LqY!x&Am{=y`6x^ z)BumOf~&K0zEq=geBxXao&R0Uc5OwwA?IB1XZSUw>x$}e*~*{&n$*2jaIZFS0Y3k! ze4Yaeo)?J*Tf}Ev8PCyPrJ~n)bjQR1n zUHWUApblSm!RsKB5fi*#BD{{A0)NDTxT9-wM+>gsr=)OC%oH$^QXn1dpl*Qk-6Yu4O@9QLYF1+6pzV8>~fGy61UQmq$ zJHo#uBlNc>BmW8q`XW!~G|X6WP4>=sJWf9kZl5?OMCVOfzWzM74Ys&oJ9^l;!194ZYxm4D=ziY4hGG^|;^T0YkAD_0Ho%>JN1@k_|fmyF`hHFl~^lql=Mp`l3 z@fwfQW-i9Q%THKKtnORgyQ)!-YrWaG*RE`>F;ez|u;LVzj3ArJVezC-ywU z|6b)UTo)h2XVdoCvs|q6o=@2gd%5H4HPm;|I`7Q*iC*n;GN7eIGOJD1qzR?WER%pFhO+x<+-p{ifUp*eXv(+=x zS%1YIS7hWP2V#+izm0@(*)uJuBh1T|&zSJs$WEwrPW;U$qnL8?s#M z%iS^iymuEb;dA1_--E+%xb!{dc0n#pU~ZJZ@Y}4Yuthdo`O$2jsXCu=V9pL1bvtO5 z*H-jiozWggG44CUI?bVSu9M-fIFKg|zo!-;!=s8otv!!>@tf#7V zpZ1OFIcpRj#bRyrM?JF`d&N$TYNd_O_68_F(!-CUw<7L$j z@_5{*?Z?Pt9^o}Jd(@{Gd)%fk6n|ASW;qA^6VLyOK|kVo*|CN`=S6(IXtQ=SXSD|F zm8a6znwsA*_X2wTYQF|tPsAKu`>rM2OAUVHLQF76o4R7BX4U^nj%%j$2NgQvMBF*P z{^~Qw*ouPujE0=JV6RXebydflYFBlm8pU?BU>#@Hb8YOG-x*jZo%Jy9HS(isR;*Rr z=f|2jYvpTRtnvAGwt9E!W;LAQIn8vfsCk7GeUa51)-o@h)iVDI{O(EED_dH6PX3NQ z*~8AfyPDCyM={Qs#p4=&47HzsD&s3v?Wm5i5piwpb5#3IEUx3Tlw<5u>sLEpwC~8M zc>?cGuh;$Ru{&EmQDa}sH>~|Cnfsn_Ail$izmq#~7VD(yDb`PWMnY}{|K^KTUCgOx zr4_S@*C^(iQ9~G$y`u-}BMqO~7^6lCV*g10E%yk&>H6=x%2A_z-u&jPe6`2LF*?_0IREsW1FsWbE7B$?KT`Ev=q0=kczv9J*AcI&NJC6qu=yP4 z1bhzE0G}0QP&au}>oYIRcRaUitAAF{yyr6fcR4m@Xics~n>yn_POyeH)v55sD$W|u zNBclN!q;5z8W0Kg3|=ozg->#a*A>xqMlGEK`V?Nzg4g$}q~1#vx_4&UCikVsGivTQ zQ1dQvjkefZti4;i;k{`Fdp)OFj1?onCMVuEN$q65kM{RozW|BTh`G>I`jg1^g+@Y+#+6swp;n3H{{s+G>_nV-R) zS&nf(mS~s3`BLmeE~3}muZ=die(quydUo+y`R2M*-g{oPugyJb{%-xOj&o+Yr*Y0A zxvTuB%-?aK<_^sm)y=LUuVsz<(aszDR82$eH6iBweUWwrF}CvKI-Ea(b&OM;)$CbL z{XTD2>v8o(<(ywd#@Z_mvM+NcQc#qC)P3-V^4hUbJ~x`eiwVSZ|0oYo^^b8I_lSa=4h)%Hg$yRMmnm0H`e+-q&xEtJD!=%bEe$g z`D4BsUhnZ8J7JEq)599}L}hHo>^&&Hr#JhX;C4p50&27sJ1oD7F~{1Upe6jx9Z(Rv zBlQ~U4ktcu?7+_^k!&&Acbxc5m(&L&)ZTI7cUr+(*;MbXVO`J3R&PUZ?i|n&%2Qk5 zf=@JeS8LcK{j+o6ck?oVIigxBdqsjRv^Q!4xpUCRZ@2{{aN<2EQW4AG!hPF70S7q0 z=2YZJ0vE2!*2&PLbJ}%SV3QQM@cI9a*t7Dv200Dxhrag3J#ybC;IZq$p7uf}%Q=i1KQdS3epyV&`k*#~NzS=}pf&DC{5tpgHb zBY2$&UUwuL2W)cU+7!G_N!sATwJms^kGRWwPsR-AfFJSuQ1|9jO}Lkd_l;tnU)gQ( z-k`qJ*Oc+NZJF&zaQw8I$j} z#~7cHk&CR}-#pg_?~jb-&H)wv-g{@WM>PfS;|W}N4wX9xpGkEXqfM>g>Pnk2L7!X! z#iiML4%iXqYv6)Uv`_cB|FaX%1#=?xV`Ogy2WbBb@&giL=fA~zJ;ypy7N|yb$!ZzP zeO<0EYS;(sjvk}ZCkb#r4r9u*Hg_siD0Uzw7^?<-#V*)9PdsO5fH6mTp7U%p*mqp0 zdBSX-W+dEWqV{`HK2@<3wqT4lHNv|dsnvi%T8g>?$h37buM+jtKDb6!p>O#2^ao` zu-=unzmMaGHi0;^RZ~K zXQ*bRnkB86dm7va_aho??v;D4-~v2OKX&=l5vpD3*=JG@oL|E8Oj_rH3dUqp`TILx zCvpb(dJ?|A4P3yl170({h7LTl$>8F%8$3fId`5H{2cG8%Ju_>FjkpkxjOw1m^5A)* z#}3=y6Pq(wPbF=F@=*`e&+zm2ip|$x1ZR!nX%c5Gb1VF{zpG=dQ{j)uh>HXEiiYb@ z@S05E#Cw#e_WtE|Mx5~49--@=IodNa?~P3k)J5XGS8y+q)N$KmjEi$(D?d~AJtfXc zeV{7iqtoy{MhZyAiT6*!_fnEOSHkNCno(do2YkohxA{IENr*+_-vs>) z$@qKX9PsZr@q5yUg7_yHtr26sUc&m$h2PMe3ZLZ80d4pVu5^;wj5E)Aalu}px|wG5 zI|qDUpZnk*PX7ML-yys2%6JW^wxOQ$YhQ=+wFYwzrv+Ii2 zA9K`KXy|h;_`>f4#hxl>sK@7x>^is~85`A(Vq9kwpT)GV7S6%S&o{Nqzk;th>OU|y z^7LAX{aU9@j_E?&VV(O=Z1(Q-t8SzjGggdx6jR@U89OxGhoTJbUyr?GC(IU{LtCmi z^~8IO<2lwlo^#u4p4biZBeQd;y4C#DkLMrWJMrFn+52mOP5k{!9Smm?3{2ejRsp>u$}aUJx}0w zhZ!ewI~@3465;oi5v&`<8Ds7Vn$yrHF4()MXQahuj`EkSGs-jOHMB=D#hF{dIx(v& zlNq!4Y|dqCy<_ux>^o>}&7eQB6&q>A9$sNI>l3k8_VS%Q=f?TjJ#OD8=L*JUOZB_R zN?E(gk8%focOhCws+jUo3wnfd?4_+4t`)ggNj$DM(Y^7Gw#oZN=6z<&>KOOBnSG#6 zutxTs?)>e!t101c82wjCz1Nj?xtCK0&&`hif?0e%W=cB)YJ|1Buy?n% zU@l27qW+P#n>~+u6ia{D$1Gd9^Uvm;x|IPDS4J;7&W^b)>ztneDX z*N_5^4&Sf*eJ$DhA>R|5kzqRrd~&8gDX=36F~aw1!S`;FVT%K{zwgs7odgvpf6v^Y zqDExcPuS_Z_;UG6WUIJE?n%jhG`pnrha zM_;uU@=mhr=p0;k2S3XI=49_`>DPDEhWNX1=4b}vKY_2kTF>j;UiG|^J8;~)a6W6P zZZqcSdZ>1dxSbIH|CxKYYg<-i%Mu;3>Yt+tq96*QAPS-&N{t@JMoUW??sKlScigOx z-iEy}@aLND9y<=_(iBaU0DBAaE1)m-t-otVswam33F?gJu#b)xa$UCH#^hX8?{8s$ zm?`^DY3gB$Y{Rvl>FNGaWt**g5R8C(QVFo17*UVz1b#~t+1%?4zHtogM-4rO*n!uZ zw_~Mk%yHW}f7-4=t6(1HYoZ9R$?M9BZDv|GHt`(8H#wed@>TG6fxi_^@GWbKp1>zZ zjq8Y=bw#nI?_vhOiygJ|qU6pzzUM>j+wRIU$4KB4&$ZMW;!{uq*sz6b#--jh)EQ!3 z6v4bz?Em*)utz`+@N0n0IC^YRHK)NpA;-E_C^j+b<^|&9sVi{~_?U0`z`4dbw*=?l zZ(;lmdP7SvFYD+5HhJn+kk`?*E|>}5V|?Eky6>1R`^MA#@vO~tUH4U3f_r*POxc+& z|3=mC?URvyo1gYl?h~bIJqKO>jj6cVvQK#4a18roD0Z?GJK?zo`VaBAZ#4Iwn#cHA ztLd5R4cC3E^#8Ut&-*kc*L%HPHi%j2Hxs`n_S%g#7{>Zh8Vw@nY* zZ%W-$t#etQw(BiEF8hYqCz@)$;jPbY{MNnfH*n8*8b{7~+t#k!w-nXF`8Vaa{z!kH zu>X{*9^bO-b1gaNt?ujGCw09w*H^vMJz2c7P4O;!9u&PhK5-58Pt?evhWTKKCbq6M zm}Q%Y0L@%v;U59Y67#_Ji>_&wHD*YF(_|+sQU|QN+@9+_?wnyoMp#L6HxR z+n@hvY`}3|I&U&VeiNH_xj~oSD2kh<->5&uW9dD`7R=2WSQ~3)-6Po2CtQOzQ3870 zaGqK-WScG9RPE~w`~Hcg9LK1MDQcX9CC;-g9Xb~_-&FL?rHjS47kv+E2U9-mgui3m zbm$w`jivY}_{NuOPCw_K?EH=KspgE2hc(yuUdOiEzSTO1-tvDMbN_q{hSqfQDSy^~ z@|)(!6#1W+I*$zJ71S?TIv>qB#_`akw93N4?FXL{tu1BGX5{l0nydHG@5Ra{~EuJIKgH0TEvWb@qQ}5Ths0T~$ z;0EvEE;zn<*BB6^2XinlYcwO~Wu9}c74tA2n|xC}W6p6ia`at-r{LMTlznV zucJ3T*WxAmO=r#|a_8q;2KPh%Ws>`(F7zonkPXf2;OYp2fb8}uQb z_3Z6lR_F0;Kk02w$GYZ)8raV<=a#Nr@HLFKVCxzNu4U*!k@{XxbszA3W$OOYf+FpF z2Z`?{d@ng6S5-ruS+argCiirnde@PkVh7l7xQ?}txRCuu2g3GF(q!-|BdY>y7cb7d|!A?*N-v5#;ljDj!C~siU6z-V)p=r^xqSeBxD5gW^x& zx~BW~NNm~sw&=lf={NryQ+e2lTGji2=Zll2*c0WS@}K{GV%DyhnaL)GpV{(( znC-uZ>Vy5njGXnU&T;BK;o4Jq&vG&p z`ziEb3~C$yZ?V)X^VGM+oaj&ZX1~zytjqpg(&QynOa&1xEY~I&CQ1uQNcpnVw3tWrV z4|v|8`CCw=X6pO~eCLPkE!;cLhrNQLcitJt(Hw74hn`S(#!jCapl=sdus+u5wc=-p z7s0kwv2@N4t|6VnFhx%k*_ox|2P3G`UH@fDGfjR8mVWE?M3rr}en)18{BQ6W_o4q1 z%*otauuj&`HchdErftqRj#AfyBK-*-ht6P2Aub2|2N~F z+v?--N#}7a-~6uHZz!t!gwHLXYu2js?S%8vyyd3qT2Q1nIM<;+P!-SjHf*jFXo({G z3GRbGf$Q-n_0;CM^SVCSc^rM1y9V4JfO|#@e4oiW(|xFM4|<^}$GshTqRKYAYM+ad zXtJRMQ}ze5#i0aV7i%yz)|l7Xjy01rGub&tyon+f?}!BNfuIK1OHe~?X3B>hurBaA(Urij zf_lca2gp$a)Mp%T$`!%d3_duQePWKg9_?c59a!Og81LtJ=O@q}@9U_c7vrbc0d^7O zd5=a9#sD_4EvoV>^=L7UvDh=fHjZt@zreu| z8TcMD^$h`fsC{%FEpNslG+?|{Cxvr?03@|h>kYrCd+ zQ604&i%pENC&bB5u|x5Tptc89y8j>ThX5OhJJ;mT|D`bk#V1EEwsDCeyuDL#&qWP3 zu`Q_cG05iyakHYPALkJD#899PJT9wqsOuR!gDv&-cDv@t=bEAGas}KMxj#<9y))la zeJ>^V24lv-md`!Dixu$qs);G~0WoL?j%VF5w#G9=(Q^g##FP#Dz|Se8YRv~rKKME3 zru`qy^T3Qa`VBm58R~7H+S2pdkbSZh`$X0A;kR_vKCxbMc?@+o3eUn1-0I(AJ=XG8 z^G(iuextd+$4{**<9Jr*9mCJI^H2Ngw{iFVsd{bJpN^;AW6^Uw+jGY79slk2dR$X~ zu>XyE%qJb{dt)p9C!9UsdR4vO`i8DLbJm~zJwI{VXbW=ew5cJ6egm~<{ms9{T8zEr zh-F-hzEPCFaXUwe^`J^&>7M+=h#dN-H1&95>u(A?<)7l;jB)QGv57 zQG?C9)fc96nOy50^jm^?rq~JA39K2GU^~1`Q#P=@8OL4sTXi3+=V-B?hxEo)yvDKN zI5n{a$9Rbn=gXG1^i8np`wsNP26l-+|OHJsZgob#C~-*8Q2rhL<6-zbU$Ionq5ho7L98jrbYi~grh#WOiy(t7CM zMNM!|;9k*05x%Ebd)(LTXLSy(g8LwFPlP6R{Xd!VO_yzoY~Y^Dy%(xD@6FlAo}vXs zy7X+H>GDsiVz7Cq91Q7CVXS*G#|l`7*ED6HI9HVW3GR+jCR4uLVW=#MHZ& z&ij`HKJhK+F~kzg>2*xm8E>1n*E-Vvp~hqAQv-T4N2bXK)^LOM^`JzsU6Qc%(zz6Il80+!)j!nhcCbn~hZpFG_EWKNr%X1PVw}Yx$ z!?-1wk3Lguk>_D9?`!t^H@H44eV%aLnIEy8{|!Codh*}qZd#vttNUrrJ<)UQiv5`5 z)O~{bvxZ}Rl528qUTVn`KOuf{*8J4(>Ivlq&Sl`dt{1olxE7!V_N`lSU_5;?x1Nr9 zock2@Sb87cc_$w4+Z*rI1@HAy$g!^Y(l-LAvhiI@oog9KoP2$#%k|V6#=;1;6!43n zu3vgmQ)64&QAa)F7*Eb*e<*%JU5?=!$G2j@-0VwO$;Ph%cJ>{=sry<-tfj>oiWJzc zKY@Ldp%;Beu%#Kt(H0b`aV+al9`*xy`@MgtWlRf-G;e2KbGED2&-pommTaH5)VR*I zXwKWu>Ty0+#~dejG8HrIryJxMR}Y9iF_nXTK&%M1g>5?V zHd50sFn$Xk+pI(D&yb@Q7}tX$1^ZhzP|x@!IIlRLrr>_p0&E~oO{UAwIOo_@-Z+L< z!JHXlK%Tj`;C|f268>A!^fzOQEvo(w!U*hVbq-xYwu^oTo%Hx^G^I~$<@jyO?^?5D z^ZWumn9_`&d91Cfj~EYZ#i9Q5FI{@#ob}A#{G~po@ZRd6$-glb|HM|!jjCtOZy2ik z#8S;qC_HB;K87-;O`Gv1^|JaVxeVmgC>f^HxXixBTDC zc{`qzZTp7#MIALiWnab+dn3OK`l&ti?&uqq>Q3^wru-APwa?3T=GdQ($-Qi!Jstm6 zyVeY6z2nw1Z{K-qQ7?0z96U}q3w8pA;-=@d=@F{(w=so(4 zT<<}F$=>>Xc7vR2muy2HpeM`+ytekwziS@S9k4yXwwWdShV#~2&8HaqGs8ZHDRzS6 z0YfZN;#m3kahy5soC8zv-DU9YCCJ}%_|~%me?vOIEls{F9c=mkZO%Eff6kqAO>2W3 z`_|sac6~Fh#eDy_v}pb}IKDZL&vCy|k2wAldAw_Uj&Z)-Xo{PybJK91_Mk}3O83c| z&AI-c^PO`(;5v9>DtFQpyHON3TxV~%?xyY1e;(^_jmLYgu6e8Q^*`dC&_at8rtV9o zr~6g5tKylZ`<&sv*AlyGPBDWng%bEa>w7O+gWG*N$ENP(rpbm9EIpf_^mwL6Yq0e` z;DmPraK=668)c3qSdZ71mALb$>9S3g4fa>6!*=|pHS$@p!P~_)HclZ8P7xAiN}ms1Ik$9)J{>f4TkMG8H$~_ro>qK06hVl zoa@kWu;qUX$Mp&KpnnrZcrB>uv9>DB`!Czdc!HW+?kPUh%PiSoKkF?{?8IZ5a=^S@lmlWXXHE7w795i*@-eeb z{LIZbz9=+N#DA_quEPMII5k_i*NC3@wo%)}fib{%(_}*tc|OlYZU$R=LarrB*r;*e zt$WKz_Zr`Kh-HWuG4&4K;1eHWhvJj3g6{+<5G%@&hmmaJbHW(LyXTe-H60`D zH$3J|-EXN^rblnaj#x(v8~-Hdu-_=kd!Kl_^Y-Sk)B-Wv_K9VNe6wYw#F6VD^#9ec$bMA#wLmXOwy%M?JW1P=9 zVvId;ZP$ELuuU0aPcY`>sW$h-rp{wM2EQJVqkf3<7!YemFM4Foe)u_`?V0A<>dSFo z!siG!u`0;J5KWW>KJhK6Wy}&y=hzfGz$R7>h#BV??>^Xu*iZ3Tl>W>$#ZH{Q|4wxM zt$-!?+tz|9-HEEdn;CNUhjM_wM2X+7{C+i)-?Bl|?^_rb7QcNH{%!oEUB8`S3O`F_ zZB=uL5p1cc|7b|ySXSrIo>;O^$e%I$MbC+EH04jGVwo-fiK^$-pE5Kiv*h0>Jg27| zeva9HQ^w~px!;>T(i-3D{-<+q+nRG-`|1<9KHI<9ro6wN?BDcdOkUe3yT^7E4{zXL)?yfeBfu|HvH{E6S4{X}z*r}ebhjwgNEC)ATW$34~OxX0$$&7SG_ z{KTeLuJ4DRbJU*XJa)fO6wBo6AYUt0=lUDmgUM&T-IwR(eof4A+tz)le~WqCO`CcF zxvY+(LcZ zUyp6K&m1kbm9;_-wZD^3u85&?v<91VOia;45lhFciz>E$Z;YTxpEzrZYG+j+QN_~l zrV%tLZ2kU%DSCiS%rzdfRDa?e+C=u3eCB2TDOgVvLu|pev5hNWyN&mQebjZQzU8RR zJ+j@@bBZ1KcyMf1z_FTQbIjtL;@ER;Oi|*SjKA0H<2xbWc~;=>NbTkO+D_ll^8c^r zo1MpGdmi(UJ7aHtQ$3XbBAz4bOzX_?(=PwTb)CI^46WpCTl?|-@27q}#^Zy}Db(i~ z+JYiAoRh%$3H2b~6FHx6w7938$~9oPCQiCynIiwh*HzZD=JcET+?ba&xX%6ipY(A&7T-p+laRqHZrTgGwvtOM57 z#1uR5@mxBN9A|@1t{!+CJ&N!g%m+1KO(W4{1Mh9B;C;@B_d59fAlFc5n(U0@=sF+$th?8Fd^PM$r+GqwoUlp$uQF~nTgV|&mV zcw4iM*jG);zU-n1-scTb4~W4M)KNbJdeCo)+nnT_w^ExS{zR^G|E$h?ynQs+-Rz$B z7xoOipTAX)L+Nw!lz)r6M^!(Qx9!OuX%jw|v*3J(1(o z0pnVt$Of|No}m*5z2gY^P&Wx!=8-MAmV6zeFsvMU$=@wRO zc>Dhib&Yk#_}^;N_!~uW@Val>W1G(08P7g;_SZv?9QRsIZO7Wr{F|TqW9Qm4_vH83 zmQ$bV9Y5>wYv6N-^T;&WnIhlh^O5t@RM}?f{5|2EHchtSoHtYUNmmTY5AnDeJ?)?B z{8k;{Td}(+g6kBxW^2GTJrYf}DY7$D_lHbR_Ym7vv88(!aQ}i9?5cGIQ`&w*VrYPgZbtID-Qqk}{5>;Ewjovn#xcHKD2jPaUF)j}j#2h)``9(*i{L#>7fbJI zcxTf^38s`7-yO&s`&Hw>ag^K%@w4`h@ApoKlV7657{;E^>&98PRcD;* z4`UstZU@-eXIpN#KIe#eyp`BfjdLUR8|qremSTY44v3NSv9S_+qAAC`tgnj_u>YTM zjg`6)R4Lq!t8=bhs&fw%KSQivn2O!likqD8sYj6w=REd@)^Ji4J8{j5HQPt)13lvG zn<<(oV#PO;t$!N@T|V)Vp!Nysc7nPR@I8Tk8FJL@!0Yw;>}RDG-(%1rnkWHwhB)(A zF|=Kc?TdelW+g_Bn!HBrDd<7ZaX^e5PzN4k|4BX0+SDU62aJ^9Cwa=rX`;m z#(B(?ZHUzbJ~`$!tiw2l60^^kJdWH5w)BScXgeUb57@tk{SEeiqVCHa#xayw6Xk$d zX2^$IjQ9?`Hr5L(;aH7ru}_U{=TKtnfS9SWJuho8#IRA@_!;L=`kXlLc#n1Pcc2CS zyO7m6bSAd!s=u2f{;rnzO*EyjgY5e^*V1n*;5Tm%rj*~y2LD^!1N9j9Y4N*!NMQ^A zcJJvoJW9Vd2+J5Mj{-1EuqX&cX)ocr5$o~vjL#@p~oUH`YV zr~U9`Pt|<=I%pT1LwAsSWqYJGEZN}o zvDVB}oEmbu7JCMcm#AY7=B^?Jo#D7~47cETbKE%(I45SXr6+v98NrhJdk^1! zrttTtrf*s!v1R{+JhmO*$Nt1G>SgkJbBy?nuJ}n+EaSXm_N_UdZRfMjhjrZIte^PP zcI3X;w;pFKkEyYpIre0`*2;O}a|Ue>_+0b(hxP}G;;?kyW;l;CRepx^9XR)IG{rO2 zWdF3un=SilhNF=|oAo9wOQbVB?I#^xM8ERol8 z`p)&(UtSA!jE5Fvn;8DdQp`QA)HHE!5B`kvu}Dpi7&;SMw#jj9$C-!umhk-8KpfhE z~FvQ4%$7R1OUPN9Ow%5a&GSnKz z!W28xX#UP2KC3Bj$Ww z<82$K_6OIAubHgGjpIYHE%-a!gC*rRP!luQ(yHHEFvLpW69?)T(}E#|9Z=K73W|Pz z8^_2)4JA&^6kF7Io>=^**YEu>#tv!MN#8fWB zb1$@DNMVbr=V{YrnSQrY+5ZZQu6a z)~Pw3$a9rg3o-2fzrni9l>Np|d2Cbl4#*J0b}p-HM!Y-vq)T;B^e*a&t{i;IQ!{_a zR{b|Lz0Z5A%ky^i{6taB4et-{6Z_QOY{%Yoq+@ZbbN-e)W3K(F(#K=4GsNvJ94w)_HPn)0wxQ>m1ATFfZ$3Tbd{deBuUw#yQocfN#6X z9y+c~Y#odFKvx`!So(WDf-2or0IM{@cA)i zXSRG()3=@_yXd=7{!TR0x2G+;#kaj7UBZfO-!arQ*>B|DZ*|U5|1C4Np-Z1A%9*pq z@l(COh+}8YTKoNF9&&HE$4@n{>t@sM7C&Pnohwzg@ws%ED;@bG6uD9+>PsfX<6cmuzJ9G-)25~Cp8E;(7m9Lb={^M9 zkD!WO|7A#_iISMQuL1WssEMU}DL(oH_fqGup$VQ%f#=jWa-D6I9zeev&S%YaOXE!5 zCfnq4J$4HmU#W*}kGJ;NZ&Uh)>rSm{e}7|p{M0xf$ERaT4fU*@ZE7O#W9%uqD1vtj zBi=RezQOM)rruYW7Pi%KEAb-y{^X`Led{1^pLZ{YcQJ+-b@ZkGOyEPgur$W~=|R61 ziqE{%xW`nFeZYQrBJV3|@K2Uv!2SjHwVARrj%O`tob4y|TngMiWKNyV?OVTuKmILY~F9zQ0zCB@O>>W**!0YEoMX>9&pdv=kn7GKLvywR z@84UToN?ZM9!K9#EY+N_R>y4T^?$NGcEtSlpVW0N_JOwrAG-_A$N&7d@$~N!TQH?H zoyXYz_lj0xX2~wmZ%Q*;eogBw+6On5YTn3Ws2idlFjs~=b>^%mzQoy^SjPDt=if}{ zp>x(eF54s4QKf*t0*^)efpe~9+zqySiXu23&<`coqK?{=tyoom$aAb%rX=MwfC-o~tKZ%uHn4uKLUzXI&+Ggk|ybRQ7QkY`)j7a3y4@ue8pud*4R z`(YFN4fM+}&kB70+|)JHSK+ze)ry;`Q~ZshyqUU3o%G`#`s7#DePZd}{08@M@+hyIZuk?3o$j&tRTgPYyU0TG_-{_J4#_y^c(Sjm{m42U8*(Y1SXB@-MzT;NsvpRQb zPxCI>;5D(vDYjr6*dDfP23yKLVBfIc=9f^*m=*go??+dwCI zfzCvi{Y1Isho?mKJA=g;_UKG#}rW1hy1hwp(^ai2@p8RuG)W{Uh1 zpRYINe9lz)8P0p-YvGeNjmfxvN3YyJ+s>nmX+e>4O+pt{+^%O|+coZw+%tUtur|e@ z1l%(-T|U%cS8cx-L6br`n7WriPgL0_E1nl`I+TNU!1E^f`4e@_wH~`t|8wrs&$S+( zHRrMO_@1^C+d1lT58K3nx;%EuH{Pe-$61}TlC%A-G6u#A=NM{SM=dyJrSI8$Dz*b` zXrdgPJ#wCUn8B6;`@yhZGQ_i=?WG+1Gs8a4O!;Of+co7F|5M*k4)U$8d z$1P!>{eMCo`8QU|W4}?fh8tb+zsWrIfqR!Uzt?$-d7E5gziRu~M^iLW1m|=Wd|zn+ zJ~`?bGh)9mm)ByQX{}qfX*xG-TW8ev=u@O%-%5-%XIMiOtf>js$J+Y~YzNIE%zOEpFF z8hps(9Q#)3#kRn_kmFl<$otsGV5MX2W4CqOPMouL)tC~;nO;Een!qUzHyr!&Eaqc5SEaBe?{w*=%cZHR>Z4~%@1^m{6@xWHx@H?(2-hS8RevJ26|K@D* z`*ce)mFJ=h)a3Y3jajlY<#?|0bIY4fjoE@{p3IOB>w#O|&&w#!&t`^=8lJs*1|MP{ z@a*1$C558r{gW0k)a06ToO@YwE%sS|=3AQQ8+)oAa;;-O(XAetpNxH~ZLzj9PyCId z{2PpY%afzVb+&(^L*q}jVkcGaj4~tn*qJ5&M&UhgYX+MvkN;muS2QbFC z54sY}XxI1jdb_67TdeUta$ zly4lbu@9H(fpHnfQPdQYZenViq@uDX+QiMfWH^R`dzQR6tiaV#sjH$Uf2 zn`8D3=QwoWmzI-QPIhPq+>~(NqJ}S+Tds{ckok z)It*_;Ch4}*K(ES>(=&&Yr9E-dxh^GUG_;)47h&+_Z_HW*MFJPOp_1g!1poV*KS(l zK6~C@M|$RKvQ3eF;v71}4(NT(xh^qkp5o-{F^1Zc+qQcw4HC;94Z46@>M-BB$lvvl4njLnFT-Kskh8`Jm)K9Sm?@302 z{qe-pKFo3U^-0dt8~>bRq&3{^s(i*Z>sYVj-)fD%?29MZS52@Vdr*^o?%$%?jPr~E zA7k4bcW9y{@QG8`6Zpi>MM`x0HFID^`TR;b2p@Mb)@$*rV%f)b9Xir>VM7&ccg8UkhFA%FsDb^JYChSFIctcWRK-}^ z4Dvc1Z^{+H@vOq%1+Bzx@!K~-deCo*EzbFgLko)ZiCc|hr{*540foxN`HOtWYku2C>ZqsBOyC>Gxju*R-`F0%Jo;M;;@6N=a3;#LynpjU=wp~iRT%f zYi?}CO{?l3EGh7O^ab`P#XeDZ2YjI)@71RK%$A?=J3MrJ$x>a$?+D+t^IrLY`df^6CeM}a5$kPIV7sh& z8?k5N^m7io9~Ak&;prICE9bh7cZM8(R_qLUQ)R>Lyc;?nGwpH?J64qYhFksAJ?w`2 zk;A^#JjMFuzE~B@?`&i`x1pNYGSF*m2-HK-%w>pPE1mB3dm|_dQ!R2psD}56@ZR*b0n@{~4 zE#-w9pt)J+s`4fu9XFm1HwKsd|Sl!?lW^zrAXS-_54WAoXht8*yrdX!Pe`4x< zeWNRHs%*1#zCST8Ibvtrz1%;mbIifGCU#thT#HcLSdX{;jpjbL zdFauD+|T1YW@w+n7T)(9i}e6G*P%PcH)&>EdNOuBKn`lKQ{N&xZ%@^BaSS*fE#R4( zXYWjxU&K0|`RxzIc`r}|?;&=)zwmnu-enN`2~~Z}Z5%zJ9T3|A<5|xVO>2W0*gv&I z-5JmRR87@31KYk5>?h;6_uWk$bFFnm?60OLBiB^bu$CgK-mCG>&F|#!dB4__W1T=f z&|?bb2G#-gQSyfRnpo;JM5Axa!(2sp4NYs|edzq+y{N}{?f6~2fWDb6Kf@Y;^}tGa z%{g}3j4|XGWA%6|aWm4mC)>4F>TG8v{-@-A*q{0})%*tbeIA#O#XOExm;WtAwUFyi zKj&`tJm$aoTQ&b}*gtbM3eGTc3Fxms`&~p1l1L=dyh(acpYo z--9AG+($CRsheUa-1AiCFlR3NuCXqS1N+vd7}#G?hZ1W6Yh#W5pd5bAlgrzB+T`Cj z>+wed-{X5)hhylihhw=Hw&UkGa#i?m2Y)|Ill??d&fns&A=gg%um$}+2Y$vmbX>^e z&VC-VG|r5ei(G~{tl->__0fIRe|NSTwlVaN7k6Nin4~TujV@B0l66>(>%Rib| z%wS7-cDMRDekca(1>Oa`(Ut!RQ}vnrUZUw8#mP|Y##a0rYP<{irZ(4%hk5^Yyj`~0 zu@X6}u*ZKLZ0h4;WOH04gFVrI+ERK3G9Bfaw@W`D{4 zmcqN`M3-%*>`%DXT2e38fPY)dNb9v-6+7{^dw<-tUH0Kqtf=;l+wsiD`?q%0d6Lhw zd=CD^ZgJh9jLZH{?V|A~^dN>B{HK_6R_=ecv*!EaU~R%A!fR4m@$^|9HWugvVr5-1?OQCQ&im}Gvn}c?zE|0g6~#S zY>~gut@Q2h)4vnUwNLg)-~O;~b*^dgZICtHV7-5n{?d=!Nx8(RAr9TYFSnE)q zOqCCg+0VLlZk%ww5JTT+%HJr8-&i^)zlHPnq$&nJ&wr!tf%X?X&xq@5>$(H3Kj?`f z`^48UI)Wsg~{7jP%IflIg zdeM6ZtbsMcc))pUQQbFq4y(u5w>o|*`F>gJ(EM9?pQAlk(i4sqaQuASsPUL8dq+gd)nW5NF67;0sj_n&#Z16leN8U8qP(HvrzaCWH{{_URU_5=s3tKtxnx3?$ zee+b~KJGbU-X{G9>Z!BZ#y2b3j(w{w`rniuXMAfPzQI0z%jX=me}k?*hJLo+w5ZPH zx|!CrWrO#@X*-|Qwd}h;fj;g#V;%U|H@f1PB0rOJ*yPR_{uKMb+w`WlG3WL#Z4>xf z;rh8KI4AmcS7#~>5XUae2Ki>zZbPJyU#xGehE-UsMyhC`S zD}Q4u4(EFc{B~g|X14515Tn@wqH_W9C{-kNAlUf+5O=hsmHbJ^X#VMxVBEeeUYaobN0ahl)sJ1u{{18d+HcN zJ1Fwa(mw7Nh~LQLa{lb$+!?RxyLD_}d?43kn{n*|@ywPF%n4r0ifzLu4%9KWF-FH~ ziY}J^o(!?|H*1O}$^(nPo64ckgDQnx|7A#_C5mjr-|-$)=?T9B{wD6voHOuRhSs$O z>t}nG$lK5Un8B9T*k7n&|1D8szp{VLjMyi2J;#URWR`63aYVbAf^)TrA-1Txzrxc! z)_H2r`o+Ck-?EyRVq?d5Ie(|S=}O=BZu#HFs`?t%Vp!XMgZ5=#>M}+C6YSG(=$ATT zP!m3GPfD$GBhHH_ZOVV5sOH4y-7QY;iCj-j532O1_?pN{5BEf80>360H$)43?QxCv zph!=+#)0b{s^FdgzDG3KnIfNi3ip;OcKzQB+0YV2w(n6lv^W;VO^|qh#jy!C*-Jsc2ML4aobPIoNu@fy)wBU@gZ1a7c1bHaE#``E#?|>C7=)e zZg>uqdd6jlkz;HXTkFdV`7KdovmbgKYmQ?TJV#H_f+AgdP6wXZtJr#eAEL$g5}xaO zJl|uR%CmmFA81m)JMg;-Vk2-JK5@9!Q%B!V^t6td>>Mw#{!`hm5o{^j3~WExw^H+l z>(CzKsc%7%dW|)Xu3<=#~`^N!g6Q}32~0-xj3QvRui9`rNJ zF$MFo){(#`PTt@XcaHI18(NQA>OID`)jjFKIL5bQ8}hadXWyK=*(3T}Z#91-=Kd{h3pVG&$ytLxf}A_Y-TbNkCtWdf=AL}lqa~5! zr|mkw7e009U2%PZd}hf9_qCpJ*5RDhHRI@sPmVgqnJGK7e^SO~ru-e`^Nn*b`>sJ513i=&wJY%TeJa=cZa*72WezpSY!mdV?MJuXzXH^H%l9J5V)iXwWP^G)B4 z$^o$v-;-*bpNwfSo*p~dK?9t@*O|5UDiX)P@ijTJ8vDSk82#;kwZ_;{oJ+hPL^W7 zrRY6arpeDt`DV+0qw4+OCq}BlHjJ}<>QXLK^j`BD@)+##fvP-EXZzIK_Iv-_v>p5N z%`d9`37?Mnd7jkcnq4~IPDVOUKiQ0R4fZEo`#-5o>oR_KzIA+O(4-|;+W)4@&N%Mc ztj^{7n_blteB6eP#~Us3S+UL5ww*Xk5VLOPEuY%eUb4>Lw9M|}Jvh{Zc zrf8xZEd9MRJ#4flc2)nNNugZu-`=cUe}|#|68_GAgZner3|J#;9byY_lecsI^Y6B^ zPuMq8RP8tR9s97yzQndaW$y!GxArn$jKk``k};=-b|J$n$s&R@MbAar!y;WHaU`-1-#t$WW90H{1If&Gd9!s_c_| zOta1LeuLV#ymL);PZZ_gDet)JZaQ_|8qQ&;FF60H)_HVZAYO&Sit`s}hx zkZodI^CRFM06Xpp+!vr-D2n+$!hNL%+;2vr$%b-)`xSBDx6po}D)uMPa|*A4^^9Ok zy>=_x11sTJRSdSqeZxp|*=~wuO7dN^U-~mY>sf;1!10-43yzRM2wtPfag6+_zujcto#!m;7+ToqH#&PDi{d+T}oMpK+;`FbJ02jJa66MkP{ zoe%Fti1!43uBF!-OL@OvC|V=ygb~;;m)uqis@^ZK{Wr)N*Lw_c#;gQBacV}IlYIxQ zwSEb;H>Tp?T#bJ03mBo;^ehCEKyA>tPM7qiDZCi+#j?GQ^;WyymWP ztdqXX(*n;!4yc0}U{~RBj5SU6zMPkik#{Y+!grqY^agpx0^?4WVyx*KSpUh}IC^J1 zcT@a{yk2TNChODKr#L;$t;V^jx*iniiQnN4z00i$zvm?mD{+h6@~*3?uWkCzpe5V$ zq8X2K&vjXkYpl0g#(ldFJpQKj*d}U-O|84@zx|c}+hzDg@s_rex*eRo@K?g?b!^03 z)PeiZ(-3n$>rg!qvu$09c`j>D=g*XVvK9LcK4(X|9+qrhd*?Mdb)Eign1h~HZ0?I0 zV&AY;_Z$AS_c!BnO^@wGOJLhdoMQp`_+ZzAGmp>tz={eV5vx#gPwmd;p%)$=c{!LS}f z%-74QTsLpzdiQoLk0Zx?Y;(I%6a#Wo)Tn11{pd9Xzwd@vg5S!(@8%isJkl-@KXKiN zdem&$hG(6Vu2^QtH^i%8JpD?5O`N=I@L`D-&saSB0={EgcCG3smMH(IhnNS%a(v4N zKj#fS=iMNme!=fAEags0yw^gFYjQ5fTGXN^Q!%q;e@oT7 zK^T9Dr9PR$`{@UIyf-C}{Z!{TeY2hC$@X~Jma1Il+x_IanfCLO-QxH-Z@(xG_EE>I zQ|Agc3O!L}FP*1PkpG0oo^`~}{`RdN`y0*keHuTw7ImCp22BbjSlUNDs8X>1je5+J zZtY7LqCHR)H&bhd{($pV#?f~O*02QI#x~ASY+~fPpbmD7qX+X9G2*@D*58#Ge~Su# zWAwKSdQxnt!LI5V3@NmrNP)kv&@WW})&}Hl^EcYuV$R)kXl#ZWeAKunbHWr}Z`P*u z53vQ?%(gGV{%Ntls`elIkbMa=*iu96W;=GOkMD|O%rP`RmiDJ|pWs{o&WjOjDb%=! zcIi6co7PNh*-y0icZE;+{GIO`JNKEfh9=Dv`Q}`E_FeO&)IQ~&;_iRu9e>iE_P6b- zSjNXD>qy7$W;f;GmitM}eQsLR|F@jysLx+(jeC@pbKf}jNqf3ZW?UDJG1mQtdbhyW z-b~kSw!7j`#L{(cxaMoHbuWM!aBny%igEAY9s)JkRb$0SG}%xNxKG{S9+s){Gu-#G zZ@VAk?9Vih*T8zl!Ilqf3){3_AP@BeJ9>^ZSCf4s*X&pa{qsE7E8rM#T&CE9to%f#`dsnZ?OGMZ0!&BNrsrA z#yDo3sm?KKN3fEed)TIq8erX1uw5BqJ>}T`k-&!%xSlaJ#$soPgXi}Aj-y>n;l5U4 zz-XUlz{6PxTc{d9J4xi`o#Ud)-|Vk%u+3Qe$+ARNNb?)6h-jg%%5y z@Y&~G%mZRjLhVoaTcD=NHbwRg=Vy%DQm8t2!RN32Cv_dVf}-o+a6hnZCH5OU{u{mZ zpPCoQ+5V(kwf`-c``g~szR{I`qA16)@v*A1GaPrH2Pl4~$uGf*a}?ikV$ekm#Xcc- z;%j5*8p)jTraTleb)9918EVFbtr(p1+wWT23cRgWw$n`6X3Ksf@8c)ivd&PYXb*{!WWS`s(u03Cu^WhqHp7Hdng6(LcB=BL1EvRKohWVI>-+t@DQ0xiD z>=^GgQ#yhztyTU3&u5wP z&%Zv(Gu#wgoX>dn$MNjv_)Y1PsTvF8!cxre+*uB~{7+2P0PjAa1w(4KY^c1e{2}XM z9Pw`$ssn1XjsFC7Pq{Nr?TxeUTmRer)3qOgdfTT?O-{5m3YftA6 zcBabz3HjVV$K~9cJ>oiRQnPj6$#5USM>AbM|IXKfcdHr59YlAAb`q7(xSOxp63I6WP zpi51Womo}u5F@Bk!{1Q;rb2t6D3@9K+x*10b&T&Xea`;$ZNZeDY{l3vZ|9QT|M_l>XFA7hTbpt>isFBRTkjlW z4z4AxLsMnL(zRx|_HHz;NnPKDYrO|W3fu>PdxELl8-nfohO`Am3akE0mqHD=|Ck{= z)8uDL_*QbZt;F6K%EJy=M_!M8Vk_`Aw10Fh1Y4SM9Bl_Bd=ysdo$anTO-+s3CYyKr~Y< zhOwUCIuwHzioFH%c@5U37_dF86J}EE8(VSkHfEpL2$mFz_MP85upjpe%AAyz>+vNMs zJl@rJ`A~wT?}N6ju4V0UK3d;$u6fd_UWV%k8#PV#w~VMoZQsWJ zf6ScSxh1KNZSmm#IY2#MColz5Fa=XE1yk-EyEMDid_=63m3hwXhw({BLI{FjN5rbE z68W=__&0Ja`djRlKjSmf=in)OoROCHlLI~HdeX;-nofUHyeZOe*l(F*e9e07g(~Si z)3krK_9pwY1XB{b!H+G24cykt}EvsP&*`Wx|l+J&5Aol-} zU6cTQ2z3&Z)k@J?Z;%#2K(9eTMiidg(tf>8CR}B3b zYYCpEk+iFD?xGl=jUE3Ie)gW~w}pTE#3MzAG$*Smp_xF(9|>TjlW$ZcXAYkkPJ1CP-)9;^eutLAsnY(sB}DIIpA@~!gt zZnD%a>067xx%`TB|18m++d6H7?X=tWC#1*8HQe&{nETC_ZBKF8Zv7iMH7@Kg$=}Gb zKJ>lpiN&|H;O#qM*LTD}!94QNHqP&M{+5c~eu1;^H*Jr5O4s!y>+y}%>v_r%>x}#r zn&NM;FKj>e2>Zq~;u1yrpM?9T2UQX{QwHZtoQ<4oGtN8CSkB}oIOBUz!ZsyqI**I` zZNXW%MCII*q&;OXTK~+{dO`n!su);WlN%%K$V^jvGtYymb{9n~&EMoU>jL<&6FyUscu?5en6NJ10k!qoNY0oMu^*E2ZV zLv}-q^(?XfD)c}5xE^GApvw+Z?0~s$!MdIddOZy#vO8ax4u6<%=?5C!CWdvIis?OEUZ&RGF`aNz2TN3=+-Jh1u?Qtw;s-a1T zlAz8{=TC5`Jqj)SE(Fc0Q46P~YSyQ~NC^gF4t0e0)i72{=@?dV<1;2e9X z2G{}D4Y-CU>tZ9Oe!zL?4|%W9w*N_1#ZVu0Hc zn;OpgTxXUo=3r^xmi>geLqAw*18ejN$3DrPa#!g<9j>cMFA3~BLAwP-($|9P!TOjc z-LQUM!)%+f_XKU?jAKXSva}yb4E80cp}#<_)a?3)^b`9mi(-Jj#Dz;v z^lW1e%%ug^k+1@{TZ*#{iT#w=Y{$2Pa;b%y=EY9z4RVnaY9e1J>-L}R+~2;b>kBO? zlHhx_>z=()FZVdMTdZwMesVL`6gxp1nkWhSKoy)VjcN`4e2z))^SbWe9irPR9 z)1^cG05MZI$5U?mk;Lo^BYgOQw%OA2yz-ogg%VJYYr?(-@i)5cOR&x`#SXl0&V51* zu_Yurw#-%=&K%@gv2UH%Htc^I?wwms-&3b=TxvUQ^l|?zd)%v*_R;u+O}1C9a|R!AO_acSkv+yhTF35K`aMAm zu{X$71@{s6QxU$$y6!co34Y%**mDf_ad5_xpK*p@T*ikLj0av&uz=9 z7(=d;88+lMG{r$Vr0vN01zRydZPwk+wwZD@=_T-5Wyu;=v9+#-H8$Ar1FCz|Qs>D`^FZGT+D(v?+%-VQc7pv4;!ETtFZp2ytnUhZ-?un(wj@+Ndu2wn zk-x)Ie;`K{Q)4kUpl^YXn8NdUK>H2)(6^97^BjTaO70ptx^hmQ(FN~GgZHEt@Sil< z=ue&=pkpI;iLUptqIa_vjChZ(RkZ|N5|$``DK?mr`v4n$Bu5L-rf{u+x5`^6mJ69(JU0tlQtnrT$M8eOLX4Z~7B+%k|W^r+K~U^&t-XNnU6B zvh9;T(zR>Sp(OJDLGMA81nynm&rN#1*R68~oYw@!#bFGL~WfN6!u5!lm z-HY=U=`)wJmKb7TCCJmn6kF7EKGN=5gDQ|W@R_Z37%w#Wpd8%nQ)^(l^cpPI;DM3s z#M7VLTTmnQuK*nzzLOyvG*JY3OqUK-Fy7XBby399dJWbv*jneTPqm>1MG}^-5A>i) zZe2e!q@Og|PKs>i^u4vmCI8K~6_=sTjO$;r19S6Sr}QmY57vnFV%?_Lg0=MZV{N_8 zQ+g&}k5i}Jf+4vRw4o)6^posIA7TgW7hvz4EZGX@pqOHFM(K+7h+K4kp>Yk$HuF{kgT?f8Ep zo5stKBe&7Z1@9x|Nx$qn+p2v31p3ntX`MEB-&=Aom?j-cknho~_qdmf)(5VYs2+h`dbDg*m`D~qKL(NtEjxg>OHxM&bzd}6AaOUBH8tB4ORHt z#LzbfXo0_(WN9C9DKd_ycap1nE z{i$8mAE;|4n)D)=BkPQ$?U*ecnkd3?#6k^ND;S9;9m)Y~d?UwYKlUNE@OU2oNiNj| z)O^E!`qyI)jPr)$pCmb9h0JYiw|d&K#>CYZobx7gKl@jV>v6JlJ;Y?(_miwgolk2w z;yPy8WIHLcas9iff$vl9*%SMar!Tj2ob}URVqC_D9iVqn1bwEy#}={l-F*lo?H1o@ zYt-U#ZjwCkmiy_(zZ<6LLEDKB*}c25-PE9T(2Jk7^(Rq+|( zVI^qSWM|ARsDs+5y$5+7wmnH=@nwnbHoi~Hh(Tt2mV6^~zbC!NI91Y|*JWreSQD69 zAFc;`t+RL8$NZZ(F-42LJtWcbV@G1EvESLhti9KtYXM7e9Wsn(@EhWdZH{yQslLXx zQ`(QTuSh2+`I#&AHc`##EWXg7<%bqD}XY8Ao{`MhD%5#g|@|!0=_MXUb*^XWUUpuZ}hP7plO-nkqo!a21*j$lgyd7#HQj4=~iI@H)t zOR^ki&sN*;Tw{Gc#1RAI0sAd;95(Wk+xd(1p}MG%TANrgA0%z;)ZP`l1$mqo-Ffk4 zrrPGTcliuH^6`v6h2I@4o8B#ec9)-6`Y%-jN{6*iuOq2eJshCe}zN>vn)%U?q48=U*zS)nB zwq^GHEA*wYGXG=@_y5$l#=MYctbN#T`gu*bjxA9Rop#xpOc60OFwzYOJgRJ*h*Ts5%fS96q`jCUMmvFtgX6#SUuN{zYMsCKbV#YNn zk}IukmAlfP$sWIL@tfNeTU7l92;&2ev-}i)^Skd&Qj4KBXo({IiIsk{Bj#*B^}E?~-&5}z z2eRF^D*Fj*o*C&{WP8@hk=y?YeMB9nY_fk#QLM>(kG*)K%5HA{dFa%Hf&-&D*N zNa#V41h-FVKazaF7*#NbCYXC?jqBfg;^*&_u4U!#q~1Z7-bW$7li~y3Q601O?#est zjCWz)v3VDVc3``GypvDS19WV;AF)eRG(&*fBn} zP#-lTY4`BWB(dRhEb*pFXB}7q!nr_Uvr>Xr0erW1H zf-OnzE*J+G({*g=HLeNrWQ5Jqe)`RTT;Fo`aeFIA#^Ypp)_~7_a$MHw1GYIn%X!R$ zHh%i#IP3UtG}&3-Dp>y_xK359bWWq+AP(qbvW=SliBiPxjo$G2cq& z{T6%k-_|PInsR35@g0r!zEJi3?oTitz9qid85ckD2EIRu$M};uKjm7Q!yE2@#$c}j z*R6>nyl;B!{hICp+p@$D+=twoTlXsWtLbq+7fH?m&VwpAV~VI%{dA5^ookTKwl1F` zZYa*s#ZAjwiI|XCAmM6It_wxCQ4%I8ksIVQ@MU(Yu#Z4P4c9KElcY;b{u_an=KtY?oIZiM%pXzyqF_Q zu@kI^*JnwG&b=YF7$2@5>u$IXFoK=*Y{PDvbSQ%Tk>PsgHZef_5bO!|#!3{OC+%RW zy*X#}d((*Dw7PyfgDG~<(!UYB{icU4$9${jT=a4K*59=tsSWx;QTyM)inU@a>shjv zCsno+*8W?#R=>liaegItQ!Q^4`N8{-y_l(LXR>dlJ!}2UXS@4V_1W6@8SVpUL6Q6f z_Z0V52{MKuy;v18Em5Qcd5vvvvPF(5xdqq7*D1>-yYaQK zXQQq6)*_bPVJq*ldbj5Nx`}dt4Td0=KJ=Y}JXQG&xnW%>8rx8ZnLNJ*=+FbVcleoW z#&*Z`=vO44pf+l(FEB5|JTpygQ>1?bYX<$Gs%^H`*Ra;cHcQ8!62JQ(9iL_PTgPv` zNiP99_8l-Dx@ETKT5NBMErM@TNWgXjA8}nEt<$!Rm?3t6-b4vWn>xB!!R^}EM_k5s zB=(x14NH)720LVvgc1;EY_qhzM@}R*x2trsHKz=7geHn$ZTJ>A4w~AK?Mv;dc%Yx5 zjnB}>IzAwFK7fxHpdbA|;ry1ww}Tpa9D^JJJ8XRmJ+be}Kl$b=T2LgPSn1oWzr|Yi z@DcOW-}V+`P01a&PTJTrP3;W6E!=0sI^5)v4~nh@^k7P|XV^PScz;=9%h;Y}_B+RE zyS0aMH?dU%P)k23NBhlBtRZd-@&jX;m2`Z>!4y3xlHm4~z6J5*AqSvCOAtpt*1HBn z5*u;EuYmS9v3b2uom}KJsd0pIwaiWOb4GvIf_q5k~d$iF{q{Pw8(irlR0iZz{4Ka%k?MQ!4MKHM+!02{s$ zar6P_$vKGm2J(5VqWqi(J`>t8{#2V-`YhqGr)t;%dKX17KC#F%o)|+c{u}hI0b_f7 z?2e~DkZ%jFHP?TMa}S2rI;7|13V7iPk?H<{zDcjYL8IWphEn(Ml-PuW{H zhU_O>wkP^=of!js{k;dUH8F!N>FZ&M4bB+bntUh3+E%199yJ--_!)zBU_GD(Te521 zpWwP=mfA(nC7B-2EK|~K>4xW_5iH3Qp1=D8`PtktQ?WqXI?~Vi?ctd}$D;S3N`l*# z)Y$}c>H%}Y&R9cKy@#A^*h#?%hzJ z=QcXF%u?HA`%SM$zNeUMKl`|yV@{nI+g7UYpX$z4m22wS6v1`x7piP0#9{Nj#=Tbr z_onaD8sDOPe{-)lkTr=2`#&x+ys-5opY`=Rb6p>3vVX(be?@NW)~SJ7%#@y?-Zwn2p*XXpGk;($%#xmAy;wi) zAJ&s~WsSFB%~|_5mi%wHZ&MsO$qhXyk{R2l?DQeG_ejzCanjORwWUK(zjHPH)-{7I zX{vs=`^2YM`)`um*z*`8ek;s2>p90%zxKi>|65=Cit+<(>y|6l?@6-0H|piL!kdk2 zWV>}=yWf!`?S(g;G4LY|ZPTQiA|1~2>1?w{Gx*&%%NF^v&AM}vr>66ddtfBEFJS8) zxxu}2($jtBbDjI$ROx2NnZUUK?SUe@nL1ZYkMpKV!m9t3p|-J)HoEPW4up8Sy z$)Y~Md~&U4UCaT9Sqa+I-9!<5H$_h2cw&Gy_9lqAfo}?8Gh4PB#5Pd`V;kClZ$6NH zj5Wj(UK7@CiY?d+hPJV7$PY`fkJxi&N`JyJRWU&S6Y`!AV_TE%dZudYq6Eyx*hb6_ zs`?t+EU^!m5;Go)LufzX5S1ObE;zK+L|pLTMr-l66A3X z=V`Lxb1c$S=|Dc`q|G=bXvw$aV+?Y_4BW<_^U)@TT1HwAbnIPz@>lKWt>5$bZI9pk z`0bD10HG#5+lcvzRUdu}BCY)Rua&g;Bl&9UL9UlHV`{wlWS)dcfn?SM6) zjom(M2Hy>0w;(rTw5X4jhP?Zvg!XV$GCZA{RMb1CsnL#vqP9NJBq#Byli;EosO_&4%`qj~M(J za+aV)e*88IeipLc?BfDJ#fEyiFR`ZFdqkOPPVa*<~X-l0B09MGo;a^&$` zYmtv!yrVTS#TK>x4ex!hMEUD)x+J)5X}jfA%o|&N!@K6m2%BTbWt~|3EAV^kDS4-T zW6Gc3m3fCA!IrH0UIRT?l2G_gB-#c0PMKrzxosVpu|Lb~zv-^e@)q-CYiWG+eU$fUZQI}EP`?bZXB*oSId&-yvaKHN?E6#t`WKHuepB@Q_YKEcj;O6la($X8 zT+g^isw6DkKTr4`LpHH>A962puNL9^8p(YQHSih0dC)`=tNu-wtOEH7;(+sRh$f2Y zIuAKBIWG(6vd(wTeHel^SnvE@h_jnB`=@W5t3Id6X{K~wEXMo~v}R3|W9_=u$E;&b zZ9ipIoMCNmdbSbcxE|vzNosk5TBq1S)gBmP1%*8pHhjbp-$W7QXRIn1djw4qwyt*< zMXbN%7d6;belbN8C0Ja)M3ru~*8F71_D^Z*3upfw<9J-xvsBB8>$YSrFvSi$XXbyh zWCPa7ux3-RhD)%%Q?TwMv88A1Ybhq%uxEUXamOi5Gp8y&RZ)M-2RO%nGFEsgc2f0HZ6Y|ZJ7x7tr%PiuuxU%9?Wg!0GAu_gI!Lt+QcLH0b<1owmC{(yF& z$aaH!%lBH9p5fjff^z`2&V&qS1a0JvE<5;);q0k@fL%52!BqQ+9Gl0d9#}z%dZ~FP zw)7LP3F`xH6PNpDy~}58-w|(VI}#g^+v6vJtdfhaX%| z*F0vxb!lRVE!bP^F<623;hS_UlD<#{Imz7wV={IXuGw{A2ii@r1|`v@1Gb^P2lc>q zQN&anlD2L1bv>5tsEzgz$R4%K_+5iGzC!L`sSVg`f;M)*PVIm__r=x(YoGBN zEX|$ySHW+DMbz{=;neShhTjOAej8k3#=jS29m&f$j61~+(0^>yr8x4Fn;gzV8^}2V z`z^6|`q5u?7Ug1GaxxFbpNtW6?@NEd91}lo*5jX2-mk8*>D7k4-AX8j87e|({jo71bxhi{zzNyT!QTS^U9iN4kC@!Q zr9M+J{M~86kUZJ4nI@gQ)bLZM)@i4`-PLBjhUzDVv8l)7kf-apzr{Pm4$1q&z^7R1 zqc3c+B1e&*7~UI}pwCQ@%T&Fu7~4)c6;B>&qP{Aq*WOO;>sM2BS zJ~kun?M;EKAzwjA_KEjsmoMX^}6=x>r-O|~|Imq83&hr`PI%j@~bD6X9 z=e&+{a_TG{>I*yY8QhiIFb))rdt%>YZN6kjoF)BQP$a?aQ%?C{2VAck_-}DT@vs8+ zLJy{-!Bzxu#8=^*O}U|n#dVLpu+ud!(tUlo#!$to$|qW|EVtjv2MNjkIk`XAaB-mSBFsTr*Sc3~K`|)@w_0|MXZ()|0hmjkjR! zxd!V3cBlv0mh~J*ZkR!n1h9Bj2S z_}hgd+b7&_C>C~5^}8TC(%6R_2ia%;X?tsXj8oFbwzK~@YB<|1{Ky+cc3|BteGQ&u zk8AZw))a?s@^ydG3-u(P;!gXB{e_+Wr{C?I`=&eYCeOTs{hMIFv+qxuY$f0xIq9-x zs@kx1kBxx)4T_k$?}2jwYOr)ZJTc-7d6P{sTW8R(kk2dY{W#a$E*CpCL!4u#^c_%_ zq5hTd+$~v;6MVVPC*8T;BxB5gYm(tQjl`CIlKoBoOi>%Kjl`A?*>>tp{wIoj;JTSZ zjkRfU?OFF8*P%$Rs_`yh&k)1kx~{*Ig=Zx_JMj#~vlh=&fR1eo-a&a5>w320`3h*y zOP`W#RXM=<@O42y*n-?<#5h&?8H?JW1w(R+{P&(V;41<3(fj!wNd8{ zd^gp{8P-O?ct7L3P z_Fzdmr}JXNUxS?ENhg**E8)Dhx!qKM#&*Xcu@N)H7B%(@*B;&AJE2eJ^wZA%rgjmG z+XZtRqKL^iF3|<=MT_rVg7>8iwkMW+^d-j(&@;9(CbVEkeqt*IYO39Rn(XBO+sTj( zc1Y{A$;-Or{7rUhW)AfLTallb%upM4Nc1L3f}A(VdEz+tC)b(JzAPPQnSJQa`!=5Y z4#n>TYvlD>(%DzQ9{Z=}|um`fGxMfPbETkrdP%jNfqt>0;;Kvw^T zAz9=H;;b*(4fZX_Np8mQIJB|jpFxW_>X`vLeR6D3JYyi~OD^I)Cv^H`y4u)@8N%0r z^-RTE!4z(m?M2W&eHFUBSHHH zwNQiWpcZn#5agU1|0jQ})zWyZ0iZ*XKE*!3)C_RpbvpPgRKc-Sid4zXV#Rp1=iVX-lW64fW3&NeT<}S zrt}>+?@bPkw?tPR)I@EDw!t>U3ed4V`H90nm7@s8VSLv_8$13jh|SR7Ys`LVVjN(D z5)%IwUC&iT&*soB%w!{$KIC9bYUxpzYa7f%*O+HDa?<}Bn&MVG<53%9G_e^=cKlEx z4ja%0d{b;u z_rzn^J{1FQ7wP1n2F`8r4y_ri3svh2r_X&_^mRMSC7=6u<(LQ9pa#UhK^*Z@l(d(; z$Go?Y_=yAh)WG?N#%f}7{Y4i`*Lw*52F##I7O|>gL=CpCFW0%fP~s|h%!BzX!Tgvf%*2*%d_S;0tP}SQYsT8`1J;%Gg(bZ9H;Mln&c5^+B9Gm| zMr;@C0rtWWJK$^rpH-GiHhybqVg_5X>h~%b2TSb}$JFR^lg^1W#Myr9Gmf$UmF;!R zt9<$I8tLcQQ+M0B$HRPT*pVk*t50&K>v!r+J}Ccp8Zo?~N+^iO;W_96I0N z=6kX^zsFz4Jo0>7tc7i7?AdmlrTd=}zim@G>;%_?>ylY&KjFBl81s~m97WeZgYCwU z-PljN28==NO%%a;^tgtsKi8%uigcLuKk3rpv{l*od&l2Po^_g@b9i^{2iS^y_@~$b zdJ*o!J2CI1Jd>3KZ5RpLY_B})#W)^=nntiBGqzXRb3IMc}~ruc|M2ewMZ|cYoLUc^}X_Ko$NLWjSQSU(z=q`a%1!j?^*}Piu18o30^@ zgRM54_9ojCIhL3rsEKvRYry_y+2`@HbiZsvxBcvo9UEhJQ4X+~>^s+#^<*u9xGA=1 zN9~ODiE;GFwcq*{&7q4KY)Qy_Fz>sp6O-H4pX~VY0b?*`zffeOR?nqM2iAnODZ*>m zV=vYuvEe7K9ngP^oRhX~c}&}9eF1?F=?* zt*UkCH?2lZ$Z~bLsazLwk## zx-<^4jD;*oQX4slGc9uVBQI@y%!3@{+k$oI3EKFGS&Gm3oRi-osg?F%yvRu(Xo5UN z>(T@=gKdf}s@CKLJN_nCs?Bv`Get2#>=awD&P%XwxA3{JbpPMrdCK;Y&I$U$4BWm+ zVz49o3q`&Y;%BfWZ?vcjdHU(wMG?$9!&=;^vIFY{tX&i3g0Df2Xc*TW4IxH&gl+)JXkHFhAyLJYU z=VzYKBY#m0@x1Sp^j*3oo$s3apz2)?uoGKEkN2aYe2h(Q#)p2uoR}Z;gcfaTC6>PA zAt$*RXYu}6l~3#-zmJZ;)JLp?8t<{Z%bF=2c+bu7?t3H0WIe|Y^)*{M_}v=WgCz;P zvo|q=EeTcMeqcPXWX}+Hwz2&VIbYTb-(?T*pEkGM*KNz5zK@~Xev@ZxZcp`vo6Wv| zr_6P{>8{B!Zr`M1epQxOS6_pn>r@loZ^)5o(oK>6#MB;!9#qL2OZNecph=$WxF5ME z`wNA8R&bAVuUE0FY=ZN_XA7t6IJut-P`imJf$s&4HoY=a~ zBhjRPOHnLL?Tv5f?4e(TydKB0r5cCyEtm^)f+d(Ev|viY4r;6q>r}K}z}lTm*|uPf zS!=VTpWtf&dS+eXZF5{peeiLOxz=2F_P{vUYMZLF42D>oYx*6giz&8f`i<(wxcs(7 z9DR&)xSeIrdFIN#tmpo}s^>geAD4M#+X{b{=qLWSgg4pKzjeI%&s-jBe5ft^t730F zt<#KaY}vTh7s|zUi<=RLJh{c&=2*8+Y5yr5S2P~5cY9DJy}vE7-R$GIH=5c|f~or` z!+m9{^n5RJA9BwF_doQYNM<-ceuWX|j^*$4b&g+^w=wga(0k(aebaXTsXU+9iUHOg zMzADLxK<}Q#(i(nak+nwePEkoa(`@Yx6qM>+L>b!tS#503jW?SQ4*{EXFKb}8REO* z_`6mTO*+`d-$wqP)&y0NoCilKFAtcRY_r$7QWd~f8KrC1=RVT>7|V<#W| zJdVf0X0q?p9WxXUD4BO%2pGwT?v$eNW5y`&yST zL;NN_&5QZbHngkqBaMAcHu?iP_Voqi%TT|W(t);Rl}=vrw*VcMIQ`5Ea&7c;ANQlL zv5z+SoYT6cgs#Vmev6D`&<*TfpRHtM9kg6t!PwxK;^E{wr% zZv3{!Z){a^2H5$%jq#~>sn(xy(eYrb{o%Oa3jY^7jDUZEBms z>tmVMi5Sx&eoGQV{1QFYH>K02Mtp#dZHg^e9~dFgu>&zZD3bIaqKX;gTT%x;;y1C% z!+!StY&m7)9NCiSHPNMK@bBO}Z#a9_<@_O@e(rngN8cxiKY5CMvpa|D>8c5eU~OQq zMh~!tnW;83Q39?<20MP3Nyl#R!3?$}b+{Jnwy1jVG1z{5il2%H@=_!3OIz<`L+@~~c$cex!~33fBt9UH zK2x+rkxsrYhH{gix{=nYql%w;WT!^@ZTiO8j61~^e*YYQDQB=G4ezwj6O;GcfcM{j zLQ}uYP&>2LhN^dN)1|`_Mepz}n37vmefNQJu+;t?obPvX6n(?`6?%LRb04>n*m67T zQ@+eT+SzB_F~}!!Y>vrx>u<8Z)PD25<-&LClWj#Ya9%sxvvf?=eqlcWd#i~e*muAl ze4@$+?ByoP0X7(c+eK~PQ^LqB3U>zd9y{tocBY3gqjH0eJ!`8Y>^&e}Lrmtye^#Sn+wqLV|hhB$1F zEs7yGvWX#fNZLhs{2aHqejg~h?%&`%L;0WvXM1ZOm?=Gj-Tjd6yQI5@u6j(iSw|92 z-zv!IF`D#3Tl=bu%6`*cX=3YqpQ4Krtg2jMh#G9|nVD$pp)V+kfu()&M3>K0>EGaZ zoeOb>j=-G#!{ajb* z!IJd#Z@T_454PG>XWro4zMI`+*muj9W4h*V=wlr@zkqdyA$GtuIpKPpwzKX2r#$0m z_lJD$JEA6JyHI5F{Fp0iG6a8DfWI|8D3af>s`7{`_?yPxwuX$qomFzg^IX@nn3>WW z&u4;I`i$t?Y0F*|GxY3M19WWkB`3LwHMFx0o&1n>`Zm!c|KvSbEIre8fdp*$wqR^n zA<=7qKVv&F2AgA=;-Lf{w`$C-=SV}lM=ivF`yk0-rt}m0N{nOaJkHsbwj8ZBTd%U^+_pPz$?yBT#aTofyP<8LWsV^i)WEej)t?z^ z<8z!PHruTa*`SFcSU<02O}cGJ>@c+c`T7)HqZ?d1nCTjB=|F$-z!K!00rh}u>oG2I z?qk`K4S$AyO0~_%>7?L*rDQeWvm6tJ?(-12`8#{i-;P2XlGeBnz&sg#@#~tQi2_mg9$V#yDTwFKib{uHhc9XoO48`_s!*Rx8J(|PD`hID8Lwy%gK zZx@UK9+UNfalyVK-`NkF{aZTE?Om*pv`54^mN@(^-c5E$-ct-dQ=|iZOi%gAXKL89 zzsXPCti_J&$U1e|@QsAqTQ+hnK^-$e8#`kV2k6*0KHin|F0}>kXiMH0cJL z^(?3CH@56h^&So*SdviU9e+y#--E_4vJ`*vZH}hC@F{*>#<c(4 zd!|W;UH`tvx+F}2omlswjlCqgbZlGThY_If@E6IS`;GX|E2RMf~n{>tjdW|!y z$OoKP$SFH=h^{lF#raVrX=5j*kx%F6&)*`R!{S+kzf1g0syb7V{EZ@}@C>3jo=0j( zY$N`b_4s>Mq7Q9y8Ru|+#)B5@c;=yvow%I8YL8^P+OPt*k@bMRWrp+{P4-Wm{dVL% z$Gf$OwY{hMu#>lmB4+G&?_>5bdv$AHvOi1OpH=(Eu#dc#rt~ICVrftH3wf_u&+`1c zLwj6kvSn^DTQM2N>A{kuW|(3N&kZ?(C23d-)&yp-C0Q@lZ3(ZVW!L)7fb}-4Ke&x_ z95UOp-l89Jh#mM^pOS08f}%4r)6|A3wy64zW$-&oQ1tszzc6LXIHt&c!~QI19o&y(&Pr{6VXyKQeW$KUkacW9pLpm4qg_^ey@STDCNvCm*jdd-K{`&XbJ z@yHwa)&VxhGPZF&T{eSl2k6*~V6Lo34OqL$-w#m)e`k77B{RFKZ_p$&MeXUoYw>q+ z@=m8`ug$X;&u9TUwkjWfKeO?-ksRb5DL=Wp>?`tacrXk#butsnLtpc`^p$3I0;F7m+$wj`W$ z{bp~_&v}~kjO~{AtRrcgY`2cD23&E&C0}_vl|Ff7V-THCg|IqBhs4A5^tB-w47^46*c| z!InI!8t086KP*9Q)H%f#tkFqJ>*hFO4gL&v;-G&)Q4CN|70i!056vCWOUxJl5X8Pg zzC5<`(}(_5OywkRPwUGX(`HP@E~4ujO66OMzN^5LzGbIQaxu<4z~(WDSrNBoXRXGu zX4!@hh$RpGsg>HOt3^%JGWgaLP$TtH8*zX>L<_JnXKF_}A9LygNvz{G<7#~7VrWy7 zp$+({cM8vEX&&5v`5we)|A@I--h9}0%-?e_u||2$o|`4} z1NvLf61&IP(y3sD1vpW zg1uyD!w8ln5O3%|#TMixe`cu-XI-?h6WaoEkgEqJ>DYIIb{8ecbz#R3Tace|mSEie zfcIYBe_=eZWjB6zK4sTC^&3lm;640ClO1NT zC9B@~dGBAM==;wMw&b_?o6{-jkL}De(>SMo#@^cJIJfqQ?_bDMx9yZI`aH=q{;9o` z=LX;IY`^tw$N0?swB6e08cq6%lHmGwQ3cn11WmGtseJ=o)R5>;5Cbdr_10d8_OQQ4 z#Np4lpYN5f93@y)`GX-@h3`|o)%ZT;KCFWKm3tG=xi?$fo5a(X`_p+iU+C-mn?AIW zh5MZ|EzWe#LfTWHa~^mM?tjC0K$~1mxrnbiCwZ>edaeSV#Q>dWts;1iLw2zR&tJqW z(bac~!ShP|jcid5H9-+wKK`~gK|FoQMNU5_^51>U`0vP&wDA$cm>#cc|Co_<+mNI1o-ZCiGe_k#Dw(!MZ7XKbdcJ;mni)%gsAb6QmWHUmAF zk{R2xtkDOV?bc`HDEdt-bN=3yeSb>#*p@l(Ieu>EIP~lrYJbb-cfjDAJ}r&)6z_al z*8lsT<77KJzTf4{NqZz()LkS`y!Mv;VV`_sujOTjz`MdGjZvbN(smdM!uHXT{u@=M%&{ z`E2iL-B?Gj>yQpRq}!I*jcr*r`7`$I)DPWu%a-~T=~MMlW3HFBZOoB1X@WK98g~A6 z2>!lQF{|1K{GEa-p8lTo#-g^7p#4-^kJ^^{=P}6#)G`J2 zZq@Mfy(GS`{Cs2KJBujPC#Z#7u7j}|gIvx*&E=uyt$6mQp*;k7ig3N;`xz^8kdn);4| ztu}b>`995d>p4E_xxaP%BWRLPLT39j7ZZz2$V+XFO zr}N0RKP^3m<%%&ar`k{W+(hP>9{Y1?KET`z?Rxm#&NBCF>H}MQ16Wfi3EER^;rJ2b zE=h3y9&?;8bG!NIM;^vttR9r4V?QA-+qT-!lnaI+Z;f?f9FOlcZt=GYIWBD3zNPAY z#dPV$wr}$|Zlrpe^h{CvhUaI=oDDYmkY|W3sEs=Lj?VXV-oIdrs`oM8$xfDRMel1} zOtA&;ctb3~dnTa6NZ6L`*_Q1i^xxZjYbNaw!d{^Gvl?BEP3!siWo zi*uzB6Zb1RvH0NUEaE(hGkM6yIYe&y?8wjgU38}NHy_!=6rAnkSn6lUu_Nb>SmNQQ zzn&q6o-KHOtYXIVBewNI=lMf4`imORSL9_Jn4*ZzbC)2F_#)^_4#sGLaj5~&cYqH@ zup}$}w5O`}Uj~~Q(oK_ove}D?DgBKuf5x#}_A~d{CMM&)J#rRFvvfZUQN`BYpFxu> zVrkEIQN?C&iWU?}GpqWF9#qN2UQIOVu-RLQDZO7PvYp)Gu+7Ale&Vq$N7S_>snvB) z>7Em^i4knc3~K_c&l0RxhBcgG3)Yu)Uczhd>oJe(wU0J_)1^ZRi9UlZX}l*!?8_y2 z+q>9$FeQtAPci;&WyrPzeupu(A#)6R2ETFN9=`=Ge%A@k-;aK!?HtJ5uf^|EpCo-g z#o#CI3HP6hxv^z`z42JQ>+ePUT<1MV&3`wiI+d~f=G?Ydt}z_|dN5l{op z4$ctMq?;lg+_tp+O%jVAnRRU6;C{%N*wS6Q=aQv;$ZWsqOEJJ2u~wO>HtYxT+CRnI z`saS=x$o(7+cD&8q6pXC<2nwl3u{$atN6RI;_pkZijTjG{2gmSkunq=uZMw-vmoVr+go*e-?6rQ`koU`-L$8_mW%*bu&xbkIx z$GPsRdT(q!pU(&I!Bao`Z!z{w^>3mCOYZ>SHohtL2Z$lo&^ERe*-q@saw?8Ft+dj$BhP7GpC9>Yo?3Y>?NMHnH}dGRG3v zgOVhA#xYa&Eyyu~C0TT)LBH^1KVyjl@_mA_tDv4DmewiL)rOkbT2mMY*^cf$&fz@n z*A!C%bnN>9a?zg}p}p`FGu0=v)qcWp_OnJn8($IB<9eyR2=?OQI|JV-q)*XePM!n( zPVhMgeR4ly8PBw+VTRlxu@k$a7U!WZYB03B#u%z`>bpo6o9`pC;j^E%As(h6&lc2d zM&xXf6G=Swr8sJX667}i9=XWdM2UPJm-YzkV~t^iM0bsjr#_EAHTDkjwWOAEfXz(l znXNYcM}ThZa~p}R9n2#qZO0Vp)SSUb%oaKKO(*6PIXpKz>YRUgKtri|;!lGsl;zA0Y`SpOdTzlM$tKXEX{PB6|$ z^YNK~lPltgryqI9R|MlQ=GMKH$Hv|wj(C^}`)tGi2IDgR64cngWGe1P?r;BA><#-Z z3uCJeY65gv0$&Rpf65)>RJ|*~NG$0mMekdNcP}%g!xnxoM2>@{Hgvte&7f5ExiDn= zmaW)K{Y&#Xuz$o@MUon*r3z}Le$N3L{uc4{gB{XsOYHqG%}*4)Pqz!aXTuIE-zI_~ zX|P$(a>;HA-ys6t>8IGih;I;8-wS%MB#XWcz0u@{9FuLQkGSlo{j0eDO*+?9?Z)$| zvcclpMuNFNk>j#Xd~Q4UDSN78N_Ty~Bgw-!)W9_?f@{omXP>ZN+Lsi?{R-?=VBc1; z>wl%G?fb*`!bmabC-fnf`>)6k})`{G4B$Yn;)5UgFHc4>iRVVszg7 z`3l|i_&eaUoqRK5i=OXzZY(@Q>UTHf6fN5P_ieC+{rHGmipN$1`{~c~oS);S#z4Q} zXTPmJ^h4Ho7A!{}+J=6ap|#kHKS~evSQ%Acbv?+A9I*tFAV&# z-NP$S?R0N!(u?34>7ojs)8j84Ke6k-n3ASThY~E#*I-COePHuD!MUAi(r@G#^!)|r zyzXbIANqCgQ#$MySifOEUPEypo8iS4wX zHK==1W&4Ep!qR)xw09>Z_8;l<#j?kFRV6b%7m=KsH=68*Yvjb&53&bE()c>G9PyiA zS<~-=ukWU78A{-Lc8_}p_YlK9CBuCt(=`6XZ{l<8j{BK=LH1xJJ=?Iq;WZ=KgBff| z@P2v!kn4dQQ)8b`nPbrH&-QiHcO3n=za||@;5AmQmwjr2-vxdvdXdknN<(=I2Yxspm{|WV?VJwjke7dkOlWpBveSp2xTNZ*8uX zCA|QBK|P>V`Tv%cp+;&U8wBp9E$j2+YDH@0orz?8Y%WQ>m( za+)XsI<_r{n~LWcbNuu9bm}W&h~3H|uf<$lbqqDJ-2lB^w{W1C{lNV(J_pPZT4(hc?|T$8#$F18YWVt_H=JN?;iKa#xpz?PkB3piHR zPl@luG1NH8^ELTRk$#eU#dpS@{;3$Vr9%_MaZI>A_*(Ld?zDS+*1>LDjb4%NE!%#hJAY`+?TfV&@A~~%(~7az zhHMA+5lem*)MS1nHhhlzMiLL?LJg>Auq{EoCguZMc0-JH%O)Fh&~FRSu@(97&kNY` z(?d=9MY-I!fO^#0f*4}SA$KOWbW@Mt>ZP$Myz?dU``47+Z0W$enBkoaR=~R)?{qWZ zJ+B=Mjm_!HW5;a8KfycYjUIOLil8rg?&{ynSew_x8r*wR4vaC#Y{Tf<=ckayA zxbk-kF@hy&3V)x_7HMd0wxbho8?q$m zoBpd{P0$jo*RTgYD3a`F-bZXV$eCgb*Y;e%PIg#BdKOvbX4!7+{Q#xT5>e;(itPFUzj+;<+p+X^aj$M|Hl9>Dj6X zf3I8oyA=uSP5#RBm-x|T`}y`4|5hjeUT0Iz33bSyf#;|i+fKb5_33AcqTX77p21EG z5MPD!(XkUx+zg3+1HakQx2_{Ir9%&jBrMKXF)mcu-q@V4UqB7wrf8xBE6#Jy^&3@o z*t!N_h?XeQf$O7l9~GR>Lo~6gd>6WGbFK$f5;o^_=-`rGjPEXQu}k%DIgXh2z>pnw;Qc`MU`fKcmrr|4oO4c@YdHTV9Y=l* z+#~(Aph&V-_NIyz_j>O8{2uT-v8(+0%_;iL;`xmC!_Mz9)6cH%$>*9tL(6qH$@TDqDSh2=fJiEF^;ppsgEM)m7ZB!i}lS| zBWo+dYqcD_X9rdC3EsEOlnyOG-y-L_CUY`3%r9^(vOIuqCeE>a*r6m=9P2FIE3*C# z*aP;$vXVeI5**69#)1#EoNLYSh3E)T1tQyv<8YW@-$!Rq3wT zR5OEp31S>SBmYUBarD=sM|2<;ImAwQ&yoFdoUlRmx46eteh(5u`i&*~iQ}q#TlF$C z?n|E6{nC38thdJgFvjlw$jN#5patv$`Rp-!-2#sPjB|nW0UaBDV#z5{YfI+&(82Q% zM|@2%o&h@4fcn4~JNfMZTjpE;P+n$heCyq7!2d+9bLu(g^flEyDYBhB#Z2u9us2Y} z(0=)N6&)vFjGY*2jkFeYC}O5M)B>(0ci8$(_x3J?ww+$VJ z*uuwVsn(4y`-$U9`aS#W$sqrV=|1A#R&EAiB7}wzD!!`r^Ysx{l{Tn$`y;Jvk z+Aq(~GydB;jzi{NY;V!WkUYV6+VU8G5!9`M`vGu1Fz&Gr#1ISP0b-~_4pebFuZVf8 zLw)?z%V6Jv-)`vf`(Ah-44R~w()R;Z?`I=eysz;tC%TxymTdJu85$e>MNo@*s7EgT zCHy{#Zfvvce`(GK9D{WJP^}g)e`h}W*Zkg>#j}mZ^hQ6#lSduqnxg0(JkvBDVvDMG z^q%0|9g5!L%{gxJnJImXs=op7H-RO({#G!>7WwZALw}QaWAQhO;1<{9JIQhQ7@sk> zF}(qOc2NYqvxX{I6KiWhkp%Xl2UU_iWIx&KRW**|fP^8Nj{|W-kYgBUuv3>gR)CHT zA3A-|*9_312y$|Ku6^p%GxjyvIA^N(>76y`oNJu(x*mV-7d%G_o`Xs}&tT{J<$UBC zDaz|&CfJWwHD}nC;y8E7p*H!xpCB1y@A5PM zZwuxsns>x^z^XpFn4+jpXo(@+U@s5gJ9&!5PVBgV9e>xg2j#-z`Vu3kNw@7y5)WI~ z<_wx-LDzZT9;mY_7S$zMQ*aF8AlPO!b`E8fVxC!@ig$9oREq|EAap?>YMqOK`k6rc-d7huDG7 zg)FHA@e|LzcZi;| zPr!RLrGLV)S(a2I+pW)+y2KQ*j$=U2?%%S-+KMFmR|WTX?*IHo@O#trdsOv|HuP-P z0`w)Ycb?%CL)^Hq$&cSa>XoY4U`zhY!(54?`A_f@Tg8t2BH2}w`o?qMJDKhy`rPhc zlJ&5zDtLy488pcfES^hW=<=OZ*ppI|+isxj*wqCHR!JF&(&#LRdPK=z9>Vsj2-_-b2RCI z-7p@(mSpd{Si<)L%a-mFQ#$ybHFW=4LE$$d?yL(3yy6|FlLYN z6GuLEMq*3P;A4DBY^)=%nQ>klF*m3MH=k{;Mg19Y-Wl6YNqpw?Gj@${WsCD2`E3qy zIS+kYu>Fj^jmfvYNQZNMdF@qeU9s0q5{AHb10QUi8#B%k&J$qV!tY$_zu{h{;=aLa z%JM1iw)SkZP7m};?>nub%8n1_7r3Vu`|j~?YR{vJ}n|j1hFEcbg z$$smOImcW1H;BL4e`BLYCg)~5dJEX|9{4!mBWC|0J?F$UwyOyojIAGIO3@{MdQ6(Y*G0;tmpyn?NInTNTNwMQ~Jr4 z4Ql*-U`ZDJ?O=*6_f;F(dCfJKA*sBct z*F+KQ?ay&d$AEK^^KYf!%%RnJXR@2ZveSiR>0dKXRaI6PPJJM*(%<#w%k zaUO~)Hs@u44o!HDs@xLuFcDM}SUDPuz0I1L8RT^jZXaQFN_=udjTK`C6;G)}G)R^mW*z zgYPX|lUQxpgjc%>pqi!T60TKTrdvXhD%&x;B7o1gh9oHAM^1Gy524A9_pN zd}j>tCBePt6J0T%aE)A>xiUjz*e`fbh(U)gmS7LPpHuoyuzHpOxi?DG%rdX(rsvqK-(qd+aXhNz4IeijJI6Q=xdP^F2lE))kNu4; zKUBUA9*p!2F6&G7Owl*I-{5b5Z<2a>jyyklO?VBboX6Um#!$XwYQLZ0ctGWt#I-jh z+l8%b9P+i^HHPv6_lXnVLvFGj_n5cb?7P*%R=>cxD|%%;UfYzugS;>7lUdR;w*OY< z8mI1BO>?;)dV?MmNnjnUsV2C8b1yGJkKb8-4~pP7VpPTO+^J{9sprBXdU}>a$A*uX zqS!5#^2ncpTCT~sCe9egk>l8437etghHd4byY6FU#EK?#T-2e}4u&N=gPoMWcq z!Q)ewm>21}tLA54*&jg9;2!}wu90icnD>-92L1H^PmgmC=$W@(wqhY)J5Rc=F{Jez z>sprCmvwSsU9j(zO|g*ewz+>x?Ct?MV?T=I3CG>X-a2vwRnp{hC+qkfzodJP9&^u- zTQXz&DRa!Xdfp4?oOSc~EylhqtM>F8a?U6HsoqF)q2I{4H~*6@=e_AU|J%5wUQcqJ ztmn8Vd(m%PhTpwU7;3xw#L=I+yiWze)U=sxkO{naA~V`iUbZ$2X0kgq-1b z?nrYk>EOOu%M`r3jbQN}C-|0OI`69SF4}lsV$^et9Zt-q9; zDAMs!XUB0zl2;XvzePOq1piW8rMBvj2k4%2DyHgvc!(tme~S%xXE#%NW@~Jk-tou5 z(ir%gKo?Uq{jFe#s=q1p_!~yi--1k&p242S=(ZuvI{qHwF>^o_Tk|qM{m`%1lGlL! z9NR{G5$w~}p0T&R{);NfewH{^P3KJEIO+WB{4VI31Ne>L+~hpuIC5OcX#slY_$YtM z&-qDgC0=<*#y_>?r^bl$Gapy_p|=$h9ots@A&`s>epmr(L&w(0VGerG8kXK+Chxa; z_vL#K)L_RqEymc3;)rd^;oXPas^{)0TG|u#h~G>kbKolp#+j`#vFtB>t=JPJj6{{r z-Vxh^BFTKzDb!Lw^hJN}k+JRc&N^6=zkl%!3>fc&7Ct0C@>YNjHNeh3_1LQ|2~~44 zFOo6-k{}O8P$iiQiEW9d_$jua&Ixw>&_ZGZ&+G9iXXG#sbHdMB)dTA;qU(Aym1{4t zbREJ78rP-RT!UPnq6bCNEUv+zN^Y)4F++NsC2=`6`_Sz_CB7+|C}MG41YNRS16`~jext*s(8U~Nl`pG zre)r)01L!+rdzGDd zgPl0uY1$VMM?87;0er+zZwmI3JzW=`j>nt5r(=7IV;=0c`Lb<9Us>9Jt9#aTUR9kP zYh}HrNr!U5zIlmf@7P1Af@3uvXtD$TEr=)2b23k!V`~ib{0+qBeCyavlU~HqyucWr z>!Z&H@HuuI$75*>?0XkeY%z4rFTs5W@_orRBr)K4=MX;wbf|(j=ArLG|ExuOT7<99 z8P_QLjo)iVGUgcIJHd{>C&*!*_Ql@NlVQJRbcbb^C6z$2?g&ZXfggopIB(MShm(C&bKz=X9RO$TK&OM_l(=;%AJ^ zk(=wH&kKz;eSvegV!y>XS^k~eT<6rkt()W6=X0F*9^Y+z=H$4n=iCv; z2KkAln7>1=pX+4%O?NCl>&T+tsXYFTK2j~}m@fTf%4V{!q`YtRGdGX3e%8X5ZEx#j zT{Xct+c*!JC}cv1n4JIwk^nU zJ!GyyjPo2%PE}ln9Ai6i=PR8~uiHB^<`iL6X96OJByT(usmaf|;9P8Xu+JDQpj~wfkwjnc9 zW3#0juhr{Zf2n7|o&)<2Gr{p`Vu&61Jn^}MT!Qn<=i8QE<9BB98xRzJU%rIj5SWQA z9h!clU<6CD>idW3(ogIo*4TEF|L5Kv_jm5|->~)jaN^&L z)6UpFWC{4);diJ8yDCn!ph%wh_p!%sEz(r!ThGieL=#1Hp0|09=3Ogjk~g-VS*PIH zwfqut%#?1n^lu@jiz1eun_&c161JY9Pp~@)VFvq<*^bX}3~$HLe)3vSl5Sg;Q+~i^eM|quP>gY`$4$Nx zOs&(fZd2IA{qwXL`?g9Z!BQ42e8>=+x{yL=h!#Iyh% z8!=NbAM<8zw$o3{8%2KVGfx#u^8)ijOW1akOL62=$IPT-V+_nmPb0w?J2isKh)Z)0X2>t$B*L(MdygI&9cj11J@W)3z@N>c=Aq`Y{v8bsdOFYs28w5vETE3zMCANq=%5t6ZO)U5QP z-dPjtZ`y~V-&>w@c&4g)ZsHk=XDLA6g7+VugKGS4^BYYrHS+#YyT!gR7YtF-n&>C5 zCHLLr+lmErd_(W^i@yu&?+N^EVFl>e3}dLGVfWtwqGnf3df|A{V-fV5=^9tT@ivUH zuaMYVP$UiK($9WqU#9lw=eb7v#lAJc9(KWg0(*^(eK(A;<8NZ}Eheav8QZ7q#EyX6 zT$}khZuG-(+i~oC4bcA)X)e`%r>0Y~dJmz}|x* znXw%?#TLxNe5|d-no6u^XwO+sRnHs;9}^_&z)nmPtbd9w$oFw#jW8czPE(|NACQdg zL!SN|i{67O$vy%)OyPRtsO#Fq5WCp_1MK~Pm?2t#4(q_VQ~C6O1nk5*uSw^;D`Ld) z=lG(V#&sx4V(Pkto~Y6d*ZIkazZr5p!@SUC`-Z37qWs0ZCg8pUyFTL>a#lc{Zy_hg zx(4;C*fIB%Y!`}bX6fE`!+vB7rli@@Gqyj;mg*GgVB3@qJIHIuHtehs);V_ z?x3Z=A9G&73VaUv-12$n^AI`27W}>Xgui3MI^b{J;J1KzHH2!;f{`)$*?T$m* z_DSZPtl#p6=2}6~H<=suTaxocjo$OTPx?%IbJL&lZ1-HJwBK@G_T9cL&pO|X$ID)v zzFWOK|0n&-KkIwE)YI)a+SYZP&6NIxW3ns{=Sa3QzsYv%*=PMG9gn>E-)titmnt2+ zzw>h5HQAti!FkR5UVyB@(tQKChn(0)EIBjq-1ITUe!*+X>-?wm6~`~{|9K2Nwsa2C zebi&$&-Dzg51J@~ePBOokoT`?A9p+t%=A1^r2C%kdwkVB-oF7^^4kFIfg(HbJJUrK zyQ)Xgf+9Kf`w2a$l9{FVh79i!&;s5mUEQ8APWi#UDxH1hxNsaf)}|*N+YW|e$#V@PW5aj~`a8*eI|kbjJK-@h(=>)6$j@_O z--5kdf@`$}Q!<0S2;wr>0Kb{iVFX*!a8Eq3@05~+=kHuX-r6I_cW zjswStSk_HmS3Wgb)T1Uk_C;Mm+)0xSw(204s8OpZ-%y)cfZzI*p21!PbzF;ahL{`%uv5#NaU*Ik zNowA}cZ1v(P!~K;mu|4_gmaM8cq03k;=uiN^*V*m4P?8J{lpvcp$6#lfn0lN4%mUm zPqKaJsmO0UKfRpTM=ZK?&avape8%?WT<7C+PLZ|_+nui^CNF}2>vvES-%^_J%QhN-wG zdh}eWrQ-}kG_iGVpLEW5ft=wd_KhV!^Y9)x#7u?OFn8fF%P!Jte186SQq;?V@<3Jn)vB8*4NUp{#gfW z30OBe_9lv0ahy0-z&?S;me`nQ#Ju#u{?gM4duG@>W1FSzS>mJa6eZ^KyiH^5#5BP% z0dyD%+nyw`>q1@&eHi97*=F5x={0ZoHSsyq`;B;M};8&z~9Rn9n)R!4YgpsQNo~ z5BU4_jiSGALrYBQuoG2(6NhYj(jEJEP4{-|eWv}|(&1^piFHhtj=#yF;|nYB zxW#eze3rHoXWi0s&eSt{4~nE=57-ayP2RJj{R8f;+-qxsd$8}xO?nBqcXKbVg8RLH z1Df;`|7IX(P$Xehb(xGUc3^vC+Nn#AS){#S0!QAcv*%C!M@LrI0#G&8=lj#6Hnd^*FsW@cxZw;OV7?cKW{z9W=1??BkiB@tm|i5OY(>T z;sG5y%)n#E@$Ntm^s)s#-qwH(9}suLeqv!IY^$<8LCllylimFgN1YY;SaaNe%T!Kg zYkZRZ)?3U~BtJo)^jm|x4s7@gWBg4N!Ex(?W4O{W=hy?b3~|JhI|TJylQHbDFZt-* zYnjr4aS=V`lH)w;;UnKM*nshgeasDvmtZcgf9?e*`95>gZ}Hf2Y}ft9U`I!0a-E@k zunm2u{h;SA=pPufSH#rFB?lPaAa;t9U~IN@$ho;*_G4ed>p}knIW=&t+kC`17XK72 zB)Z4g$_Ln|{s2BWYfu;FL5@59jPd9BTzksCh1dQhi817uCY@SU_&mPJrt`Z5=rF_% ziH_~0$OfmK@en&;eq);@_9cpXrC;}MnQdEsc-n{4KH_}Ha>;g*{om*{&c#2GUW4UZ z4aa!Rd5k_UG})fW@x+u1&UNfo%&kZ4;J79oir{#jV0TQGQ+C+mHqU7rmt*=HTdsHJ z*=M`QPm-R^Qw(;;Se~{sx5xQ~ys>2eyZHY5jcoe;0b;Yx_)qz5|LntWbBO<@zdxz_ zRQs&eFUQa^XKyX-?@f37&G%-Ts{5zB)x}p{$hkB1VEdVieGA?hi{2r@@0L?~X7i3I z{BCMF^j-?Q@kPv|Cbo)Ay;rTXa$f6qlIP*V zJCVLk48Bi@BBs9O06Mm+?+;CJH9bS~?!03BZoh9oh7&RpyjYo3Ai#ySM+-m%6a z$^Law#nLx2!x->QK`ach1^Z*DNp0qsVhbOG9_yxW`k$icx4~eecOT2I@j6%&>%?CKl5vrJXs_Ax zM*jh8;W%T@U;|<^*of<*1ndR1>jn0f-sp=S=oPx4FZwr(vBNra#?+t|^Dy5!z)lSL zRph#iZ5x^snkW~r;|FrOSQl6iFy{=mB)CWV%oP0%@MP(4f6#*}30r>ygb_5!axit@ zGF^J6#(nKg=J>3WvvhxhA=<&N>g^!MkOM2A_7u5)#u;MXsPa>fx?6Kk(SsuSgkz91 zv89{#*T2_D{U>tVE!Vc0YGccJx%T=?ZEIaOychG>r`y>3n$HpR%!u~1@#g`E>N?J`NEd(H#B`0 zcw@*9ImWgumtvsm+r*77JKTIZ&vmlQIj)oCRNW_X+@I+6=-IvI{%-n+{kZu~ThScf zaQ6RA4l&OAHa^Fly*`bH*86v;oIB#_+&$0v7S{mM6zLgXGqyR-a;9s=^RtE(cptof z|CF4^`MNdEH2qt`8GO#^>3H7sGp;D!cwMX!YOrH3*$?)n2==ZART6v;9qImBq{Gzx zm-{eO!Tq`k?)&^E6v6KXzaurVtLk2uvNchJ-=(_Vsd(oaLF4@^D3Y1Ux4whU`;J(8 z$Kze^=Y3D_KQp}}m(a1D^zae0^nCpZ;>g*7=N!CarXFlZI@WSVjUJLQ?1XJSVv1z$ zZA7og9^fa=v9?=Q`C;qb0Y*sM?Zf^AdEgw6Z_+WA#0=3y5mV3fRSZ49Z#|!OQ4)-w znDQCN6xo)Zc>x{!6ixL{m~)A)`RRqecEUX)vz<6-36HbSy5)}A9^Yh3HH&n^KAqsp z@#7e0U)G5;TY6q+*ILbz?sMssP3M@wM=X$Q{mk*${!@}$1Ns8`Y@#IC2gb-QIF37x zHOCw{=GZvqNb5f4HFBv#{UJOzI<^v^W3zw97`wrboi(h0^|s?$B!<`wHrFPXoGG@b zaZlmCvXajDv{l&vyCu5E*iO6sXAE)7Z|Db_C<(?eL!uYKI=b+>(6Mj9T}eW!G;CH0;l=H_d%LlNu)uzsjPJ`d0he#fqqbDksA&M+U$ z3tKkxX}!d`hI2g5vMCNqK+p8pgs)4kP2gHHwoTcJVu73yEJ^kNY1>vT@#Gf4J=-t_ zd^6aR&{V_NW{I8pOE4d>221q$0_$e|BiNGQF|s9!bl0PH70gW!rb!2ne3N8)f*L@*3^x2g>=fi0 z>fIQ!Ke6Ql{Fdm@5~s~$$K~;#>g|{>kI`>+9dr5|Z~r&)tigDH!FjKF8+YBK;VCB1 z_Y{-uIo|q6J)@sA*}(R5Y`f)7^=^9Z2R+Ap>&rRTo$E1jTzbLQf+A_K4Y33F=DMGx z^KR0$PTy0_9>*Q|6ho~iA9l}`W0rCZ_MVv1q3E5l9Sn`lmY%747d2h_N))}rnkL;$ z={Gj-yop*>TMV&+@>lu!*4HK9nDW1|<%gzs^}+jkfR4Q~uhz=Fv+K8Y^35Q@_l6;w z*!m7J6Gi&cH>V+xfUPHpXN*0^VrL$n&8L2^OWeD+Bs3jEK<|S4{Lk+Su_8YMG>!39 zehY#j`G(`pn(RTQYCP0qp-=VO6ZC)X&$(9irY1Ju=K}2b*&Fh9z+SWG)MmaG?=Y-6 zuhr{Y$68q%>zbkn@>mCTsL6cvFr$y99+-a$eDqc1tNQ-8^-U1EbmDU?ImBZdf;`u4 z#45)y#^-!;okyPnZFH zmjE667R1s!c~$vSG}WT!4$!d^Lp*sIYL$boG2nwDs`jgBFF8JZw>2CmZ1^UA#j&?$ zk*E9~Y)N{_z0xBr{k;$P+uw~UJNUZ|e;f4Q2a9wwb)PX^dZudp#L~U#i6Nh9(*J~A z)%yi%t$^Ngzca1H>$7aJW@5;v&Q|>?x+o9eGgErT_AKq+iZ#r2!~QJiF~@D}cxn!@ z0`?=*kNtTX&xd_u&%AHmN2Kk@5o}5Dd57%5l05O>uv_}Ow)H97zTm%!pOV=8w{phR zcmn?$XMM&a{vMAs*q?CBlU&LL{_YR`U}_Befu`>W8NMyR3hMFA!rv&~WRILP#{d6% z-^%&>`vJac}H%Z*dTl|~P$M0J?(lP%whdA;w zEse8I4C51@mp)JXasHlT$02v%n#=(!!5(=}r*xkiSz^E8Jmk%fE!TO{&s^%kY0qQV zvW@sEyf&}ZvTDuj2m8{3A_=}n^tjLXo;7s;YN7-)-D@lNTHS}6;J(g1zX*OK_^qg7 z*MBi3n<&B3JCEtop$1#;Q%#g$@=g*|Nwf6sW`^{ZV4T6Xd4~`)K+id+o@1T!jU>-_ z-Yh+jW3rslTbEqC>jXt(Y=Ca2^qt7H@pa)ESq{}TRr=;VL7?Zc?Nh#%U~HcywwoQl zec7MqvYzKI%IkV(8N9C~7@y!T!Q|OGu+1{Zp~DbcFxTQeN%OXcp115bX2g?ME?|c# zb|R0_KhYGEDH@;HKQHy{vn;9J8Sj`Z9kZ2}kD2w6&Z{N;r09G!oSP?8HrNTqMNo_S z^?+XJtBG=fV`@%6IaBQ5tj*Xy$2n$1KP#lime_&bGhJgdrNcg`x?jN%OBCJrpa)a( zhW(Z$^1hYK^#=Vw4bHy39%KuOB>PYU_Or*n5VtS5K69-L*iDUIzLDSNoIRbj@t?Ll zwm;XI%7G?IU>%v68gGHjcI((Pwr83B=sR$qBkkdv?s#f6!Lj7{`FJ83!+HQ;6+_3q zi6W-1OF)Mqc7k|A{tAkEFxc?b!1>gI781K-kmQ(bf6{Y4c}vg(`!U5n@SY*FkJu)P zpa%2NXSv}0>DnXkI7|EN&*QAmw9n^UE$z`x`-DD3wz2jbtOJO3UW@#Z>e%lXd@aS< zPc6^0q#N5TYs~37)G7hCEr?wK`<~%gJTc|V5SPb)N2iwgwvOX(^4l8U#JPS^kKQ-- z(Ab9GkYhbd>@(Ps-{R}%7N6^(6Jv?~mWw|_UZ!ab<>0nvVjMrEKe6TeQ@oZeb1m$7 zeCk7UpS0+^NE+KtIbM2yn%g1qV>C$gZ*)zPaXPU+^f-MP^zqbl>eAH-~ zx6qr`TLk;XUKX)*-{m)lcdMp%F}{OreP0;yy@PKaz<58av&2TQz?d z$e}U&Loe*}j9!}JXS#2p(;J}Y@oBH}vxXMy;J0+qho~2}`aEIXOKV^qUO#JP%`KSm z{iX8#C4HZ1(#?>Lj~L>~r{-i|qbK@;B}zOi&^v2jEj{?OF4jQZBHTCgnJt~Q;4_Rf z#1LOZSHJYl8dwkCqWC7ow?JeU*okYA=X|6Y(p!KIJ3w!u2=<5h40WHFX-?-;0~imn z1#9zKEwSMP;+iN4=B$F_#@f+6A35a15>0!}zH=;*Q(z~Kn&eW0eLBGo>j8XCu+Kw# z&No564f^|_j|0ad9}nj2(K9^^(bZE?Z|&gx-r{jje=9_voB|8r~~c9;*?Py7%|xVN7ApVHyBMsmoXF-MoYQDpxH=gz2y+`qti zXAV7P@aHl5NG$2Ve&oGid}9Ah`@}hMW2E!O`j+j6&o?CJ-^ofgbmPBuTk`jBGo@#2 zN0t|!YM(i|-c6s!{7>VpIH>8n;gJ5sk`Men0NH~nnb{gQeM z1+sm?P|V+DtHz&jo42K&i}Vw(^CsDc3^8xx?6Xdeu`SDb)Xct{KBFE!^jpl6tt$S6 z*wglR$3CA<$+-qia4vFg_5*CN17Z!iW=S{hm$giB9%IHW$Ub79;&a@S?))sfYTn2( zpLFtTz#6^gCf)nO9(7Ry?g88*YQR0j_n0QV1iqhj-8ZY?Ufe_x+|Rkk`!~S93st`d zvnoy$vGly&FI3rJ^Q_N%lIVJ-=iO_FCZ?X@dw{-qrjPN`v+fW)>l(&O6vaSKke9K~ zwjKUc+TT;Hl63nmw|oX0x~1)`&AR=>m@d5ptOr<|v5h$AZ0XeX-gyrE#Fc>D8tiy) z@EAD`un{)}5*)MSyMez(o^!~D7I};9mKL?eV-;a?!n3BMIwc$P64C$}~-o;P4 zY&VwdMep-Bn(Q!xEeTbB2Y``Sp<^e9`J4JF^v?RVpH@C}LYGX6H>G2egq&a?F$XI|*> zeTQ#62HO;TBcdMl`OdQi^D*}nc^#}}h}?TuEP3Q-u-RUuGe;HlYM8qT`k_JkaASzBHcduO`zl3=`r*F!$kkW-R%Q6K1u@kx`-;3Ia?Q=B)*pLOUO^!3=& zb;-2}OV?$_*JzcUdx@FSO_Oelbi=*Mbm^I@vB7TrN$#q-#dx4S*w1)scn;=;CFq$o z%wS6b<06QwV#hp9vIy=|C)~G8_7M-{J~8A2{MJvo<$HrZ{}k4FvSiD!AErr%nb^`f zUx4%HWXT3T=PY}ikDug>IOG#M`Fg7B_-rf5Z@c4=`yYV6*FQ1j%PftJ|Mvfl?D|e% zrgY$2LQ4$kuoG3^AbR@tQuLkTMw1=pg)LjA>f20aXl$1BZz%s)eI(v;TszBp^pMB7 z*R0RW+L~-nlo#Jq&OGKI?zDY7{*zpCPCVbY_0Ig;xUeprSDar@aNfa8kY{dnZ5xU+ zTly3ExWDPncTLL{^H^d>|1Cv1d7Z4giWS#nAGJJ&PNj%YkQe-UE1_;@O;Ma-PR~dM4+& zdk$z6+IlP6Bc%J3+GrjBJGuTd?HccaYoZ)rGuda`kRNt} z{WI9{x1dPA!Je>3Rj|i^4%lG|*Fd+uN_Wna9IDrX9q&&ZKXkxn9Xoi8bS`ovmh{Xw zy%2xKQL_o=>Vo&9ObI(agU#4BWrH116Y2|&bsjR)G=^+L$5upFue@JO!Lz!-1~uOE zJhqKI)<_L%!4x~7XLRf(K%ZhC5W7T)^I}SFF?8N7!Fkyae6Avi0pd@ZY(Tv(m}dy) zq!0Rm77`miVxTBz3i4gcazt&9iOFQUb#hja*O1pYwWi;~-zW_CL^GtDEggLSyh+D6 zr=?n1FR~l=TGcn}W50@6+P^2*+bY=m!k+6~YC7M#-W_?5+DRT*ZU@)SjRcf(styCb1d6V-FcSwlM7AU&S&iQ zBh8ZTT0QNTb^C~OjwQB?W1V|S`<+9bmT=#;;fE!hOU)^K9wS?jZ8;|Ej?b~yi96}B zCsmR@41GP}7;+3Y^nL+5e(JRV9h>uSc{iKm$#uSU$f=Dh_QZ3e1F2ZkKU_KvS}~h_>}+2zmIb(ukBp>Z^r9# zJkMj;)4hF4haEKiMh&q7KgV^w6PwBVao~4pWDABQ>_n|Peu*v}R#5&bKmP{lDVyw> zsWI%J#@_eiM{z*yX^Rxr$_$aZxf>H zZy8fG{e5GIonTx9e*We`j_0FqYEIEap}ywn%%%M+g1I>!92*#MY>-{OaqbkZeSYWk zduLYs_Td|%$GL(087sc=+ZoTtJpaPxIa$xkD?qOTdl!puF@opjCiphg`F16KzWea) zN3=wdZl?YlG1H|#K@2rExtfPLo93sN+}lh&ZN;6?|vjwsV z?D&a;AsEvq(z?e@HtNz#2HR<`vOy1ua>=Jwi5}d?lC7sXN5m43z6E~b99vU7K5DWi zYI=V;FM5E^o{u;eh@}>Fn4>U9^vs@`yaulYNj!PvGrw8VfqnoTUk$c$z+-&G(a#q2 z2ksS}>l@0!)HQ6lo=uf*d{5yz&0zPqNzeFt=98V~xdbYpm zBhD!N6OvOGyKk( zC%gSmva1Gs@-M|?_^pL@F!>D@TU33QfRR|CBcbR!2JlVeWXg79%MSjIGU7YSlH@y0 z52mEa-vfWEy9ScnJpM-SsVDS$K;Qo@yf?@^NABUY=dpFid;Cd`I3|`&<4^dwKFJy9 zLzm3hj@;?I$n&`m%XRF>)0lPJ=RLUD95cRHH!&?i&v^~#?ZCDoJNWwPx`v<%uDKyf zFyq?p$MwB*AAu1xNwanD1n!}o`>5b|i~DUA+=JUei7^sZRj#Ol=j#^F*nXe+dCu4K zc1^5!=H_|33El_%ZZPyLJ@stO-x*Vn*uB)GVkZoBfj`0oqkgSz;H&Fub ziO1Pz8!^y>D#}Hq%rm`d;U|ZhThQMM&VHy*J@Wi+aOSd4GuV=xbDY~fuJL(D z>`=sdI9Gj+wm3#-U$_0h<~0%DV@>o&oq2#Q+qcHdOaHK5pm)Pso|q3l?63v*L*U+c zvSfpNFFkd~AV<(7sm&Z+cr7Jt_+ScbRcmE0R_q&lXR7p(&P$FzaBh^?bNowW*}{j! zZXfliU1I--By2$+UC1FF4M*O}Rxdhv&-@JFI|MAa5qNbYSig^CLg8zKC&+8g(r_ zCvpZ&@`)m!@p@QW73_HvB|sly2gCsJuoA9$$`-xM=TKtrORG?W+3pO3&Q8!1uAd z4tgRN#sgdS8vE$|yve1QjE~2Y%&|AU$DF6n_ET2HXXx8x`?tDtN9;?H1n+T=W5^ir zwTK-ra?AI|Q-A%7V?fOMoA`QioqYV-aoySH*evZsl9!nppV(hwj$F%ne^?v#8&&pi zIgiQPF+6=Oj{Uc?DE{P|-0^Z=W#2x|v2zb@d-cgSwU@@n=q5RiBeA6ezePQeBxCN8{3dOlrA5_yR?+*~;v0>=@l1ca;(L$a zJ6jb$-}UruuOu4Z`U2aR{vF91_MejcDVT$AOf9|>(Nhyhz88&4fBs%Yk2mnM2G+Er zHuW>q<+~K~jDUHkY|M*qDaJ51qr=NIKM2Xs*dJ+lth<27N!N6ZxDwU{3X*iMKA@=oj{ zX9#+uPxpAdJ23^wFW@-CP7Uh{$wH7fBs>a{2 zbzg8?R}QR%=V^catM4tzdc3w%;;Rpk3p3%deP^!ir*zy%y)5aFZKqzNH_tm$FHQOz zMSkNrkDug}>y0!o zI`-4Xcm+3~ZH}q>ZL@Cam`{>;VvreXzR~0NG{@LxITdq*Zx-17?c$V8elw(-&F^@? z?>h8@rEz{fm^=prTM}yeZnLCk_})`Q52oa|s~H z)^VQ49#{Fm=e@55WDAOpvq2bE{$7e+i!Pd!8P+`Qs>dGlRxLo6=*EA0~&hP5!4aXtLqc*i}ux4X_SL}KKACNOd30jP?!A{hO$A+(c z0q;2+AOG#G>zNTsNc1UoK%5!UPnK*x-aU>xb3jo)zrlH~gS3r0=&olwF`0UdvoGt! z0sVUo)<>+fNrLynGVhOV&RJ0p={Y=)$31$SlB}nR5o}40D{wv-#`uUOpZcB?*#y0G zQ3US>fIbB=%+H+Epcb`P;CVjDs`w0bU|eXjF(31;n7>Oevg3ns=!{+esh6c5Za(ap ze7#z4x^^?RBb{p-aZ_x;-y1UAFU^u(bZ^b{xbNCV4Dr-ro+@^%wM#Fux0s8V8DnDc z6ASE56XgPX&Hj47+4H>b_@820oIji^=+t2@`lJV-M`RaO?C>+jjvv|=OvS)0w#fcZ za?bHqJIBtWKj*PlVBIHGHgozIuLEjgv$WrHwba9u4qH^6OE>Tt`#JAU9b2~BkIeOn zS%J^xn{-T;r*zM;XnqN? zzlndV>sppowGI7cMw~NQ+V7m3bc`kW){%_cFX7mnJk_8+EOF~E+j{J+rF|YF=YJ8d z;j_N)9iQZx=W$QR6#Wx5#j|HO*iX}vo^9Bl$j9!C!FSp`&UrU|Ue1d%&b7v+?;`an;hIF%~gMYiae#gv;-$@OwLQ zT_{y`1K#^jrfgs=rP2<8P9MzZr@qrr3hNgDg?v zZzz+$l?eX6G6M8G&URwEpiYgyqfoyH=BJ+`=#idtzu8{pV~w+_j`pr-e>rBHBUR^D z(K*WR1iu~pMsUs+`L<|re9?*L+$Fv%&X7C!4buA*-(L6@lfV0PefuebZ*4oi`|+)> z2k5u&dwk;qd^!Fp&#}ZCY*W19omJjSN(#dxXwtZdxdcYXKcr8H32k0$dqOQfOUD^6V?TMYLfW>h@Zggn8Quh6eB-#%g0v( z_MBtTL_yav0`wc?!AxxF8TZaI$A|Kv1x4}=&k5N@4OTiQeQvhnyxlstGd|CeJyE2e zoY&HMtyNvG+=sX?HBkij!TxY>AG&u!6GhCbTu}wT8%Cn>T#VlwUGfRumv8tt&CqWe ze)kly;yt(YE?kNljsG!8Z7Ft2vO^L1<}$870Pb$`6k<%jiv@43XCTcyACB{oD8 z%$xbM4)(1D==cpb$BARgG33~{1AMSWRo)VXV;gn&h&55firtzZ=Z^ zWXSiooISs>%P~jhojK!AJ)M_p(R+~n&doB%v+kT0dp7=HpIn>e*=MUS=N;$Z68XHe zj%>k{yg}T4^e2j9;IWXV$2_vE@_h?yxxt$8Tb?=in_&N|;F{rl-a4ke~g`k9k4~y5t+qvvlrM-G1Q~w=d5LJ@;`e`<`_Ab-{gU z1l*Gh_n>(JKafY=5+!=k6SiP3L#zWIA0)>w+c!PepZ#w6*_Ugx?wo!ZAK!`dvvf}O zc?{+W?E%MclA6`=w6fM)Lwi`iEI7ae2?)aKlO5L(LCSCdy@5$ z_Acx7!S5W$ZxY{s6Rs`zc8n48LEc#M1N&$BwwF29)3q{R&K2_B;>31>=Y|>5&61w+ zGmK~34E!uSpOqt?pGeq=s^9gQE<3YiL#fX+#bAmZ)L$Aav7|$Z?-5gS3%*kfzEcEz z!{}lL{sz+YJ;V&@n{Orhwo>%%h3_z1@cm|7z)zgKBB-ODeuX|7i*cC;bLxV*&njJO zZ`v>RyXhGCSfNAb_;SzJbAi8~@b?t{_EZIb_bLL3-H9ouD0c{C)jKaf{@#MG>zM*o zV9$u>zBBsr_ci|3*7&=f{w@goz0W$bD3-~wTbx|_-5Bz3!MI-2h_!l7CDyxR&8+(d zbx^b?tZj>-eE{~uus^{5uoq2xVhZ~uAGy>4y7P&l<1^S(P)na7cEFwk`=42|!M-Y; z8f30BuRy1_>rdh~TaWDP$t=b#&W_Gf9mDo!8f0xc*>$44x)WmDUu zKl9z9#=RAZ4Kt+UgKJ8(klDsp#MCusy7U?>U4t+ZO}g1!pD*+jLq93W*p&3%^o7{-BqFlECQtci85FCo`7>0leV9>_UwdW)XS4VWkN3q?LyY0X3Wjr!ov zwcqF-Gt2(#f6s5qm)Wv`^?Cj4*ejoZr{vtcLEh<~@;z~$^Vm0<{3k;`<5+p9KmE?P zJmc8JZvLP6{*4}tM;{o$mV~P3HS~ie+Z5gl9%%BvF?lx#&~G@7ta_h-kyz5tzaMqI zgPctHPR^X0T~v4CJpAaV#5ctjO@C`MLwaV(hN{0Gf^FmwP0$1A2mK42=Nikc+9$~A zq6qdB@;+DDCq6dkam&Xl*U&H9Khe*zn$CxlA|K$lj`X>jWk1g8DjV=yU%Cb|Lw4iZ zi2G2H1nz6xjLxm33)BII>unEDj2(olE5Bf3v$R^UqUW5Z#akkjB^`{d9#i! zIBp!n6`lN9>6vWLApZy&B7QN|@KI6PVGS(2x zV~KXz*O~S+>-+E%`4}At(@`-#dklzAy z*n%7w!II1roqKO|#bN${TaA4;S=2kv34J8anC+Zny+$4Kg#Ea|UO@|rB*&tQDmc$G z_<;C{W2dD4^u5`6yb;&bDO>oFS-<5GFE4QInjY6P(!N|vufM^)++(PpIeoS*ORRx; z<#|@wOZTmj?p?lz;Ufm*xo%3&{jjsoF^_%95&K|y#|QBZ}r)ibFH_SSCM4zx~KvB&E9A7G01!EKJ?wf^X_RcvJbyw*0Y?7 zL-tvJi#eypxJz>}%q`Po-`HCJH{5EoZye)&vZ*Bpd18N-=(gLTW52=r{wDrb`zbbJ zu30YmG8{kKK9;BK59f#d^N3@A1J4ie^8`79E%}6>S6$C8v!nyhNYkXld|=BD#I0La zeG@Q4x>?ep)Zg%J0cNlzf$tMpAF=^|SDYSIFm@il$Yvj^_K@S&k+NAE`P2`67ioM; z;rmN4B)5E1{1el+l*u@COWpFL&{G5^6Bvb~?^Tl`Za+1X>&GB@r~ zh4*0oR;9mtHT@lozn}3pvo4z$_b$d^4UC1Pp7n8U(1Um9Cc56Qw|dc!zopT0#oyQH zN$(h`&j~XGo^!V%OaompEW=WY;W1X z`n*Q!rr3hl8lq-va3Lr=u5v9px1ytXAkTx zTvK$8Lcib`y}9Okz&FkOEtBt=nJK%AA{N(YFt|2FO%gq`b?x4m@|z~z6zQ2&HJ-qS z>=&whH_qIpJd?+0zy346^(o&LUfYN@F3F7jHLaVvTkB?YO4C!V|hrDj|{(+@9u&3?7KJRyyId`hg?4;Y5 zWjn@jY|9T-*O57G``@Jde3J7r&vy8*Pi}SJ`nLL+a}6WzAC^n@Pbj(%Wqe<ZCT@5=bGReYN814 zN8Gn+zv9^1ZPmo+bJl^FvSK?V!j8 z@9)-oSxfLvS7Yzb_1^4$94Bfs^rt6dlmzdUUGQGYJ8>5!fekZ2FJiZf9i1B?l_E$ttQ0d^78(ifTg5X(3pd1J_bvgI?}qdwu>Q+}!=-haSbJ~750 zu}=1+9(36m;v6H6RX$$X-xSMmY`ZAIjAKm=(1#rSNQ2$Ozm!vAkD2#31bcE~5&eq{SV&b{fm z{*32X){Dk`!)xnWBUHh8BHKkLN1*n)AG-x9ZdCq@pr1{;XM3S4iSSQBR*zBj0Y6+HEI z5BHz3uAZc0&OIe@*A?lI_X-_f6I)cx2}YV<(KYdjrTm=Jzr0qdAvf1rr>9{)fNi}> zXU;dK{3kh&m~HDw>y9BA=Lyfnl00-v`=5OFlmBm$`=Z}?8t>b+Kh-l9G31RUzmM^g z?CJcO(mz4|r<@$$$~XC$GxWU4EZK(V+ewe-rR9{Jxjl39b2;ln&uFuySA9n~>GGK+ z9ZJ>pE9L`RenU{0?g9@>4T@N!RxizNx^JUc^6r)rVtS zxo`8kO0ui2$=@_4wd61A^o^$JTh6$EpYJ^6@GWO3pE0}mXMFW4qB9@$X1{B|I?G7^!GXbX4nLO&jbFp*F{b6 zx4#qORbJP=Zx8Mld9wsEPq`>o-aKWXX5p@%yje^O=&cgRI+!#Bc0p zts_{HHypQY$~*hH@0&bpn&t!L!isfw$s1Mv8=HH)xW#UL@U4UF%X4eLe$RJG!cJ7} z33!j5Ty%#i*G=l+fK81z8@lU~gIzlG=X z)<5T1|0GA+-z7c6{S3IbnJL|H-+LnGSpQb`G{#wT8}H2dsm*v$gS-dm<3f|q5YIZc z!EfCX-zT1qGj+s1)xX8ZWlpDWDJR3;WIN~M&$jg@KX@%&^DcsW2KSO0_fd7r6_{o_+m9t-P9C@S5pDD6o#rnLit#>(e z)yl0N=T=glXaYf1fydo$J$<%Oxa-N-d9`fSM><9U3`a}M^mV@>$a+|%x{ zH%MaCoPDNzW=qdh-Rn-c2cACLxfb1i9N`LORDX_d_4S*@OCW!1~#Tp}qHU z;&}L25Tj;_eF48|(&k`jyp5IgKFCg!h+v5Ep_i%1k4U`9% ziy6mxSY-qL^(Ew*Djm*!XxgiCfbSDiIhnWolKP*=yy`e-`f(oE&N-YznWq@%+Z#=B zC>I^O!+Ty65K@Hu)o1uc9!Hp4~f1Lu0;;966d^G!_9~Pggiq}dP6&~{}#)+o-^}* zqAQj;^LAQyj%EK%&-3_3XD#NN{C_Ja&;6-q&Ox6S&K%oMJ#YSz_TZ*3#es9=Ja@7^ z70(dMHafm1oSUCPS(NFP!@_HFxIE{#LP|8@EdXK?{!7+H$$j`zvngnrl-H{%>#VIU6ym!jAt2R z_qYz3&k)RwIRf*YFXVX9T5~QjQ>D|F{_O+gWR`40A4BgMFgCE?Fb?)%6DJRfa39Ou z8~xwFy)Dn4`0Xpwfpu5*i{r$*N5FC0Vx+zC9wDiv*AzQots_9sw*AP--v-6f^)W;h zTi3`GO%$=X&VnilOV^GWTt`8Zgw1s&Y&&k5bEfj32PH{#SoME1?zQDD0 za?8Va=Go5qBgXnh=DH{Ssn1is{mjj}#}@epxj>HfEN^o;*U>cRCyHXgx%emeTt?QvAon@-ndWxR)$?z$#vuvV)-AVu ze+#`@P$a?gW8PJ858%Gg0`4Qew{+>INMH3gL(){~;Cp1gcQ)NSPl|kREZ#qZE?E`)!(ZC}Qb7GlLIuyz_n<_zw zY~vavK60j@KQQhP%*X8T5zn;n6VFiBMTxafN!UTv@jT%e+vhxLT2Lfc^rbgr?fGt4bQ(T4%qtuAF(1v zoSRkB=c^??AZG>CP-~`iv!%m1XMD~n(t)0a-uTGbg8C)cE1uIP@11?fDR}PZ_knTP zIde^69pEGO1aW8&cyEy3Fk?@%Z0xTnKfuTSCcBOuRN?bx z=)MQGEpLANPT5osB{6k>y^(WnI(^<4ikmGRur1MB0{g~JHO?>cdCpz)hniS=cG)I2 z5AYEuZv-tQJ|KRBT9^+oH}G7MBVm7$j~?zL zR|I{!sDgdV;3rNV`6Jkp;I(CmpS%_n$v=g$Z!j;YL7uaZy<0WR0tic|^mNfZ1v)<#nLgLHz zlfIN^{M@o_X+O`l`NCE#Q}wJp;TfD+VI$utdj6j@`JR}HWwz|9cM%w3B}!HAM3+8; zEeY+H>ckdR-(0#_qUc*p2A?6P21|874?rhIZPoXkCHVekigbKKY~?gTKWew;Ges9A zU_Go0YQUO-eQ2VHyvO}=e5!o;7!Dm%XdL%Arz+=+&c%}0N#YYbQ6Z|Jlz7n7l-xs`RNYkam3T)SubJOW>|7|Sh zaMI+vapu`RbIT9w>2vn5O)s#G9AXRB)zhAEKALsx6L}}j8M-!1jc?Xl*U1!36tTEo zxV92o*AI+DlWwv<>yBF%)j0B?swKK#Hd~Nl3E@7kF@4TN#`u_=^#(3)4G+qfUZ>b)=JdpSPN$=_0M6bf< z?%zoI{0+<{Gi95n^|BWyBkdLXK5)FrUxdfVV|yQluIUzVPv9QWgCe==e;CrC2EI2T z?Xz@j#=Wdbnj#$*?~*~6JRx@as(i5ZY=2@X)32^t=}L+G*J%lJuwv9g5NCkrGEw=5QiF2W9VU;bST2#@OpgP!zMPw4vCGQ zocaJUa!RUY9=Qjx8%=)WIKAju6WI8H{2{hr%}aPsTDoS??MIRaRZvf#azKBNMQn>6 z{ik~D1U9sg=sT$C+_S#qGezg>8&7#X&ig6(H*D2|?{%jfaSwE!bMdc(oI7KFC1ekY z|B0;_v>20q#y;od9#1u{T`^Cd1L%GxoU*6qhhwgFuQ%yj*JkP1PbK5E3;2!W=ic;} zJ$J5a-3z%MAlAf;zMPvwRKfX<#9pGLu^Edx>S4W*_pIx9%mcp*_}$=5I@gk1>sg*- z*k=6t0Bi7iTGH+NiF~TZ4?b4xyXk3vty>nwPBZs&9Jw#x9|uh~lwfLX?)6X)mYxG6L3|3=BC&haIe*79i)V_T zEBJ_$n|s{q$ul*2Q)j609MHirqC#-X|lCN8TIcwgl_KwsefVB^Voe%!_$9 zF%nxk_*}B>oTeCHTcVpS9c-VnMcpaSywlG4jurKqu^z9Db)KASrVg4I0lF#D>H7q6 zaK5Gexvt0jEURqhH-bD@V$Pc}zI$5Q|0G?1ll9WyeQx=VdF=nDWGwo7Ov^tRvt5o2 zKKJjj4<6HFU{~SegT(HFnDgjm?90-z|CW3kYiMp}O9!syd_CJn_6tis^Y$!qtm&DR znX>mm)${aQ#>;bkDfULudqakIiIXXx+0uDG$qc@E2!A8VvRBm#zPZ2BnE;x3| zTQ0scugV8`Z02H`^czKf@LIgq8pq|^y>dR%HQD#A-c|p^QxC?=G1o8k^7_2?nfAoG zW0qVWO&|@nDblB|qaIXAuDx#Qe(xC@M@{LtBC-Ii^7V9w%at2!xm_O@a z?clx0(lO*xZiZTD4|p7pYuPn+=JXfEpU8Dt&-p9%>`&xSt*O%A*j%e(zR(oAan|hU zh4h%`JZ!I6A8YQxlr;7s?L)S(E%EK(Jcd5DSuP!C&I8VmDY%AwJ&kmoeWP#XpX9oh zdY}5%*iW3f^t8`4%pLj{IQNu0=HT9pvmVHQH?;ngOP_CPX>D1jKHG1)*Kw1#I(px@ zf@_#-xrq|2svOaSDhc^MGjy*pE#0T=v)t+4gl>OJHP(xKekLRP2Xd}; zYO3JbKSUFLA35dJdqWq<%$EIzb9>ZhS>%JoJ85wGhWs_y@p~#u$DX9?k(qVaIcEKq zgD>M;OJeA!biAfDW5*okQ;_nppZh z&u@Ewhe2XP4~f3z2j?~E#=adnrzGA)5i`C+X4%7sG*x;A{}lAt!gEB2AzGqHC!e~S zz{XE5H9PQn*Ja)06+xYQ@$U`!cZzS21Fq>YHoc&U5qSKfV`7*Kesahy0Xja{kms6H zwm8;PatE9ToFkkc@abH2Ue0a0U)-4TXSVE<9B-HWs{5beerWxa#LSZZ1UXfB9v+{u zZ#bUiPV312o1SyC&K!XGz)BR||9?uZ&-zdMI6wD5w;$QR!1=k)xUAK2=OgoYj#F#> z7RN^pd)x%)XBRc3&sSnF#TGT{sHH~+pUHma*u^~9vTykPAWP@v9zFVgl0|ujd09v1 zd48*pw9c&8@FO$qTZX;NG}*>}+i%jjr#x$l`WuexiDSq!cG^9iAJ%J}H@-&>-78I# ze&X0p_f_;K|DWWYy|9}o!PI@<*hkzs#JZ@0=LOHNB6w!i03BAyY?DikpEVxKecgM- z^N76m!j69QL?;(%u<2p2jdQXjwgl^AF3gL4@E(k$=RF~he5fxtXDWxjK%Y+x<(%AV z&YWyN+1i+nZJ)U{;mD(-jFw9Kam+?nR8B#J^3B~i5zJzZ*}B3 z_bJagIgh!X`OaJF>1(UUGXpsf9XmgN&|6|i|5LVVfoHL$pUYjo8%zF--x2aVgY~BO z2bf8sKd}{q$~TgbBfgO=NhtL<{uYCr!ItE^%@9lQJ*SH)ioWw?nrs*$$swQmu0AD! zy#;;A>Cun+BCv^(Ges9A;khG6P$hxAXa_~MVXx8io@c$M<5ZPFKwgwj^s=g7va)mHFby2>6JeVPGHnE@J9>@}`v_D1Ip6!QXg%4_eZVeaQ9!=RW0~<9bd{@|J)4Y=1Lm&T(CqGwoTC{>Hb*XvBG0 zCBMP>mMfjl*3WtMG;eg`r_VOA9G|jbAFx-akGvifN#?^mtKeGXnrxzoRh1i5N#pwl z_Y!pENt3S#q;>4SAwPG!p2MGTzVoVT{9GS;wzq(Hzj@GQ8)EwapCM))*+mgczh8!^ zA<^;OAa+va`xe)p`Sx3$@zdV=ZFb|#`x|>atm`%|zMqUm?31>`dj{zEJKrkw{RqZElMN-LZA<(!eHZM~tKfTN1|Kmqr2}>q z=l4s0$F$77vQ8~Mn1^9*L+k`L{vu}7cwG8<9LpwO2LC#s9(qXI$SD|)ad&`j?5o;) zgO7L%(D8A6mhkZ#I&PUQ+sD1@_`^zYk9fj)jyL7Pd|@m04Y^0oueu)^?vFQ?{6+WC z6a4kytUu$Yo#&G0nDv}v{p@p&i@h%M_H*HrWUbWazS!=0tEc9PTkTXn>;wCe{SPUs zyOHN%eP~@Z@c!ogN4Fn&#vFH#ZzN;F5_z7icV5`?fn&C_&oN|wp(w_@xH9{ZjBS$AGjzA4hd_DsF-*>=3h=X((Mt&IIQNu6u6e&*(WPkPn3 z@D}GD%RRSDxfbM*9PF#;954xy==R-Tdm;F1QL*(IKf?9e^ zLEk*4$0G*hRY9GhPZK=jis0E;6WH0e_5Yy+Ge9TbdGzRlep}F+F;>9%%wqst$9UL|d+oL@$vGi*`mpE__QZEH|y?|WxtGR-&E`;xDI*N zZTXD%3TfY)oYAjKX6#31|C{cbryR$T`(Gyaw%#=c`WEcR5KH)Z@Fe}5n0jWM@GNSH zk#zfSzNZ+uCtE)7Gra2=4(o#7JCM+R$W*@B(xK}6iW$;jB}&y;qKhfE;9JiKmZaf( zP(Q#&?vDB{Sp;KP;+tZN+!wtGVqFyBxiQa7l?`4O>uf=hgxj8GANzii<4?M4s_IW1 zL#~A9WZBYu(dWUIojKQ=W2f%iCwb=E9Js7fEP$bR#rQShLx_viEo~hE!s+w0|-#4;HzbVPQnCB2%us+tg0)U$D|%4(XXD`(*37F=yfRdZ71JMr9p zBHiQc|74T@6kCdc*XO-x+Lsf@EM5EGlpgDp?Th)HHQ26m>}l@(Vb7}k@N~RJI`>Y! zDP|nom-CbU9)C$Up7TvQC(EAtSa+Nr=(k>u5qBN33eT7Mx1dOJJ#uZ>cHosF3a{QYE$@S>@TXP$Bdpsd&3?9dJT5O z$-B|y&yceP_3nd@oTd2yIzF?ddpv4xlsLCJwkyczQNP?Ph~u9D_Z99hPmGrswdPh& z?Gsxu;C>3ezas5BbL>avzAfhAHGL~f)KL#3v898@>}ifm`V%=1y{5V|_GYg%hM#>c zJrB|SoXfe^9s3jM9zEt?L$V+2(MmXm#81459p_hIfi?lvOmnj^E~t4YkzBgB_Ml2~-yNcflEB`A9BQbg zkL8rU1IG6F_94k>q8zv%5_-Zu*PQXHTwrd{qn124ZTlUoQ9D%I0(97dcm|*2O?nYC zo~u1cd>LZ4sd0}jJ%b;cF}q;h?Ag4q=kd~eBh)yjsiAfYdbu~cv2V(EBkx0wE((U%zRRFvPx4_00%+fKDDYQ+;ZpOUGA!{m*_QY-)&gYA$DjD(WT!g@@F_60=*4~k@F{ZgL;$EmT7bly<@6IHP{T(^`*f0)6RWDTszYxJ5e&vlb) zmULrZmM!I?pD@1XV%g=pQRHV0UQd<2bzaX0n*5okywjg+(94B02YY_NHRb5XwU{BE z?X2fo_xmL0hk4!NTQwQ>ntfkiz;W{Nn{j-%q>ueb=Zw#}EPdX+$((QfDc^DAO6Mc< zU{1&};0=bT)>G}hme`?bs5bKLq$>qj?v->f^9YsN$UQ%=rDCoglxo8sTV z9-sSs_QGC)*Yr1%UeurRsi*TC_gsehKohKCiY`jR*B;lQsnTJmYrIK^a**#MUH6iKQ#d-LZU^vyfM2U?7q^BA*;rQa5V-y07Q z%iQv|e&3u-`An03<5N7>6!n0m-(AoXwjImTb(ZZimnmQ7mhSO4KFGCGI_#54fztzzj-^r4X{IkBwN9+VY^u&~I^7@$L5LNpE?2oZ;M-FxL zpl^NX?HFrdZGeuSvFJtLv*%Qt{+T8lMo8;hHgha-Y`3J(6U%Yj1F)Ci{sCRg#FlRC zw|z>-k;JlXAAazCEKA3-bbRQ(XY6y{RKFe+Nt4%Q-<#~wV@kqKFs8>}(!tM?EFH^o zL~WBiaSXX0pnp$H={!4;C(f&inbT(*IYqY7>xG;{oVhaR7SeOiwf2#h!SB4>_tc$} z`?MJIJT~@Q<~YZ)B)W8bumkGGg(cs&c)p+HxlZhv$nknP7N_si{8(2Nthc3g<2zwL z$UDJ*LQYE*>Cod@#GgMzE7auhGYT^3WY~96z)GeGBH{oFe-io4gD; z+&@>`TanmM4~XSF;^fVMUM)b+e(V!+?Q`xa9X};`T~v|h)ig)gdWP^EfzI>f0{$&V ztczYxFoye{{rt@3*#ZMENA3qx#c@?{PZ)Xxy6{* z5>@lHM9+8~*@uol%QH^DH=5#kPOPB|&P&d>DLD5G_7DXUM@W(VH<&wtUQG z#C*?n*zU3BpGenc$(}IQ3I5D2p8NcXPJj9~!MeLBA*UohVnCj;k2rZ-lf+TYr*EIi~2n1lqxro!PRX*53@tl_&z*tgQfH21i$mA^qt@vG%)uiSkDYtyY(&~ zRN*}IPmpUyjDu`}=fb=)J?2~_!D|{?pQ+M!Rh?)TihNI;`A>OE=Y57e>@%N!_>fOb z#mt#g6#JI5_FH>ujC}$BPoM{TpYbtS@*5vJA4}v6wxr=);G9^Ad|vf9_mDGjyEZ@h zsCQ1gT-%Nzotww(X)frFoqp^ZurBLQlKjk5p8dDnynpE91?RfoP2O^H->UvThFRi& zW2AY#<()B)ar$$=rQW7!PtlPlH-A5lLAK9&Y>zWyOym<)G4L8@x)z%BlOmt5Bg?L9 z3uYH%iEb*Ju zkL}n~eXe=ZbC03%!2M>dxl5K{>9>X%(xD3fhOxxwc$WB2T!Wk!ntb0p*L|T z$^~jNLpJPyG5T@NEZK(rF-ltwK26bs7VBd@EzOty z#(jq3KsLYalU)nM%p*i*y#PR z?JM#d)`fmb$NNK{DgO?t<^UsDlBVdnleKxJQ?!+30rkg5O=xgVV0^8|NTbf^J?@QQE&Jb1oeN2kRn+e9x4B0oF zQ>cx8^j$Fq;{Y~(a>#Yvc<76NUvT~{cKT}UCH@u^NzVZt9J@(!d#uN6@>=i%ImZ3A z`eue~gP*x|u|(0i&;p+yJIbk8~AUIa6-lb++&Pg#_oVJu)= zQ>A-umdvpT*2$Wo$C`=34yez#?~)%pK69JFmV~BfhNX4KX39VHt{9Y%wk_wScFSiN zD>G!TgrBXaU_{t0O(mVUQ;-*Sx zP3&(GoDcc@@OjYV7>w9!*IUxZ;F|$Dcn_&}4}8w`dZ=@aW%gCYGtSArtTV=G<6{m@ z_`I^jXUGBDmgLTaYb}WtF=8)$-C457#K`FZI(vOWUKJb@B=!o>p(akBk7bt3qY3U2 zri2e^ANLJYrNe1+k1fLYWA4v2fekYtw*}O0arVVW>;(TAv(Nsf9FKJyyF{E=jk#IJ zKA$zjWiPO%pQz!RiYmC2T#+)sC%)3ZF?HB8Y8WMd8 z*CMIU5GN06NPOTt>SkgmJ^RUn@_^%(PkH!rj`b{3Jh7i* zBb_(sx7u9qK2Os1^m@WMS<-LD9&qe9mRzq$>>e?4U?*}P^c>4Nb(tJP&#^9>ac_+9 zpVGNSwbW;cZk$Jb#(w8@)l9Jk>lz17Yjy5Ye!kyNJyU?^j`cU`=TwX5)sTdp$j`&B zXQNrtPx5mY{YI1jB2*3WS~kEQX!bADQ%<4D&a$AuO?WM;?Qx+E0g z5`^O|HP{5n;6nhntWz+?T9J5 zC?U}ez8j8D#m$xu?thb?;B@V-9DsXSsxv!!Qz%!ZEH zjU~USI@Z83hbcG@fO7(7uqDmS=NNJ%n)GtOI35#O1JCP}`$Sca?=v&qd(hiKkqwJyFVEnl+t=kc_Wg;x z^>!ckN7lsVJs`;bod2Zf{H!~FX`CC5*O&+6kDy7O6#2~5`v>%dec4|XKUw9qvMnd6MLzs6#TMuN!!|K;dIB3iIZJR)^8IN_H{4s%E$z>eScX^^ zRSfk8#<}5n70rcpy{$9nkk?g1{f>1o&I%YCmcj6oOv_G^ms_d zG(-9hc(y_hmgJv8k1FWR7zP_Zaaad_M`*t^$6!jHoUyH(%DYv(n~g&{W|`yosP`E3 zE~3XAkxSqs=6IElb(H|U%SU_*kLCXMonz;?bC5sf?33%K`k59w(iG`tX$>dDvac&A zQ)RP$v!!S3KV^%(^LdIa^4acj?$>m_+vl8Hevak1{aN-H!;+Zwn#RpK*ErXD3yJwM&m zv%aaWB(R6rA0P*cn0glTOs+hW^9oqD1_;hGk`=~ug zm?=N-uG4}cxdYy>pkG+>nWFDw#y-oQYVAWh|4Fvg^UTS8vhKX>`xCv#+K?IdVjSjY zJTB5XS-MA6KlYYAK56o0wvNlmln=Tn0mqqRUlaKpYH>bU&UAj^Gudx_EB+16|3)_T zEJ40@x~?Ut!q=fCzBimVbnTiZ{S(g3a;NJb9rCgKsqS-Q>fFdQ*^Z%`8F{vmCBgW% zJ(r%&Q~R8QY{8T?Te_+7HVM#)&k6lu1dPvoieTO~u8;le{rV`1XSi-` z&-(isLG~A_Vwrrux3vdno)wUEa^?*~)%NzNgLedb_$%~`(XwO?A(!H{jX zbl^BZ50+$x<8`CS55%mWaw-P40NH~o2|jKdPiR4ryfLfhnCQ|^ihSVvOTOoz*Du(Y=enQWIrdF2 z=eW*&n#O=~u;Mx2Lt?|3ceCjSGhyG8EXVk#?eRUIo2;52@GdY!6D2@5Lps`9rh1!4YCDA67qOG)=*;|tfdL|sPlVQ@Z0yF-@p6@eu3Y~H>hzB z`c1I|bYmY9Ul%2@)CU{?42h2KjNRhocplEDmLBAv;4dQA)mT4$h*7fzdo~372k6iO z@(jKeuOEZcGX@Y{EjIX;xT1@{5pBYaQ5@0jCDd8S7VIU~sV)$DXpcU3<-O;@_VP{X3H_n$As*Bgbker^a4WOOGz- zo5y4P8EnaKV2wG?^_ES2o^Z}iV|mYp`oaqQJhkjqYYG)Qzd3pzPF4DO-ncwAZaeO0alBP)i z2KMR9%X^pg9($W*mG2E7mn=D6Z&bxIJC5@=aw<2|WS0cCeU^^r^9vpS>9gHA2Sxpo zd@T)KSEe1;VL5E#w_N*fk{VNzZeNzfXWVzV|G?6{$Y6Id19W0TY~8yuGwx+b{4Mg_ z%XRp3-IGpU&h6@1gr8ZK+((~qj%CiP+FM=MqkK<2rtLBMZ)1rTgF_?4x z0p#a-(a$~c6VLC7_2`{r=)|eXdDu%ZNB5se#}DLW>~}2NP5JWyV&pO|a~cPEp69%% zg&Ax~!!b4^o&Wf3FX_g6$e3*VMJ>)&khVT$%Zk>BSFy1~b>ugCGnCT9xIpL^vS+)tly-BaBDrXDk~r9;)d z+l=EmP~sVaG(1<#l>Wq4EI+@pKH^!4&pL8_!RVa_6^0%mTq{@ z>0$+i_pzW!nkoHc%Lk_)+c9L-yWme5QG?9T$1!w+?0HaEbN3Tm6K|Z?Qjcq`NSdW< z6g^9HGvXSytg_$mec+R%N6~RJOUG#>s`UNqKVU=7S+ap1J(!ZlKBRrf>@TuU+}Dyg zH8a?fZ?yP3-Qw>t4}Sy1{|V~f7>c)m`7pmOieTUp8d2lc={ z$4`0YV{ctoFfKS|>3a8Fs=x8g+P=wYzy7m#Q~Hf9Kd|S9W3U9rgkv*W^3ZSJmcWJ;*haR9A%X8D8T+#w%B%6)`k&v|((zH( z19W^Va>>th*%@Nww$$gJzZ1oOi;Vvs8Q?4GJq2TsZ?N%qQC=WU-VR30kv(Kz*=P3L zVB?38a4vBme+5Oy?u6qBQ|y3y0{4bYzE3R0PI9jGoMWHm8N=otHiIn*`96H=&Uunk z_5Gm829M8ts$lI!uxCyCIph4{m{lEL{w~O|=J;@qu+QXAYGOa!8`%?6I_xiSu4PYs z(91!NPsLyd?AH)W@QmeI3-f^IF|@>x{)wD_>eQY-Z2VjB&d`G;3A|sl#FYMtt(;F( zz3;#XmL#12+s*#)`|r(v_PE&|&vL{(s^k-SO?eO6Wq-0S>qWUwOzroL9>?S+t70ch z$1=n5{X|pF6T9ko9!%Mv=*r2EpKa?!{tVX!c9!UwA^VNW^_B4Tcav{|2b{E76O@o57Ye_I3G8kxo6mZdCb!=jaqo6u~>x`5xt5 z`k-58|B~NhTQUdhmiUZ)r*xd29*cQ;j?5E%igOOsxZZuI#=POUrE_ds;3Kg0^och#=%A7f+>YLe(T^7(b1e=VJ#Suf#7ntaZG())4U)O1~) z*W8mm(zSZio8pkKb*}xB9MAecwK_H&uiLT8#|M1{9M7Jhc7K7-4@=IOZ^-#8&S}f1 zddJ_+-JV(#bz@O+Sv!n_ZRr#E$a=`IO_{OMTAyw6s3-Gmd@cO!;6Zyno))bvYk; zy7s4Zll|zY-}WiVeWED#Z{sx~z0N9~ePBPDC;@w&@iD0KgKbOucl~|6|6k_b?aY!K zS+m14IXvVHDFE_iMh%DpQ6LILfhZ7V{uO2alr;$7dq-qe&v{s%U17L5+#RvKvU^Cj zph#w>-o>WJJ37ny@UF(c^v?YbxfXq3BMr7W>kjn=?O@0IyGX8dPR52Vrr1GlZy#}k zFXy0-gEQ_nNSHyFgc9WOjTozCj=j@q^NOmt@j1e0jiz%4lmmS80N+W@*@^>wo#Vmh z_;=-uakS{agv3VA;2Uq)ikqk0rW`0CZ4cS7AMiRq$*y(y6xSf%I+DH_LlZ^J_}dhp z^LE7eJ9F?iZu~u5Bw^`q;Eeq}@+|FtlFma;u?2rq19lNreQmzw2z2`G@)O^I+gRc! zUIh6gp#4;xCOZFq?EjN~Fl1N7mMF>tbbOhfa;Qgb`lD~gu>|YaV|`g~)|j;gUkh~n zEhv&)6N61`h%IQ>FD&_tpApC=<^}vYwq>8pi;tYDXP7ykXG^h6(R0}J^nA9@@~Mse zBRw~3>L1->V!TbTK2@+@z}m5%H9*JDdXY~%gAFAle)^#Q^#f=J^y-*ew@7`6u`dF>1}AOO^z- zW0v@eU>%-dU1|awR)AgwXOx-1whu`Txl7#oBnIR*(RKFOIs*-XaNt%m38uHL)#N-wgmQ#eQAsDtcwlf!2X*&b8R~pdnK4#7xM*Mu^Ufq zda7w1S;C)vQ$Dk$XU_d?+k4#7e#=|~eWrfV0YA{jdX_Wten)oIFw{Y}wEvVvIb0vE zQ+)yZ#e2(phj!GPZ`g`I;Wa#^^H0C+p>zeV+b!Z{|_z;E5sYr* zHBkcA#&qeKD*K78^)*9!J1B=uoV<7JH#yXSa$#~0C2l$PQ3FP>CCxeKye8|{-|J=F zYstF847McKgX>_HbpI`#W!Ij88EnZ6{+=k(A=g8DxwLSHrScbUUowEP>yT2vfta}ZXuFuJk zFVkdah-G{08F9jMqbG`Vvv{`(+n>lDwVZ4FCdt8%H16}1L*p=x*{+Jg);rtS_fEdc zAJOkq9k)l$L_5w8MfMYmcYVaITjIBlG+p{hl@B(5p9MoQWB)0gV?Q>!{g(Lbw~ln* zS>|}wN7Tr^C!O3Dpqrg)i+wpS>(s$U8thlN=96^amd>;NN;>x@JtmJ6iH}@r zt^ggXAl^iezm5I3Z`a?zPZY(Bb7uU_yd|6dRyHH)_=r2VNcZnZ=my^qH9&_Ipz}Lb z;kPP%$KxB|{C}m3_22#i9UrkB-vs&Qms#l>pmpL^@Ewu1MNkKckN8x-jAd)gHGU6Z zt%3E-*x%$YVn#l;V_Q1c0=O<_NjJXlku5Q$?+n4}te&+HFeu5lg$amzL z=w?W-FJPX`w}~RWHaAHe$c3)8WW9@MvEIa|pilZ<7w~7Q?95hsLoI`kJlZbd^_g0q zE%N)8_b)8r_c)UGx|!0!wq=#i;G2QZ2uRKhFoG?a;jGaUOFH;m(sVY-Oxdsle>?Lx zwH4p^kokLE*S9;EZ}>FtR>y6#%z4>=>LtD{=J9(Dr|-1K@%_+igJi8vntUfkz9)Q5 z@^!Lab*%u~`qDMNF_IrW(`0A1_7ZqcS@!TDZxs2TSoN=c{z?wz+^F)Oa4!M(6`XdH zFH>Y2?n`6eDcjNhckQ$OpRzrVSF&rqH>&(U#m_$ENHpmsnCYEXrNh!QnCCIiVuNk$ zv$Q|UrF?_G3Er|{7oWln!p9LTX;(!upk z*;C)d$fa$Ia~bCypU?1R@cV3r?4pXHb5aY?@ew0`iXEUgQ37;)fSxI`jr}uncS!t2 ztmuzEPl%C2uEDl%OK1Ktf+cz4SiR&qcFLt();O;@diG7(uh@$JJMo_WiLAO8o?s5d z-pyscSThv0{pw`WAUs1esy<_j>J%iuZ*ZDL0yUDHCiSx4mY5j)Q?#7ZIik`JM zdOVY-Jb!dE(>gWjC9&#X zYuqI>RkrapvK(<;nzj59_r#&zCUk@MB zI(fEFSrp6o-baoLMb{J7ulJ0UL!17Fsd#3~Hcfji^W-1O`vhv8dH9U$zmxMAbGCcT zw@9*HtVjI@TYDU4&?GZO_NU}_S)WJE(=H$Mk?WCfSqs(AF^pXbS(p$V^v*QuxVE7Bpav)8|B{b#yv zO*)js(tFc%=}-^sw{(2#efq>u?1{6D>wO|~Ti0*u3reu{4xR~YaNan^w!$Xv+*fj@ z+SZ-#K0c9dbCcBVL6L-|_dATh_D$w|$8YWIv#hFpvUPTtBFn53dq-2w$y1x1`g+R2 z@4TD5#m{-YvqxI%GnYQ;;|4J}+hjY}Ua6n#M<_-2Rf0XE<6 zHs9<5Z2aWRlsDu9+T-7XegPd{7bSs>e+%*$3u9wGtP^X-+C4E9d&N$1*Kj^Dt`FB~ z1%5vC^vpn?4{Z5wyn1F0JsX$sGrGsSVurN+Bx`!7Q0G(Vw_Ui6-@d8&6~X$jP8qKm zlAMh59HY(_^v^iVk`A_!EkPTgJ@f#53vy|*L|&J3?WWd@cQxWL3vEf@Y&(J_302?GGUs;B~#p8EbFZWoL?P$k&bQXsYy+^ZI*lw6tfkUgS6D zp7Y+!e*50Z8vEOFY0p0~6njNeJhOE_8SX99xz7Ub$qe@?WFNZoY5OT|gZ#A3Ezj+d z-zAUzoYRv%Vhx^TRUCM>Wq95_(RdCXl#7jz=W!P`!Fv#fXo(_y>K)9P1xbt?K*v|b zi1P>Xi52g2+CYgKu7}(r+xFqV#m?ND-J+jU7WqAvQ+ACnQ)QnpAIS4PeYP#BVI7%k z4%u7ho+4J9gOJ$vW!wH!lH>MOI*jn)2W+Sb>?Nqz5AYEq$6yjAHEj+1ZjJEtGl9eW1WE%CF5UcsI_*nUm|!xOPbV*?!W`dE0KW&bG633^@{8 z`bmE7o;uIv%#;m0w=*q0+lTZUTYeyB{gi&!biHqGEcs8)cVyFh^Tw3_WXqSSRoD1n z$j&U;P=5VQmu{x?%$A+cR==ZD>lL}*PxKz+Cim2H`>lAkPrax1=q0hVRxlD(I^?xS zpFxu}MLPJp-}c2TUybw2lk^^BUz#S};6p#9_9SS4Svbf3d`!TP1R zXkzPHAhDOII{yPY*zZ_Z>=m>nj=Vt(rYQP*1=<5=Y$y-*A@Q}KNCNeNzA{zz4bRt- zd7CDEYn|o;#7@rKt{hXOKe5>JZ{j(}HJ{{DE%KjYPk!3KOl;}E8r|?3X1SDm;%nk- zHRJkO`np=Sbj^o!*rMw1;}iUj_4u25@pp7k^mjS7uk}l1(tX8vo|uY( z$IraR!IsS$p0Ku=CHutjn%Yy3K3bCKrbvGWV`ffOFyB{mC-)U+U$1f<>qq|{L(^Ee zCNt=gPz2A3o~Y7c^EbU1qKT5g#t%ENjcgC(oE`1l-z(YE_^dnMb&%9=L6N*cd<0w4 zob||QL6L;hwtw>e4tmlZce_{8xi{(BNa{lkmfrPG48`7Ywk5v_{~a)OM);JjTtge{ z$Rd{hrnxcXH;$7Jw1pY$q~jxYVm~sI^Rj;{&oK9l*TX*YtRsn^IPRLKbUe$m#!}s% zaMsCoJLZP%_`3Ax7R^RsKfR?-A_)A8bJmpl9sQG1{0Wy@;;gRGNN!;hP(v z@5JDD8POG6`psqp=v6)-HU)X)Q*#OG8|)f=QGDbPIyn8`=x2Wm?AyH-ZNeL?@-nL#;%(88;0VUCOcDP zpG@6nz&)3#vX}1JlX3XS%TVLA@%=8jrgdtbdiFVnJm>Md_PGw&nI`*Fww|#Wp1C(V z&*B8nb@Sx+bN`f8ZJXYwynpk%dBiz^^FbF=;3KEUyP9_M0{)YnN9?qVe2jtG20Q!E z$)i1>L(ZwnVVnlrZ24Qvi#d5-CH%B;93SA@f$Jc9K#UyLr;4Ss7NA2HQxxSuJHSVr zT+^ikw(D`Wf+jd?alR`F&R{SC+vJc7ThM+9YSYII(D4sJj-gFv$~NRIan^FpJl~P# zZJj*&r9Gfeu|uNcGuTaaXum|h4qOlH6Z|j(+m`lwFTIj2_T8V5L$%DGsF!P$xG!$f z^Zln~USI23(tozUtJD5>xUCJo_J)zJ+mpVPV|czoPb}$A`1#%Re1FA^I1=`Ss&`Lj z#Jg!pnxc22>7gUvF_m*;haYM3*#mtfmUOtCb$;sWzchaF)ZTr*l4o1zjx_dHy7Rv) zbIqo{!0YI>otNuVRTS zh40O$`}fiROJf&X*JK9RL#&|cZ-Sn{&OX~n+T^yb@k+XVPkm(FaU}kkDEj;63FpzK zKHzaN=1h|fJFQEXo|zFNCu4t){32-VSaPFC({phZZ?1`E5?6*$*iLDqAw{GdR z$+G_Xd;OO5R}@_f;JU!Xj_cSYVI)pF$FfeI*{YebKj)*b;Pl&WQPZ}i{kae30XbDpj{D5XCQ;! zqaHc1WzX;vGx!X?A~;WRp5u&Vnsm-xgL9wGVVuV}mlZ*iS$E#h{xM7X`8?%0 zdn4!F;`U9oKk@S%$@6_AiN1y38_1qm(lhxzhu&ViD;-0gl#6f5cVo+MYSmaT5Wo5D z8)|dn7?N0)Id;=M9?M(K@A^C^_ko;=CfyY2#@8!f$5a0#hc@+KX|EXej%m{0v9&ip zg*{9zGVA!V-*L;Xc&5lU+;30#p8ScNzcj`~lMeWHQ2E>Pg5yZ?p&i)2c z>?hLos%iu4lGp0gTdZ#hY5SBj>TJn(xbIJ7uH`;j^o8xXV_7Tj(IzIB(1Fl64a*_C!=$aESH|te85GSt*+RdOyF3v_`#(N04FOV|?`9RxG=*lxw z`c5#OcQ~K6{X>fK{xqKVJDL0SI-PAstmP|NKggjyc_+8)=j+>aZ8Jr-@!6s49Pvcu zOn>0N0q|SDNw+&=_TSp)7gq7yB>AUT8<$dU(!#!URkzS*D0B?ADR8=JFvgW z&e(6ck2a_6yjQYCAIzzW!QcO)ghYoSi08RGf9iLM9-z}U=a=YXOR_gtz`cP!V$Twv zkxQGd{1 zuPI^3l5&e+l#`rU2WtNKk$>A-mgT4G4w0cW3{Skg0`kxrU?GuV<) zBGA&={m zeCLelt4U_^HSl#o_JbmuYdI2Cy01BM22Iiw>5%tP-eXmJ%=-_?etbn!{G`bDyUcp8 zf!}oLrb_4D<9=*GzOQ|scirnn@H`laDxGKY3I2@REct<2KwX$(2Oh^M8RMr^<$eeH zKJz`E@5*`1hxYdUQ?jX#8%6#nd@YdupvpGBwnNt$T2LgX&M&|@25b{E_7SfMY?ukR zxz(oaigOp|2f)t0CVR*E28nI#=lo%2%C%0ch^4a%=M^M25X;z4P8IpQ<8zOt&p)n7 z8+6-eeQa{7phgoVSe(^3>xINmd&Tv1(lh6K!|#`QywA35!#mWvrQfUBwr=U1EXVP_U&+q;nRnXc-f$i<>&W^*&iStX zRP&YJYhyW&b;oWmD2n}rya)3BK(Dc1kQwi-S8}91dCMb?-+5Wb*FK;qCzJ0f`?Igd zeVBdLiJ@ECkKFY=obN{h8+K|tlkWIUx(%@rEXjB9cL_O`IUnCij*rw=rAA_>alg{1 z@-j^}lwj#z>v6x0*vsCt?XtJYf&B*GSJb8t<9->#47TJGdCutL!ji9OEi+xVnbOUc z4*Xp?g2msJg1$&6J+mvTx*^p4y|A0DTMA6uidBbs=9Lbgmhker&g= zR!h*|6!cvL^JNWotYeep9^ihcg8PU2sEHz`p6Ad5+gUn>#7{e*#yHrrjr+5m<%sr0 z@}x!VCWm4fa%^wuK))yW$t!|6RI&8VxIwH7@{HTrM&dhZ@|_I%%$A-x$9b+9?b-o8 zL#$lzIB&7jM~%!Gf12x=hfSTE|CP^U&9cWeu&jqou472zCSTK`YiwGaBX+#sd7t;7 zNS+Xz--I@q5_R+61F!UP{4G&sKk@Xp5BX;u>~-uAQ#t&;lc6pB zQpb}1_;1Y@-yGxnU*Ve}|J}LrzUrv1*Md0!HnJs~zPtLQ9W22(@smH5Thkh{RxmGY z`GDunc!1bDT<4Uz?oyqT{QT~E@5}_YsnXqcq;a;S<981EHykI|H0kCXzir1)IZ~ah zpLy6hzGQ>P*wb7;)u-B+Qnd#J=atNmo!PRX{`yzCY*-1-LzyN!Gi7JCY^XY4WjJ%K zprmu!lny)Lvp8q;m8kk|Wx8}TrQay&-}kl9k(r!x({FY0^+c6^Bai)@OOEBXx$b;1 zFJiax;%ndJsgC>im};759zQzsgC@9^T+<#DN#Odk4>DDD#`_03gC+^(1$obUAJ=1l zWACc|UigXM81lobSW`SxWOHwFFLR$a!Lwur`MF|$SF8x$W3V1@yegitf5}fB>X|8h zCm2s=$j&U;P&IyF9=}6T`=7uVXy@@{-7(j7&ZlyuIp5mkcx5df$m_~FpEUViQ4}}6 zcG&1AUA|Y4Pu%h;w<-s=&I%*W7DaM$ez-uaC9pHJ>7oWpXA$d2>?hw)?4-#DTjw0$ ze8icDv(6Cp0AGn5@@eauOZo{h@}{7j>EYXwEo!)Jo==_=aq2-6w1p{pNOagQplxn% zd&s{8GJ_A!ab>%wYhpjX?020k$#ve8p071JzE_<2|1P^-_GtE5|IT<`&GS`^KF+v( zS$Ztw+JCmg?gv%&cld7mydOT%pT=@)`;>e0pW{C5sdae5Ynx?@Ju=c>!H50CR?PU> zY1!kMi(Cmm(_1{>ktb6=*ay68Zocy!)RlK*$^R#K_ilCEGWU5KSN7%Hf0v&7&w5So z_S?FjK5VzS#fIWQ%=#&tVwocQQ>OOWr?4-fKCra6pJGpb+T0i~?x9<(9r>KGr{FBj z89IY+23rz1&yU~4HS*sEXU!b5e&%4Wz_BVDc8vFwQ~nkd$tRqXCAllk}e&h9Eeb#~h#_RF7-GZ&!9`56#1UWYr|SSQ5Ac| zPHT@oA87JJj&IqJ>*sN-U*$S}&vT~vTi@~l>*F=TCf*P55d-I2j#%e+()mx)_2~<` zC;@Y2?o(^f1bdzRUxR(zJ3}_U6a1Moe%edIcxtp(ejk zxi+$4>-*n~?}A<51%Ypbd>h1HiR;_pS3dozCi7vQ%y);x&U0m+jB~3^6O60M#>cpJ z)G2C5TbKu1Hf!s(UebAv=I1NE@gda7&~6Doqp53dK4SJ~NsSvh=hXY9FLGRGs3x=o z>+z19k4}wGSjsc!SZ{V7f7YkwaHCZ19l<$fh#hdg=?^UVzl+aK$QDdV*omt18;rz~ zUUY^A&eJoor5n!TBeA6WJl}Pe&rI1*+`fICxvo{y_b+t%%;fPj#XQzLhaU6#ROYt( zm_L1h`(0@~9;?T0Ib&^*=3E2&Z!+he@gBA7u{Mm4aRPJjoSO6!aNSLpZmM)(-&lGN z4f#xy4z`iDktLYg&!(q6U!|Muzv<3upi{6j;PKJDdzXZ7* zdbX)m6WGKJb`>kmG@NZXcX2-AzvXJ2m2_q@C48Krh!5rUIM;C|GFv|C5Ce2-FF}9w zd&6UKo^z-2-@$s2V~O4qRXUtL`Z8yZ^X<=)HbXSAH9v!GUzYY;4&^Q3KDx#>FZeoJ zl0&ZZ?SCa(?3v$@)ZgkCavl5Z&-PFC8U5Va-(vXex7;skoH5Vm^kdJUOPV4bxPDI% zcm64fWu9uh^S`PwG&Ym#;Cp4eW`8k$$EI>%Cy5STQ_G?KbF$KOz5JFc$Zfb^DO`KPAsK>+jmr&rf()m$=9APL9j< zw67_4QsjF_-fP~2$oc`^>%S|{d*!G0th#qDlwVq#U`Rf3=GZQ3^9fUZzsn=%Mwj1I z={J_n#!n3XPKdbmCL6Z)*bKU45lefc3a)<>B|yiw1#$Aorya~UY{h|oJ&qOQvdnYy zoGqv3o?$&(FeJCA`kSo>i@)FWcVK&9%75~dd-LbEo4+?j)!(K+!R@KpLq)7{TdZVS-yli$S>-uEr;ohfN{(j7mgb5H5K688&z0sTS^7}L~R zv2I+G%5Nk3od^k2df|5?ep}-AB zVH|APC;7bIb$(w#(YL1z-=bz>^KI%N$I)9*Bn|avf<9|vX^b#}CTX_j{axG^ncLbw zQeR6tcsz`41|GlXG&Co(rQhJY9`K#ddX~2~PrecTwOC8W#rUdV%$`S+ZtO$i^Ze(r zf1d0fd#*@kyf@E1YrB4Pue;q%y4J25e=sFmP$Yr-vT|>7Z|m8-6;JZ=erxyEv3S^ZH*hO^RA2rTd+%Mc0Q)i^5 zd!q^N5zb9Te&XcPhPKq$!ZouT>LascXBgWF@zeLp?ok72p7L|wUHw$Xr?EHYq4}61 z8$awfkkbNmV_%UkgMWxE=-)7&S2*t`-EOJI$*b6z`(2xL{%)J6{2pV^()z8|%Vk^RJN-sc+DV_#u=Z=I68_QZE`pKAQB zzx~qvm+(2?d~I>$$#t zmbq<9bxY`$j$!wB?(mEOo+$vM=lOroWSb)0Z&6UPR4!J-(+CvvbEdAYXu<=i^1#M`*0@j;#pD!Q| zZbN%hBgXaQ`VO%Z-Xq>WEA|o+yeDn@Ea1K5BbR(+$2w-N?C1H4HVOCAP+I-p!+@xxdSOisu@qp4;pg7h`42E8+Qh?o;cK zDe=7y$+x?cA)ndO!QTk^ZfNXtj@z_Uvq*Q}jHd>SmvLwE9PDq(VXn-*9~9Z(J(=Yh z8?kSZP!s#I|8v}JPMLGEe&%84xci&xm;1Eoe&!j?`-pEEMeh*4=b`gX;JpM>dgUD< z|5lv5uXhOVmH0kb^^I`-hwK3R>pLOu8nxrSSJchkT69NoW(dF)i{g!JjR)bB-K$!z`Al3bJh)<>6TcIlZS`xPFKgb4T+5PL)-CbbpQZhloToWQ=X14n{G6?yAP&xdlH-@If8ZS3PttkRpFxv^ za=|gnoHKPUennRts#tOE+_9(FOYEPeJpkTI_@~%{eL4hf4Yf|_BQs?ikJsZz+J`*- zwufS{1J{>g%5JeHNHe9IEgfvPl=q}NH%sRhwKsXpd2Th<0J&q&4at@$ z(vA1`Df@9ReAlkg9{CBrFS8tR-y)mfUN+c3Y+ZQOr}Jr>X|kz5MG+(3FGxRIdc?^i z{}XZ_=qq51)-8!=?0+T6cl+&hIr%n~FPv2mh7?1~333fk$C$8zT&{5! z-W$mFu(!7SP_;*meKXpew*5`<4E`NZYlxL7x|cqoD{qSZ0^%oKJ}82I8B1Y2S`*f| z2CTW^y0AZ&es_g09iMVoPp>8Gi4OR`;>u%>mw@&=ez&dsrYn#`zu!)M|Er?&9WZG8 z_AAI+g5Q0IYEbiQTp9yor*Gx~llcU!F^mK@@g_=uK7%cpagDQ%YcAC@tm}7iTjVEg zbN)^DwY|mgjSqNgoBgg+qaG5xcD60YL%;9hOYKZvAN!EEIXkz!WIY zE-d*n`3z;f|I!|Mz-O{7+i}(#VI!Z|ihV-Ww<1G6`nz@``fZY#BKw~p_wha&pZ1$f~_-M$+v=;(x`8IPy~p z&(ENzXW^7?w)7i3pYiAC^iTAmI!~;KAwS`KZ=G$1>a^6Kb!5i#ooOAqbSQz>cxb(A zx;FMZFH72ghpBwn!ebq2Etv0;o#&1H1Z|;(w2pm)et|KZborhrioL>Xbjnw4Ud7Hj zPxh-CpTsy%`)tiQ8^#64ZgIz4=Mzc4H)!iVn#S>ttuyUArgESsigZ})H!*@GzwJ2>ZP|0$g}v@R>bnt3g!^q=7NRqZqOKgs!` zKmN}Agj^%{Q&jsT$Fn|_Yr9BaX?@U7ANF@~O}Fc+@x)Z@WXqS~Z}F3-_)zW%ZONY( zT!);q9X5V&9yPis!P1%;`;hhx#WPj5@xGn0&p(w-`R^#oIq|(_*>(RtQ56I3-Ejdw zZP$m8o2jYx)U%?Brg!OiPQpiwoT5HkjKy-Kv2E#3&>!S6+;rzzo;gc3;9T1$JLkIX zJK2wMR@o=c$$sW;`?d}vtyK#>OY1q$Ise0ucR-yCwO4|E-8V6)g7(PFS(98?2li)q=3u+;eyoB0 zSvm)apW0P0*A|qd<0G~OdE_r~*4(Pa_2Bxf1Fogv+L|f-73>k>-Yf0cOZFp)jStw$ z0rorh!V=tr?eZ+$@@3{F2cL8Ne14LnYK($7ZD`vPMLJ_*d`*;)=)hW3!5XvPJzYaz z7kv3zklP}^5Yu(+`t5e;TV4y$ZS$KhzvJ>7ZV`)ruMuE(u?4^LE>V?VqCLL}FF_w) zeW^dj%RGkI-+=rKZGFvHqs)rycuLkY!&<+x9V5@VmX0Cune0c;W5{ECr&rD46>h&C z=8E4r$oc^5xDIk|u5r_+>fT`P_|NsgZo!a*9h~<`ubP9vZ;5Uj-%nU-cT#?p|8*9# z-sCe=`i(9BpWs$I`&t?cdij8}%_sI$T_D%`O+Ll#x4gBr@0PRGZ>H+ZY=(4L2PJ(w zoYGHvijxcW&B))9eB*-=EXk^GfL+Xh?}N}1*u=@DJvA94W37VuoD$y*nj{pl>hqqi ziuqpgz0-8>6v4g8ecS}^1=zeBWY@U&$>E*^e9m_ce(LT7eY#)kJ;t~sPnd(}K`r{= z{m=8i>zq&p?}NtqLGK2>@9}=&9m4yBcS{ejr>MMF1bJWYBi>WgP|cz_HucdpcGjT@ z`rQZkpe2fQ=FAvb1L^=a>qAW>_6X4NPeJ=ku6@&U?x|BR<36$v{cQJS-`btNf70$6 z>nBv@y~5YRIoM{T>vGeZ;u-FTe~PKvuQ>aCH7?i5ZS1q`>hHT0wRr{g97lh`IY{np z7zz8bB%bZ86Z@2{d!GBAzd3lGAfMowb3)r0WW8z(kjK$we+Sw>vDN2|AwRT4kq$HF zSS6tge8k8f-(V~EPr)2Nt#w-u+CKT;)g8y2o$q?KvvixLwotx-+aSkc(!w1*7JS;q&GbizQa`hJGOE%RnPTL7?F2M+S-S1 z@ck}JHO%kSs;ZOcj6T0fQ~ndV){gb1&!;i3G_R~b<>Rjj_hCu@=tyWuw~goid~^XP1Jf`5t{&lB1Lb>1J&$;L`=8rU8`VGgCw|J5N z318E}b3d_l{ebHTO%%bk?wp51THpG+sqwc{F!lRU=Qk+9HwqYhtI%&tQ*^<13_zcO zeU^@G#hcn^@J&HZ35kz>mY{!_0b^r~BeA6e^U2^NuL!qCVnY)nf&Jtwisc%vL;Eef zmP=zWJ*{untNgS9+P455A93=zR%XdI__pABbM3ufX6!TD-Y?_;`9Rx~D*uV|mtxeS z?!Lf%FvJqvZ#|fj;Ag1qn{-Z=t!iz=2)5+y+3vU{dDe?``m2KR0b^}~xlB<6b7cNg zYs1=e9k?D)<9e|E#F~6xdsF*#@!PCGvVXmYr*!C|665z-ey0tVWRq_T+LE^gb=h0= zLBI6b()DJnjG6I!4qG(&p{7#>j zTzr{a!+y@!rb&N=^G->dk=W9q#&^V`Z+p4U;buEZ}NTp?{ezj z-8H_u>DfI65_q-`(e)gks`vH$W(*on(Y@R1U*lYoj2RgJJIGtnmivDS>M_^Gb6tHf z$D(-@_4_pr#;3UeW5w4L`wk_Ai=U7v0^9+e+>({eKI?q~SB*9}t zCpHsX`a5p@;iKIxHXg>9bDe`U#O+7_3Ge3eJMo#;%{uk0pRy}Gq^5c|{Y7d6|9 zvHUw@%$fd>C-_Z~{t4$byXVc0tP^{J zbMrfP)jgkZepg#q7jAy%SLMT2%^_N%NHD-qYvbXlr6g`1` zW6R&-{74iRt{0c*7JReCW=@(?{rZE^czcl+R&Cd)LRGc&(h>TcddJ-%6`SxH8Vpx zw1Xmh>KcQuf0YjWEj5BBx#M>Q@%0@;zk%_+1Gav9f)UdGuGkW^;kPN; zZ$YgQpc{PFk=X0t>o+ishu^tiCbo3O?Kv#zC%3th-xAa@_LH-O+h>XYgm@S80b=A( z7if=7KG%rrHWJwQ0euVh0{deL_EFw<*u=;i0lLB0L=p4}J;-Aq|JKh`{Vja2`2JbC zceuBDFq3ZIPb77=;5qDPG7=kqu9@>z zZTl@9M^fhYqWbOa~t$``Hr9MUfC~-;4 zA?F*m;)ebE6UNaF`=>0mJ;~=UpUaT*0mqz6-s!9I0e)j2a$UxeeK{AsALMv$m-TV1 zQ-QVm4E%88ruc@)cDGe|Ia?YzkPjElwA65Xz+DC?|Cm2jf;0G z*M@7r+K>9zIu*@@`7k%+D~jS~sr?XDP;aU3*Ba=Z@pYci`=s%niuclx&ijRNbd7_3 zL7NtPfI6;+ZdhCD1Gz=e&YU*>E@}dsJkz4qlr&p<27fy!vVlIVTjIOpzk1x2yjrobyk}r#^4~sxf|wpBcy}*h8Ptl=nnY>IY|DJHpSshDfsb`;KmQDGY zt!Laxj-!tYIfwW=`&Q%M>C5A@e$M+R_Kd$jviwe++%D_isgZN5uEjf?pYukvL;e)% zy=qJRma{JH@MXVs`){)7nQ+6;31ZeyITd@yjvULUeB$4sy?Exoiw(5}@+_@m8+=cE zXRNne>Yjf0h2Q#__vAloVb6y(#h>Z2-%*rf@;(}|4{O?wIsQs_y`Red!MH!oVGL*P z&6ji9FI^`w680fW!v2wRt#A2G97DELr&okKZewXx2(L6jS%PHTDEkA2vhV&at{*%hzf}$VT|6NIaw>fLt zb{t9mNHpm;_Az(tlX>La?5>>5EoWcqMopZ{m7~r>#+z&XXMH^&ExL-@hY+LqYuG#)XuWHY_f!DH0 zF9BJ0LfM zpLhn}P#)KF3igHfh$X)4-?D*y1$htVz2>~3+#=|MeX`UaeIfxL5TAlv+HB!I+|N>f zz`er#G=nYaXWux^8Q6}IHxk%A@_F9+nTwBD6D2?&Vhie#lc8;Uz;$UyjUr~O9dl(J zx~}`wzHWTC)3>}HRLRBnJ+bw@4~dVQ61mPH4;^}vQ#y6If7t8nZ}u5`forj~?n~of zT#V22TN*F)0DlI*?X7kh`;j+4ZBKHWGnVa~_fF4!kC@+0wj=h`&VJXR-FKkfEg%04 za&P{dZ!6d9;q_Wt=XbC_KjmqUPt`ct@_mQg{%lu&X^0OD`C(l+{m!+lQPcJ*9Y5uc zxy_h&lZ2AU>+b7OFZWEomhEAG;4i-k&zw)medRVio?BItXGRk|KX`r@o+CWd1+KP8L!nuF%` z^&I8d%)jgV4~<#h{Fc7s4Z-)mt?zpC1IRP9qkU1WEh_(}F#iAg2K@xqiNTCK^bPzw zK0WWb7F;XVi19P8738_2+n=R#@Qt8KZq+J+Ye{{sbJ6u~oSoF~(EEmW&eprfENsPj z=g_9c^_r@4gFe9hojUduEm5RH_My)YIon~E2d2i)x-t*5v>rDce~lNYryT0{ zPvZL2v~G#-9Zfl~b)A30@3f`A`GT(4Cluw~_&O`481{+VIXBBwKihs{5A_N41DyM* zJZo;Abz&y4&pg}i$99&kW$BphKOtv~_mf)Wnosh{asGcc*)+B%UX7JD@8VB&pZrBN zP0q3JpOTzEUUBQ!`Hr<0>v+c821y;;pGm)qU+ zep%xx-={q7o74Yky;phEe2Vv&2XX6``2G~f8xJh`fw*Or4pX(D zi6T~P~MPp z+y*%lTly0@uPHB6WZ&SL+VA`sZINb6&oK8B*2FC7tksFvaw>LXzwi_D^|r)!w(0T} z!5F|}I;G<`Np21H!`#U+OFCdzvDIdZB3NVYAE*iLtyggWHNkyZdEe=|$n$H8et^$x z>EzxZrwE^+hR#4Yd^YN-uG^Ad1-}=a;CEZ{x9~WI#l|0K9ZT(xUV>R|V zwH9rup<3*hs{X&mq4DsZ=@;-X)+cgqdHB9dPkl}4Cj0A~x*pd%IW+cnP{aM5IyQ`; zNoKaLWhTe6K2nV*eJSU}<9d=kt)=yu))bvRXxNkAVJJVdWq$(a8biMIC)t&sS+c)F zZfm`!F?^yk&XH)+OTz2wb*`~T&ufM~f+l&A_Y3+Ay5tSw_GjHbWcf{&o(Djkn{*sG z4xI1tOw~Bq;X}TI^~yOj+7?Or-toRbV)v*?yCIhFwc%RLbPsS%&5#Z|u$^U7j9T== zGqm#GYVhy)^i6Q4e+RhqUC(ssRq)+!B%1UmoVS%jTWT!P;#;BrH%9q4fu^i7n+5@$qilX}TLw}9;74I_xZ|XQN%X~lIbm!h;_~tih%D+LK>@Tu^m&LQ;e-mB(o>ckF8T*M% zt*>XTp0_O+lK9+@ou(X%}+?Y`q{YHy14lc(|0 zhTJUCGedS}%YH@9MbEkUKK+T_)3b{9wvj(&sqOFbyYB?rGloydq8kbhWQ5P73GdEh!z`i(7rhB;=KH>`j)>0$<3(wu+G4E=2b zRp0!+!%`dL8n&}^%(A7wr938N_MbhLImc}* z>!oje?(dYu+!nbm&?jTaOxb|XdX`&$IM)$-Tv+m**Bjr>?>xtc@><}2UyX(Q6SsdS zZ|&%-h^4iwVy1hjNI&np9`_;=s@Qt>jDYvSjAuK~FF&hV==k#UjySPfoILW$$>6*B za}IhFTW75{mF?Yenu5?E?O%*w%QA$7eg& zvhOBy9y)zJu@pOTZc~iP_3SbWz=41PaKU=w%#O%exkO_RR)Ei6C>{3rI)b_vEe6U@ulPY%=<5T{)e zL+k_A7P?pg>wd#=B=IKp8>Zrh+{(QrR#0N!Oi9B&B0eIQd~|#{e=F|>`_X$d%N2VV zUrXc~_=zvk5qsrpuj=>HrQc6Czw7cFFTeT9SG0%Or&D{2Jyp~GDYOr$B68#lb@l1Yx@~ms32=004 zfo&x5oP*xPJlL{N&K%qAsFB;{@jmG}AAJT*k}-ch8}&?^dfoxgx*_n_G|w&_+xcC& zOV3H3t2{G#Cj&Y@o}aEoJ;2WWwCJP6m^=r@`PG+dRej$oJgfD6aOvCK;NSW24Nu?l zX3!*y=zQxF`~Og$XbEiCf*f+qk`C0M)>nP?S9wq9z3??pjrD81nwRHX7@yu5VXT_g@nr9vN^FOJ3*CX3iW!)B2sEGSGW?%}YU<#&S3Z~qz z)yywe9yaQ|SB)|EKKDn9d?yG=NY%CG8mIrXXUqZlgln8@`;^2_ntaBw9b^7fx<1R6 z`k#7H>?bTeBYwh9djGB5sX3mUz0de7yQ|;7K~>+3$H=}v(N{cwk(oc~`Kq=*tkXQs z$9XPJyT$c-`k(Bxf3~0I^b>!5I8Q(6;oNzw#cAhb^g6tgxd#1R{!ijV{ch|BKe?}B zw_ZQ-xi|6^^vt=oU)AIo`L4O?_BG{wN_ogV{o^6;9Ou(K*OC7T&i|<_$Gov`oY#D| z`>}`Q_tgHSeOSzpwvpxHvwu|`cTgo^>pk$slply$KV^=gXU^QFIFt`K^ISVs`-+|7 z=)f`hn3r^5ji3ipa*L+FFEWF_H^e&dx1X-RVa$|%;#mFgJEF(&xX5@&d-Hg70bk=5%5|l02ZsU_0lmwN3nnO%}aKIY53dw`Cg z9Qpt~VG8zJ_T3R|NvKshf<3x?Dd%9yzCrGa+AdiI{pjBkjQu@FT?1UF{LcLS9Z=r_ z`6kG3zl-00vH1;HIYrkJ*ACYT=juE*9~0v+-WH6{JUo{?A99>W?hWEC^3ctGk?$PK zoNFHvf3~yU6w8#uCWh|@YkR`loaCId5B)R5b3VPWkx&EMr<{ss=$HHdMjz?{`;bev z;e6lVdN5OZhHLnVrr0Z<>TZ7eoc^27IeCt!-qZ1%dQ}X(CdhHnWS?xEGvK`SM3HXT zSIm%pQrUMtWNWX2oNr&2Q#qN&9v84LRKYW)1Va*E7sN}-&wYNPZ}l+LSB+O&^I$HpgKt02cxtp?^Z)7D_3e)Dgj?V8X25s768O8HW&ZX@{=PuJroLVM3-i`^ z95ZXe^|A!l`^{0&_5e@cz}D$A|==@W9zP5-3FPjW`g3u&mgjy&xgv))n- z`VHsqho1P!u{?F`Oq2a7MY(2bfBYM`-znRhd7b&6*!27)&pCg3d_AoZ`cJ4SAN`55 z=TqLl;XlXz)Sl_s(4X*mxXGuQPy9WNf9llZs|RP^Er*(0-kFTf+V}7|ui{rO+%yyQJWtroDL#Owx5B?`|J~`R`M5o4_`A>fG z$^*CSJKqC0y&m`9(%%J{`X#&m2ka+u9{LEHWM=DqFH;qV?7Qhxbx*X1cgP~YS$h96 z_TB1cjI$+A-p!Nv5KEM=f9R4k*pf|u7r+RXB-HdhFMp?*@MGj<2Y5pI5Sf{WGsA z`NUT2wuYzgUEYZ6VO_2Z&MW74Ca^PAwy}RkKG#r7oY$1|8Jl@P3z)Z=u^vTor8Q-Z zr)Z)G?zb+s{?33AG|3{S_Oc#S$rEBzkW0-7(4h$88GOV>0vmtM>#94c@(t~YP53tw zUAoR%2UPaLPd z3+Bi=^~98p4~c*2x1f>0hN4);e#;RxRg&*iQ?!8Z4Y2rKO`to5#P0G}vGrTo5PZu3 zZ2ZJ;IYn_pK8%Acn;z={_a!Gooon&i9`d&U9e=KS(#boa9`L(v#XP-EQ|n{4bk-PH z^Cj|m;T+C`ExYO5kIQvhbzK-=!`NBp+JPxby5@X+ost}KfjU#A)0cSwHZk`cmuuL* ztyqmcjD5`DBj!A$bBSfBS*aEqs&J2X^ujJ;##}vTZ2UkDY(YQvTlU^9YJFxX#-0uA z-(8&h_)rX%Am(_*W(>`vac}CnXk0`5o-2yUR`6{xaHz=&!&Fk!j=ziG5efz%Dmpzd;RBHK(2(ZiT0)AeF1rn zqn{ANXX*Hgv25QYHRcw#&%G?WdS|Nab1kwxVm+HAl*D$^I(X zC)qEj_RgVwbn>lE^u(6FVvi*cifW(*_9O8PQPrFNfWE^|EXRpqQ(Fb|V0^wm^1t_J z`fm)YzU?*rf00hU^Bv&Zg7^^RPBB!+_rRiF^kp7{<7XXojd6W)oz8%3oNIlEoxsLl zLSk>hJp|lW+;>y(H_eK_ZLo=v@0ulh%TG>;zpecDlcoPoBA%hfb=mKHWcH)GW~jCb z#x~6Fq{(+uieQ~tZ|+%No16pIo;Z2qL$>nXaqH!pTP*w9n|Tn= zIkr!^b!;ab?-O0I8-+Fcm*HIegrWRTsF7#s8tfZ+5Bo&F_4tWzJj~;%$0u{T<;>WB zPub)*CF%I)|ABa}zm+A95o!HZuNnE4uX10-N{sg#>1Xjxc0Eu2gsS?#1@rjRUi5g? z$MGzeYBH>Gw$XcV`myslaXkC7?tJGVyRbxu`U2;ysQW~^CQItDvqb-td|a>e7WcqA zIhAM5T>F12cj|f861&0PdGr6)H)9;+Ck*9fn(UJzpE+aM9!Ia6y_I8b@lSm1GDrJ9 zsV9g0r(9ycp{UMGUE4Q$+y}_|f~CJVo}kXLp**PRopV(Af+cy2HTh3=dRIJSpV+y6 zs+Z}~pr3?@w*dr>pmkDZlYLAzQ2& z(hTWK~?`J8f&SK{4u;dqO0fm8Ctv7#9L8NUdQk>jsoY3+w-f_uXENSDt2 zHN@7lW{Q?5(lf-jsLD6kLo`tm*!XuybbKY4?1$1ViNQ$hq-Q^Qrbq|+Kz~71Y_UHc z;HPc|?&&ecWjyCtx;9IC-l*~e^B4)w%W-P0TjGOx!Lb^3ww=pd+Y5-3OMTP4I?ow? z>k-Hz8{a#~Gp?_ypZqo@nkZuFwc&2J0N!??hC;=FFbHR)?H zORfv(!IZq=IFh)pyP@mriLID%PLr<$9>+GZk=W9W{Vi&-*{`}-0l7sm#~N^~9D9pv zpKF*k!6t_}(2H6?hbD-TM-6MtTC?_?2VYy9k9Ii+tUc@AnTO`b{>*;OzFoflckmH| zD)N3#oo$ZQbMi41&2tOn_c3W)<}`Hex42iTY{+pcgvzfIh_z(2Jn=2+$3& zGhP)3_VXE_n>{TBCqs(%+>x+kbTrG5CH7>fNZntHv1vEJq6xbu-2a-L#2FYDBNLRFq&&oTI{ zBWH?bzjeovH=Ot1A#da3^Vm=E>?ar7vW1O&V!nvE{$0*fZ~KeJc*TlmiDj4le6JJZn`G9kt;y?B8%Q*Pn z;l1hHkKWaN63@Lq(O>nPm*d0N5>GkVPky%F>HCs@^SMt;^ZAYLK9=;bj>P^6CGwF^ zoY#9#_g2>H!+qxWb<3gm?@W^oh4*s3pWooUed!&3*8j?uuYUalx|u1rNjKSVeMjAC z+mCdtMXw?WOYd;d6IFU<>z(Nd@~Cw``p*|^#eicT!IFfc_w{}-Wy2Ot@Bf)08`g!Y z?=;X8_Br46mM!|(w#09k#|iVxblE3GKI7x{F=zYKM_Q+!=v8$mXZ?zGe>XV>rb;(Ud+o_cKI=&9D|Gv^pO|}l zy!m2&&Oz3|J&-VhCYj0pt^Iz+v!FIIC=SWOf$^}FB+{uA=Y!Ilm5^*B@d3G-vF zTXdbf6%?IgXkrFi5^7uvToXmthT&R)d2sq|BTIlj#THHb0@7f^2y8o!oF782>*<$u z>=igh-y*p7S&Q#=(mJx-V*efqNqPQe&Ssq=|OLD z47t{k*i8(vg^wfa_Pvti!w~Ha=G8a(Ut ztm4^K`ELRR`V>6JGF5p7KRGQVHnAZV{o*~P(O>YaWXvs?H}lWnv!CPRSU1P1`-1y) zRQ3P*2lOo;_iPD?z2s+4Y1(_n8@A%cxlKM}-;TVhzrmnC;5g3vIX&>^W$oG!3 z#`e29$JxuQXX!mI`_P~A&%9T*dt17%?I+n|uf55t7!b35${e$9dFDRZOV5EPhGOq% z%7L6`-%q6La}7S*j%S}^|C4g-c?!(I`b~Z^#}Vu3+}!8v?;O`yuH#(TZ%O=w7(S$R z?99{kV}E~fE$7%&-VNVh#H?H9eEU9?pY}V)$hP%Q$NVJE^K$P`WKV1SD*w|w_aOhK z+jo<>CdcuAN`1)1f0D1oclt=za@L#tH}ZXEed?ZuE{fpYnRoO|mA(CbE!hO`+enyV zC&;IM3BNmLxvTOIhHTTMgY9?H`L4-!QJ)+0cn7SqGd*l%X3BOy#u;J<`53d_ztlsl z!0+9b?RZC@ypso8(tisK{ViaYbW`-*26|xI(s`~uWmT^XW4^+3%d)A)6zRXkYlLLY zjQzHeHN~?(>q|L5Az?7h;XWz}{!ZHQ{_JPT(w>Ok1b?TH1L#1$ z!6pVP>DW~`Z>lEKqCc_(jN32!F}@RWKf%3`jP=BP5u+9uFU#y_el^(I`>o?Q_7Oih zW8-*!V7rD~dM?2nnwVk-=+Fc?2SDH)zKnI_xMBA!cK@cR(I z75O)${M!+~9~ts8_-jCo>+D0?Z;5Y+mB2nBUNkpgj#F&G{4@B7bx|(lajbhR=F~(< zV8aZyWYt`Nx)t+cyv&phTQI*N)&rbF&fOH8Th8?sTnAha>!8H7g3OR-yTvtyoG;jl zW%52?KfX_JK5`y?@zG<5mB7YNei0lm$KPTds%&6A3TvV@VlBO&d5sqccZ7_+H%F+c6YTd+SH_Uk3Qhg;$!)&zAu?cwxdE`VMlPTo$~Ms`6i zpl?Bc#sPGEQxHdDJEwoie_>vHEA{p9+@$kHjE`(T;3=PZzvA5(x%SM>u_t}L8K1h` zH~0CZHs|L)Bi3zK2fPxvvhKbNaFCk&FE&us_`x>|253woloVXNvU9)E;+(z3(LF zy^7_$e^d7yYV6aCXF%vT>HJg9%NnpoH$T2yUyt#aL*x0N=Y+wAAy$G~dd&;?$)(PH zET?*#?CbLL9#IAF7Dezb!aEB8|D%n6_fY@;q2K@3tp2}5`Trp;!IZ>5iIW%K5~+o9 zfNu->Ll4mH$M-#lc(*A$I|cLHg7x4yht_%rEv`>|+)v!Y>hW&cf*i$BY= z-m#}V`(M?aV-Jn_j`om)Kl857Cvp0bhs@yn4O?~aNv(4~&2MRrMSAAbSk6PwJwDa9 z_P|e=k%xRoSI)mp(HKwU=iI5Y@Bfyn+Kl_)L(j3F=+vI|_I)DjAB_E*TArVoS3S=8 zKjofzwpXl;Z6v-=xm_b??JdW?{%|eNxDOn+eUn?c;QQ5f_BnfzAbo{?+I)m2IK+ys?YGQ2!5ZlwEq+7nw{Pcvp(a!uf@Aw327TS zgDsiCk8No^+lee!p*e$A5+Y2B6M8kTuA@ ztUG?nJg-w{-p~RcA2Q>3mc-{9y5g_M_2gw6eP3V>PyVOayZn4ySs$@JmP_^x);yn! zDcktGTl(A&T?Z#yz8h8F1E1)M8FHwnZxPI^Y0mxOJj}G_&MERO-3vo-zvO#{`=*Jd zze~Cp@owt<6FJ#K({qlTo#5{fYUxqb>txCg^IxbL?@>N}SUg_YT(u*T;Ur?Rugn*EwfA+*jP|hI=382RQeZpJR?At6(0?DbEMH zYu?-kT-Uyyk*v$o8UZ@KDe^kyevB~%d*(=N=>~rj7J+PN@&Iwf+M- z^(8oIGmWuSqvWI;=Q`F8PY8_>HA&_ftk(=g4+p>t1~a_xnkg?-PphGVB{CIsQ&R zYi(19ZfU>eP|Q^6nH|px_Oncr{VBOF>$mzGzv*X9(L7FG<*e9~xAyySfRFte`UPr% z@y;=@=fRc@r~kB@a*Ck$ZQLq) zW9nU{@~uzb{kHyn!KHs|kbifOe}52KfUm~CMMzFjZjXPHkh%>2M&S}wJ?T5e_rF=l zzgsA_Xd0J!Fh`Du>$0c&X-eOMdzSm3Jz|FReqbMdeB`?a_xTd!5JLy_DcYCv4~Fbd zIP2K&peL4e<9*W7eqf#=|yzSb7;=21?w}lPCKm?F>=VI#$f-1sUBUhJ}t&$ z9T|5Ee3>EpiKW;lV=I{>YdZWw#Q;KTdarVslOXmu*?@4!`r}&fq-58E% zIW#BJrN5&pC-an_bF4q*+V4C|`%k$(nCDr8{nI+~4fUBS`v&*oNzOZCzp)4Rdx9R= zmO1uQy{XSD%A0tuCHEENIzIJ`$~yK*kLTHwtcw4JrRPn?J?z6qxBqX+9>?Q;*yxt_ zTjGDhIZx8ZVtLD>_sySuuD5gydx-V`F>=W7!IXT3uZNq=b+>r-waYa-x&3j`g@^ zU&#_V$PCy03HO7k+!q(N-p@0W->gK}JGn=+qf1v(mP#~UJ}^EVJF<@l;n`xLZag%uZtzhm*QfIeUN>;e~(~E@}Azs zJlL{Nn!ei@$8uedUZ-@<>CbjiE%V8Eo;O*QYx42h_ewgSbusoK?RzE9{A{DY8lTvi z@0u)~^QzZSuTNNMZCPvZ`cLVw6F$!)zS%9l*}eGIcZ>Pk-#bYy{TY*aFjtPfaV~Ui zuekPo{df6@4M85DH!&5fQNw+bp?>P`ouPN!t$mU`b7@b@;A0P+Vhi4J7w^dNH`kW* zJm|d=*u=(rWd z-ob<<_T5=HMQ&|W~CTth9?V5m1VQ4+J>*BgHq09Qmgs+*uAZ4H8`!JNVBZRwWTwb|Mb5qHuAQ+@R!QT1lbm<8)EoCa zrQ^sR#$!4}^I$u(Pg zRXsgQd=qTyOYapDJ^L7+IWfNybDg|%Y5trqj*H{vUgG|pRr;6mh4&WkFGD`!E8XMN zKn-dAW-sM*)o(#B)1>1w*e2&>-%^fo4)t9W!B}9O`LI`TE@5%6#(bzDcPq|1z!cc- zF_p6qJfBz6HPm-e#L!rbjf8bT&W$tgv~RtfL(Ej^8IJRW`02BK#%8P+vVA~N&c6lc z^>6idFQoaTH@Q7<-IH|QC-SKWe(D`Z&s5nbIc|NW9yi^2S>E#Ods-{Uv+T#5vt2Lq z|69JrUJ!fW+0(J#$`N}FvVF*{<|p+}<7_>*PI#V~F8xN4|KyhUiH|~t$-elU(_>CW(%6UO{fX!D<~Ik?^^Q>kbly3d{8Lorkx!r81ASh2 z@{_xRTxZ`+=Ibn9vrqbpd*PI?^2t5(v+t?)Z~LF-;JIY^+p*i5{eix>oZpUjtGo60 zxxZZ(*>~z)*Av)p`zDL>Zg6e;`X-J(5>`I(`N=- z629#!DSAkB+xSe8ZkFD+&@Jt^5C1EM;!rO*hFpRBcZ~`20AEQsXUhlqo!9T*S*|aQ z74X{xe?v@>f8QATEyHZ-rs{i5=etnCG4k65{3o}(qP$lyM_|6DN;md>B3*NnUK`{{ zYlUuz-MIC$ucvvVXTNnMF}V30`$SIlFnL|`+K)IlTN3hl^|@X;*HHA$4%&hJmS_Bx zJyS30JywtFi#avsrDJJY^Inys`^5JP_7=oOfR1m9BAo9U+f{w0{@z)7huzx4ig1fmvNq8JVS03TXn$r&_ogRdG-h0<0A9CR@j!rYQp`RdN8LhYJhI={ z;rAq{vhl$R(5oQMZ#V`Yv2}ot+%w;Gr))2J(yIpOuma`}wzG81@zbVH70fY%j~J|k zeaI>}h9dZV>jXcv!1j}m`ko+f3&vvJJ(!ZP1?P8&6>tr3Jxsy%vIN)D6x>l^t4V+7bW01#`Em^ z*`__Vu+^h?Dn<8F|jPjl_5J@6^+4G}1aQ>6sjBDG%K^XGJaN z&gXI_9Un0um%1jHZxO8DjJ4w(<@Xnw3<`g{tNM1wH^L^q|Jy&l z_34{mhHrrW4%ihZk8gq0z!Y213()ZuK`u2lq;>i=G4=h?lo*#BdN5a>y0U=pfxn3nY)SCk)|c`VT{^L? zoF+;@FM4jBYpyx&1MUZ49k|xN&%5G7j9LKhf>_PUOx_gXxmS9_dl2th;-}}?W zf6F8H274J0vwpL4tSQ&nZ~GJZR8yia(zK-8_axbyZ;+4wsqW@)m$~FT$4@y_XSVc= z&r9~*^pScr=~Hv>9Gjj$gJ+Py#!vi-+~;rWJC2RM)-8RUpUSRl_=f8pJ8k^<9RIiF z(pW#?Pv%q8aiM4XH+@&k{i&AzCqAD~a_HRu2`yc_Mfxka)`9E(Q>t=-`{jiDsEN&Q ziXTEQHPpg-fyYMfsxcDT?(#zkmfpc&#Jkw$Ir;*>vyuM^EBYfnCgaaHkdtx#mcM=d z!;myf`bpLA62Na1QxyG<0sdWMq~AL>zj26~z6n~#@7yLGY=0_Uhb#%^1tUK>h>Ds2gW=Myf;9T-O&MfJ`_qzOT zk8gQ5KFQgt&E!6=FUkSNW-e7Q*CMLcrRh2@Rlc|%k=RoZgB>=$66E`<$p^&nSHV5a zJMh%|Vg{cn(lhwC=rITSx%VljVun}|j7=>)V2F01$aiw)Iqtciw*5W56I-wH@jM!$ ziLJfAf5=oG`P5Or67g>Udja|q)OfwX)5i8PE=4p}%)K46%rWao z$KCTL^O(0dKKD5#W4xkX;%ASa*zd+-JhanzE`vqM>j<}EZvX5eQ9da?aPw*jiq}$Gvc0ak|$ex z#-~vGX>QJ&v1cLsL6Lp3v`@W)J?@FBScX0LguM_S@&v!@83P?(6Fj?$SiB$T*;j%t ziH*OdXB_#!UfjhB=rsd+-^MFxyqTV>^m8xwp~npH8GKvNpJRdbz)oTK_mQc0k|Owa2tB^{@y!puzuEEK zPGA#Xf|{1dJzU4PLcS4>_%68l?-URCUbqB#Q?wX|elysT%$sB4SUE4O8TZr>OZZ+s z?{RGQ2=K8dvEB12cf{PD;`Wng{glKqXZ&V+UZ%^dsy%3ShL>b&&*__XL8<6C->&JkGQc9iO=?_+eTV%(o15+9)dJo z`bkYb^vq_D3h<-n_-}Ogdy<3wFUGWf%BJ|=rl{wQsXh6G=Q2K|b=$~Ske|u9xAAYj zmgb87#851A<`Kh}b8b30nOjcwIk$XJx0Ul#eskUZwx*~1!}_}T9Q#C4Z*snUmO1uQ z-93IHe>%pi{HkNjJe}i_&VSaOk1x}XYbx6%Vn~zkudH(qRnOp|SL33jMMRct#*X{RpU1f_YihPFHIKb!lDTx`!rh02t!Imw(xHhWmiGRBp(Y~wOFh`y?_r20ikSRfaL{E}vGg0l4PqJcGgCI;tAZZm zLX*#Ieyb5PB)VhBA=U$o!C0T-Ipu!TPQjXF@SQl0zYF>>KZ9MPj@%*gxxj8Q4*C|H zuWw16j=k!)x2E5)ia?qvomh#rY8orUx}9s6<3sr?VD20PbMYLwjyBhkuCJ+kiF=Xz zl6%zmt?&Q#rMR$-oXUfn&oRrKZ{JOxd0qXP(};b7dG@&PeJ|yG;MB>T4-n(NV+~jf zGo^FS;V<~LW?o0ukn>9pwakGr&vCq#%%5XwVgy^VYCmUBM_OOf+3Q=t-v3ivYgyC- zY`>DuWgb0XZp_#C7%c72(y=UC9J{4sJM}*GJhpYDIrE?F5o5oSPqo>f$Mt-2{Oq4& z)^kovYl3c@oc+L4%yWAsb5HWRSi;3GBz^sd;}vl__T@-2-sH70XhA|I=dZC=(X`&d`jd;~sUJ zQ-%A`vk2zF+?XTl!`k?GY8;c_MT(BiWS_sob$y33m2YzKEf9(A7;*}F(Ps$8@_6{E z;hSO?d?#FdC*=E|*!ms_=z#xZ$w%E3BgW5TGA`^GyTrNV{Bx~r!Md#aEZhijMW*)M0no{EmIilI2U)O?St@#%p+Lvp^E z*H(W(Cua-hS)!i&uGsfHl-DAk`leV_F8N*g8OGnj$9j{@(-0>g=7lcbNs%wZJTgP} zr&Q(s7S_zQ=s#g9KhtF2DDse_a!8I&y5!Q zQ3+}LZ%g`T*q1+{EAJguIe!bDJHLTvjiKJUCB7$^dkJ!meW&bcO^9Xt)QO+uoO#q^ zXPldTPr7qYdFB&8eYW?Dm~*lu|HSdrf7*ZR8Xs|Qxu5M%q-(M~>sItX{jcoy!~6MM z8{4@LK5WM<=fgR3-13&!Rr^Fy?1_Az4BcDxfOkm4yP#=Fx6g8?8tZ4@o9*$DKVhXk z;XF>>g%;180J{s`)!Ky-KIF-nUyk}S-}YITZRc1zR@57?huA^=lKVoJ&n)St=pDQT zQxdkQdQXRuSkgbG@U2PtS)ylhuaU;FUgdiQ^ZW!KQqe&)E=@{HSdJ+cQ=(roEX-{8!Uo>^fdUs3ga?;Tw^W=hZ4kE{XXHNm_&PS%*~ zwu+(ay#?r7;O_x>P*el9&$#{0L$;_vT6do7t9lQO*+dD@@$CTJH0h>DKbf)bu>TnE zNys5uu=VbFGUbEpw=c^cc}OV1((?vJP$gmO*^sf{a-^Ci-O$%#OzE&MIF@BkwMDw2 zH{&s%nVwnR-+OvCp=Z0w=2CJE&L-w<0+<2r001}(^Xk#F+b$^pMOWr)uNHk1Q= zGl6a2md+d){}tqrI|b5pOa6>|kZBg5@(&dn4;+1v;@AToT>49%mtf#K&RKz z?^A`}yM1pL{uH-SVt? zXS>ex=$dn;%D$0vtk1M|r~Ymp#F+~Wu>+rTu7#DrHucc4!9C~)?SUdc{1~4xzt1J-GhhznG1tY~=)4Tpj5X9f+jOtjs+gEx>KmXJG32krl(&yu_Q4@m zfc}c6I4}nNyC`Br|L^%|UR)oX+nT`U{IfTC9#wUKJuf(q{!1_xV;bxyKe2aEYld{F z4=@+QT&>?Eanq!~qeKqU+-e+qCzq~^pTK$h4OMmKR!{8a$2X6dZA)@as(f(zZ}wDP z=9Yu6URb(LftdAAom(--s#Kv*1N9v_LrIJ2f1&NopCJt{-%B&d$zmQ^b=}27g^8O$$;Q3_f7}B|xIfhQ1W$y7) zeW=eX-t{}@WxK~QEJ=7;C&w+FXWP<#%f>f9F%w&Q)wj0HxP0@&ck?g#i@w_d`VGgs za$vr}c_sB^Z05?`o8TC$;5;zbnQVWRX($M$9&*?AN|B`>IFs2c-C2V*`|h$v`%aaYJol@*pk4!ts_U! zB%uUZw+~5w#;IayuO5QEeTp6wNq*lk-@jq-JH`W5{!ih1)d|19K{?>}lN-)+-A$51 zZ5K5_$4~wgTRhD>$I#7F{)n|%l5nmeKIc^F;GQ0f_!69lDYj^FjyPA~^M;>Xj*;J@ z_}z)$qWGN(-3;kn@uAjN$w&$uT|Y*@xehq}w-B9X@QwET4S%f67)3ysPWV zDZ$cOjsX3{d9Fdie8W~e!&<`#mLzb#^ZCE&Gv$yEu1AibNj{P1VtvLjbjeJSZSY&q zavf`xW9VmmDBo1+KZU*IWO6SAO)^tt-{=R6yy>-yNH*_9U-yt_oyWg zu!(KKIE=|Wn6trVK0y4W$p=NyqYK8Rf8p7yXD;7~{M`uYZ%0V%BK)nXNjLT*4Y85H zHu$&jcd4SW{oLnSzjzNw&wJL4wF9q-<<$IH7mkfJSc3D(c{Nk|7F-8hH!I|~Uv;bl zy?MuAZ05k4U~hcHG!A1SYfxA}ts`qX510>ruQhp$Lk)fC;bU@tVvge@&iKrOd40bZ zX{|cn@?t%UWTu5b%OT$utT(kQK*tB@OMG7kjHNLfbLBX+cCe-Ux;c;Gd)^$c)^>Bh zf2lUHrC0XYv`3DxiILx{;sS|1#SV$y5=DAf9{UVqaxQz!6KS36fq8QrO_T%tTsK8E zO?}vZ*sJIZLsX5!Sb&btv3*F%-Yi;#jgQ zhY$O|3+8c-HFZu5G3%VGPxv&Syj#B-ex!BstlR&IbY0HHcar1iXZ*BtKKBFMe2)Ds zIW!K?&l24f%^ls+I(GIsKg;pZGv~b1-Qy-*n`8F9ljqz|`>glzTAuL{$C_o8&ur~a z?|7Fx)x*%sdX|lS@C8LNlk@EJ^VjxGlJgt7>Yk{IeZtm$pP>fZGRLeB#b41bao4_* zXHWWM&iH9>^N_DYwS94;}K{(ffr@a?ZLLeUK-kY94R! z`x$cU-Nj7aV}d4W?Awu>>!0+Ti++xkZTA>4?-!#k0!WZxNnMBRen*0?^EPV_k5B5j>UWQ1?q18u3V_W*1IH( zgC^S$Lr1>CIa#7Z38tQ>8T`bD*e|%vm%1Y8*Auxv{wkLC=^>inxiAx5I+PdidrfBW zyUoQ{<%6x?NWiw`Nb#w73yS2@Z$BqpzKrAKyEe-yKkUG^C4SH4l&(QK-x8nmIffgK zlbiGLV>iLEY<|BIHI8@6Pe0BH=ZAB}IRnla{zW|M8@2on6~BG)`xn21P5Jn3jK07a zT*HnxH4WTTqgYKSK`jrFnHxL!zHPa<}kU$Q5MYlU}qJPSLdYnjO9_nIVrD zphFLd&)~n|xFzFR&+^Gnj^~(Vu0@{*U3R9(2CozA$2zW9-y(eq{O=&okc*CdqUzja zZh7=DO?sxthV0AwOtr7{t-Kqqxy9^r4zk5~NGQS5TJ|@P1Fmt8`SqXUN8bl|{j3kI zE3Clh7}=hS(O=^1SPUo+ZX*EI-G#=FI%fkec3Tk9-tF*9&!u5NkI>? zT{JfD0z>lv&!@@9yofFN&vj$|9B%%dVpJA<_2k2Wl^vk_+ zPxrRu+Vk~^Zyo2Psh=6rp@^!nny!JVbHK4d2{_)o4uv()+MVEU0-3=#6WGMP{+9G3 zwgqD{mo1nVaE)4i&zHGtZ92!uv2x$(9$vbqdzBRIQ%y{A~`Xx_xDWKqtJJmx}f7xZWRc8rb9 z_%Idkq9$x3XTmk@>z|&sZ_;)20Na-KSH%qLcQWL=(OA1+>sUWwD$jh9|I-}z{zUdT zM?aNCb)Ud>U_QyuIX#VGJxl8)<=u4hGS0dA97B5EzLxECPL2;9CuCpN9sfku=tXYU zKdC)))-k6u{$%Gma%`XZ_J1PJTI^isbvUhtEC%**+%x4ij_`kNNxjfZh_@2#AtHTawUeylyV``==J z+c!1d6X!Md8@oMR_n-D+Zm)8m=5Bw#tnVvdjrAwT`b{ofAMfyWhHP=oWr?2IdiToq zC;EuKmeinIqOS+eUCc4(pR%XEXY3Q( z^-F#Hp4q;%Ucr{k_;-u*w~dv4H{th?8Enau{M*||-*3>D;JZ!+UxvJvaDR{4HRcn} z$uj2_)&2x(f09owH78R(A>$vJ>d5f;CG4V#i({uK`iBUHM>&6;R!MWm`t&ls`mo+4p8hX>Wsc+YBIYYli@tf2X{0@eXxO>yz z=U}TIeF=Iq&eXNi;=1D6GD|wwBG(=F+YsEZzIT^&?rHArEx6}>|F5Lucg(uwh`c2U z-cKy;a~wH>DhZ4|V-D!H?IXuENY^;8sAlO{GWeR9g8U`&9_aa5IySXe?tr})*oT*3 zUj}Ua6@9dGdEr5_^WfNG|yR9Y1{;XYs7me23=E+@ZxW z5X$PQ_SO@OuUM0Vjd%(U^72C01 zO*O1RjkTE3gE)QIBUbdImVP@xZ=y#(&JlBA{g^-Vtube=3D%Z4V{ooGpYUBLACmch z&n?ES>P`PC%GZC!f3`abX@5(n-x?%k?tf^_zU*Yp$ zNiO=QOy!zSYshnrmjJ0^TR)&`k*6(g_ykXP67&@~E8Y~Ei(&(iTM(N9|HL2P}%?N~U*C#GWN z)tcX8@9J*X!`Y{PY5m2n|6xdnmMGG1IACxG{-yIEihg@&!IV7N z@|ntSBf*ed{QjZeNoHb8f1>HP!cU++_w2Gwk)B}=8DgH7{X;pQQk8r1Y3-lnt#lmu z_-=Z?tVi})pUN{^x;f9m;5XdFl5VQL%b70y6;tsiioW;d7&bI94)8${)byZAg6GDZ zo8Y)O&Mr#8dis3SIH$gjxyG9)0Xn`NI6q6r$ZMhq?jP=>8tkeuMH3}4^>*wP`-0h^pINc4qwslHl5#c1bXAQCs8Ax@u(r+(6g#HV{pwZ!8OITydkw`RvzO zjUqp+`d=CHK}~G!mnZC@H=K8i;d316oF?5A=^6HG=%NN&`}jy;gX83$5X*H<`9(}U z8>$%Rb4t&isb^6YOWzg@_6_1qOnrkf*dx`V2O9D?U&)ibZ_nYZJ) z$FmPTgOB5ZGmdQ^eRziYc}j2WC5mFi0i9UUvzO;F&t=onI_2Z@vE})@X2kK7RJ&rW zSeGJ(z6bK(7f#VaV)LyKmgupL)H2SDwQ^s^Va^;6$GimRI-hTjjbk?0Q*8W^&o#pJ z@-6A3eAcY8CXq{TXaZkTobjvX#9DA%Bj$;pb4d?!yO`0(V|I-RRp28|E@MvB(f_2# z_uW_hn_xW{pY;L8Zz0j~b;Zfcl&EK(CtJR*xibgWjCpa~tPS_q(tS3M`+SGZp2FV3 z-qXbt>~%w|1AN)YP z{WOO>&$AabIsVk+oqxPoFJkQy$M-3lJ^BUilXKV?GsLp}O7F^jMN#~>u(zL#2Os{w zNmaj}!ZYSojpz3&=WoP34)WHIdA{OS^Wyah;NtF-mw|&Z2=kJUSoo~o~`|6gOM?E6%w-**zVn^_6_+ zCtmAMWP3P=_R|9$`HuRfW3K;UNQV{_$#?uFcc?GyfO(iM9hNA14>kEcbjrVl->of& ze%F8{{QF1OZzEG|ej|zB{g&j(h}bEc{3rA>^v-nInIb#mxgmeTh-3Ri=DvAs^iN`E z&CTw|asI@fsy*5AnY`ZT`B=xf^Y^zbd-^_Sead%(?|Fvrdn2)>XX^jbH7BwUeTXKC zU<}5rg1P%xd~81Mp4P8O=e%%ktKiz^dM^p=DYhWTdDwlwT5=(l9SWCDT znVHeMOP0WM8Jb58w)X!SG)X9lrRPvjU>o}@&no`@PvFZCa~{7Hm?j;{g{j{apeJhR zNPdSH0lH&TG4~zO-!>8->;!fZ^l0ie`F$pV&2Kiu7?bfb_@D*oE1)OXCWqdO-;@IS z6K4+0i?v#U^RWfzi}U7lhmTk%NBKR*p#IyZSV{4GT#Q-85X^;oa_P&AV z$It!A{kjGBF!!?W=YmghuG1wZx_r9k3UzAHqI%l6%n8*q`mJ zJ6=`8yqYK>(G5N`zy8s;eDq>2Q*7aVm_0eOj^_zBd-oI0rw6*_sbimz`$SXh##4S( zzTx;@!5W-2)HM=_wyVYi&+QY*v1D2tC$jv2ymqhj z9_OV>!m9t3A>T=pFSE6G8un3iq``i|Iazk)LkZZQGxj6xAIdqY@@05lm?_;fo+F7O z9e9p(o@McT8hZXrJ-><=dLHqd<$2gd35iXN=iDYwHJK$Fi18dHpXXeUXB`so(U*8t z9%FvjFb6$Lc@DM!-4NrsS`*m#!FgHYC#MR}4;}DlrtAzcj<0Ke%yVnLz#70xkTXS7 zJ~aj#I6mS$JwT zN?jGqbI1Ic564>s$AdH+XN@&v?Eo7;Ia_diMZK7JRbP5Df6sGhp5ONhbJ2a*bZ@dp zuy-tR?lIXvztlVSx*@p*d+8GFx!#A7*!YP<71Yp!o{Y=B@I433Wocf_leu#o@O^Gr zk2n^Nfpb7EYY5+UvRPaD(ffN0jkN@0B7r%0KJO&C8TX=JyRda!z}WqPBLB(Kd;!1p zEQeyIN;g}_3iE;X|55jDSCZ_=wjC58sRHVglmena6o>**APPj`nz?O_whO`|BC~S$ zJ^Yx%o-jPz9qd^jo{GnE515Zq9StpUA3OPi)9~ z=MK&5J2d6PyEglS9BNSO6N>Uo&bRMLPVJjO+|s#@W!aT`Qsw&z-mj74FL_m?U25li z^lXnyev|JNpR~EclSDbxj+dA#7e@A}OZlpC?>6&KU*AJid zEPGsE_-<$T zjyHwB^$mURTcZ5m|Dj92;T-aTwpHw?Nq_XuI2n5rCGcFc>{^p5xYkW@pL4(W7Z5kj zYx0$V=ctQa|6xc%6D62>X1l07yLxvVq9r!(U$T)flSDUN`ll4-W|sCJ7zz8bzp_t> z%^4H=NNuuC9D51+^4MmKc}oIwW6rP=*e!6b^X*6Co1%*nxX)}e7N~;pPtk%RnOQog zp6H4}66Y}I)2CS_eACQhp>q`cc@Kf%Qp0zvA;#%)aSPv ze&^_-h^60gMu3ffil#hrm+)AgBy|lrw4rSV|4cZav9W$!ryX#OmVA(}7kT(v|2>BJF{hjeLeDv^Q@dQ_)X(;3{K*~~8?=Bmn6Vy3(k!i0hINCQIPDxm_kHwE=61gC>>HPU zbG@ni^e0f`Y= z6g$px#+GR@W+eUex@bIu-e?k(&&>~C9i?W1|W#l}w#djxZ4{#8()+W4utqV|?ef2^JnG{(eg5J_}YFw|zHRm1lTPGxnb{=bSpZ_PuI% z#-8jR^ZBk^Y5q6;tXI=Ie4>Bao;>R1{1s!%zQ57yH^+YF`+A*nus0>%#d9t?`KRwE zHg&#RtEr6{Nw@DNi}G$vpJkHhpVF24L{$tHXV5p{yvQ`!nH)o3FF12;x2S3RluL1Z zNbA|Q|CHnyV(3|-pOEt%n({w^nmG?U#S zzGJt`yzrfV`z*)NZl(F3vD0ox+cW;oUf*m_zA1WMPl%nq{^GfJY%0gthqN!t9DmYF z8rv;rD!%GJbV;bePVZJjdQ0%WW1E=S(lf3_-6hH&wG&h9pr-f7#rxv%t~q7H7EQl> zEK&8FR~IuV`t8m%=_A;ZU>n&EifnK#>Yh~jP8dgK$bO4glvf+J+oz-s_%6@SdvBG@S2!>%L}T$0b2h=(zb~r zs7W96*97A(BCk``dI8sUUTECYg8RM;o`w8eU_ar!5zkZ$StMar)f6>Y@tiiEQN0_^ z^!{0-o2B=i%#eNKDaZcYrmDut*5303`w}!!g!fwStN1fhHf)jmT`?EWt;f8l#j*qzec5Q0lJHdaF+fK!M z^xsvx(ht9b@xL?YH!-ng(=T-xJJ*Ekxdqps`^WbM_Z0WTSDakhXk0+Q^vApb9nP9X zHC&VDm1lSSQLWe)mSoXB0qC};>@B>{SmGn*IQA>t&T@RP&oAYiu>U>bTx!<~_|1yi zwrk4A=XmbRK4kXyI8U}D`}DYg|AhQc$nB_|?W~`*W*S$HS=aq#+0=Krfd3V=VP1Kz)~o!6 zHG6`!bqxI{G_^CkYAr8J`Jf*Z*>L)8J8rr3jsW9Bs`AX%zK@Rl+W#dvU;J*-;yr@* zhNbg^b7SlL=$t7bIZuZChPU)hZwUTB49yI^MvR;ixz?xbp>Kb@?{vNQFjnd_7WzV# z=#y9xmLvX)!v4W!K z07U5+5}Ho%+oA z>zZjijE`rTy|tAVdZI{Qv4_xCOY6d#upX?x zufvwj+R^T5Jm(p4vl%nwn0;9e<(MWNN-#B7=m*T#cr6$!)C*g4xiRI3c2Hyk>z3)V zZ`AN3Z?Nva&G?I&Zj1a5JN0?C%Q1AvoAgWx8)==mZTl>Vfo)6s-^r(Z{3lPfhH`%s z_jA^?|5KUUZ;kQ0ylPL(^>W@PdQ0n-_1qT!rxfMBV`?vbhif>`Hqt(1_P^6}JL_FF z;I!?($*O$wF1|QB4u))Ke*t?x%nO}!L42LNq2qJz4*itGvk%=6GxpsiIXC}ze7{k{ z{ZR{gqDj9&-V?WceB}*K?eI_GIQq$!4^Dr!onx6}Khax^$!%@_hIGv=_Xl(QY2Ue@ ztkb7$^xvTBS$=|N{eL37PeD&q>9CIXscbt3*@Gzw<W^y z@;m3y`*UW==C>_=>zZN<|2{V2x4tE5_-1oL%(=)Ws6#!|rJoe}P8iQg9*Ly4Iv@GuzvZknk5_ZdHNT^~zH9emeP>$xQ{ReZxLz5q;f*Cf*S?D>_>Omi zpLi9n(T@9}$5=fsj|H1p5uP7wQU%v(B%1V+m~r1DO_!djvNKE14e$(QnrzthKTPSS zOD_S>?1;Z}Ht$XGZnpIPV*LJ6(>u-9eg)Zg)17z9oO{Mz*(3JJCTZ+@k~_xPHGWtD z?}F_C)&aJtX`f@=yw9<2#K~)-2x`+u74&97;nMeth=`3Ad+oqp5d_m?T~5hKTW&UHR{ z_^3^P=lWrZV zArAJ}m7=+Sg({MKWW5mLqbrugI6zpr`e* zUgi6QcWdwXi0hOk`X^N7nO(J3i7EX?lOM_(R-6sUlP(|Bgl*21CO9L8&V|jH6K4r$ z1(bku%;4XGHimNr-z&&*{*n$~=T+#AHQ8q!eAz$M7xaT7+h=Z#vv;JiJH~lEMH5SB zco#n3ht6QmVJHtwo!`be;v14c=e=PG;^a+1 z+byWSMA5uHf%%&$9kyWY^7`3E5(jb(HnAnR2HVwaNU~d8h_=xslE|@eJ@PkTD!ipHvIAJkbLG!TWU~`InxL0wds?-nWx6X z^UZ$3zQkV0zPSW@wD<5S9q^6VOUb7l?U`SRIkE;zzM`0E(#ZvURq>|0s{VIcUwq^i zje~yZ&wbxySNukev09R2ow;S`C(~qa^>3zh=!qhI{rLyh!Bpv)t+`~T?2{&6rpRXf zZglx?RQWSpgLgRpUEXiThsI@|>Jop7;V14K>sjuUgTEc*+^lcqne1=TKKpLE^FEQe zJ^8j@wf!XLRr_}_>c7fy90}{2{G`oOAHH|bIDMQrC;RQoGRIH7sqV>U@A?q-*-vwG z9_qzX(%|{&nCv@eV6!~s&4%&~-nP#f=pTb#i<5usM_cy*7=MXi}zfrr>AwD(x33PK(+^l{ANqf z@QsgeeJk*{Ky3U~kWc$2ittz%V-+5Io?FwLfP3``?q%qL`XYd1kwipvOIW@s=F+;SF*yOk!Z4JJg-*HQF=*wUm`lc^_ zpFv`iM_bmG_4hSly@&k7ki>~`-DXJgcf^sjA;$IQ+H*f}A1&QCJZr%H!#%R24!Kh{ zvGzyt0Da2WMNMFrKWZ?Jd1(Pcq}sv5j*q?O)1yMbUovDeRx`cvb6N z+o}3yOE>J}FoGos?De1GHa9t5R0}=FTB_x>@cQ()7Phl= z%(5sKxDR|^WLeXFb?V%MW+pxR@MoIrpHS59JAA!k@}Bt(ID<1hgC{vo?G<=zd2aK~ z+#TP_%j9*=e)Jr(KGL%iQ5^hclQ ztaD*3Jl7f<`^fksdF+?o=aynyIYp4)#1zaAKY26SQfJE71rm11BDrEcs8NDVJ;msM zvw!RC@Oy#Z4e*&R{S{SlICJc$4;Tkcb_tk|VO&rXtO+n*Xo;=4WTtHBFDQx`)(BYN zOqHEs-EK_Q^C5Q2$LBVdPkEpCUE@2lX$&W{$A>i8MmHtt_}jIHkReE@#FiqVGn+SzGs|4 z$S3-X7;T8ZqrS-@ZWd=pf^+SsG`0N>TW7e*?d&^c&iPb#4d;)C`8kff<>dG$y8HdE zoLZCLq95;Y*>)e#{hdGeomuWV}MI<{}}HujtE*7mo2XMZEs z6}f_Q?Xi80ru6TymEZImFpOYH8vDqjZWl!`7RFSC=RUO7Y*Wki$RXB630D0_N5T+QY`x>nph=cs>0Kw&Wt%EJv-K`whIFv~ zB%POKQ#(_VZr@FkXKr!(hVsmop2vUcJ?6S3Sr67_ihY2O7P>=tZ(mpPdHrXsp1V(~je0^beioH6??KZ*C~-!iwM&N!&D4KeGR-(h4g zL9H!3_nTyGo>+>puB`7ATn8jnk=r@fc|*B7K*vx1jCnFH>@I(cJnjeX3+@l!BiKbC z@omBL2iRY2TwfBKSox#4*n&M{3HA{0CsR6jKWf^S0NXK3e8h&>0lIDLws+*P_d&nl z*eiK!w^Ylp&w8)5FH6V2E1T-V47Q}H+WT+#9C#JG<+v_#z9Hw{^nbH`)7VOo*Z7s* z<^IY^$Z8$5TJ$}@Pt^T~7S zXBFEyZo8ts`(!*b*peB~+me`d%c1r74O|b&c=`jxOwK9tXFSiF%z0TScf6q-gG%eMsv$r^vtR{|Y{H#<07nf^%euCW@FkH#%oWVsV}b z&M#;WY@K1i83!fkl7P+m$ax3oV83l*rb^G8e$LMf=V}w2`<(y02kiESZxT(xD}abTc(S)1_ysY_qf$C#+Sb$$o-s z@e0=byHM|`4Kb5*y8N#wZ{oQvxyF9mr{sRls$MHNo)g;fk)^D;Z zFSFwu;4A=aL!R|4=h3Fg{wYOmPd=&jlQy5!ead;&pJP+!aR$F_>z4R?!g)y7%hK_u zHjcZE?VOKp|Gyz~U(~n#)EDum*r)!yp1IAr4#b{vb35m`eU|NVpAY%|maQ7EaD8&D z-z4!*$ZbE#KXab;g)@(S>|1{{_Ml3F?IqomcvqXeOD6nYH}p;iE2w%uG+jDOQS=)X zw8W4OH=ko$aZ~mC*o~*S{UiE$CtHlANCM+NaqOLJQ7g+=ZE~LVp&Cy(=O#;x|0c=5 z`SCf1JQ?zxZ226#q?a{LXb-WKHboZ%8&#f~mh%x~Rd@d)CR2??#i~ zY~Cf_FcmlCqFdVkQ`uAPry4u@Lo#m9X{9-{4y?x%UL)3P9N;5|eA-j13D-t9?sux+ z9u!Gf+Czc8v<5rQ%_f=Q?8*>3ZG2FIrQa&7pOP5#pi07N+rRnELvc&{oo{Ks^N<tDgViq4_HUma|za%HJ@S&uE`K9z{XEb5wxK$V<=H;;#VH`h3^UUuR8RT z)`b44$^G&4{P|wm%HcWnvyD!S+VlrgI(bVmKl@Y9JFvH~*Rbb!51P`=mJZnc03R`E zX`i!=57+}UOZF2v@1{Ss`@~-#&T97RS25x+1NQeD-v56hb3ONyCH>#XIiKjcpC>)n zIs2>{2jum>T_^im+ylN(X1a%p^q;`}YCdW2T+5;MP=A4~XXpt(W5{-)$d}>yyy2W& zW2&v$(hYNBZcBJ=dRlYz`Gy^F%Xj&=cu##6>A;vWjPpj7-*{d_b2Rp4Nq)9-u64_% zS~rUPpD=ZuUeS*@_Kh0;EYY9r{!r%=zw555brel(oeP`|oE1fIR@8v=3^=!*Xo{I4 z9XR`X@O6&qEaMD>Ch*miHX1GyuhZMkssPo1Su#o)a`-+yMJOXr&jEPa15*u-Fq z7Hz+5eP>zvPQy1B=+eKw-J~`}I#6$j$+wwkhui``In;D6{uX&(bya)wZ6_#_Q{Q{~ z3y1@`8T)BlgQf3BC&b91?GTmu3C5GL-*PJ#u*vg0Sp(*{1=k?6WOHqx2fkJ_tqtn~ z)T)9tqCWl5C*wq7f32T#Xis0ROwJ`FpLW)?{=>TsGsc$`{P#YYkMBKB!L{a@cdGU{4)lCz^C(OEt*t!fnXoyx5}Z{Q-EN zu#O}K^Fx~QZWQ^=iu0*ULOxR%KQQi0VXOi3fgV&zvoudLq~B=rpRgvGDLd0+?LLu3 zxhE^#uQ&a?{t%#q4tq(!Sr2HLcmH@3i;aV#ITf z?I%f%obQ_bVGQ_cI2FKxBH2#s+ajmjo)rF-^`Kmx=-|z#kqVyj(yI@ zwr=U1zmY?AU(pnYS2<6yrFQS|T#)Z_i97!(_l(=lvZrhEi9VJ8zZ*Zt?_?h5ncHG7 zIQ_QW7CY-Xo@3-}uZ`Zb<_n76*`SFLY{^X3`=062VFg9+m8MBQnev@%-bEAsee5Q4 z9y&Rv4|_&E!PyIDU)a+iy7Awdey0<$_~pJp0k-hiv72im%hq zwSe`8{F`a#JD->b`>=_V2Sv~i<6}Nel*H7%2;7%Y6HE6pjG%FE$Meu7*B`|$;0OPm zf+T+iTasD^8;JLyNS^hpYMNcuCYX}YL=h|fjZ&p=y*HU59a^GD|F@u@f1@^aVTc_t z*Djd9VLe!vnZPzhx}gp2p$4v>{T2*eV8lsLr)13g@N%*5_}3Go<6|kwe~2 zU_a3nJ1O#Is15Y4vZ3n#bM}Vgmh)(j-Tnni)XWk+ljr}W_gK#=dE)D3$^FoRBFVjVg8vrlai88~ zRSa%1e2!ak-)DFhpe5|HBxe1UBoBDTGCX(CA87KI2OL9Yrfk>;^)l8aAFu|j$qc;S zr)+T@kWZX#PJ64540UX~e`HS-=_ejLa=f83M{%yn&1UUxH2F=D{weIeCbyxM?I+!F zxBo=CO_pxgHI^!N)tZSG6iJvmM>tEM221A)aL%+~>)g4)`IPChUr`j#a1Ndf`Jf8U zXwE*)%&K#pZx>zPJoqkRuuFW?*plR#F5TdB`=<6?-%ysmBh~n}H1%Dn1Wgh@IecfK zj~P@++Lvfg&W=2Cmmmkw$tCWbE#Jdkl4g>oEQ*;7Ph}J@|_Ww3+v4Mm@{){jl3pYKdu+oyT!HN zl4eRTf@{ed(ssw%v2Imt)i>Dm#k$fT`Lv@wjC36s1NM@i{<#0>4;%kiySS!9bz0z> zJ=!g`;~Ch3=ZEKL2|r(!JacAB2S1lt;;(`_)T1s;(FOf6-WFqJ9Kg7tB>&btTFm)t zK8n+hxxx_CD^Z(%=#%j?rY*dmbd8Dlj2!F{IpmSgxXI}PNu4G?`x5)r6kFu|-1{~$ za+Y$PPmFV7i=lI5UBC}5L4M{{jJz3iNmHb+bnY+~!yF9bHch%I(qU>oz?@8#4ohoe zhV)l7#ZNZteK2LeqAPx*BtN?Ox)+jdza{a^ko`ne?1{6@Q{Hd;&wg+AZ}iFY@KlGG zA;-D){qK?Yr^k;iK+$;7Y|4o+aKjD7s!x(RV&-o{E>i&2~`yj_P za;|mcD~jTfbL@MPK3ksR*-!3Cm+u{Co1fV4`uK0gtLA+2)PK&q<>dI{z2KJ^s*|B^ zwyi(;9eWva*i)S+|5P70Zuz&o-}3QHy{naApAT&LPu~3w=6v$3Bj<%C-z&;IQ2UlT>F_*{LHh9rL(AU9_bwIqHvzZ?*l{9`1b;G z$lc$BHqe723GVAAbDVM13-~ijHsG`VN|I;JJdd$!oKtMUS`ERvRjuC*)|NHq8f@X~ zk|o#c#BuyfaE-av*o``Iy{Jttu`PJUchFoCb@N(Rt!=)hzUD3Np%RjN&gUNz z`^538*e8&m^SHNRB${;Cy8l1LZ7fHsXPp@Jc&;*Cwka?493vn5^x>bumW11wmd3(5 zupT$2{NQWBwRvJZ#PDZw`?F1nYi{ZOE&Ibbv%f0Gcz#G22TeA3jk26+4f~;En<5=- zBWJ>Xd@acF@9M5`lSTEH_I1;x*Tk-xs~Dn*BBst3&K%B|rL$%T&Y!LG2Z^1*2gJ>k z{s}$hp_?L|GxifWkE`H}Zh~{1?-_hY<9kf!`;NZ#06O1i_%>t6fhKl*58|5;IzA}a z`hLW>svY03h9o+^65p2SgSNC8(Uv+rpw0=gE!>87rfLp+e?sCTm+w}Lr3t>9Rl!*J zCRfGMeH#`6w_sm8_r0k-Z;PsZ+}IxSaW-(~EWta6!OjfXnI`**t(uuBJJV%D36{nI zj0dV`xa>zbu@|_ggyd_&rRlCzT>&gEB#&myZEcx?_yJPGvuNpzl+ECi5yz_7k7xxjkp^spC5# z=k)#m!S0;L2aFpZ@)O8~`T*zljlugs#H@cRcdB{z`xNsyZ*qM&*Z)SI@3$xEG5DU# zdBn2cxi^1qm)qQQ*E!q#H|?i6cx+EHkFlk_BI`K^op-Ss?_}s^Nr$3$J$QQG!#@wU z>{s|b^KYg5IQ!3bdoy<8IUhaycFcvbTEEF#-18f1d&6Zwvb^ z$$iI&x=3jWR?FcGb8pO!<0HB$xgs81Z+(<{d{&(SjnmIFlbBZrorQGF{Je~*RiMgnV)Tsficd2 zc`@G+Y)RG5SpnDVypE0`$pv)sw&2;~8RPlt_~M$Q4& zzo94&dELX>s3wD&6i$y3W=!Wu|nV zu_uT>u~VH_y8HFGR+=mNxmI3B%c=GBHL&EmWQOdME#HkZ@0H!++Lw!DT%X{1)VMG5 z{JkcpY{#{KwZ1tv<9gY5+V)$X{a`z0Igfjw%gz+pOM5l@bv@Ws^~4OCWD%S*RdA*N z=SdNqYn5|NXB}WqL5v)OZHQ-R^NOl?#(j-AXPYG0UeXP|9q$sH-<$qONOG-w-2biK;e$j<1Qc4K{vq=wk}T zF@lC9uJ2-ei);bk%J{a%cQd}BwV)))H@YrrV*Q`06%0v(k390HpbntpCvX2B|6xi8 ze8kA<0Xk6g2KmNuw{Z>n-ePHf%$xbM#$GGdHLus#I%%!8;QJl-K@&r8Z}ebE0(KQU za>#o}Q4Zh9_&&Dc`fQB@=pP?(>QI|L+>dMT6kCe7c+cd1=AF}ajr)D-IoZO`+|V-z zOYrO(;$3+~AhF50G2|ycBbPS7^To4e7z1M}g0Y_SVLdWUHuEEoe79jv)Mu=tO8)r| zT{_^~iVe|338vaG2gb^{s0EWT$=>q!s7swCEZGyMsM=3QVoB#b;9OaP_YJ>?`27Rv z7&0?tLwmzkA21X23ngGI9$S@ew#IFk1GIx88zWy|p}n9eW^$f=S#rIe z;5vOuO}XeN`M&0UHu$YK=|9CaZgQnQZgG5{j>98;4_rjrf!zUK+jTg@Rs+{j|8NDnKdB*?N&g01X(70d){;d<)gY3&X zZD?Bq#)yubL6giB*}y%_-x5%R9e+5Q^FzAwOp$K< z`@t!Peph%R=eiCu`>mh;r<(X_Lw*-iY~lGX%^g@H)@%l>E3n2xY~kzCbsd&y^`Gmy zCH*%$`V_l4Y7syx}Nqu`)<;0kY+?Zq_dOz9K$W|%(>aLyZN(k#=I?wSx2@DMZS#JxW~1qlE5|Qnzx`x0{0sC z+;34;Yw7+s<9I%}*UcHT{VGmu>$w3xKTonp4$^wg-Ip5pjK}dxj+oz)WbUk^B|6N6 z*WI$EYciz64t(u={qQ@dNeA1=9c14ty+w^8d6LJB&OG4e&vEA=Yl8Iw)~hAF)~B55 zI^djtep_qF+N{9Wh&6&8Yc}Fr9pCY~;9lX| zl^N2(c8U9mJfIEV`uOHY%`WJZ{(ViSbgs)+OnKDUqUv1|M%1JZHD=U7V&nI?XxAct zM80$Ichzh0O!Dka!85#spKYFVn2B>gV2?N;pD7;#^2eKXX*vCe-%Gt zP5Hk~IclCZ{mlb>uXxHo{h!!7^^N`nwVa!C@SivjKf1yGE=_GdrF@Y4s(<%GPS&$+ zf0jl0H`xFGmRsG^*HZuJCg*)J4(C7R_BZpnwYTp{=Du=HOY=B&=be6RVmH5I#7`Il zb2C-?3Fr99;A{^ZJKOz@-P4%RUF-DQUds8O#JSGDVNcER#C5a(=5yS!Pi1>?PZ7&? z=)?Be?#WM{W5{f0J?B^_p0Phm&l}mr?VWGx9T4*GRMy+&H?8b*-YH!Z*%MW|;r$Zb zGW+ov=h!}_+r7&>+u3$|+m`lQ_L#?%WWLCZu(E~qKOi$ zKN^SNZ;M_3!;s!SphON5X8fJN-vNHt&vM0k{yRC+-vaIOj-TUCy4!8-F=mGDSeDeW zjlK?;2lFyB=IXh7Et=M09N;5P8@I30&AYy6bLKL}7I=P0=6IvaZ;EuYw5P&IRQA~e zzavb&GnghlQ^H1mLT+n)ruyjimk(=_=QdNi=dm;&=G??aYVGO)Yl;uKM2YJ)CAofF z$1QkXcy3PcKgE_}Ja;^QpD-hjHimZix8T|48D9Z=Ko6#*!B+zG41U_<+k!o73Ga36 zfAd4g&Co8}H=P{t8Dg3J)=zono^jjGvuyD`FeG6Me>*$By>)$4%dCry&*a=wcg}a@ zS>v5uHI_`yUum4^IaX7<>_^Wrbk-}w8bS+pTvOJ+2St+m3b@Ch9&rER%MyJC=%?+N zWm686z|Y1_cHQ^&0N;(A$Fp}bLJPJeIegnGe3J>? zzHH+oXNoOq`c7jXvJg*w4C&a&B0LW4s+!aVa)|Bt_kMgoD?#HMm>Bx5M%>@RzWP#s z%oj?0x7zqrv#BQE6pPyIsKxg<)A`00_}kk}l0)tijLGAnCRB|JY)|>19oWC44f9!o z@c}w9`uX|}sBeKK(WDPSj4_c%AJmy@&s

)|t6~&7U<8JIHI!8nK4Z64=BtXU&pFRL@1aewFJ!jtOs%SaK)ea|44!%F4}tC4$S&{^Bj@W`jpvtV7-_JZ$G8UqS9~aQ5Mv_MdCrb=^LtDA$~AX1e~bVmZ$``RK@#o8LYp z>;umkdB&b>w{hI|cWT_)<~p}pH=pYu=LN@!Sw~`@UM-8GqF-=Q=OTk>-KEFPu3) zv0KayS-wGT_gngiu^^wweU%um*Z(HFuFos#i@zC&E&Y9v8S!^Rdn330b}(nI{gyNS z<|vX&e?OQJe_K>Z*nf1di53(|nDu|9%QjUyoc7|K{KAkAn%KPGiurjQXFI^x6|2JasNJFu^jDAvj1N5CF1-ZI zafq78`>_4lXFcz&)*Ji2C<%VM$aL9I4{5(+#9xu~-6s29waqcNS?Z5*dk$0j4p^fl zSi3Er*48nu%Mx5aU)!PU3H!p$kFSXmpyS&Kp3g2;z_V3M;ea_Evq&nG;?solnSD3P)=sQhW$@{dR(D;OlY9^&)oWzO$cdwab0L7@;SMbjWilZeoK7}>42{Y+75juX=3voMxYz)E|$KhOnp}= zqVtVKRI&Bl1xA35pZH494%z`Zs&;UoCydYQg4vSb&dMR}Ohi`DXLo zOB69851n{dZN^8RQ{$+b(-6#!InDrkE6%uz@@Y%0uKugOu}!|U1w}H0e+%-KC~DJU z+*LL5m>AcR4?lC8kxTxKB0o5WP9O9|pEH543v#!h5Bj3dE_STR(A<`&d_xP+ZBOY< zYrwj3jaU=bk87F1M~u7?`PePinKd;f?x$?y+k$(Mdy;z?2}?e%4f#{`=!0=FcIJ`i zg6{@%ENaJ?yJ}IF=b-BOHa+bFO?$x+qd8Kg+2a&@L4DGE03M#!(ZD?Hq5D56S`aGhMo=v2Lt^Va=Xs zikYpo%S_ofy8Jha{GY(}`Uza?OqHGCIrs@v?cULp^F)al@)M?>A^0Tsr#WYR+wS{Q z*)`4#?XvwhI<+#N*2-;eI%{_G+t;IC_)y?TqKxZQUHVbo>6zxqTdaX9nz3zzjen>xs{ta<1OkJNyB@7rprF5@)^JD-{jD{zd7UfBijpd-kHna5;N%W z_rucP2QUKvy|DjiEyN7`H$jnYR#jWkgDPpZ-o>AAj-~TPyw5jDQ>4FQ^KO^u(oc$f zu(TI^7q`u_M;}wtZ0W$bM`B4go-eW|rgX1q`=j<^i+nBc7crGvg==Ox9{RPvDQ*}m zWACB})@1}$@?>kzG*kK$jlDG}lJ8jjR^s1GLbvZGd*r^7x!tS0s#+)HuYj>Lml-#r-ujcTsAFmH<)kG0qU$1$M_Xn=q2%2QZ_aky1blFAt z-u8Wt?-kBJrQ0K+3ZA3+hNgH4c&2$Sc|Lhg^Rr3}R^T?Lq#n!*TRy}14C6gn@`2Z) zYh5x^HtfLb&b2^C8ti`)kHM00Wx8xrq#MuCa>Sf%Bk`Hjw!bRJ*oR!Ht>c!y4#;`X zWEa7;_I0n)xi9j)(PWo{SyfMTQN@b0g)@e;XU93-Ih*2);+*@GtRN!H5|9JzeYdE4Dnlx?CB7#aV(|4nM)|Y>Z2Wwy0rH)fb7uI7XJ|77--Ei~d)J8XUg$M^ z_7gkfw4;4XW1v6AHbhO|xIEsjI5~jMIA-+4x3?*JfDZIaUyPS_uFV>*1U7v(_5GEX zYEz?%t$I(mzf(G&_CT#AipIpa82=Wm17l=PP&HQQg7Gje#>bfXwq}}igAaX*Evmlb z4N>?GC%AvU?ji0Y(b7H2z2&vb+xA+{Q!F<`zCuRd#U%+uRToe(LCy zVLi-{o@ugAcKv4^U&*PQlP=#YisGNZHUAFWTbU~RQ@A((7T@E@S2V>lMfShV)U#;l z%la$Xqm5s5ZSXs0{gnNozcUZ}-F3UQd8+^9uj=O= zPi^gg*Zy7Hb+SC$`aw?!uz}#hu&o0*u2vpF??nw-M%b|cd_)oKjNK# z|Izpb@AXCWs<8^cBe(RveCpVyNdFYtj9@3-zE`p-{}n~?8SmCz66!ZtoCO!2YPb*N zOfU|^c*nt(4Xib=?yv%`MGL0n7W|FIbu;+L>1soL`u8}B>?ZI(LHwl1=Q>sOSc`Gc zWSg!16Xu03pDEHmW$AZ}lic=<+3sqWsj^QT$8SAL{IKZ(W0FQ4kQRAYorJJVHXiK4xLJz*zmE-l3k2Ip`%en7rPlyC->?<`4!%Z~xvl%^s+lRWZ=CbNW}Z+J_GQ0) zIS)Or;Va$kT*ork;F@Hb>>{|{Rj@C&ph(X84_y*!!2Zs;!CntdddB`!PUV`;Iia(P zGmCTVJnJ~;eAZc#H{*=soTDu^esZedtmGR+J~Qzf@>T*nlVe$TPEEC}J7&3}zU?d> ztIBB_C*MqJz;~D;AN~=b6YqQnitjRfS873#|e$DU-~ zSI!(_r|)m<{=s-|^=W6S^zTAj+93aiPiv6(DNFBppFq7c@6$ed%zGa5KifMm%QMIJ zIhJ?!_;8-@>bUbuWV&WCk-^0!zARy>Bhcyaz#J6Z*;~p#TKkPjD&4V z$Ch%qUR_MF6I@e6Y$%t07$fWIb*^bl#9@jZphF9aB=fLDr{-53)m;hq(d5hQ_}u|H zgDz=`bhG&F<6y|XQ6KzgE_s`IbD4rQU`>`_y{2GY zN8)Q;6(_$(JKCK2wW>}~bbYzTutn9i&)@^%OKqxfpIv&BpZpaR?G?bjF~t_%gNF7N zSb_I5?{QOmoMHb1?}y0of}D5LU$wo(w)%p6rhKY*okQ^5eA=vl_WF zet>gtvZgVd@f<_{_=>2^*Q=x{SY|s_hATHcY`D4Xge`l{EV} zKR8!-!7v&4+Jgd_$W0u2jV0 zd({Jcld59JcdHhXZ&>(p44)xJTk7-uitklZRP{dul5sRa%#!b0jH?~sgDt9ZU_{P2 zM*6m!a(v z)MG7%*y6l@@i*ahq8-p~3gWa`f@jz?=`i2070*=d73>#9`vtIPOtBA|_9pQA=8_KV zT|JnR8T_=NW{G;VA*cNL2Xykj{L1;-yEL8=V`3cC7zZ`lQKyL^wqWn=2l$AQQx2MJ zIQ=u~cS-b3PMp0ygDPjN*zzOsFHv-san>QRPydvE3*J{w@ZX?Krpq=(x}m=eeM7y# zxM$ELO_2^u>jrr}SxaC&U|eYOWy;0I_r$FKiCITx`eC0j;!jk?p5Q)w;=Bh>yQ%HT z`5gV!p7H$99zD}#|CFM(8J<6b-})=r)#j(vzi8_|*AHsYCPS@kKk4LsN>%PBOB`)P_Xrm+C}9(SMPTtXp*qC$ISpe z+daj|Ipg@Czn~~)oHwH#(&Jbf3ouTP7x@Hv;C60nd#ILa(w`uQ81iJv_XK@2wwYkP zGhBypuw~!i`t@K*8vCkzJ8Csa`eh7^xh1d}v&UAHYw}o$XKK`=Ej60hx;~kF-Ozhb zB!T_&hWArsO>F(90aG-7-#A$M?E@b%n88lEeOb06-?1&fu@8yQc8j(}lKu@gOtFb+ z9?W?P){1pwJ*Qy3xked$tSfD3PaW3d%a)JzC!g!gHD7|~8=9CGcKC>sztz44=&q65 z(x%4#vm~LU{c1|zf_)Fz16RPl2;L`=^Md1!*?uLRdrI%=?cp499Ql-E|4>fGe#;!Q zzSSn<_W{2fbiE(I3W~mcnI?S(Tk@yW|3~Yda2@+>pK^ZKxATx^ji1=LUe=rHpA`AP z>u1TDnkqeC1FlUAiX`~H@jX`KUiH0;wC|L}hG+?%11N!?6Wd5XKgjWdsu=L>-5{3R zFWEP8-(CHiDLu1g-^lYLj_tf0Lm!DHJ(FYBn{rNE`;_w-Z z;v61vmZ9S-%Igv1j57EfM`F`KXUZqA&WxN1WWEcC@7r>U;c*dy1XFW?szCeba{iX+K1vj_T%q>3eH!Pgv_N z$^m}bc$_oFNqzdEza?0QEm&X1+XHmQi9drc=S}6AEj@$32k80v@V(2uo$q)1xtGx$ zAIhuo>>{^pdc65X{)F!Z%cng2&dGA>K04|0-6${ojv=4$v+$`Lm*)vz z{Q&lacMRni=h{ZL{~>rR9vkDb?ifCkW4E~TkVQ=Gtr>iuK>H_#VwtDB(_fChZ}HQI z?RNh!H*a@jOUkraqCrphC_rBK2!SYwf+&cBD2UR}%JRovH=5XcXP#4Ex6Q>ecl-~6 zhyZp*ox1sKbIhmGc~5e@xPJHXlzX!~2YLFl@11_;zG@Hk1;kkYt)#|p`E8${7 z*@n-ySCY6-ah)8$^>?StbP^Ae7(WcL1=scj{u9UAKB88WG`3mpsN>vEWRJODlH7~-0KN=yGuV>( zchC6Jbqnsb;r?642Q`?=FM_f78j!R-F8l`DE69T?s58=>73tg~;C?|(n{_c^%WPQ=5BG@i1S+~TNnQDW_=k_V>f08Y( z4+-N9TYhj{lMW?#)&J@1aZOe76`mjF3aVgUnM-K@5=C`0UVD)}sFJ&CT%w6`Ftv_n z@Dm5)!1gSQV#u#z#9qAFUj+M1PZa53+mvpw6~P{Z%wWr8-(XKVz(zbWb+dk|W+qb?E`deXibg@B_!XY@X;S}K)=|DTk-8`O2-CO zesYN2g0V4f#sZAX(8fnx5#&;t`%o{n0P|drY2NOY9@YxFen%bn@^KLp;|5&UJgr#xuxsGvYZ~dX{+ZdH$!^ z0eZ%Ezk5jAh(pIVf+cD4`~ReOtsyYQ4mh{6M!`z3c4c~66ItI3b~B~34i2$HqGKxp zKU6_~jH3vy3Ah%<)uRqIyk@h0v##fTgEn?zM%3pTs7ozsdj8Pqi~986qV}eS_9phV zDZDpY?y%cW8{ZJ?0_PUs43n8^KjF9N`mWyk*LzS_7OD6 zOi>%A<~GAzn=1VsOY;mlF59fXlS4J$(G&xYa~rvJ&u`?IQ+NEUe8)`H&vdmvr6~4< z=h(JWr~Qf{|2wK;PIx!|gsJ?KCY!M@x2sY?3Srq?ok^7~O+?^{0H>%TdR6%PBw5&eHZ%68A)r z?}TUk6Q<(+4Z7+=eS_n&oH3^JnrOENhU_MdKb6$_ zY0j(ouJQd1avz`6tMNSCV)0>r6@z~g$KL_pK+GAp`1@5bBu}brfZckQTW38Oi6*@y zXqypnThev9YQqZhb=Za^CX;+)~aH#VCg&zBhjRrt@H4Fpvw*=SUMNO2&&}C{?dE|Q?iK?^eUgud_y!b<198)38rIU2A)DFKpI}|a=k@uN{l#9eUaa}- zarpCI_Zz*b4$NRnnyPPFFcORJT%zbZ8uVaFeuCpIa}GAQZ+`pU$*nq&<7~6czOLAl zBAf9wBgciCy($homzFclb(3BM_mStI3Z9jbXwsnwo>87vo?D*Z47T$b=6S~UM3E23 zJ)w3DcyA2v5wxHr-M%fGbE)$w>lnu^?~I}C>#*!EuKDyi=T`UDKel>-Yd-OO++<7h z$2!$TeSkI15M%w6**BDPqseZH^jZI*OF~U7t2 zC}Qdx3ZP?y5x9NIB0o7*41JG*7IH~qH(ff+fL!V!UB@w5I-b}ec7ir`;^}Yen^EVx z5#N#YEo$kzROh=F-?{h(7EH+U||7}w)Yw`}z3p%-qU7Ba^C-cbMR#)%(@yTwKN0d3T8R0Yx!W=U?bSl{8CdUl^i^{iWCo zUA7xVc34$4#O61PXkzJXRmEV9;MwN4h<>v`T3Q$x=dEWvs<#TH%*Sr=EpT4`82 zVFs+H&=Ny>W~==QuhW)Y>-LRxS?^!%ZT2=*PFglD-R~``Vx6h=kN8&&ob*BbN}<6ta|sRS$bBg^l7M>h7V8vD@A*W@q4 z`(%&3m2+aI%8rj%a2$5?eYWI$8M(x)0R857jQwW}Z8M}dQN+}FHiIog+$WIps!py+ zTo+aFU7*Fcf}(E)(BqrImc&mSIk`sGTa0l??gZ^3=xd}ts=hn%{i($FE52c&W5dUH zDOgE&+vD>XXN;FNK60vXeI&W~x8PcRz0Sc;PNRRmkLg<(-_Vx+-JPk&_d~|~9k09< z^=I^f%(b`b0)5a|k)IgG!MtRa+RXO}c5rNy{zQ)1(HG-koQz>=EG6K3AlJfm^No=2 zg?vjyG7s2WjGcVyFm~#?pDdrogq^r0xHg`NDR@4b;91$CruQ)GyjPhO?<3OhGO`^^ zwcoK7^8{;-*PW5pAFo%eT`|5+zOEm3yHSM$BZ2oMU@a3~be(;QCCLo~ddZ<|Q-K zK54RLirRqfrrUOtIp_4xs%v>em;W90AqM-&()00(9RDf~-}nVpIXAYR-B(QcU(p`? z*fS;CNW;5hY(v_XW#>H$YSL{xB{3%ZZ2OITS_^(_A6i>}%WwO1-n)Jsca!gOo31PK zDn7^kExoA!8?KMf`u_y}M9r%v0ptha~yw*Mx_xqm9@+nlwqW3&I1TfQfV zeez+?@B7)#zO45T)|oRV=ihYqoyR&}T=S>C7Bwu%%Q|h_PP^Nd#AUGmgq)l8cQwvf z+T>WGpKP7wvW@nQTTHfPeO&s4dyskk#=4gm|oPVFryj^G98~OY@bk+sVy)fgf z%lUSF$(Nx0iX4mH#IC9-ir{>=G z3GPW3+#j>0zk+*ee2pbc0#aQqSu>-E(^Rc8ePc7z+Ib<#kZG6O$ zgPdv?&HWVI6QuR=C0E?z8ItD!`&L{}(B|IaJ9)A@ZYk${rs%Jv^^CP`3)Vc=!X><3 zB3oighn--p1=d|y2Sw}hC$N6c#FqY3*yF|rtRsf@iN9MRdtyq5^BY;ycQP}i??lx% zIKYPNAL6_$ottg1bmH+NPd(d+$zw(zLDsRgphz0e3$iC_(y`Od_+B*K1MYtj=QG4} zRRzyo6Fj3lvpmZ@>%0C3-U*)j9?!X-Z9muK!+gO0BL6F>chWDmGluqA*L`M5jB{Vr z#rJPeRhPA)#XK|DrpWdRa&F|Dt$N^^ZXoQ@aGjk^|r08gwQf0q67*pqp&lvXe_4>e9y!xNaDM=YhC0mOQY} zvdBhlLtiyP8&=qc#xwOjYw6n--?w1IcP_q>VW(|q13tjM;v2UoGd*nyi>ei8QZ4pJMcSfIrLsX;TU9&S!zGQ z`U9**FhyRYTC8J35_aHq4+%Y3k~i$H5s#!+6ZFgY@|e+Ez+5mV&_xlj zB!7a>R?e@=2kHWSKnsdwhA{%;g({Y=0miZCZ(SeE1lOD?YBLv44EbJB<%b>f>h>#n z#?pSrRLoCkihoB@%x{@`mdHi^30?W%8o6!#lvVLRW%2xr5i+;2-DtAI*87v0YTxLx z{}k8xG_RyS(4Sa(@Baovb^a!diF!yw`v(4-{bs9*HTJvxHzez-`AI$J{8YM+pXUAT zc#p$!qrXPjTTd~z=h!E`$NOse6nC?e z<2>s>ktOA$8;}1davXD-+ti_r%sMt>yW78!RrSnI@}2V}T?@bUQ{sE&C;sMl-YJQ{ zamKlQi?yxmZ&*(h`OM;PT)~jcRJBjC-})KjHvW}xpXa!2zsXmzx%N$`e^Wz8X6%0@ zSGtDZ=+v%a>wG&!6GbeYX}hQgi}P&Q&|w5k61L8`Q}m!nE}dbAsA7M~A2dmzZ5>%& zu&U~bE~*%u&2$E;oRPF{G}aNGZ>_&ezg32Qrxbp#=r`pQ=-9e^O}}ThAa*1cxq><~ z*pjbs9P$>&xPAlIWR~>5g=;p|WV{}KmkvdE9?)&KoU)lMoqCL|>ppNln(hntW()2W zyt=RW+QC%&6Sm?Y$F`_dBw@uEkQw{z@A6l{xEX&7iX?DtTw@hnFW21!b6`9#r|g$` z#8!TQ?}_Dz=e9~_Y)7t$L7Gqdd>S+MDR#gd0P_LN5A!rHFponp&zYTU+=nH&KP{M& zumx)gYsw1H4gMy2)JLwURbm}={nbLu^ z_6gQv7{QhV*6SNxcC)0L+un8Bn)clrlkZJpU*P-H_#sR2Cwv?0VkWk9X!_oEGGzNH z)Hrjn{e&~$ZP)Oa^L1G7x|SP7c3@7bV4j;O!uOARS`)PGBhFwWj%TC^o-dw1o=x77 z@8>t3Yo6^Py6lDLRuK0@jpv$r=QI5DEZc7Byux#zpeAJ7Cwf;6$g!?9(s*sl5`TSQ zYaU*4&XHrWH&MiGzL|5@66PE~afbG9pjM`;ZRm@83@uTl!>s?IC*5{zP!qJLaETGSl}*zgfU4f2sNL((tbaY};oH#XB0GXiw{MXdN9M4MQAr=J+8V(L2*-;_$Q z^gRt78-C)be4UmrwA2Cx9Z)g{RAJDN;n`>Hvx#=&!PaKeQ zg1smPsDs3|)lV1nN#Elb1Ab}|YiMViYfjaKec3Qb;#eju9zD|_TRv>cIII-*>{Vz-|>GUhx$FKvb|<`{~jrb$1+hpi<1PQ8*{@0}se z`cGw5?mJj#{sh(x=c9i@Q{0VDa)}}LC$Yr;O*ZFB;hJt|X`iKixosV3|9`dg82)Cz zu6dL0=k(cr${dF;x3fOp+++NPJnJ{PmR!0NtNwn zSH+7FG|7{#GgIdbr!&shT2okW^lY(K4^c$t?-~3K)NjQeOvxr2_9{OyFaz?)-=eA> zHJ70NNs|puJMo$9cYK!UFdx{mpE&lkpKZsr=)a__>Z6 zY+W00jiyO2i4}8!4V}3$Rr)&|o27G@ckV?KMeyAE8TNCG-4Hi|EeX`hx@*6aJ+5O( zn$yNyOu>9HZ@%|=PO)YCRvVam?#~KI`}En*z2%u0Vh8BZL=jUlzW>xRL%LbgfqI5K z@(uMyuqA;t&UEQ!Ne9-%7EH-4YOJlSuj>Pf)?>pOJri5H*YQ&ht?RH3ygzxrdMBs$ z4=DO(1uZe8XZF#~KI_Cl)weLy(3#&NnCqUPjUE3Mjz`DVf~~mUK>eTaNguA)<2nl05Ui^C3vRa4M@&yl>0n!t zzM|ep5^Z(RF2d zYBMh4s6+iKdh~;hTA8i3A51H1mC%k(1I;V&JxrBbifYi*js>( zof_ZY&QzE0a5cWAVIP9pHPxUFkP9_Hcl{ZCtnj&yuG&yk6I-tBTG;Rv!ML20WtH7v zCw7P}sN-7LnwW|KVgVg{RqP7RoD#WPk{ITV>)OKiEz6<%X;#v0tI38Rc&>P!{7hjZ zjy~usgUt~0iXlI&g!`K6Z;PS#c3r^EI}PZWr8cl8yy7X2^#o?ZYZ8(*$_(kS6P&qw zf;A6V|A6&yik+Zs+qu4CZ$XhX)TEy(+&_A@F}9WpboU4NYckik1^F4 z4`X7?O>iC1gTi$kEL}5j?WQshVrx#!&1N5RMlAA1lid{QpR%gPmmn{e@au08%6eaOuZ|auJ(;8`x8s=+)o&ncx=!bOP&&sh&@OgtMR3;HS{Hbx zhJKUryN=&@{O04gAHNmz@5dT;V)_Hr*n)cOVO6zzJhw%CW$5=)a^92PRR3hi2H345 zci?j!bCIzfNh~={6v3FerdL$?Gt32ZVrT;~)R}_X^f7`hN&k#%3GSKi=a$aB9|6w- z@Lco&J!5~4aV&BMJL&e5JAx|d{?Tp6He-xMk})%OsDkSOuCE0}@&@x^m>*N6zrt~+ zbPjWV-UGLvBss=){#1H=GhG`t=3)!xu4xXLQ_nGy`32jTY~|~pdq1RuZP@L@mKkc- z06TGA%wS6b>rq~lh{+VS!L}*gZ0Y2VU`evhWxCpCNt)**}HvW5BnvlPMeUy^Zf~<6x`( z3HAT`PuyAKY@hXV?&))Gi}7Vy!j>i1!ZlXG+%ebOmwcb9+T2I(e-(Z{%5iUrGu(4> zh#`mPif8WCGl;(^t_fs*MzIaC1@*dE0e!R&A@&LCoj&(r*%ea-<6u0QrZ$wo<9FMB zOWPf1Su_v4!@xUTL!v)H4C?@}E;LcZs{csZTM!4gI`$FwH<(e!@~Ni#eJ7vtsShnE zk|x`h=HeAS{76%!1N*=Tnk1B9Y7a5CA&JS1qm8dTK)wDzl^quQmuT!|vA-4eHo-nx z1!qLyym-=ND}r-qPjLP;{*HmfmgB5DHcRIX<+Y$l!qhh!;5&|~(x14+V4DFo-A9%~ z_LJ;Cbz-;3*O9LgA2~(Pe@)OfGscCqt%go6HHO%M`@@EhJmQDGXJr=Ow*u@f{KS$2 zJ-|ln*7vg_hQ1d9-wpY$hMgM35S#O?Bj-WU_yBtqS1SGO^jem0{6oAd`So1k&Nw@_BF-9Jg~h)9p}0BD}RqM zSl)8o-&TFz`IBrP@@JOX;B_NQ`)<;4J=UEmX}0tX>k|yI4!o9mt(#icfOV1e5ylT7 zz6jSq$7bw%)eo`6RgHsj7hTUt^TvE|ty~MSI~bCgqIOpe>JW>LjTrV9_NXn`i@z0nc&=kf2TOdaQ+#>=P%2yGa0yj%B`Fe>Od3ag2zPPjES)_X2$06`7DFv zx=fLt;kto2IH|JTV9v~7{w_4xPKs>rm{oHwZgIB1iXqSbcd|!c#G#uSx~1(|;(N!^ zbM{{W&+|K)VlrFr!cUlrztLs?gu=V`0q6SU9NUN9<9AfWn7j_MW}GzHGWKWNlkV7` zO6vVI=P90APuy~zVr?&~_YPun4DFwgbAO||uUoEtmc&^9RFaeVH1Ad1yo@9Ffqv$b zbFyXogfs6|`&N(l=d|D6sWXPQ<8C!>@vm&T*59c2)YrVs^(VHbypuEU^yl_aK4hD9 z`z&q0Nn&pHKd}|nJb71d#P$4C=DOt4&eA&eA|_`*(Z%9#W}+vmbl5t3Ju&2a2f40A z{go*EO;c^;Nt5k{<6MWDrb@q&$Nz4;*-svA1Mif)OADd+5 zcOG_86=U2cZzX6?#gaoV^~M{BThR}B&<>{BumxkFA3(ohKXN6CuJsAxo_t+-ubA?~ z7ENcf@c=#`wg|>o1=nS01HKiYXX?==j(Vm`hZ(rvJg%j2RNa4`ffmqHlB<{pMWEU%79+;CUcA{$Dm_O#3dFLK*FML0iY+(D84_H5t z*qi+1yT($x%Wh~BGt)gp$2J6hVyE!BWSMQ~PY~b45Ie!TccaS=E5Ul%V(n!8+(Ffv zYFJ}WmTXY89z#z|>1Im@uj{PwE8#sLOYaZJ8F*j2?RnT{VoL|UdHs~ZcQUaK_@;&p z*~Lt3>91(|76^`U+tT(=_-ReQ*4p@bD#9_4r0xS@gVB~eHi+DM^ns=B0JZ? zJo)`Y?*YCW_=z7e&qeZv-}|mL0Nl>fzAC@5ZOTU777R&a+fSwQvviG;t^-?cqcfgY zc-%Ez%PZaSmak%0)x8iSXp+Y3IFdCQdQc?UD@MTHVa`3}W@qn#_CS%{`_)b22lA)b z;_L@I{vxQ+1$$%>U1vznl7Mb#FP%XFy#zj&Hrc`L6|qQcH9^}f=>}UB-{)q|&Y}b> zeZ$#&0}A+d18xu54K{pTEI}=5PO$}j0sX=X&}%>(@l8;JT2)Y+ep-Tdwz<7!2m6Y2 zzCZP#Cf&9yi6@tSGy77LT71(3^eLL+h;=Ttut7=C9%2i}qGP8H^pI{NOT-Y{HO3P2 z#hd}{Czj%YJV1A>bCD;GAs)zu8ZZ_Z0Xja%*l&BzBc}+i70`)W%BNqCWlG3f(^>#StQS`UxpL;ED>gDE(346y}gm>%aC?8MX*M-2WYI48|u zOIDrj%#dzuvm`d-8cp_0Q5)zB=(h^SFapL{5?qgA?50Xzy5<|qMW(5JV{6{bl%DBo zXYl3rsTcWQvFd;PS-<7rf5njhZ&6jt@Jv}p`Wee|DkhVCKh=k7WysHM^!5QoF|U|< zpFV+i?}-}kWtNNg_yt2g$T7BM*%Xt(m)q!@bwix?+;+@=we%Q1?RV-rPWr{>nEk@% z7|X8M|6BOYn4Dwzn`?0{@>PD0JM~w2PrkFp|4r?EJVUhq7QW704;$_5vwo9~%@RE` zFKujpLXUjpr?|g&@s7)qoZPmK{S`(2+nMsT5eKhoJ^AJ(58J=NR^6q)QRcr{R<&x}nAGF<2FydJO`k0>q-V0v zdQl9yhBm$-c2IRahHJ|#p7BJ{_0zUIb;sh*G__#{ZfBc)NbE&WuZt=?UdG;n5)#`E zh|Q2sEub#UAnRK;;Cwg|+xca3og=7;%W4w*~llzHER`>_P;0&4{8MqV$l0k#wDtRvK+uI~kXb=gMLrS_6e z8_>zaK0~5IP1rVNJHclg;mw7>OnQ#D6bfjh@Ha zy`#lV7FrW2+d%->79&ztV$Qkzxi9K_(6~*vO z@SN1Z&k)ZO&l%4h&t*HFPwdcxC0Ufy1i7!+^21Y1lMnD)|3ntW-(YMWXO+HmEx>hU zY`^)MVz=I5-s4r3_X7MyafX~5Q})bO8|Z5!XxnD}6FE{1^c9?Tw_Eh%aUf@qZPxMi zM3Mf)(wy8FvS+H=z#9FEDL=HJNCN9RRKdFby^pY$1WmF8Q+o}t@0com#opE=VQXK^ z*lszFc`_TYYi&Dd9D%h*06)@{xboMSWCZuTlWXJ43!#(DTc zkuAfwn;VXy#tgP3&?k%!@ffMq)gRxCILj9?b)E;#^H3B1R+nw3?r(aQ`1_>>Hu^L4 z4qME1ff}GZ*McsEMt)%1pJNXm5P@Gezx_RsUnlI&rjrLRb8aT;t|H zZB=!i;JNa%)ub1}vk6mR8xgZVki=$gIrihT{)udgHzjoB4aWWst}#>9HY?VhEYWWa z*>5!2GsNWfO(*6R+pJrT~-4>Q=3(Db)EbK3aZ zUgnH*t($DrP88`TkJ+}HY>|V!F=gKus>W^XCl*>zB!RK?M3ugE<{X0a=ZrHa=gj?Z zjy%tc`%8JE1x0dI^&@Jq$XK($^UTOpOCa}Rw*oL3D zC8~aNPQhyR#&5vy>(YDB1Kz8w(|%&hmvLNA zHBP z{3YS|Pb7Javj&Wx>uQ2)Wp0@R=BfyusT!bT!{>Qoj)2^Z?M3!iFxS+oVn=Q2-)OSm z$gx{7hMLq~!ej8bvYc@Z*oq+D(8j(6bHrRN!5lJ|oB6ELzy5)BVG4dPo1T@)TbYwrzTlt!$%CU8Ru8oftdLL)HB5IfEpuMk|!Q>zK*Wzm|_clv->yv zlFsjY=r3SR_TL3qqhW|0u!d(?-&eqz?>*p@Q$E;9qI)kHu^%qUlcMioCwwP+$5c#a z^Ih$L?{6@IB?!7=h&35aSBl=&G z#HQGF<0St4oUm8VILpT6laQbGppw0z&>OPhNRij!DH$go3SlR zVx8Nho04?=`$LE|E!99T4@_O7edwRi6;}_I)>{|>>+jY&%v$_Jmk&z7UcsJG1$)S3 z9}(;?T~y)y%5ua$SJ(%&=K_0g6-(#58-sIY_=t555~$S#wYQ*O`d<$ejS;e+c&Gt& z40S(&c;{PUBexwCwZYfWbsgX~(zYty^WwQ^nvbpXaR%EvoS!8*v~Lm{%iIl6RP6;8=&cTKsmq$GuV>gc9UL&zab)ff;KT*&{qcA6!>r8+k%`Smf#wJ>-`k+ z$cGxBFX4K~7PZmw*+13ZqH68|9a|Ayae$6($iGC6b`jQS_n@`!&y1u2Gs&yZ{4=m}XRMmR{an7sK zr|QxVpku?o64axO9je%hHMFrWkz?_V*rX?<`W(K&{5Sc|h5 z=b9}$KJ*p-DL-scb&lgaw?xrd95|Q1!f{CFAy0;ERnQOpwV+5c7U+p89T>C6&DfzO ziu4)71VgyK2loWGeoNF5itJJJ{#;r;_Imcm|;+o%Z@mP%HDU zu4Ak6euCetcXI08I%&yKk6g{M5Hp@2C8<568C{ z|DQ_N`?uvf=K5^s{@W3I`tU!IWB*ilAJjqrw4U?c<>WZ)Tj#bKjMFvW^=<$7aITl)jMp8vpQPhn$*y&dzq^f~NkR$f_9;iiTW+=a8=mRXe*^i{ z%D4~9mc~}3KjFBcm=k=bZOdkS9hT>{*p74#a)>5ooJ~1va=vVFzU*QQrEB(_XZ|Ik97KI}v6MAbWY(q&sg(RDS!^_^_4Iq}X1Z+}a3OO{@LzT*^6y{_C9eV}jQewO@I^@yJrutN=qKN+&MU`PG=(2wi5?mYVR_$`~R zbBHbYt=`27_+5X(x@@L&*n)MN^&6I`$KM=S=QBlXKd=XwAsu!i?;qY)m2ZFn-{#Dc{s~3@X3*q#>bPw`(l-1xU`&j$i4rge%t;l@8}nP4d)+_o zZ+;f?bAYeK{pMa5?qA$XByH?n@LY_DC(m_yJhzs%6O+MC3=(@2-aX#AB3MVN@Vm}C_zC2L-{B`oOs?0DHhwts86zyg__>am zppB21CZ=i>!Sxuf>%=}w{OC(`<)2XZ;&x$zP#ryT|a#9xuMe@f&JNLpJh`lP&@b4)iy=C@ih%y*Pq}z zu4{=s*R5&;b2?*wOUU*{62FCOSYpH10(96RZ7<=cJ%cTIg0G1pcn)~ppawi6JToOh zdkDuMx7sJfKsz9gKIoIa-M`1MbMp)hG6cy zSi<)jNgI0=JFa7Q1qPs&~bVc+ZgQ zz`mN|U4weiLSiS5oFxk9=MP~WoG;k&W%44y@TT!Fqm! zwcZTrW=m&(=)sZ%?>DU~FPM^u_d+8E2o7&O1vwcFuM?_&z7$oAj=kyE;<|F3N9lf-MSo~l8uaas51%een6&oSQGIsT`5Ki1t> z`NZC+vYVS9oBf|i=Ki-7^_6qacJ}>S`Z&f<`?US7zFqrCR`vZic&dN1uejcu@0G0{ zVhs1q zHn(#ub=~KVzY}v7(|?SwdJ2Sx1@$IQsda?8fJir)L4-u-##ynij;qw`rG zvg0$fPnK-JJed40e9uqh(U<#2KWU1~jL5UhzO1{BYg^h*tZOXkd7M+@eh25Mb}-b2 zY%gjXYFf8!;YS+#vcxyU7Cz7R<9v)AKXFC)8hTt05^BJ8dk&g(W1HoY@5D8b_$+hW zNHv*v?m-WVBz(Uoyeo+j?*z|l4gEu|-qg^qwxuxbz~l3Gx*SpAfGz0RkI1-*W6c)7~;tz{}YG>@?a#YblB>12I$y` zTY+ob`kSf^TlhEk&|1rS%kOl4vjgjK#=q}dtl6yFJK*mDz?!}U>w61ktog_-YV1wk z6WAXh?-}eP^TL+R@U85|kp0Gz9r)&UN`Hsz@_oWo+)4h%_oTndpAgRt&N2>GuCs~>Jev?sWVUy(2MN&ShK6vTh?0E zSg*6xT0vE9(_`-Fuc%)@&)AM^iEKxAPFFtEfPU#8n)u!WG(Pr|!oH$$a}7)9xUIkM zFa3>%?{|f7c>0?Sf470HzvT?kUQpzNslWd~j>X<(s{uMO;Jl%jEf#+(QvX{Fji;n< z4~+5qZ%tvdud9vD_zmqTTAWv_t~1{+?iu$LdV>4tHahbJEtrx(y9C7LJm({;V4mp@ zT2Len=iTElI$@fSdu69J;|mV>Hu}SC>PFgkhg^QVRRTllXOia zKK5|-bIvy~qyzR6&|dOQot?Hoa$f4`OoWaN-xgJ8_7#*kvro>Cg0m!yH{`e+Z#%W9 z*+mKHa|Db57!TtzO?pXiZHDVGRr))&=Eg8j&|XmFyD`5sz8}&Rf1}F&6IT7_S;%t8 z_Kv2QC!A|P&)c6eRReBy?0YA>@}DT-L!RV!4WHkC+dq*btszyq*BaI#)-Kk=@3mHI zde!>Rp0Z;?*_3yv7fO``G1R|S|?L;2Daz6?Us(o((zg1&+VK3D(1=Oe$RT2w>PBeoB24J0JS}wEn9&`i56APrjUop4(43uk5bziClWWPn>7F+dq*#uH&~k%0v<{SKu-DLa4S~QgN#Fp=*>hE)}@ZaxFIhFS*MSmN-#Zt2z`0uLxoiJnj zD@iTW*ORoH<1ioE59!jMxNc!X8vfSaBXa+e7vyw$5;wsW$Xs8~8 zcYlXI)Lw!$!|OLxapd}qUan4)P z-4Ek8Q@YvGSyzEI7FL3FxL=sELD4!6tmi{)!5Yt6zXa=ki#=dV*0eXUPrz-@nc7Fp zmTvfVHr}x0hx+0>Aikf#w>y}L^BZ8(zx_KQh8%LM@YpOzTuYPW8hhG5^IQ$h6YZ%v zWKNmq&HRh1`^Eii7m93M&jHksxsA>CCEfI}@2dF@&>_cDpMIy#+}Dbo^hVzv$XG>Bxf98Bd@=~wTXujpgZ3&ZuhT$=D~B~ zIYMH?55%7g)h*O!O=MqUf6$uC8e9@B)?|FFyTo~&^*THb-&PDYS$E$-EcK|t+W);* za2De4@A_K{@OKyN^fwioAs>3wV7x>5cl_=$I2YEy&yEkXMv`#;f3s&|VY(Z`kq zw_D09(od$Y9eTiAJi(lrGsgB2bBeUyq;EZUNP{o4)IQ05WS{NWM%1MKJivDL+fw{3 z$2Gcgp$MKUo?kzMme^nALL;ilixqpfs zw5st5_CQz%=U#cX`H_?8&&qdiSPL*a#UUS8=CxP z>v_#gwISQkZ?-e$)+X;2UH+d?6#ofcAF><|=hPZ&3~Npktaq%}-}?-ER_saaQS3FW z?IYH6)^^&Jy(sn}ByGpBX6N-fuc;;aoDXZB$MzF>yUw5F=UC?C6^#3*w7BL^Wl{W3 znYxGn7L0}3KONI2@lSPc*U~ixxcO{z%ul8Bk+<9@`z^MrM&^vkzO3iCY(I74PTME# zPx4lre?F;4EQ|x&v;3r>>8v+I4Ytm7PY`3@R9q89ES=M!U#POd{!%}pi4x5E9~~R*E(Yfm z&H;K33hx8&u=Z`(0k&5VM|=}SOq~q?-PqPSHw3mJiED`=eGB^SVhP4~f*pv-RJC(H zzIEZON1Hh3Qv;lTi_f;E{3oV-nOj_md}Joa&ZAClZ~1`x34VXkvFBKHY~c7T?X$FR zL~XZo{*n(0zfl9)LlDUnOO(r8f0|+VdBX2PL4U$$g<$ z!Slel7~eaHBZu52+y|0AVJ5cps=0avbNR%Q4?OqCF1FxZuT}jCuMOyibzz8o0ehyY zT|`$t)P!wQHbX49{Up0`Ouf{+`5b58RG#~5U;q0xd7bncy0m5j>+3ttxT%~iSl5>* zTIYc^e+u@1A*%L-DT?+8V6VswwRhlst7~t8^?>(Pq-oO4l>WrN_^@U8mS+av_1-`X zRN*=^##1D@4z4F(r}d_5XMUKc#otg4n8PNRSLV8D&MWs!_m}%#1N13;|IrOTgN>Lb z#s%!g{wP;>?8K1Ka9z7TYlHda(w+WS1UHRZ9-E&Uy3@^=&dZqMIZ^mi9%(kp*k;cqMfaaHj>?~hUA zJ5D*@-|6%%jc;#e>06u`Nw@!$IkqbP9n_>o)Azd?I>++NJ=;~aU~4X63g+=fk$q{Nnd=NTeBit(eGAvO z#Fnu=k0JZfXV4@|=-9~_3D?3mMH59ZzAAYBc}50p6MYSJN_9E59E#j z9UC#kzN0Dzp5pAc^gBU3{m|bKTW~F1a9zkuRr`!#9z0h|^93V8yI-C^KU+DzD%Olt z!?v4r9QEjDUf2&d>`m|NjUl^P@m?3b({R4mx$T%N$){EgXycz^2kZr2=d)b0o4l`B z&pve9PuaC6%?m|)mSO)o8M46+s`k1bu&=WRwtzj+Z0S(H{&&2`B5(d|%duHs%7v27 zK0X6&**M#cU?m;4rIcg86$#k2yYN2|M!SHrKWd z-IJ3l+sUr~+|w+H$?a30k%K&S+itQ&JU(PQHNI@)3_Wj$ow1NsJ!B}HbgI)7a2Ir7HF(oO{EqjqMfV z;>UK{ZTl0sqVBB*z7yiGA^#RtwLZan_$kT7mTQa`HDCF1jjX%RYn`YDbZ^v10 zrL)~m=Qy9|kl5crToXmiINS9&r`3?O(bt#OSFx*N1N0^vU|Tv@uupD1SCjQG=;^&K z@-?2j2goCy-0!o3_IS<$O|a*Gp9we@gpM7*W1jp=F;C?D+(%I#zp-T*y0RoqJ}=#Lo++@8 zJnGN~{Wih;Fo#tzr%f=|g*n#!Gu+EA8{mg6h$n9eYEY|0ZR*p{3HBR~yZNw{V5$$G zuNu%l<1n-VKXE5hHtJD#1Y6SBhHMYu+ZU?l7?^9bq;nr8@3|Q9d!tmv2-bm}plxjT znt>b_>?eLEXuqP%U*5nPMGO)czo|*L4Y>q4)SqGtk1=1v`ciDbn&~xkN;h5l4g9Rb ztj8nRlKg&Wz4z~Xe(TSG{eZs-EdE9ie@EDo!2SUv!QKMwGbdBF6YphBd+s>cYX1bj z>p}kk{uW5zO)$PHR$LR;=j+XK#$0sCBAA~M`zLe992SxHQRbZcGIamAzf<%NA(kAT zg$y?PvY$R~koy#u>t;RI$#(11gDJWw37%_{?KhpcA!>jQ{Q=_eWoaFIW~u#C$iL+< z_S5d`Y4Xj3tv1xJe_%e*k%l(x$Ze?xx+&5PV_=N%y-sSaY@&#%H59z2R_U;`24}w4 zl)> z>$<>R`M#0BhOY#~KS4c1d<)RYu@7ngOmU9KwuQ%mj;#f>`ToH-5B~P_eO;2@e}{^; z|HfqB;@gO>y$3^Wd^P zpbj>C&_AFk28f*(wrqyJ=mXXT`z#&9wKC5z1@kZj^V8pupDpyIczE|LQQP#$K>~K` zOS=1*s-J0>XFB`0{F!>Z+r00_@4DMw3v3@puI)(Ybk&=|mTYluXFq_I@ZN#!iEMwT z=REXzz@Bx&UIrtvr9+KKmm0Q!l2RX*6MCcc%R-V{E^bAF$3e&^ho8ES9A86JADBssT2dx2w!cP_F9)TWOX z6iLQnx^$?CrE@TF9?mqi&DQlj!L|RC#(Z5UvYq6)zUf`}!c^%eJMLYU#N@X1q1Y4q zPI<=N+D-LLk^aQgdjn7YYfy}IUuWh6KDMPW)f*tD%YfTBfwtcU6 zTJyTrL)P38YcK64dyO@acw#5(qwqRfqK!SruC!*-Cw;>d`vJ!LI#J$6HavXD$+twZXw`J8h4YjRjx#Ky_w!fiM_Xh6; z;Isa>CAB}reG`vvX`S{fh`ZsOn=jjlqmBFxPxq?pexKynv#+84!FFP;-(;?3y{A5x z^q-Q~j;41dGoy_>*|NQ(YAyX!y8Z?THDTK+Cx0)Dc>8L!k(tHc6#o}>Z+9-qu3KC3 zwWaR9Z3636k`Xk8rqC3cLQ`l;&ygGR2%;F7xoX$Z{qDo~00JQR6F~+eqt@ORhHUWU z!=C-l*^hQ-DxWbfTe>asHZk=ZXBSK7z$zwZL$P$0>tgHdHbe`GBut&xp6DNZRsN;3 z*N8LO{-t(;DVgaimjvZ4s7ns?RTZQ2MALn;bdR^V*M}r!?qA=#Q#w$t0zWjdbq1J% zvp^9m&Ig-AdaTI-wlh^jczv_PGz zGE2Jg^Qy^J=Nk8dt6GbpK##t{g56njqJTsZE40AXH zb2>#4%r$e~bRBw?Kdw8ns=P#z4+%X{r5o%^OMRfuhaMB>!3GL#M1ro3F3@nyXw^d-7M+k zo??r$FScQa5iCjOi8-@GpJEHHudgv>?Af>C{>b-;@0~1rx~FWXE{ubAlpRkFn1Rcd z*wK-Oec^=lJ<^)@K4Q7UPmH@I|*pij!YB1v2a!UKZmAU>>&XdCP zJHd0!OzB%RJ@0^L-x53v{ai%O1ZO1Eq??g++dj#nI$gP|pl$jB^b2}OZ1fL_jk#o= zi{_kbJ|v55jD6w{(2Ky1?7_ySe6#-5|Dk^DpmWaDnX|@uzQlQ+v+5LEv^c|Zj$Mh{ zdG}_c4s}hFZpcqN&_xM27yE3?`I!Cz=VUXbnXWcX2*9E)vpUM%}{#`w{ON@25nakFna=Fgw|5T6oanMx0QDpyJCeJ3-M_PZE z%Q24s)3U1{H{P}5_@|gp{-=6RcIUHvihF1GoWGmv>}wC#KHGErspq(N`cOMhZ25kI z?|t9vmi^dYR_r54?}Im+eU|OApJJO2cE{t-_%}mKen-sA%Wsg`pY@u4gM8BSZ3t0l3(;UWIy^A)FlVG-7joKzQ(zN>#cid>i+Dailu$zMw8v|K$9~$=W#J~Hs8GG z@jl7Brs%w%iorXp;P;OmPzHQvNhcntYo>JawF7Jh-w<2W=mR>oZ13{X$D+PYL7(aS z5{v^0*@m5Qo=n-E*zz$~<1&xTFZ28psPhTun~J?r*dO9LXTR%N;vWqp@{EI~atY93Bq$fbSkAF|eE5kQVhh@$O<00HG%G##w-l)CM(soN?jq6)tCyu&Bduxw9mi>PTo(+q4`gmSUN$~FkJWJLW@Z9Oal!P5L zJ)eN*)Jpgn_Kl<-^`Rx)29h>=qDXh&dwl!b8rulaT`tn0g>QuZnjg&*^R+d1Fk>Ez z zv5tR;9s2|O1$##g(4okmS+W_&SURppPRkkl%$78^^&^%tz83wXpYHQH2FCLZQ*jy3 z-zi&~=Mg$GlVhygzZJ{%GhEM1wp+J!T_lX4NfyC4nF|;pT|Om#Xky2lGp8qAwi5W7 z-Xzzz20VxSTx!xg=MxTSM^NXDllyJnPf**W+g{JRi)1nbN6CF8WLVkS*n= z4a(R@^q+pb%Cb|xC{LH%D#H*3pW^7Z`%ZmqQ@Mz9UDu}`d5PybRIWwL!IX{q_?zN@ zcte@IRS?%`M|BGIA{Y74vGv3zM)A}of2JI5(GIqm;?S}0fH^a^4f#xyUIgu|)K~hn zQvVnOe&UFyohs-nKKkvx6L&&f=Je+>c@1{zwZQFAMz@^OPaK;iFhukmv+r>QCDs!Drup9e^ z;?{xhpC?H?^`He=$M)1t&e?UZ!VFl88QYP>Ww15H53wJ>N6ZO!*n#&dWEV@Y&$sjp z*wUfu838;mGE3!5_GP^u_3T?m{ah}3mKmOFFcVuk@a*F`xP+gR$R5lj(M|bP{&Bvt zj?cQKg>|9ES^1QXbzRFAxoL|wp^FmGpCRgj&(ic6 z+CfqI6P&$oFc*-0wprq{ev?&x*qYnSP#IcKB&V(s^Z*?@IPOnN=SR}sw{*qcsIvcr zrF+xy=qE$APc+5A*1i4?$302coyyZi5$C#OZNUiXvL$N{Tt@acEcxC+AKVYe+4eiK zMZR3ud6C$2`AP2&{l@;zM?S|`Vl%dVBkPfi@=x)#%F@^HNe*4#Pu%L#H|&}}anfa5iM+4f^rrpoWXkp@;5W!|uvE@uKlWVyMyHN( ze3dO@`%1Cswx6=5`a6CXEt32Wx^<2lqKK|DTIH;#Gg%F`&SO)wphzyAxjsSMr`W1I zTW6;x=C8l#lEyYx7rJ525&-Jg|vS8J>UO_K6XP{v+F z*I9hfN>J-$YOS`*7$XT`uwsu|-vE7t|?we{Et5@|Z3imMChYU6``H z+SmJPO$_O6j&I6CE|1sav|KUXZ=}aS zEcKb&68)sTdVtN$=sWREjDxK*ROP)vdw|b+E}wSCEX9NS&iuj@TU71gBeA3#);Dz4 zx!9sr#S7Ly)L(y*b!?Pj9AI;u{_8(=>?b4YHc6m;`q4!R+}EcuJo%|lt}UD&If5k# zxv%qA`Nv*avagT7HxLw*bv~92zL7gh7Z>IDj zx^fS-$JvZ?8|SwYpkw3gR^og{Uz+Ms-{dm7+0xx#`az!oTRr+i>=Z@N2K@qbZ1{G7 z-b4u~lb7)V`i>at*MNB1r7u(L1ZC)g`i=S;GjrfG?&}OoF8PTul-(YEWi}gqqk+_=O$E=%&b9(7O1<$UC9q6o(5@mA>u8-C(OfR1fO9b$J82`64#Xhk$m&|7misaH9XGWBf*6|bTdRb1{Pxi%@>)iCBI+>+5VT5$~ zCLM!3sfUg7J6H$rpsxKRVv+A?@@I<5ne01t$4#wEV13qLX&u2}J&CQgm%;W8)Q6RD z8(Gq}!EW6W+c&tK9E**zeedLm{=Sn$z<8Xsv-Kwl4dU+b4W^0ZCGJmI|` z`4oda$64R{&IbH{hcZKD$ToDxe=E=WPdUc%EwHKlM3HauEl^Z3^tReShqy74e5t1_YOjmh|5_N|pdB{g@ z`b_`bcl@usoC|b4hwjHF$^%pD#dPUaG|u6ACo4L058iL}&dE2w&Tka@y<&)ZfDM)? z5lfw+-z%0N#|#SZtpWLAi>mk7E|#F}CZ^b;sy{=#`o;NQY%zEbpuY*mkYQX;O!+c- zo|rc?q@Qfrpla=c%V%6Jx0KU*_V38~NxIFhcAs#}O_HB>+W|JSrJE+5d19VhfL>#+ z(TCugaBYmQ5pj8*pY)=3fc8dWOE;g!LvGrmKP~!1KMXc}u!5ra9(~-Bw10yAB*);J zi7p+o4SfgH9byT_)Wr-~yQZ;@gDnYF>$?YwwO-ZVU`oP1sK1mac~-G4UMn7?C#? z&y`~?W~z&ixq>E`JLa$m=5|-v1=llQ$YpX7QtS@o>3G3*i*E>@NDXW=hf@k#qYAh&$t%PJf4GH@Lc5i=w~ElsAB5b*#wg3 zDE3V~a?%FvsUP%lr~Sm^@%WG{!5nx_rgYC)p6`*a#kacam$(K<+s2pqw&F6}8#nU( zV|^*M)c=ICeKqAFKmBQ<1k8iyq)KPbnqY3Biz1kBu03-<6Gb}Lglo?Ad7WqCtiyGN z5}n8FJ5HOJ=Y)Q4Z*b`$B(Q&XPLR9b${F@c`q(hR;yWHgw{a zU`|?svcccQj$E(4#5Lo(!4fsbZAlzDK4O}fnxn;aXTG$So93_tQxco)o9}`F-wHWv z+r~FUd@Z1Cuo1@@Jm=iXPq~Gpoi3K(zIpkepndnp(tRR+21V=9*oMT`#7I!i;M;)wOH7_S~Z@-OJp++}lNV?gy?L^UHjZ1IgSn_eGEo&>hDZUu~-G{!2cc zM>(6qOP8H~6)^-cE&QBiyUw^lCZ|RCP)UkfbB437eGY+cCO>2&|#TxVe zI9PkK;bTp{W6N*I_ovCZt>5JQ(9duDO*y{dPv-j6UQwSi#OLx&=h}Tsu7B3)%5&pg z-07oy%kj?kO`WQ|H+DQLkSD~y^AFYegma#8r@bkcA=WyQ@{L=}R_qfwN3M&Wedy~T z`v?-JW&0ND5{op>jkN7a=Gapos&7^r&+R_Qw*JLEI2Ch}*9LlvwTJw~P)ug4 zeDcj+*|n$MSh7RjE-Cv5Ys;hfq>Rwy1*A>} zcJOW)!QA9I!j@s) z@ORB|4Lsl2p$KgFxvw%^HvChNcU>5=^=KFQL`}XW-Qyr0-&Pw#G*QG<+c)q5aYJlD zeIVE0fgI!`ZwpG&v2Q`#5-nZF9oMXCy`Bu&fc?0ODR#o|7o#ffV5toGJ*fXu9+7R1 z%W_06Y}S!Y&_4a>q6Fwqd=ulkQ+c+a9ok#MeRbb0=P&i+plJVtmY__W>t*Tsxz14h z7Cr61Gxpu8eHPermneF+0M8h6vrYM%o<%&9Zs0Rpu|S<7Xa{JMy&v44Ty8uUL`m@c z0LB6}!5D$@Hc^D<1DWT^dX;@u)e$Y=dI8tYbm>Jb-MfbJY4iR4N!C~!UMt8MG|3`Z zSC;4}T{ftKwRplBg(gaZy@dU&ilu$-^&H}vBr4A;JIq`9wd@WbH_D{NFku}w&ydLmA7%0ML8}t@-?i218>QSG3l&6a*bf9dy1P$rIg zU6c#7F=$_G_bJB1*=t9?yY9E4`??6ugus2sz0SSJ_=u$rV{3wZ}jH zKfsoA5>H(q&k}iDBXT$Si$Gd;dCNu|bFu^G&pLMJA$CR%`Zi)dJXZYlfjTolhms&B zL;fnL-$Wsg^4Ea;C$v3T_pE>I150}X`vS6QZ`kZNoVf$aQ{ZP0nX#92l}m~r_D8MjBh0{vw^TKYf5oU(_cUL6d~7J{$TE{X&rq7%MPts0rIF?PIRy7cj3-9l`H*+Z zo^f20J%c@$tryv!_$KD5F7{QQbLV=*WxC2gp(;MZz5RC?%3)fp3uJkK^_*eN-Keqd z-^p9On~fYd`;(3MZ(-j6$LF&3n;fe1Q<~}+*QIRzcO><1cyG*d#=iNDq~22;zIXn2 z^=>}rvb@E7V{1pBDc>lvpYUA5hI~i=;CEf(GPWbLJ?m9IlkKOzq7SyajQnN{IsQrS z(NFtbwyfcIIZJ#ue16LE7I)fmy!F9ZAizeQe)KtCL8t^5WXXRNLB6im_IP~?Mr?(+G|vg!<$**arEww*fuDOyk@!R0CaWXpEq zyhD9hg1$1I+-G9Q#aV9gyw|yHoqT;d4b;&z`iXV2ERK9ev9a0 zCJOJn!H_gtdeu8@k9XP@=YQS{iq8N2fOmo|UhfXP8wlPdiuQq)_5}6?^J!1<{;{-& zl(bjOWBoHT-9g=bp%zI|Wb+E6h48&mDB9FVNJ)mvaNymOtWCPEiB{pJbfR4@n z9`lZ@Z@|9Pwx7~5Q}H+Omjq?UZRxp&Y&1NQXx z2Ks0gd;bHB2N)mYY@&!+|GLgal562>R&?!*@7FBtJ7rTnmLV})`U^#UztO{vwC*~VBXW_OHkv43!u&H=Tk|pm(oi@5!Sl)-XKc5`H-jA#+X*rFYg|LF zrLXIGKVZiXH9`Fo{El7HPw?61c6^Un+K$9F#TH)UOY1ws-kZzl*w~k03Z5UCrZSAg zmi`HzM>lv@oh;c5KI@hxp0hmf%#3FwW$RgP*-hmPE;tLCC7tsj^u(0@4b&-ue6&ly znWL5{(wX0(dFI-1J-Mzmt}WLJKXJ@`56Dqt9*S}U`M2!EI*vMyZHi$`oPjwLbABGd zmV{jHI*W5gZ(@p_sQRtx6Z{@^VF)-g!zO>nKdcwO_O9x*2~Y)I?Gxcqlz zQ?46D_MgCbegbolsVZl-*2O1=VxV2f_9B1g7Ju3tpY66;POa^X?Z|BZ9la~}-+?xN zTEBi-hd8seuYJRaxOWoYpQ0(({{&lm`6t-#Z}fO>{GT9i{eQCUn3E@ek8z)V>YX;1 z`!QFxXGu&hTgPsSbZ~j5Iy>pu9fPEtx%swYe!}hk{K@^l-mE>Z&vQ*rt=-JIXXP?_ zJIMaiHxvumZky$n{|5e>JwFqy_jqi6S^}^~8DR+B#Rk2%036gQ@eD;rs>lf_=z&0ed~b2J^u7 zENhCTpF`}B^qE-Bd_`xtrR&DM|2kLmZm4(v&hG{KO`v@Vv9JZdBTye!(3EqE8t|4=}%Q@!M>EFEI zxKBB#4~|E=4ssnZo_8>2m|_P{*K+APviExbozhKD`uYAq4E~zHMqCpkK^c1nTV|-7 z*(zu7wIH|6J^3^?d>$Wt1?rj-`N$3X1Nj}$I(1yXNrw`+om1i`ZX9ft`>*nkzeU{I zU)g6Ddu{B=d7nP@dD+`tj~wKxg1l2SQN&W4hW4Q*C@(?m3HF;0TMw$_u78cUNirW* zaD9A#aH&}i{*YmLJdtK%G9pCcwO|SD^FC@Cl`(IV}LJJ#yaNVW4g>Qp`+*53^ z)D~?vwN3j|&tUqp=@0!<|El^~^bFlRNBJ#HV_}@k7xM-y>6B|?D|QO%7|OOWH%;@x zGnu&o+8MGje_OHS=bCZt_5n82r9%nnvTfMG{muOxY6Cld;-}cc_n9R&d__>-<7hD^ z`kV9OFM@G0ciabn&iL@%zz6+8uGdr_$l*FS={ATR3CgBOhur3T(?{x!qdxIKjv6eD zm+RRWdtBcxS-8&pR-evkm9-)8fpy|H@iBMgH)lIfZINq;Dts<%YP$#|Oo5+R+Mj}c zXXpoH+o|I(#3>(Zoipl)^+Y@PsIy``c?{U6{Llq`rVcr#)+KuevZ#!$M4ySHy{`6X zs|h4)nELq1w+}}2f%%*PI(>6ris~>YHz=NOnH za*~_2=;sZ5=H|cIrs^2{MdYz~Jj@ZeA0x&~><#=?Y~~^A7D>j!oCEU%E?ai_)&=Uf zl#B9-{YZS|As_7w(Jo*&_)EZjWVj|bs_ZZYb!+sSw&}wRwq(Zkn{1al%w>l8&Dduf z*OBWvAJ}0h#^hMrklDUf=DxQ?zcFM#Ipgpz!TQeY%X_V**PY8P_SPY}13yzjO}h~MBB(J+{dPVK}j%{C%Sx4 zFD&LE7?P$*-=}PMZO1z%0)h6GF|19BAdZy{deS2yMLFV z9GR-}--UHa4&)8b-Ay*tc}J1|9o{>#?5yE`0#!M0(ALfV&h~CUwQp$8{axCzhySEp zE_Jc}Gnv{O|1Rv?PgM5a3%B@NoNbGJ{9vg3&!(x3pFn@!#p8d%^`87s_NAQfpx(`2 zbX{)tY@=?*zHIwecik-MgTbElT(|D`zYMV*5=JM;d?`#--Yjst#YgstBk=Yf6HSqD}5po^m45%O;eemAAv z%v70oT;5kX+jGXJe}3*Sb0mxLl{f5bg;8#i$?*plF!$dOowPWeXtrTG>3 zh9IZg=+es@@S7=pix%yWlfAaY`|_5o+JBMODI437Lo_i}pFBf3o1hKoi6WgjW4-_# z8!=OC{HnVIxeVnd@DtYqbZmxl7rr+6`nb&*_iB^8QIg%UmRmlc+(dbU*Ok{!jrEt; zvDb6cT7JSYSrS_Wdl`FLjlD2GN7(PYFR~YIo}WCYcy94~InPTyCwqJo`&8F~m z=32hmR9>Fxn`gi3k%N9C$(b3^20mh-Ge(V@0vQ$FJu%GQyTGxlYh^{!YbA<<{BB{OG#U-Nz$hwYY2G1ieS z$hm2=2Ssveu9z=VxxN9mB^+-_J!9KDNe*&xKh6N%Fzr(Rz zapa~w`o>%_XY`RZz`EJ7K8hrLMbh_quvKOpL%hbP@h~RxG%>^$*Qcvo1lJI~sV;q>PxNsF?(Zp!{5Pl%JtR7D)E{5671!gs_*ykx zx05Lw>;&rsZnhzNW~qFV{ij|dwkT_L@0skM>%iw2o`sotJS+Xo zJ(5dtiy8-_C57P$Xe$OgC;m#(z?n6R|Wu zC(K!9{Akb&WDyvv&~t&fnz!hN8GntoolZ=!SaMeOtjTZc5ciHMe}>pxe$v@L0KfGoITUx|*wg-Pxhdz3 zT>s{`&HEej6Ru5chb|fN4G!Qr|g-n@=vI&`3vJ%<4ffmMf=6c zEe@OGvW~3>S+@;254OrD-jB1)F;8}4vTwv*Z@E-{;`NOA56#~j_P?vI!H|R&6iJ|L zeg2!qEBZlEdFWeU65j}(Go-Y4(LM6pbl05J7;eX1B&k&QrPTp~l+qbW!nCy3M zWX^r+OMWQ7{yW&asDbml?JS%6QN+|WD1vLkb!rL9*zrRTHu0)UTlAxe8Ip3B9jaK0 zGt|j6mB}?lA*b4^(T^GT4Q1>rAdYzQtdx&jX3G!6J<;VeMY?f2v}vkzU_O2dYOu7=Wo$?64EWyKa`)A;+R)bKH_{Y(v%`a<<`iEXhaS z6`=nvRdvbR1lNjn3*48$HN0VeS1k2PMaiF9BofF)sQ`zo*8qG_J|I(E4Y;DFQhX zTRITa(>_5x*a^xl_{QgXWo`|9V&0i&K*v{w+afnKQN(Kx3Y{|c3^pL{jJ2)D_*^EQ z98>I&l#3vy3g(5mYC%c5ZAk3wL*SSp8#%fl-wF15uvG_aciA>_&`0{qe1YfAWqk7k zBbP3O*W=u72N z|H9|ssj}J9Gq(4rLq6J|E&9ORFwb0ru4_@^x^tb-xo^0ix@gk5N4Qo+^{7uC`b*9x zO60`{l(7?2>R){uzm!kksrX%`i6OSA`mLoOV8aJF&ap+m(KNA<`t1gJ>b7M$6_;t$ z51aKYi+pCOPtXPZIQ!@FDTy`NH)3p-O=VN0o2fZ7%pW@PPf(TTTb8chJGj0#n(Q}- zz1eNsao>EBQ*qzY75gnE#iIWd)@){|4D}5=)(q0jSW86`D(g$@koCB=R@pb3%01Q{ zG3|jVyV*&%4cQ(jvY)WlY1dE&$GQ9@d#a0`<4)hVWpaFqt%~^xTl4!n46f5rH<#_V zY>K_{$$zW!t*xj{<2o+Clb@c)H-CTg948+C6ZkV{T=sp^b6sL7Kh=5Xd#dwf-*4J^ zs)L<+-;(3eGfz2u-EVTlJ-j3{@7B+|^*6Ql;3>xbPttpgWlQ^$b>tI6zLPE6Phc;1 z{JVV}Ur#LQH_m5FtLi`ehuZn~|71#s9u!Gf|Mp*{ZP-UpC1LXoQ1Csli6ZzO*hLNa z7HEcaXa}3~S1=__wiVfz&Qe3vU~`5#z(>rdIM$k6^-@pdHh4DL?Xrcs*-|Irh{*Ea>>_k;wLmNvJwMn}t z**@jpqN$JcbJ175)6(z48DC?VIzup?${NzWIR*FKNKnR(e<+UF&2uz#Y{s@M?L#g> z9_S&l;Ui}gul~@d3*N_u_BUt;*tT%p&b2zgciKv{O&>Bd^@-e_GZ*hnn!CmOQGi}# zd(A2H8c>E3a!XQwh^k!V;~JD02W_~2OZ{}eTJ$4Jd?(alY~(m$UQQfCog4PM9{qd5 zF-X^YCmsJT#=aioLEc!hL!J-M)r@P(J?8sv>fYM{_xC4Q4>^XqlWwfcQ*P z8L;gO*iDmeigcJ7JGg9VJ97h7FjslrD8Iw8r);Ws!gZOVh!y)5dzhK*XF-vy?1%gw zDflfDy5RTH$+Jh#Af8Dzo<*h z-?*ys0QvYEME*W-$KMA|wZrq6=Pl1w`bIzLKYKdk+li_=wc=7srU9@hajXksg_ z$NZ6tyj%H)V7{0)+Toetywsuo5?o_nXY$cLIjZK1`5fvyV`>^JbF%&~*#r`>*={|{ zE@Dk?Z+h$+jmw=g4}Cd9dv)W48^N$B#aPC%gUcWLFL# zW*^WdeK1S7X7E}69l5x_4~EK_rt%Y8>m)P1FG#YkSf@oa)@$rZ zMZPYOuk{vjTQStZ<{0AMLEKLvzuUOgwf~bG%5%f@Za!=^u>KTxv%9V(bM}d;7(=Y} zKOvj){YmK4pU78~2XZdkZj$Fgw%?xLKe6Rp)^mMqXW4N#nPY!fKifR>^vjvWG4^GN z?(#Zx%5WRYpR%=deW&!3oowh&crD%JNNW|{Ea^Ytw$GflroHTmDc{MK?I%?2<2Odw zk(s4(rc_sbb z$7XrPxJ=9pnq(2IFUE+ZjC}@M(m2OW4)x0{>6!9NdEY>66|`AIRX?~ETo+SR2CiQf z+&d#d86Pp!_gKgWl)KpS577eRfp*T^XBvEA$=&F!f-`G9?(iz2*VuxC`kUe?n7RzkOpXULA<3D~6Q!Fve>xy~yoO)i#(+{wjEuGjA zIeZD z+!ng~QKR3Kty_8wGuC^PEP^#~(q)4xhSrJq*AhB5+pTZehzIHdI`$LSDf02{hu`~$ z*jk&c&m!0h+1~&i8@_pf4Sy9)YrM06Yt479|Dk=LYd>h(7x?!Wu*Fij#eUEJK%Et^ z7sCv;B#?gy+M+K&+Zo%5A(ndNDT4fNhjL5yvu*pQV&EwjyL~0{QJ$hk`&+ufM&I1` zss3-#*big=DFZ&!rGv{``dP<5r2R<1mgz^C81gMqdA14e3*a7smKf6a8+;Eg-G8R1 zdlEnOk+v`CtOsCim?_4tU1I$H-`qo;gY>r?x{$(GGgA6Aghg;VFlPjHUS zF+SVcp3A4+RM!mYnXNMC?;da-hufJQJ8@Nzqlm7)GbZMSIc}PJt~>Vz_es82(7C6$ z_gT+HOs#+R0L}r(h8@4+Ue{hZ$<5r=I7eI3cB2pKPy3}fv4y`)@vUkJzJuj&W9YU` z=_7t?>iW%Qiu}9IspAJ?>xHFUC&Zn$Tpp^^g022!Y`;nBz2mGyUd9V0V19tPf*LT7 z%x4SGVP9|^Bsn14KIyldr_F66Z#3E8QRF{aRbv;}k@IiRRrf}f9qe=Y8@Xa_A~Qqf z8%_4i*7`70`bm%VbjmaCUHPp0lX6k}e+uub{|w3A^Ajq2RX{!KPtwm2OZGc-q;<+C z_W!eGi+&)j)27Qml|^+kuAgnE&a>sT{cibB=K9?}ob}E=QMS$ct(<+Qezx(`ayzb5 zu8;3z%4W87^K{?;j(uF#;*#x8k@ur-_OcoK>X!To-luQ-xNR%#`?u$XpD8yv^~^c( z^N(+X&@TA<;L~?Oz7O(Ua0tE)LP_NBgEjr#VE#V1_1)|S-wSP@=^NYP{B&W+c0!zO zRXR{!K}p~2dbEM0J=h7#U6cgnE$Ay_S`Xhoi8-0chEAU<=P<4_?>IX1zTQ7|23|S~ zj|Asq;I~Maf_Gym4=lY?1Mk-0GLky3KczeWQX5_EpG?`dXnH?}A*#;lygyH!>j(YU znqOK6t13okz#R<9BIYl}h#C+#h&{lDFN4iA>0NVHIX`IaZ=MajC&W8olU~&qd=vix zwhVsCNFXna4>&GM>Nj$zefrUPAJW|9_u!^7V4I1~dy^<)rFUv#cEa_^yF^tRw1c!h zrPKb1x!~HE#dV5umD~|WEOm--KJwEJG(lg^{&m@~!w!h0PKmzl)F0Y`bwFR9=+R%x zr#S57ee%1_rWodkaneWhAshKiTtlwoNNlca+#6kT3D#~CQ|zEt#U1$G#g3l+)T4eA zC7`^&BzFbGH zui;)GmO5O+47MV;?$q1qo>*}YS4r^wOw3X&d4akx1-Zu224&NvXKc?h`)_(tj&GnY zah7Kt`s==1R@pL)6I#p-YsK?|>?+Ry-INr^+<9L(<>_nMKltsa@J!)1(D<#0XHV5L z=T(m94t}V5ZcctLQk|mukzXJux0T3+G1boFa6()U1OU% z7rcIV*S9*p+wmQ*2PNsYWk0qYgU{eY-@@Mld;HGNZ~lA_r>s^yg$YMu06N$6W#5Nhk43=Vs1F5u+JyD^vpZoEzY)F?_1q{y7d=ZO?c0k z>@%VW_5kSY1;NmoVoxh#YJULq8}_pnm-fRQdok-1W-1ryz}{H}`wn|)Xa5nb?J8L3 zta&J+r@ff{fqeqHpv->3{!AQu2B6oFl*vVI?;o`7KA=y5G?betf;y&4H`uJ#h@s9F z^zLQhy-)!k8oWE>Chw+9b zA8^j)Jp<+goQF?{J#8-E^58q`G}&MTTM{^j_h3n8Y_GBt+XQ(T3*%>QnRl*f(S5*v z!M#%gbl*RDKf%vhVjZyFM(hn6iB0~l`;EMnHfT$2(l&j7A-15u?U#JPmIQvISo}_r zzKwP1Fa_Vzmf-iK{sqL{AO{T54zN8z?2f+BAA=3>Kh-_$#DeQ)IU{$MECKTZ%#o?m zJ$HF7tt0LK6Ef%hMsI5WcPXlSW9i=cUApT2gsS*=EbiqGaL=DK*>>zDMbhiC$G-O} zYwh5Jo}kRS8UhLSPx(&l`yE-7@9*M1o^r$(EKmPa+40Dl$mJY!({sI>&itQD*?!8W z98Ed@6zCa{2a{2G*^FuD$y20}aZa!@7OI$z4+jhzvgMRwom0kCYc{heL z)@A3)(!QU_TpxWVw)AgcT@ZI$AMb4ASl6G*OYxsz4|0t6qb}PMGwp+0`U(5*JI?+3 zEU(z}^RwahT$%N+=MC~3^7D@Gg8Sd@OxZJC| z0Nv%G@(!wU0XbKKKGCM3d}1H+^tt@b=NM#;LwA{eWr&@EYc~R}B@|s(;QGQ8TW~)v z?#cR}aG#!J|4q;F+CuIKF@<9ZTviz`vL9XYD*i*~uUeXk>jAdVbUaKHFo z$@fc9KfylYpdQ$Mlf<5OY_Ow!`T&$q9Cz9&PtimX%-`0y+A;RLwi$Cjzy{PcLprp8 z99>l5y=26mxAnX;Eo}JAiv7oBOYGz;(U$wR)R#<;{?Q*|b3dHRvMJ|Oe_qdEJ=^$= z3YNf*pEk)st{XG_OR+g`9wT<=A>MhKa%P6gJ3!AqbZlFA?2PpU-;F6d*M3~!ejxX& z{~CMK?{5ChuE+dP-kb+?Mp&E|I5%+a&^ZH^;4Co{#8AdQk>!&P;SmZ zoIN;ma6Pyd%qQ2Ss2@W<{Pf3X6JqK27L8c0t>&6>t;9Ju_Pxf>93<>}C(nHUJIh_y zcs_v75QC2V7VZ)FCf>0*KHEO&P3>eHpKZUZI~VfPSaR&Cx1*0|dC32Xt(Y5mzkl+d z_7VLlk|vL@>snXgd)fDS)BV1+PFO3Qy-BckUVA~v%KnvLEwUzAXGL_a%Nnr$Myx~Y zMdiu+L$I`NS;Nr8)_Q-vdq}eW*$03b7Ti1BL)=SK>;&Z-#GLjnTL%A5aNqhK{v?-T zx%Yu}05jqBf@}vv<$b|x^d`H`YJkrY-DPZ%xslS&UrK)d&^Y{$5!$18_Pxh_2$b=&23Qkb z7sPJ)x%Xc8igH)AGii^u)fe77{2K#usSMbyW1oWGDEO^n1Y45dGnOd&-KO!|O~PfQ zDbi2yWgGeWm(V`_aGy?z&k*Z)+fF$yxmvQJ!;U%Wl29&q{=SvuAlA8ZJ=+|UeIu?H zGTWc@9G~@XpKtwF`Nw}l)ZYR51_(95_dtIWY|>%JH^6zwt}>JZ zzMYvN-PHJY=5J<`?_~$}5lg)#$WJ?448BvUzf<^c9?^S@;nsKRc`TOXG1!Qiq6^xj zZTH1}q+G*C46)R4PRkv3{3Rg%m5+0wuEo&(UG(0vbhaIO_7>4O>*~zAcrVmDY32P` z@54n*z2kO)G}wnAZV{_GQ*1Gm1JJQGQ3P>-j-8mPbN8feJ^Ke|1@3#@r>p+OkSyx+ z7FB1*!kLw`sLs7zAPx4BXwsnsQ}ug-d`ss>a??hws;{;i>*NA<{96zU=-9jZP2Z7I z6xjeDxNO^y?W7&C+^c}zMHTpgSa2C%#__I`edzcz zr~j#(+p|tTnkWI|@tADGKg2%3hA)F{3-^J3u7LS~7EH-4kkEo62}||r1GGWATnDaK z;o8Oh*pBROvAAAZ&bYpW(GV=oay+!MrPh?|0TUVTx2xPQ2J03Dj}edc=&oBfW# z24~EY&5RT?)gJV~5 zrk*W;&NBu(zw^Knujiefd;CsR`He@gKaSWJ^R;q&TkW|&H<|luKXq<6XOrK$<6Oo^ zKf0(fPWsoxRDbDv6INeHG$GL`cP7(apU-X-Q*WWkRfWJuum+>`X6yM0LK6u{UC+@#je%c6qN@ljo zKz+usB2SI$#dT?nIq>{v$-LaiG3dmcws-cC+P&$nd*(g+($#JTzsu-1?Eh9S<@pBg zqyGuq7x%x$IM4EFw;#C@XF2_pYt18mkUa~%2%nj{qAKG4r6YRm!m z4fj(KGxna!{uBGt*1gaAUa`-4T_J1iY3yr;xT1K2y^6{D7DceGI%~`8PZsC}z>%B#OrmZ~#B0UU zHp(y)Te|T&{8MtovvNpNvamWsbQ{MfV5Scn0Vr?L9?h?mg}|?mO;#)`8bUiF=Xy+#_6P zt_|ZV>I?na0eUWvU&<#s->yW{?-Be)u|fM`FCB5Zq%@6iT*9VA7x1_Wy@TLJ`UPZ&i>gKdoKT{ z`k9aN$#2_emp)=U?Pqyh;{J+lr#$E@_%@gQZ!+g-%KJw4`$;FqC!ZDl|7kz=%l`C9whY(p-_jIw zGG)tbl}-JlCd5ea9nIg;_?}k8(zh`fi7FlV&IRL3T6_yDhn)I023=IK^quVlJM~i# zPn+~%3HnLD`TNHu>OcMoIt>T9D z5L-mkxHr#sT9d9l)^z@OhvEI{c^>8&nCD)-oAO&4@4`L7yRD%PcxUE)x9EL$^B%2O z-ZTAfi4IjzXDIW0&HG*B8D0IMUrqhvS(WG5DRw~LM|>Y;tqkjkoqE8ZxGqY-^DmIM zim5heXX#m!{W+_2ossbYpPwpUgRKerUUfYH^vT>tF60gycA#wgtuEV&e24OGt<_Ap z%o_TgllPM6-GuiO!P@fv1I)4YE!|*iq8wm@A$CAc=d}%qtsmIeGS@Hp8r!REmzJId59%y+6(B|sB1~0yG-3mIDRTNbLPtJQitz| zy+l9JPnv9ieJ9v^H~Ph%Wk_(rqKgdX_oYkPkFLo0*x))Mto+C35aJ9ltl; zVr=iMTNZX-zDyZNZQ6)xz3&sgKiVgk(NCsq#`jsCf6b77W6S<6RnOG;BCUUlwV!2I9ISxn z^p==Orw;HXri!7qi(u?kFt^M(>%baS(X`G*AZJ>0%3ON}`-=UieaboDO!!>*JW$6T zWA>>GuhKtLXe*@d4^HRb^rgWx!_!{#_(X z>hOUo96yvR(^RH^Q_$BE(Dyu+Q>XsKvE<22m2VW;$wBTK?3hbS+wn0=dZwy871sjv zEwIB7>jL(-y3_xYUsoP*jLSdCs+dpg7{_0gL-D3bH@3Y=`ytE0Oy`iZnBhD?w%zol zvjdzVaM{v!OWVJZj=9OEw!n2Rzsaq0P95@%pX!O5ALt+IKE?f}jy8Tnk^fKRd)ROE zpYpo?H#z@`@6dJp8>;-wH~!!HtS{RCNB}M+q3Sq%(BPc z$`bunw%u|b`?#cB_Q?|e-?9}0#ARvwx6;>?k*+sO`U&;6ajmp&S!6ruah*Nol>HN1 z^(J5Atq)!6(UB)hHt_w5dmB0uW`g@V@ck-N_|6q9eZPY7f%;O19k#w3fptsUkuzvX z$Byq1^k)jbxeZbOm-;N0DF2XM%m5wRw2%B(#1Wq%m&tZ??6%Q1eOT&?p#8jp}_u2V&jDe#E}#m= z#`7)DuW6s^@)Y%a?a4K<7QG z5HBb%+0T9y^$XtYu16A|%UM6`D4#a`dtypAZ}u&pZ*eYUj`8~BwYlj_YgS_)oa^s( zA6j?z9{aFV^-pA0eSj?bKA#~ID?J2Dz?T4=-AAX z4vx=d^2}gMo-ikda}6tSxvIPs+e8slZ9%79^{0u#xn%!nZ;ja3>}{Y7JHY3p%0|6? z?T;PsU!tgulO`K&19}no5>rD`roP7cXOE$8*)q8vpY;ov1Nam}&QDa;zu|r|4%4JV zNibH%T{NFupZMKy#r28%NY{Oa-%q(`6~X<-P{uw)#ZUf~c;z7{?b1JwVTFz~)YSlg zXwg6V)&uex*50u8O%yS8?KIe^+aW1aR|Wlp5uo>o#m|(8p^iBGw|)`!( z+mpGjU#_cDwtWHnNtcZ^r(euF^S88rcn;z@hv%IxN^pJ$@bCZizm-S0-a2vQ;CTr~ zP|~v#d$qAw*V3-@ICL&Kn@|MzAI>Xq zo!C0ZTq`)^oc$`E?}7iNm>~DwD(H+~_l4pR?a#1X~h(E>78XUfx)7u8Qti zZ?vdG8houIXK=G^)nzKr?7@&UOM1q3WVZiCFT4jF@SJ`!WrKZyUlC`U^r;HQ&6t@- z=C}&hh4o@h*#ljafIY*-5vpgsm8L5^{p>yWPRM|)M}lhbF)=YN{B&9`#B^!H|4RcrJEL-i+T z%vt|!xhc0f&m-k2Zg$(Khb}l9oX@87!#Qeko#l-B{BVX!fbSN>tslVWx73#y?4O`d zCi`WZ^>5`&Ini_3Icz^A_1Q+*Wc#T%`Ta!s!pAxA&9J6#WWT>#r%iIcwVmz#Lm#tW z)^9O48!^~3*kE43?~|SS48FPilm4wgesBDzYt??SpYRKA#}HND=DJvXqtkc3E$Tm17c9v)@Tmdc2!R|> z4yN*-wzG^6eW`-JF^0jnFn#;#`u4?h92CBX$<8?Nr@b8vNx%k0;Dax@$g{K-?6t!C z3-4=s*DLX!i-f9oUVgjc9d*R}D(|d*Z|(8!I^=^t&&f^C#zXh=rTchG=Lfw+nSIDU zd7eKtjzymshnR=NMm=No91M;Bc{Wv!B1Tod*z=qr+VVBaeW-D+O8C)-ryukspxi_e z0J8Hw)Ob%y`H1Jatp@1W+y?Ee1m#RqIkO`-Ic~V#G0wG) zyx6fB$~RokJpPuqHNifG*N}BIr){VXw#a*EY0t6$*oRxxs<9;4?-}YwP$h4W$54+y z659;WUAI&RBfy?HbK&RuF5TFM|DYYtpvDWFmnIu^_D2$f%{YgBmg=GW`6o?s2K&${ z8+@85qAR`zv@=8ztixvg1be0m_8t4Ni4xK}W%m6E_8Y{REgf5SVWZp#; z=uLL&=m-6wUo)|#gU9IdZzb_VEWv*0g8BaqTmFXiGpy&yN;dl;vmfQIob-$F6vnA} zV1AfW_FU2a;@M~GIcO#*o5u5#_n+>sD?yGaM%ue8?zLTQHMKbf?IS6(7OY7T^aX#` z7SO$JL-huKpxlBI65D=(@luCR6D2^$w#73i@}pm-L|^GIP&W4I@;|9Zd$i-WXb*OP zFYzmIeq>8b>6xwax3Dk2_04`W+H8_V&`0{4VH}L7i6U6HXK%eo>Y<9f=9If22l;4& z_Gp*(r(hiHW3!|e?d==XnOps7bFQX*Fuw%f5|E>dDz^GN0{UNqsWCxMROx1E>`iRV zMP_<_B$>Ao^8c7a&JAZ;oYzv$W)+;%COE%UaBpecd(u5->8x|!xfT@hyfgi`%Ay48 zzmy|kdkeoUS@n)ZY-XuU&K}nmt~=b@=mUM}>JxBH8G=4D7RKapb(MiS*z(H|*F+IB za^-TiVK+ni7UW)n_F7^}&$zD0j{gaEd^Yu3n;vV3Wc~6QQtx%8eg^CX_5}N53ic8E z3C4l#MfMZNB5&B=IY!F8r33q#{S7Pd-bZp4PNr-(?B}{IKT~zjI?4C6H+?D26y866 zfM<7@i7ox_qUl*2crL%OWWV8OdL;G4w!nR(&s{KH=8g4W%~;!^{lfk^_ZQ`!pj_E^ z+Pm!is!18~U*Y{p6j5_meF;zs>Ml8Q7n~P4E;QNT ztlP5Pzz;j}uhJ9?Io`HY;&a;0a*+>Uw{E$s`g<@{e#5?R^*4K0zD&84v3-K`3Y>+> zx!{_}^)t>I=dE%**IDX1OOH>SAI{LX5cgY}Vou0$+CG)b;ZONL!EHGQW%QfGX7Kqf zzBha$S(`UM*CB6I*^TR6&eC;nGW-4=y6>-lrySbf-|~01`_lA z^IF~Ne`|BTb?i6x_G$Z6cAlI36i3}}&(Z1M(z&!gW&4ROpP#6@j%K*7-dM6{-mcNT z{_`3?C1D5Lb4Fq%-F9qn-lKi*p1ODY-p==*b^$x=3$AMsS0v&2uEe*c_2C}*0=Gjepv8n{jR#=5X}tRXbP9%4Th z!FlKj%0L~oU`WFLg437wO0-S;Z%ozw#Fmd?FMfml{|-4*A6txtvsXpa+%T713#Z-# z`JM0iT~PNGev4e(hXQ_c;ogLu`%c&W3SaI&&+jmb_qD$$uc3`CXp`}jIAiC&I`;y5 zu!${yU~lx0=&(a#FM_?}`90^(_934W>hPcRPq6;J8Q)}KnrP|vcYC- zyU8iP9h~dna+dZbt_k`>-oZU&BToG=^hZHLWY%0%~L;B2kdaT`GePjJry2g25us62gnwPJ2L%#S!UiTUMhrF~wTb`$)KF|;P zGoqiKo344fL4CGQmA|FvoZyQD&dm+iXZhAfPyMOV^Bm_jVw_zR;r&&$PnZicrMI9+ zdXLpu8_rHU)~Cf970#;Gq;Y?J;BCzAr1hIfV4zS&P$UQ~Qiw@*J!Pj)@RV?L3 zQXYz7eY{qu?5U15WqqgEg7pXX1N(ygF~t^~i4*LfAU4xe*>U6?an{z$ne$%v{=c0O z+eVxrOYG<;TQ)yK_&(P4OtCIt|5SI2;n@aSlISoKTe_)wcKR!PiszXuvs5;vs*e|_ z&oSs#&`uMKm-%20n_zudJJy=L!=7T_vDZwMzGGic_Nn$P=WXcxaSmaNrz|`BnV6+~ z&oxc$&6q3tLZ9e+7h9}9s^@nf{qDisAA}x0B04sFW_f8K24Nha@GQD)D5u@u$fPF z_W4OR<@<^9ANahrztgzheASYjLZ`W*_9~lk3oXTHmbS z{Bk|I!8h04%Gk0Ue^aIZ9jv&IBMo`2XZcNg{TKbpxvdZN;}iDFGWQF66Git_?x)Z5 znC_?CQ_YZG1oy|Ldn(U;&pnm=)Uax^o)D@mIR(B zhQ9wm3tUF-7wpqiUxKM`NPI_nzCp$JD84Bf?D!DFcfsd-n!c0q4UTVSm2Yce1WU5~ z-JHIwi2tjqZ8Kl?FgKdvoX*Nb_;wT0^lbo+;or*B~VCi~xV))*&s*5BmmL-{A>(P#Gy+5Tq8 zNqf#cRNlgS#`|c+9^!Jwa-4;1-P;VdW$Pti-d^547(~lumP<7pT10Q^6@m&^=cpZEqt4XWlSOfcJwXoYTu&KhlGbgf>>Tvt#*qDF%XY$ezQJ)f zU)NzJ?eT-R${+o;2jIPwkA<1DdmGxip1$=ddS z^@c6lAH@mwLIxYO7k1?6l28QurYCCJLxujRZ^#TbL*Jo_730~AO|YLTbEENcP3IoR zGc>=k@(k_gXv#cS?*N}3Oi5D|Z?cWNJLZ^nfO!Vm&b-;M=Q@|O?8;k(+d_9+Q}M6` zV;+L>GY>tOlD}cg-!#pwVb2(B_>i|JigeniFZ8Pj`s)5tZUOaM;EODx$2dpKIqR{s zJ~h?@dzB9`ANFHi@WpS)pZBC9`2Dd8ey^ViY|n3#^4Y@qTjV4!`Duqf(YKjs(hYU% z=o5J|MP;C$rbmAnAAQbX1Nwy@F~sMb*0CG>vhPsc79I!vq`&li3+8?Z*6mzJuO;<> zFEKM9hbhuOVL#+N;+S9hNMDPf|2z~(~xuu;J?J_@}Bl@$_Jkejq4ofgEdA_E~nXU3SIHn(SZ~K|U=P=9DhqB|5JyE37 ze;A^Qt+4@PEP`<}{u;0*%u!w!)&rQsAy}Jy-h9@Wd)A7zV7-c9t)B05`d-%s-|%XJ z|86ME0G)dLR-!~aIc9*49p4Ob*s0&5@ox}BjlDy^=%4oteWvd%?Hyk)xTbKuVBDSu zBxUAc#q|dN%s9%-5%t)YAeMPtM_$V0h8EC15Cc6ZQO0kHu70s@;59tg5uX`;Lw;L$ z?b!!QurFHd5%$S{O3t;Fk9`R2Nmv2<71+a5>##Y@M zP0u%(p|V-hc_#V|#GKsXdwTAg(t&5PH>fA3iZj2Hv=%q8o@8V;>(5zjt~J-D^k z*mLa9XYaE2<2upPHJh`?8R@Fu>}&QZdr`TI+MzxAL*M9k6ZMbu_`PiDcaYBSKceZk zl#!sE!3X9+SNR)?{Alkt@LST2D*M(LX3!*;#syt4#v9bZ63hV*o0%$q!oEd*(jP3)9!)YwnC4x1U${|ZZSHww=X2R&Uk zZ~9a{?0{?b2$m$=?kTqQKU#;xly12Hg73#ey4liCcxHH>8~8SK!1p_o>+<&`z9~To zroJly-8Vi z&8l{_)KS=(M#a!IV9QoH zBdRB^E57nf%CoG#_pvr!6Lh{8PTG+Vx$)o1541xLay6a*qTIae(caXvcnO+hm9498 zsSl0o5zq4hIuzxYVvC_ZX6Q3uvu=q$P|otnL$07nnj$?jqYw0>3g(V}8p`Vf)Z@#$ z=e)o>3u9*u03BNo9J5s>5BXpP^a1J#Di(T@ z=sDjlzC;YZj-4q7^@e(b&AI7M4bU^TyKPJC_$@)sDR!d$(K-sQ8?Y{5Z^2Y-7vv+i z+d)pz1Z%{a71k{FozD!?XQ-;qdkJ4?3Cj5U{GrdZ_lD}hG5Ftbz2%5FOYGL)zZNJge{l+@J#FC4gO%y>Nk$`Om zTM}~Hz_`ALQs4u(N-fV2{zyD)>%dM$)lS5BS0i+~=WOu!Y-QYKMLeu?2nYf^}%I7FF{& z1an$6$IwG!1L~m$J7URkGNUfb>{C<^w#^rR=9vD{|EX~>rlzqmcIL*vDOar@Ygpp< zWag^G9N}xG^exE2Jb8}DUDO8c46%jhi9Wc$?lb+}!gImAtiW@}{GCvDVxOtHPi)n{ zk>h%_;r<}Y#YUf@3dX{inka$ip1EL7nkb^jIxu%jbI4pWr_6B?%sp#U1;6b-XOG_{ z#L^md!FuiZ9>=*Xg5NJVi=5FS_>RXnz4IF%wl}I`h(q3du-j)!r=20H+MfKL5`Ch7 z^p!qmn#zoeaWZb^z6sNdnZqr}u_8Zhc&@Q!@H0(1v4D;}_mA~6 z^p$>}UGyR=n3!Xif z;MsJG!Ea~!y=01Vd>3pgV>9?bf1%0_u6KFyJI^m6=2Ki#Jxq-O7*7$5jWN~(Y}6Ti z!Ex3p&kNY01kQu(AE;4>w0`=Z_7yRfr~jw&Ux}&Fx10Rb*3I9xU9}EBfX@x$vYqlt zlg$+AH>UP}k8`==Tyi!u*j?A8Z_Z6X*%YA z$b-D$ep&XILrd(|k(6z-{y!;ieI7BUpJaQPyKFymte1H@?PuKIRc@)z{*9?R=m$k*<7bASr2S7>b^qkv$$f3}T*iHnXHDJrww_CQHZBKC&#f8i zr{H;d9SoJ9T-;anT*!TRa<5jqJ&kKh&rp}kf2Y2c+c>YwS$d7K9NHt9Tvt=wo1SBr z{4+&o9r}eS8|=W>X3J64FR>E7S0MWrO!hy6gKul1=$qLLwxqGmva8M%=|DVr>ILjSvE*Z%(``)UKiRUifBcgnc|sjN zIj*Y=C1G2ZGsR)2@6WvH+a1rEtYg*wSv(W#tXDnna$a|w-J&zfyW8N5>Wqx~&lsLJ z7xDW3*OY4{C}SV0f1WAXKZ>Ir_BwgE#yrpBdj6)}l6c+~d;y90- zk*D91O?*0|xek3Mx^yUkubD&b+<5v;-|2_0S-`c+H0f^?)n%sUyo;@|t;91{m3h}~ z8awmDJUnv|^HM`T*UzZil0*K)O=2S_^GsW`P2WbaCG&W!ca6=gq+_cW$n%EVLfR+W zZhDh%=Jd&RZ@Oc48l%UQ<{TX}KQ*L649aQa4_I=U*?_!EpWfLnw8GB}j zKe5lAR~~Y^4RTXQPJD?aPiCnM*qXq%iz3L0q`U<__;P=Mp0MqdL;g(=KM$~FsPCc# z=-9R(2f3WzZO=dQ35u>)(8LJPu>pR>Rk4(72=X&d)}sj4lXa{Cdxd>e1m`isxuFhU z;xb)jV>|hOgX2#5DTjUg(f+5Hv(7n?XB}ni|T$Nx%4EbiTB@KIb z2=Bq7b?Sok+M?Be?+4bDwe>n%Hm$iC(*FvMFR_1UhxSaB{>D-tp$Ym--wiex0rf>N zZswzirFqLRugoKJ$UR}}I$lJL`#{q*z9hQt16$AMjo+el@9z9wCHQR$n;Fu#z@PXj zwjeL}j1}~_cX0oJT;3`JcIXM>$=3w;q#Dp3AlgKY`?h?!yw#yNtgzv>VB1ZF@#o7jT>GoH!(3Dz^OU)LO) z67xNd^*+nw$WXshUh+8SDcuJC#8pAx*mLxkF?fC%a}mr%6`ObH#FXAd3DB`^fxqXE zJj@$;$$!pGOY=jY=o`>i!#HNJC824b8ul!#AK+RKT&reCbW^3j;oNSksV(>A?3>HT zeo$0qe2ldU=AStz0c*j&sj((Q>r&WDm$hMznQIsUHgLI0XH8feXrhQ7=df`W`F6)Q zJblaC`Yt#{{}Oy*2jp0yD<65uKjN&T7lGX9>IeO!zw9;S2+(`%of>D0{lh#^&ij-- zc4J5U(B2~kh@XNvWiP@=Z0Q;5njnVQ8lYn$d#BeqML>z!Y1sFW4Wj z4%k0`753s3Td;qD{p`K$I_j}=7D_rFJ@lDu=zo>1Skv@O@eSkRhb^;sKKT-!X?idv zdCr0Mg6nURm~5wfW5}M_seJRtRs{W{uZ)#BXN_1h_DB`%ogtbi!Hhk*wCC8PO|WmD z>#@#9*E!>w0988YsHpz&BbMCk5w%I%^rcqmqWn==Y_WI-wYp?t4`P^|1xMm?ahgD}{3eG3K zBc$W78*DHRY`3J|Ika;UqHt;#ZFLuqrcRBiYF#h zR0j70ITBU6pAT-5dXwYOSAhPUSDjhzV_o+%p3R>7U)=k67A|7xnH0ta*Wowu)%|kl zUb%Iz+{!n7pNwaks&;cf=nv&Ls5hRMzapI@%eOo|)`NOd%^Wt z;!FR!U=I1)KAzzw=aBa^ozorfqP&j+=X=Y}b1?5ZReNhIX7g;S?|)S}4CN*!&#rp* ztn2~p)#rMoSk67y6rQ__t^+(f7hM;I^3%3y(y{T3O?~H`RL|mEJBX(rO?@7^4wQ7( zdg!Ob2RP@%RPpplIT$;T&lKsI#kEij+6~aTMn2cckS^QSBW@kic4QTdC+99IGtL@w z#e6~g0CQvT!+#0#%%DX(v`O15$Yb>!%rqa^vd_0V`SEo-$o_#hzp9v%PkD0PQY^U7 zEsbkRH(UBo;M+t=VB3N{b;4d-g1yI{+=A;kXQ+$$0(DRmoFih%F(V&2(Xmm7&j~r4 zZ>GHXwaDjqeDN{mPx;q-Ym*MPS=zTNt_HV0G+Um3i7uLLpIpL_Z#15 zEU{DH@KG+>V4heD)`&G@4cQm$70w@LgLCIIf~~3!@L7TwXcwkz`wQ^PoH6w86WO0U zO_Tr~8|*q&vsNAE|>S)a>G?Z8(1l|F0S%nfT%wI;012w1C=a@Z)d&cON(m04T%0eP!H zZvs1Q;N!STjP}RY`x)?_)(6+wjh=q1IoZs^_5-o{>(Q{c{X{utK_wtODOD+6zT? z`psCXV7x=HMyy#A>sDwuS@W( zI0et3L-6c6#o%`q(e+!(5=FnO7|K9hz3}`-6YRRr);vHoIaFSibp>=`}9ru%;EA#ZGF<4{C*4kFVM4FVJsj3EPl6aNJGy zFZ!_Lb5b);}+``gxij^{+~c`cqb3pPE^aepkjUp6s9QYP*xD-ZeHAKJc=`-*RdI%4SG z4Cr&a;IU*$46#*^m$~YKegS%uop$iU-{d^E_G}yK%eQR#X1r%?a~au#B?+8w>n>w! z!IU&xdZzwSc#!WWpY$dFOwoP&H}H+G>U$LA?|oUPt_Mrfl=z;-H?5N`8{fS62DU{0 zriPAZVo#=QnMVF(+murd>m%A&lI}xS`t>9D z`F+0OX*~z;E!eGy_-g-9W+usPb%00dVZsnWW&sAqA zKRZv}_0&dD-m3h@ZSs!W<2@G%*r=b;ANp9i{%}3vdKBk$=zMmyNxOz}2^Qx(sFGV_ znW6=Z4OX0e&i@H>)YT?!KXbzMOiY!Z`QaTx(5Ex@r!q0b(nbr=4Ync}GkwU=HuIdx zw#K`N`UuZ@$e(=V-opJ|>H~N#knO;A_RZ3DH<|tV%lu@Ytl#owTlUR*S35O82jW^V zB{N&)jQ2^lq0gX67O|8U(6?ZpvG3S_-h(UcKXmL{aJ{Zoq%r$}kjGT_gPCYE~ zDOYX-KSOL2oDCR)^0{yD$432(v&VTPrp7)Xw;9qiTjfku ze{OX4$rr2-KIEk>+HZ*>9T*2=-0FW3Bd!%q*NVdRLf5w*pkuG{@py@4%;Z5*HrQxm z(~kO4H1{p`SdH~ARdr$u)@cdWtcw}&8w0w3YjD~1mObKb(ms~^xJQ^e8ME1yQzpXMpK%H6PKQ&Lx*W3Ctr%f=|tlM*K*ZOSTW1E=V%XOdK zx^EW2JrqfqdoVt%>(qJ@3+PR-&QPQSwkdYN`U87q1?cz?R}XsR%6W)?gIvaKBC#=6 z_TCmvW6ungGt|xSCk7}3b@;9X`}eoxc{D!kKRKpo%Ey|s)_I;77xVzV zs2@|%C#c6b8B-TC*pf|q6xgdvu(yHz`x7&KkcRsKxO~bUF;x<1)6j>OpnvqW2i5GFBQSOvDcVO)`EEj^jx;xb8nv|KQkgW*O3?Sp`LiwlXF$X)VDt9 zg71E?{#(8RX`ONgKjN!$wb&1|k=vTGQ-`GOu6}s0aIRoz{MZa-gKbBQZAk1>c7qRg z=VTvFu?72aFwX%xv;ZH#zl$26W5bX2V*Q5Lf;H|Jy!Q5?oPC$dhW!HV!juj63sw7% zI^>NZ`wB|zW$$SuHruE4Ex3=v_$5pJaEo)@o8-Ch1mDx<@>`xWrpL4Bl+5rfW194n zA=?%_FZEwSOpaSeJb7u4{?Nagp#M#<4y;EHiX`jE8nfQ)i53(|=&{Gxn=`JnJ>+wp z(H>rLUO1n?xq0@w{D>pZvuD%?#?AAipG`|Vo35~Jm7AW0mpH%QQN|7<(WIZ``V}$A zTh45=Z;rp|E!w2NFhv*T1?pgaz&<5%Fejg29!-@FE}wE;>TWj2Q3vizmh>~1v+lSo zN94*j>$wj7MwLBt^EqwV|4rXjYxftJiupuSJ?E1%$$1>?`#5u)Lu}M>7VyiIh(%_| znak*ovu8h1 zbZoZK#>v@-v;J1jG3fZ5HkXI$;4IsIllJ{d7Uey0%xQO-`9RO}V%+EoTN}Mb1XTFDV@0#)4gd8e!Z=;@jo$q%pZ(@BOm5=#_A{5X9WhgqcyPWf-`ci)sc)a~THj>9?2{>*$$QIstN%P3BjlFM_kpvV>rTD@ z(Yysq@+Y_-{^aBDep%-4e?pHCFk1Nk7gfnF22@9sZiFim)^F*#a%z8U z&J$-&a%#VBt@YsFm+6}y-w^#bfk?{Ospom9Dt>WRIGZ|KMQ7`Ix8q!;_sOnj>eYqmM+BUc^+JwCnybKZZUvuKj{~ z{Ce`I4j<~NBOiHTh%M*`{aYf>&nNwy%bPFxGPH5SKCW~5mJ=KG_|o@V|LNNdwxnTS z+@StuKW!uS*OCPGoM9i%V28w3ME1w0=~_MnXQ+!ApkrH6kKf}TdC1iSwk>M-;ZGd# zW+uHvPW)itFUV5@bnN(&gLv41%UxwC0r@i6?K7nt+wi3wKrfL8iO&=}Xn*7vEJ>(b zdj!{@DT>}p0UaAYO?2g;4aU`3H?47DPiWtmDt*N{KvJfz$q#?WU@x3Meh1LCit85F zuPG>Z*{jYMJ{v#f>cl9Ab8YER)ff5$>yWlvQh(z0Lb?wBD#$;g%_dm_#su_>{&6j6 zx+c)?t>>S?Gm$7_GM)e(`xcD7aIFzDBsy%t*qidx4*j5iJMF1iRTiwx2+$3-C2FiA zYY8*qwf;utIP%nhHlPJteVPZ@z&^i`Z@HY)^w#x3`h_lE%u_s5s{x{gHXNmpft-htatV?HYgwLHNc5*KI#hA1XUN6=R zSSM^q+bGX;2CL?lH5`H*CBO&Yrr54^=lP0n1bmm^d%{THCj4E3Zx2nB03DlI()lJa zf-y2?#?CxIkG-~% zWPVL!u0L?|oAQU9U|)Gpah>Z2?6aMqjDJ%O`pG&mmb^ZUxoXTyFy@nEwElx=Dhigat3`Kk(@_-@aJ5SvqgT|$DC2|g6vUN~*jcxtbsb`hs&$5bV7uj6 zPkCJ^+CRqo$z>!qOY|9RNyGlk43%Mt!aX23_xzOIZ0X$7VI)qQ%Quufns& zx3Ty_52j?s&oh3O8G5EU*|I^^vk>&elAg(RS~jWG$A0Q+lYTs7ud?a+bjA0& zaeQmrs%v4RE@OL^_}{RP0N(Yxq)FbhS z@i*XHr*!VBHZr!a=!5&^GSar6WRCey^$~MH9_y!c9WrzJxm@MI#kKUIwY`}9)45O zoVjjg+B8G@iR-?VXZ&xlg zlr**>ZQIgYd>>nU6Z^+M$=}wdY%aUrl9(aLK`xW?+lD;t#1X#|&W-GeDINShu>Ehf zD@I6{i^}-r*s8MIpuIPm>cD+=f4ll#guk=igyWj>Pq72`)Ekxk7QFe`zT|hKX#W9w5vJI}d-f)W z_VE(6Dn`r%W$N&`(PXC$^VSx2*O6xh&PiPlO5`Iqe)KCtpQb>X8a~dOjBp;*l`U*=h`-PEiQ3AMrOE1Ki6s5Yg_|!e2E<7>C(yB)D|q-VpG)*N87ojGw`kq#@?lJ!J)xiW{bwybXv&)jN$*=Ni(>pWPCxVKL2Nhs`9!Je(E zqn>qcv97G+7R0k|$R=1*)_uwkws`W(&RP?L9}oj8KyLyce1S6cU45m$H>j&Y&f&JO zop#re+p$@abBgEe##~W8p}rkpBc9w%bjf^)~(WUx_3o8)003v(K4MGUNd2EGxQVxF+$9pzy6g-w^ngz&C~c-^w2}$@yQ(FKS41Y(;f|&k$Si ztpb*4$~VcWZxbU}A+Z7Ng3G5IDUN>jxSrDQ5#J`BYb)2+*w1-SGdGOavexb+))NR4q5WfTT zE{gE_waAJ8jMzeKoD|7e6>{mM5*_B^3Wdb(=YcC zSp{QeEuM8`&&7UZZ{J{#vNu_OuP>7LBG`xQTYR_<4feEhHJumE6zAvJ@2Ve)qhI5X z(DN$a<9dLO&Cjwt&;A6y_Mdv*#oy9($PsMG6WTRro#TFzO?jaN^a1LZEX~KsGe@#F z=IR5~pJe}~`i$$^5r^G=XTNQ~$Vn$dbtRQ=cDlz14lP z=lmu0;k-_I{%fqmo5XJg&X?P;?z(TK>t{8{fIs^$rIQ8B=?tX z*)PvuPiuSX-};?#lvg6}wX8So!;>i+oOYL8zvW}{nHqnzrUy&q4D~Lfo0@dn{Eh1- z`R--=_+QE+mf-uDzo$*<23viBI3Pz8bm`fyXUvJ+opkctA8bj+#dChu7`c8m`M*IfpnixgXovQ`WnFy9K`!z#*F*iC zyjRdS^^raU{V%dN=3mff{N3+cpWGf2m@npS{sqoB%1|!2?v(STUHg?I7j|NH;Jtt} zL;AXa9qNJYD`N8AWB;AZv|rJ;Xu4hldM*#y%#!|`qpF-2r~~{=i=4Cr&OHtv>gd16 z#{8fUQ6iRHInM3f`f{_`f2Fpw?s~_%jF0s!&sgH7D1ZJ*lN`a8%-G(-w=0f(Loi1@ z=7V`?qNg*(xq{94lAZdQpbVUkN`3ebNy@|&)q{PJBiNF_{CBYqSP#=;ZRR0&l&L44 zT+T~7O-%J;Mc+90&ov;f70-1{*N7&%ey7;+8y~+{O#NQb1;2OtcTFVa8g=;C58q5x z*>R7qoLq~z?$Afpfpy^>c?$ntfTV02za_vBJK#48>q!4*(bMlIw%M2XCW;_;7ga3v z0Y-v;nO)VEm_e5;f%j64{p9svomi8MZ6&Rh*9)1+YsWrfPqA)Y^T%&U%xjO|m7Da* zI%xe^&+6+ul54-#b}44ZS|M3q^6=Xf>rO0w?1L>>C)y(Z1UuA_)N_9^lrv3rTex5B zL0})eVV@!&VCyY67_J0$%YdO@1CYaBjpwFxUwa5yo^K?%@k8GDRG0m4bBrzfkgJIz zR?Mldtywbfl##~uF2Bhhd6y*6)`{y*-|SC6XRsyVZH)g@_FJ3o%PD8-FZ%dEOLgeK zp~&A%&97lDSKPOD+_So53DB{(v@FVWlhV4j$pY<$9_XYZ2g#%Z^~0KC*BLlcHlbf zSwH=T>VG1~eDdAOmGRl^f3!9U+bwe)y6va5AK%0F|N1w43%ikh{0)uoX|MwHDzb0Z z$=eb|dd77__8pLqys#4fwuiiZ2b{7S*B99gxtzC32il}ijG zw!Z7ZywGGjDYBXTH-cN=0fBFVMNIymf@111&H zg85xomW``0f0a=Y$_WsiA8&R|P|%fFFbaVI%` zNA9LQ!alh{-C4g5U;CVL$PZs&FM`X|H9;Scl$XG#2UGGV*y~LUu`ggZuCG9f0Of|+ivN4VrNkPs68=6)w_+^VW+;!w~C>*7#qx>OF{{rKC<31H_x2Vcfoil zPq7cMWpX{aGDGDRxZN!2&&iZ6=6Tpx8E*j&5Ba~;#Qp>loD zb)s;M)ivY!&5-Mdem|`IhN#~Z`F*hqes2VHm{P{zJ4_0(Y}#}u4_CXke|LlrrmK6FtIu)!4M9+5xCVy6t`g&}=MF6M$c zVJ(;w=PRnGj(KDLStHhibunHa>WciDAQwzgL{(i=%yY&#XJWBe1p8uZf3Qc`J5@CH zjLs+D6N=!wAK(8@s%(Zje2MY*hZ*~jd^f1y3ECcle%zqWROvYu9UJv4C^0U^Hbsl^ zGH&Le3+9Bm@jNnDzZ$VaiE8E+kT{NRWa1(8tH?- zi6IzsiKaErd)0fE{Q&r8#Gu=b9Ab&0{lgwIGwHS=pX@otdPzCxw*&Souy>bWPxGFK z`2o%Z#2`?pqA6~~UZzimea@cW?Csd6 z>`V41d-Jtdm}llgwfd}_&&B8?&bL$N+7r&b(0&Lx-3R?!+{d!0?~SuguI&>w&hG1% z>PfVuXZtrgeRF@ex8~c}cFJ+|rM-RwUza60tXtml7R?7_pKX@*x&2p3=d~QF`5l#O z`V-{b>%&+%_NLdPj;9#g&v@E9?T=GGX6nw}vgL3@ZQzu95K zj^EJUf;jTQy1;lDV%%=>O|cJta_#gjf_i4WOG?z$BJVAq$GrLejLmtG)$j^nDjIJ5zpN1J_l9+xNhx z?}1SGKB(`1J-!3-EpX}EUS_BbE!g_^e<%E#K+~m{fbW2&N(a6P7O{2STUs~n)ynS` zooo61##xu&ZJeo#&a~XuReKvb;=Il8Bz_n1JBKqk5*ue@`VXC}`CaVa#QesE62FtD zvA$!zhEal+irIIzm?=Tsd}eDzpySg;%@Q8LeYK%_9e`~?V>g~-V&Q*X7t&TH`wOYuj1NIWE)Cc{9eDA38Q(F;T*UDTrQH1vvI<_HpNZKX9PG9mcKJ~dy^phf+ zA>It>%uS1WsEM4|9ZxRn-%964y3bUeE!r>T4|qnQW9#w}f7au*MN*%!oq4E&IbnWo zbBGW22gpOd^#+fhWm8@6AeOw0Uj+C1>-nc=0?&pncxLddD1v7gf3KK&X7aq{@03f= zT>g$Y{G8P@_w_f%zsV=~yQ86vuOB$xakP1Eck=RF*O=5nZOr3P-MdQH?_2(sz;)`J@RZRs4p}GGhLgAzHMi#9RDZuRMrk322%71{6+!?-{na?&43+rA$57Goht(&Z}2^(=N$Q zZBz5ae7$N{{VSkd1=qlR+CtLCjz7l~*}JZt>mGtRIPdAaXSVi^VGp&~PgD0S^aS^@ z+a>lKeR*CWN6;k88}OSP$9RBmi2Wsu-^CO~*9%w}UfxQna!TyI9_rsPnzW3a=#GV;y!+HaL`ZG3j z$DD9ITqo<#o?subXJ79s-dDl?PsIz``0~D6>SOZW%KNJ*e@oEDPdsC`fIMBcO|0r# z)TcU;tOM&ZA`dy7hd$(@Ke=|?dnK-gnqeepd*-vWP&+J=RK}o;8w)9VMW+KKCopS<=#NzDmCAlB^xBZ^tiq10c z=yB$ml9{bGa7N;+v;xjhz?o`(fHT-F=Cr#_PR5}QuHAE6G_PH2#9FpQkmGw1_gP7MmOSiX^3>SZO*Y1)h8pY9V_v9t=oH16pP(H%MrOwiQjk(=eGSObL@<|PT6FKa^cBN?9yBSb7E|Jl8!rNRSvT? z&l&7z+p$@8#hqk7diF21Gx_?t?r*L=_y45l{C`G2$LYs)y=&9=8$WS3KK0Fg-o?K2 zIY*AEs>kfKKWxjgf4IiePv2~#eZw&~A2kipL=ij-{fy(8+60nkUJW09pzWCRS&VN6 z9cWv(yw(5C#&z3Ijv<;TAbnDS+|+UESum)nlXefu#^QQLJQd%6a$35IAvkp#X28omcYjqic}255Qv z7RdKMzWo`#0WN*-gAw2U`2LsKdMgDroP zZQ|E?w*-^(u&6o{SDmMe&dpQ5Rag8b=C^F&_pE-`R({|9O}PbJVd(`FxXh;@Js=-0*KH;F(NwdWBl^?-&DVs(u?8ekYx*!$z$8B5BiyIAHIZ#y)+(G4AKSj-ToW`vUjE5~XTvF@r4$ z?bjc;J;UE6i=eOj(>B6xmwv3*5O`nCgXr`@(J+tH6Ue&h_cB<~g^wj!uu>$Vhy4KC6FGJ&?!;qfNlb2|w}aE~HhsGos+INSp09dN@J!;_1U2w8uj{$Sv$F8a zo?)+jrt4#kCcl9n z{vI)YXeSSBYQx2WJeu&&f?ra-%c0>tl@9Tq!U4XUHPS{NiK$5x5q7&;>s7ld}awlCc;Q?CYwT zvj%63`GF>X5li<{7u-`^FK}&56ft$bz3w^5rMVaf*noTf#6J2ou{CeN{GH$jV!`d) z$9j&-vF>Au57t4h(>~%_fDXh>!CY*?JTYg?-4s=G&3v;DisqjAXWtmw8SFbj8-Eex zFyw?9>==tS_9f`I1NOzcJwZQerv9Pp;ChftZLWiy)Id%%rDuxTj0@=aJTCh$LoHkW zvkx|W?A>{Q%{lCE+WXuS+$Y=%+!Ne8+)F1@wk@ijQNXk66U5PnT;wkrt1*``UnS;@ zwJ(DGz#i$`|LHx}^qwk-DZR?iJBxkpeZ~HzFFwZ*>&f2m zy1evQWBk;+6bnN*M^~P5;4yEKSo%!C*yJL27mUN)G%=VXjnBMM3-j2u=Bztw+mAJ8 z4{X6Y!wB3);-_Dxs158TayL;PU~amo0XjC~={Lm||meUQ?|@j8#oVfHd?~xCC*M}N(Z-Z(lI$M+p@hX7h^#a)KI8JbIp3P=IpVdy~dtH zT4x`2!F^-cqgAkHi|{^X{}SW9OD>=dOM2HhjK_Xr?=;m=HP>DBQ0K0)={#HGe7qzx zBkcH%Z7X8kzR7y@b9-ydKf{dvNYkW4ImqYiZ}t95a}-p`pWuF{JaaJjC*Q=lPmX1* zb>W_6o@-x>SV?lI@cPdw%Lv-YQP zt7>|O`#EN5Kf(x_=?Dw5#mI&}R$sd#o&%?B=Y^cBFkb z*?;|OJ#K#69Ow35Er;stF)v&@*NyEQhwJ&o+2_{&Nsb&lG(OlyZtK`vK>fxx{5i&U z$K9l3EN?N`=7lbs$^M;c%XaJi(l6W4bFB5hS~AWPx3P%5*-zV6+$YXHx&74FYlOVT zWdC2SkJwvJ@^{AL8uItF^IIC<)5Zh%GW0dhNgI8N9o)X}**5;E@e(WPwl~?Fi@ZHi zq-S#dRrSKww?7!71w|74JrK$FKvR!zgXi}^z6ECZCg|^h{uY?O{qfx|Q`F8ZeG|-d zz7gvD*wZ__N^ie3uHGfD^Q_Leg|o7L+fDuUabQ#76NN5K4`W8?gv!FEEdS<+9cemh{Zw0}e__8v@0DEh5o znsgX%*z!T;yzl|XT8>Lz`?5Ua@G({omLwF;D+l&j_K3A4zFl~-pRu>NseEQjuX=vL z5KEM*+QkmIKgIzz`)9;=$#Q@#gP$=Nj~a?#zF6BTSbNi?!xH$3W1i>(=**MnihShF z*O13XXMEQ{+z9Aru&o5~^qs+$1h*}_Y(;eS1@sn7Nn@L(?OQPhTNBh##n832xbDdw zi8*6#hNvNn=83r)#~x&k=x1o3*k{QcKS3>KNH?}+SyJ8Pb{k2%3$ACYZtCZ`!ZV2H zQsWt==M~St*K<+Ny8H}YdVW?h^epAMI^%ge^!#l+ga1>u06l}BxFzUIE}o~aXD#FN zjAJfUC+jkKSLpr0J7fju-_jI&V=FI^zl$Q)FI`uH`wu(*DRxNOKs*pvFJQ-CBHsOw z*zgfkAJ|pniWU?}_BeZUYkpd+F>8trBXQcce1M%j@NWG_>;v{f3DDibJCW>ba@^|F}^dXuk0XnuFpkph7d18*JcLwOp z8K8GTZOly*UG>!jwP2@qVk&i|_#zuI?#H!p{^2^f#!OfHMvb{ZF3rIZTlYQp{t0&G zj2KIFSO>NvTR?y3unmcAiY|)a8N#!q3O{4evE5j*1AWP}1>)4suno(>2Vy+Hwv+147`}c-ph^OD{zN9{`Uvtwgd!DuLv9H+|>>b`;O%$>I<6qFm4pVGVW4|G> z5nn}*enmELo@}Q-<1N9y?1H^cO{^tr%Nj#rEoEb$uDBnF2l~E){EU;yV{Y~5ULbFc z`Je{I0_JUt&iGMJO?#?pJ&WdfA9!6^Tc8cZvG!e*kk;87>=AOZZ`gy-f+G1^PmRx> zG+p}1l8t!!lanzSe~B8;?3wmj)1HA5((O~?hkbyp1x3=>He`b>yjQ#S^16UM`#7gT z6Z3&ByCKFpvT{B>7;0zmpKWX>Ip(G>^?~fO4O!y+VV$<=(!XIUZeQShGlC@voSB~R znJW9S8SLaBC*x6D5!5~sRXX!r1nb5+7s38|J%=ROcjL1Exd+T^pT-_%KiAmT?BgB# zm>8hX7UX3N#)8J!s$)`XmH(G?ojGedJFhrTBgX~oMSs769Qzbs6>ql2GK~8zO|kDN z@&ng%(q%g#=CoDWw&nzeXrhEfhY@T^;xo>-WIwU5MxUp)?MTP&V_e$CF}a`hr5sOq zzOu}**?#Kn!&=<@IY;(qyLH#m-_-9I_sO!wUdXoJ)yb9l-8}C1E|+~%V}je3C0&=V z-;%k?RJFflrTx{|TY9d|z|T4)He?a6XJZgy`$cnFWVfKW%gS?{ki>(e#`TzpV#CT=X{oqai1so6leP> zpYq)7XWz3;pKP0~_douiO76e^iy_@K>EJeU2lNByL9U?uQ~ksg`wjR{nrx;>cTSH} zV_esfCH1wSNW%Q@Kj@NGEdDKEFeIUcv`%~L{}Vbz7bRHw{x^aux%KY=VFpdI1f72a zD7MarHwNGSF22QiGWg#ZogKT_ztl%-$9rb$3|%?La-P*0cX0OAZ_lOQp+mn-o0$5& z+JnmP*Z-350GlBuL*FTia*?wtchPU$DVD}!Ovau%UpLOyoZr>%oWJo=lPLVQ6vVDC zAV-EiUAbS^s&Tg9+L6#i5sVM$*tY13TcT;cV7|dIr=&l5svzeOMdb0Y;UkXt660*i zs^1!m-#+>c)Du&BX7d|Ku)eP~j<)TV**7Ccm(1AyjofJ*k3Cd_S<($QbW7WjoOz)C z0Qa$UKDTqOsk~6Q2jV@mC2Rc`T{^6gZr6w*euyHR8@+`cANmR7R>54o)>7-rdN)x* z(q`>h^P;$x=4zxlBMN|U&<-6zo)*ZUgd**)H|Y1 zuB!{K5xkzhe&!_ax2)qk{ZsRJLLbMDnAcOTG~ejpnyBw2*XsD2%yC0Gsilk8J+8GM zu^;?w=z1RU{AxVI^bA~j9#)=>{M|y&(57c8&(-sJ&2x7Pp1Z_s;+2bM6Z1j+tO0Ac z#nA7f#c!i{S4`d)3EI#_xq#h1@?^-(7)>x9tg3kNszG&7kLQaynxe!WA%2RgJmdm& z*s`~PIP@vDz)$Rn`w>HI3(&EF`>xa<8!=53vEmxKWEJdTXbIaai+o&Pi?wGhir|^R z9+=$wg7<*;0?!9%0egeBgeuq{_ ze2OQ>W}mSiBOkd&>Q6uN5#I!UbR_-Hyz7peltgSY6Ex~*- zuSIJxMAe#^E`15sGJ_4@j|Sh?GC175!koj*^IXpg+_71X6ZsM0^UJ!oSabFebKeCLSoXIoX<^oYf8*sD#9 z06k;-mK}IE_kedb@ZSE0rnq-Z`7=L>&HbMAt^D6o<6M#@`baG4rf^O>=xUoOJ+sx$ zt=0OrzVQpq#XnJ&EqOY3ZG!#@*SIxr43tOIP&F6`)sJejif1Z}e;{>AtpXz6>pR^gv@!yO) z6%Rc@d%xk9pFZDk%X{|gQA-Kw_8CWe2ia#^mf8PF@5i`B?LPy1$^0ab`}|dsv2(7U z)bf+qewi<9@Ax+VnZx}~``LD!<)?X|Ke3kXm!)%Ln{|BFZ_<5F+0_rQTeqyT8NR!X zfNyU5@4ElqfFJyQj=n44J6;c_6@Ux4f^}w|9Bmx zhAg7$?6`j^ca@$fTF=5+pR=>hwoPZ>!S7G~9-Z;qo!_uU@S8S+t%<7NvqiC0zhmjw zgT-%Xf!)xa@>iX+IfHM0FX}7}JqaGu;r`#juQP1EBPG%B*{ZO zy!N^FdJCrHKEPHIO*%G1y9n|^4V>R`JNgh$9M{g@61LW{$9`drS?d<-jUB%kif4Z5 z%iNHMaWd4x81n&q#N5C)#ZK^Cpq(Z9c*Bw}Lu`G6$75`0i6Y(C$Mr%Dm;)H1eMwOq zcrRvYAF>?`wP7b*2Q{q#-PEH_a^CVdCq80|psp%dbFV*=dmh<^pE2GueokO7@&j?O z1%1f}OE3=eTYkyL??=54x|jj)2;hAIBk9TxR5kSYj1v!`SSeDrEwUCZs{LGAcs0kfkP1v97b`-;aD%xnS!(Z-&~$5kKPnSE7E#a?UCrv78Z#XpsXOeHr&P z5Aob&ZnpFxn4=O*%@uLX$4WIbCgV@#YGOs5jK^4v3Fs@%Elp<|{%#MPb7=24?-1j7 z`k?pdHzJlkriBe@8+rRd9?N}*ckI%+YXs;}l>@pU?gTs30DUIN&san30G$}}Z=L^& z;M>Z$z;_qy_=)QQy1_OBbYt6=4Y)SyD;j?Z=Cx|A3VT4XPhiJ7cKJrkf7Sfwy2(|Q zYm3HpNzQ;a@vPkzak$2<8mNmoX6{&%B3LWdzX|pc`|0+L5L*pDal}{kC5P9PbzQ8%vF0<@C(9n= zlVd8E+0;Qz^1s$N_5^j8sQqc25;?gh@{-HY#=Zn`j1N=nfVzhu{}k+{&6u&KtS4(} zXcvKmE%F*O_pCFr3D(~1*at}Hq6qdPdnH5OjD5sDK@7en$junk!k%Dnut&CF&v*|l z=_ftyuP42x9K@`Ecd2QdkuHn}+m@a8aTmOsq3Au`9>8~U#!kg#w%RAzf72abb$$Ta zmgu?989NjY#2d#Tv(5U5KFD>U=qv-AY0Q*vw)9NX`Nwb`GPdQ|tk1~VW1J#MO5tZ(J~ z3E!-P*Ui#>z8S}{pX6VeE05LFzO%k!kJ7e|q;0a@x_wn~=Byh#aVsdgSFBs&H&uGZ z`Npw#n%dt`6lZ4IM_qdT0^-aWk9{UQ&O6zD{hxKei#>hxJ#D|!cFpv){_m9UYTPk@ zNb{3gy86LS`rKl7%7LES#OJpC*+xtcisU!&jQy(_U*!Czwp&b3*Z4`Nzhls!aLivN zr^YgWCTF?S{Ipg%)?;Q{*6l}5;dV*m*=Cu2)^ja+&CyMj&Uxf@CizGC-~T7)`n&dB zq9k9FZl?4d)PE|kSU-Sx`Z)*rOp9@fWX5%M)mMYf_dYQaO?sxN4O8C$GhOX^u=oxr zhG=5*9Z*cs6Gb{Kz5@nB(p2eN-v_5CZ^&oHcY60(Q}4(ow%&zP6tSzuWi9!wqw_Q8 z+s1D{{T8kKuIBgZf2sZiZ7A{$LEIAjex)ChHumh}_cXt2$-mN>dT=(^8Jh7;lRkqj zd4jKrrGAz3w{jFQReuk-7K3dHVjO3^$wm%xk<(*Uo#{E(pI~SFB07E4fAO1u-x0d5 z;p^jfu)sd>Q%@R$yh{}3{z8`xrr4tC_r>71kEr?`)cGxRfX&$F_DxpBlP|*oPUQT8t(f}j{{}lu(JmC(3}g4GsY?1R>q3rTCmkC;Q=|hqth?>JS$4(Mz;zE@Llf)=_Du=4B(Y8TnM3B3 z`DN~B!tL=({q?-(`M(731T&=@+p28D4M88?889!97x3+XKJ+IKIj6|)GTPXQfsts^ z$yLauYvJ0sMqek_#~xgQm>#j{*lf3N%5JuF$Zf`>2I`^4A$SKb(e$3)L5+9%Oz-a< z?`P`oF+R1Kk?O$@jEyV;Ke1ES)I+Y@3t6mA0TOCXFo!b zp225&Tr2sg0Xc##X`1vR9D|N+B(`*FCnw{13~IoJ+|;ESyY8Lyp5^|^_nz-L+gseP zNNg+aL9ThmHF_-S>bjm8YsXr!MnkY(;5BTQy@rpNCFsi<7UrIOnt#>=9b1ik;W*kO zV9kIwIfx)Zz^AgG4K(y1ofF7IkqJA!-{c>=Bve8 zvW{La))73fHP#Z_#vW_w^<+(fb`h)%)R4rxJyTxB^O%ff7zdgl9(K~*&iNUi=ksfS z#GYc$46y^w#MZGxOW3v}-V(cYa@^>$!wP)9y2++@@f15zIim~CX(#Kj(f%n#XMjHo zW9A&3D}IXm=YCUpp4jqnhA~6BS<+8@zT@m;Y&#`=o$Dr>{M1#%(6zH>MX=872lhn^ z(5vkDSy%c%ryue0?AqF=+z(CrxoXdI|M@`%TN z_oQ$2ehpxx_qP7u*sAu3_tS_yjxzKozw)0r;bn-s=@%>qHZgS=JsWnne+u&!EFU{!gabnXUFo`=@IE0^-2^owq4x3H;s9lJ9>|6Iv3 z51ns-P%rQuFf$&$3FhyB{{F|`9jzm);O~rPN{1#&V#S$|b0O3>IL4CL9p}a-IeCYQ z@k_a?d|FG+(p%@*sk3e4w;{g+bq?nDFTYQx82l!dZSuQR(B87+hZ0Q5mycMTojErT zotLNbzsBI)927}d@)J`-(#}5Yo4+#zO|l4b*I=t|!+9O>EkR#$GVT^l=XMxkeSjEZ z=|_Kyh?J<9CzVTr<}=6hkb1W|Bj>slAJJP_z$ClRnv(Zy*NFm@OMrJ+Gki+zLw7 z7@~zlCuXV^uAh20^{R~>{|wN{gZ&e(f!c~7AJL8FS?M zVV&@Qg1C(1m+ap_9VgT`h3iK8I*=KBCud*Urb{>2=<9q#I_!WsW!|^oInM7Jp6i?E zdOYiwNf=a$>aIR zbHSW3A5~C86Gc!X>s{F&x)0d@JXd*-@Qj^$&ve22#w?z{f_JX}9{YMGhu!*6dx_i+ z8({0AXpA9tz_?vd!wi}vHsXk%(U1P*G1!~@fR6pFf%cBNbM0qM#S15jx!8@qv!ATnJSzAcJH$ea)up$jz@PN`emltC-`eX z-w`xPaC=ENwl&%K=3r>!TY|iKEZQd9(XrEq{{EiPl{f1}ZN8mUG4*{Wf489xJ0Qlo zeU`Qp10%7dGY;c&9n{9!G_4V9XJ)Kh=WH5O$)Q-Tg}yuTxNdTLENW`;zOyPp*=njeRF*+cwmX zoJgpF=3hr8#1KnX|4r&g)~H`DZ;?ryiiQHY4qo7V}O%)`{Hg2XxkjHFQq;p))3S z`VwP?^eub#cj}D13N-6 ztjmtIVSO^J17IV@aoAWV)(cj|OvN-&1bZQa?FR8vFoyF^<=ldO;eF!w%Zzsn?-}47 zV}|sTEt}yTX|P#;lAZH*oGVwzqIc~{lg&)&X7gUwSu!)eIF}N$-o)YiMB#k+K<;~s z>FQ^0{@g$7IoAD=^9@@*XgbRPXBt>ZqJIPZnjoLYbZ&g+jOmJplHj_AsDgE5OHZJ$;zy7#xTVBorCzS=l-@^p4axNO}|^rlijf%?cw@scOUKof+76lf*PJ^*-!k{Zd=(s3S$+6A(yz4*N<-{yC|;pL-xH3>c|1;2ekdW`&T`0Su%Fk24gU^2 z9`!8tmG;*OcKg_WW=aRQEp2}%xB9W4N5K9r^$*zZ8SMCpCub8ypwr)e^c+JRF@PPv z$#GnJ(R{EMw)O=K={5EP^F>U~x0Mr|cPaPDNaLZ`2OPKL=URO2Q@Ytn$4{*LA#-2% zK@tn)0)2q~rb-8{ZHg8YN$-QIxnNG1uNBV-?r-jO?&}u!6mtqq5D)0(4ftVvV9QS( z(1Rrj)C#U0IfE_9_4#^{zFzxiXRzBx{Ea0$bIe>%!LyrZIM4DD&vw5DEd3tn@$PWj z{t^9(U(gi;MR47ok1E}BWjWK_H|bCU){3>O z0c(1Kj~M#UcZw|-lkumZcCMSbU=Eky9bMDAeM#q=q2UZ*u;Jh8)8d^@o)L4)8Zu8i z#w(JHPaR9Ou~&J2uNn*dsuf;3wuti>MQ!?Erw;nT4jX=AyQqS5zz|K81ZM=( z`J1#@oLxio|wA~^9IZR^nJ8{I*cZ;rkg>@Co&Axyge+kgHsIeEuu{P&EVD49dzO@d3&YH76-WTUy zAXgVfc>R&IZAXVGwrI-h-2KSUp4fst(gpj4eRINof|=OTtKKaqyl+k$ZS1gu!aFU| zr9Uy{%WSoq-i5rQ0X<{;lKsTzk|%k_KeeCwKiPYnc}{tY_n7$5a}51wqUgM0YR~-mv8}XIzV1Mbg-2IZ|$HMNktpn<|}a8bM1sHrlLj7wnZP*n8|x_9}a}i6XdH z3~lV}W#ZXuMLEnQANf_&(mAi{?8h09^X1~Kt+OG2`{ArO4zOj4+J=5T;Cu<>gK^N* zhOKdDf;ygXT=o~mFV)Za6`9FC{8jAw2k3x({U-E<8Zx)B*=~Jm?3SRth2zGrKl0t= zlF!`yRsNG(+)8=SnWGyy?vp>qT6drR#d=P~+@SBxj%{CXY?m)%`#$=358mWB_Ukw8 zoF|Wk-EG%`?1>^BJbxptCpy@drG1|yea@Jg#yIt@efbS%zgxTi<~Tz&JkjJUV(R(T z#nQ8k=NSy1Yodk^KhVavV*bCt<7Ih{ z%iR19% zpOH=Tk-6pac&E&<_`2X;9{=+PP4XQ@el!26brC(Nl4kuwdBqSl*niivLrl?vA_?oi z|8Kf%P=n1kzFf3!=P%8V@VjJGjVrd+l(j9QYHgdI*H!1(uJi5YH=EA9BPg7a z#W;SO5eqZU#7$@6A&}TsoUa?dJ@uQB-?FdYwfw#o=p%mP^4pT%l}o=XiQl5e_|!rT zJ=O8b9b@P5T?2I(Y)~Gs-_mhz-{eU7Z~B)126OyGmv4zF{4Pa*!ZDwu`*-E44=mMt z^U=r5hgv*-(;hk*vcYMmjeQIDUl&N)*js`&_6)J^x1QP0Ka3Aw2l3aMb1Q%UION&$J{W-z#O%JJ;j_bH_R3HHFApauqUhf0R2AU*sge( z7fRGZU9bgf3DllhY6I8DbxyGZ&kyqjwDA#B#Mu`gF^*r-q5RUA!Ib1VzC_h?oo74D zfaiZp?+ont>jiSa5KV052FB>3h^6s|pf;Fq$o12Y{u$@0@sXXE!9jtO>hr%!Tn%p+s6Bt_wo$TEAMD=v)y9J%UGTR&smmJa|6Uo#go5j ztRj$8Pz$;6?*#2Gmi$0m4Yv9L{hKJF>s=4L`#A$}7MT1Eo4+^ncW2HCI!8>=gCe^c7Q~Iz2YV4+f3L6n-ClpgH?*{mcXN z%zAizkgONj)}u{a6UZI1NU}!E<5W(rn=u%VI;+_KAzy+v;Nu#Ior1ZdHgYhZDniOf(fb727fi*#7FF*rGo)u$ zyaSOXofWd(I)3N}Q|-)F+wgwR-0Zgfj;zX0KjiOnj(f{9ls~i7{zTzCb)4;{+TYNl zAN|4YQ`&b*;;;dAS8-nFjCE_0g*nt7VE=9HPwtHtd!N0{zQ)GhWzQ~A6+1=I9vf+| z_1IJ7Wemoqo}qIeXTZ*RQ0Ky>^J3TEf#wD5nWDC#9}H0g^1)0r=}-h`S77W<)QCZ5 zIK$o;vNtiS;zbXNY}jEWwsiQEJNIddhjL*1>6@wn9G~ORN3aiR`#agx=f+c?oyI`_ zGvqPOJb6A(y?=B3jhN?KT^o-&;wd2{zGoEKC&o68({KPmG z+bc%TJ)WJfvjFE5&P$R-&(5mnXzZWH^ioqhj*>PvM3HCTU=#AIr; zPnl!B(N~Q9F4i%Y-m7lE>zjS`sQL8m|Ir>1BWRK*MK@;$JL{r5kZ zlF$=HI^=JH3lat3%1U6FcV$6Dbjz!(tC7>%DY$Z!@~Rim-2}!c9lP9l0{6dyXn#^dyeM@=ULth zV(5HpmUM%yim7)()!Df7{MY$;>nzHUYMUM z*h7;&tGx?FZG1DvC+C^#ciK~VPPS~o{SD*5cE{!Z-}IyZt#8+uMdZ1yF+a=^*Ioqk z!2B>L+}B(8JNG^J|La+xHg^0iAb!7rInRFDz*s9_Y}2H}47Mb-!Vv2M_Uz|+nFr6O zB{nmqn=ZWsAM7P#8-8*vLH;SIX$h`v z3+9G7T7voNg1KgHw_qJI*hcsZe#JNSCErk9#yDYY>Y%P7y6yv*>3(qSEn@Hy!*gbc z7Ls-iAAaJtMYebvzt9;ZzOjCObBsThzgIqvfa2ucf_GLeD)X05aITP@^ zQNJID{$37C(C%Vmm%r-o(Ot|#V#7xc;+mk3nbJ*{UPR81Zy%7udGOPxs-_u#<1hSu z|Nr?vx!H)}`w-L%_GP&gmmzN^`*Ob;<6tA^sUCd9Y<=tEyH^o>1M8v+zKfY5J%c^B zZ#przIBetx#+rh$sR4%Ag8HVenSH>#cflHL!TK)2db92@gQD{#&?bgHuN=xn-Xa*6 z@lUYB6vQ<##HNqxp{AxhKy4@X75Rvz57Yo1A8}i-m#B?vW1n#y)IP)(+!H_>dyQ-2 zx^gTro%=@=y*Ewc44v50p>n@*wieubg)??AC3y#D3O@CthSwUz^_|znH7#8a*TVcy z)x$VV{cAjjkmR1^6h%-2anxD@`rhcW-&m@RdD@K2JjPrN)xw^kZsxfNYGm%skd7Vy z47di*bB~C(*%JGHxh zZ1fwVMlSN5OxYNdbucZh2k})<2f4_(mD|t;d@CTHep8T}eB|DOHSdD;2lfK6FQ(Xn z{bEM!p(R=L?tt@s!aL<1TYhMIHx01_@3CtZ8w?Y&>eHy?L$^UZB4L#tR;JceZ<~l zkFtN++uT#!uT#A4G1=J5>{<3Fd$V8mAp4EIMt;U&JjPvu^W@|VsWao??>+i^5Puuu zzaM`BKM-3FwsHWupotPpjlno8okyGWt?>=*Z*Y8;Q+c2#R#lB+a&G2Y1=l=eD~cn2 zKX9uf$I`bSV9T6-w{y(CjPJ4SBfg8Jx}iiH`x9HfcX*6<@~Qv3KK6}RXUnCwvF%AZ z?yr#kEyUh?$GbTDEOQ>)ZqoJpI_t6D^Zd?O%O+U@>q~1sv~T^qK(e2^?8M9q*xym) z|AwV8h(rE_uJ}*Xh(W&YOI=eDE1ri%&n%u@JQsO(aosb~r2}^BSz_mT=V#tjoBYh( z2)5)C)b&J9zO3hX=gqdP+y6<9RPQ(XQ$EIi@@0D-1AA^~y&XQ<+2;07WKoX4l5^}c z_v!z(J<_#*+lRRFB{|mlM;dcWzma2ZdQ-nA&iJReo4v$!-F(=_n{m%RC311Uau4%7 zcmjV<_x1cAwG;jpxc;deVhFwmn*9$wQv$vRmSFu|@dsUPs3EOCeIIQ4PPo%|K=gT` z%a$o>XZQw~vCp;<|89`)f>X5gO>pTf2l<RldyPtS81B zw%&(R6wU^}6f0`5t9(I|gc2<6j~;BT{Ro;Q6ft>Uuy1&Gh^hBT;Vi5(@(^3+#v9_FH>LKIX~{>9GC;UIXWF z&Qsz)ZA~_CZAjZJ$EDwr?Swj@Cv3BQ%E#V0VNZ=!F6Z@XzQ@nGZvTNKbxF#4uljQnm%mLS4HUF=5;J)WKIDb3hZ%O*iT|^hO8$LY~ zcoyVmKw}NX6kCw*9V6{m#)mGZ*a3CAX5B5Q(niFo>D$boMv#{HNp z=By<=uSn+E*q&q2=?}I$j($BTlCaWxqO-oHN;lY6^3k@Bee(n{#4f?uQ@BRviEC#L zn2RB{V7{66C5qO;&<1=}Y{fRg+>w*qj8QHyK6T_er|ND&k>tLBE~?;OG1!SG2l=;P zj+fv%r~yfvT8JUGrg-f1%S^SqSP{z@)RJ*c)CIJgVs=36pjO@cOMh!#`kVREcND(K zz!XJ{zo{O9Y-$tJ_}hH^t*+_sc0*LaVvJH*Ld;q#B zF;~pl4#*Ge1w$J@anKV*y7N}$2IogEK|DFQuEAc@HSzao-i13r$F}6d4@GV4yeF3^ zx{ta*8rn11l0dr$w5#5+yn_p8?|^o;5x1#|v~ar7hS7F}b(K$o37 z#M1Y5tz2(F+t`L&f-$F{7V@{q%{6gtFyk7FB3#+E#> z@0~pR(5{?Gf+2Zg-zRy?ZQG3N^0|e5!2V!=P3<}MAp4X3%D&C_0egI=`wX2u-eN!V z{PMnKuh!UmNSKVnc(GroX>qpF`D=1E)S15VH=g)g(UOG1-v(v+mz_D&N2 zQ=IRqpZ%7^p<7y~ZLq=oH{txJEHQ4j+lD+L?q>Qml~gYT)|&3?;u=AgZJ z#&J&3S%@=E)9;n4-!5Cf`2f8O?EH=?Iwv)qedx3GUabEk`A_+RCTWUvz6JK6O0K{E z!H{fUu=(bfn9{o_iKXv*FdnF~e`4#K-xE_lXrhRv?}1%Zv2|7(0s0O6O%yS8R_k$Q zYep?C z<+p9ic7wg){ec__`>+-5E%ua|vDav?$icX|KH5`|C+DE=7T&u^^1NaqM~Qyq#z(yO zJd!@x@DW2_ljE1-fcTf4ysRnf8~cL2Fa`UDddW9q%>AV#{^Z-f)CS`L`z@d1YBWM0Do01tY~{2r2XXF zf@e6-a-QwC=X`$d6Gwa%^c|v!BBsXcq6&}OqNY<)2eu(rfR240WLuYyxnypu@O(4( zO_TtA3*v~sfv<|GIO3lu@^Y#l1%1i4MOSSl=7PDp z%~O+~m>a%ke8kO=w8_mFBWj--w+m`s)R7p}rW(6gx=(oTAZg>HFS!_xaki+cH-l{k z=o#Ca?50R3pX+SOU&L9bYhwRUiU+n{zKOx zs`Mh;7DX{rY+`j*9fH1`S9dTau`T&B*n0B0KQZ)qop*KSU7Uyc`?^3M0^e4QAs)IY zf*eDTliHw(5}?0h%5Qek-6#9-8`~{yN9J0xo!BPI!P49_|3mZ48cfYQ>%jbHuo>*u zr*vSg40(pwg4|u8V=D(OVyKO|WPWSRdy6?{t~a{uQ|rUtLC5Bp9&5mLa*gCe-vS>w zh@E1G#J1!E^cMD+B(_XZ+vL8sAsMfVp|MMVPA!Hu5J#S^e!jk;-)Eh_OAFqQC)__< zRQ{eFpc{PlJ7&obJ>qUL_#DT37%F|JMRQJ#`TBep)h9F6CKvUPm$|8o!Ef0h&l}_8U-CJIYsmYCdYjndWtX2mP5HaV@py&n zP#*dZ?Yq}Cs?UtRwCP_WKW(7Bv8gXPsGIu9UE+GsvF-3TNn)v=eug%7`-Xhvo&h>w zckEU?xt+g7|Cw@;k2ZZg7CAE5Ys3)ic={M@uVUq{;@i z&qL2`$GnroXP#nnjP)FAn`QRZs5wjfXN=Ez;~kEBC!Mn@C+h;N+16UK=IjCXNE4ks zr~SuXW#7K`vi39kp8KGt`vDt!p14IUePaLe+#(lyn7mUAjZ-up<1#*Vajxo|ud94I zyK|=R`dd%Y--(9U2UUMhg40f$cxXY9oXSN$sA4Jqh~E=4elsxc`X#%lf?5C_8>|HJ znd~EWt1oBbp=)E#xn8atx@fxgiCzAt*s4DC%Xv;ckJqE_EVKVf&p92Fb6Ce`9XZ7g z&`-K-{|1EyT)w$R(D^d`|OyLcar^l;{A8pziHpbay?o0Sd$`YSpRXs`@(TQ zmF|DbjjdkT+V?OYu;20)*VOlkBA;=L+m_s;H+UXlN1B>++fGT$ySOF)jef~}i~C(0 z`9Jk#PNw2ML9S0R*-!i@a{QD2DF^?upi$yjdZakJi|9^#y9Bpn-V*a3S0`UjT$kmG23Up4Ko40ilg zFfO&#fSR#U1Nn%f?>yLQ1NIWMu(8kC?}j$i1KaJ(?G^p8k$0#b*0%}v4fVQiulp-T z^-@zk#-hEWzMG^^JHTe}Sx4^Rn{jS2+2&l9mISbrOGe1@U!%tB#!tZR^&j^&UCe> zAlDK+w|Rz7!E+t_oFCFNwj+z6A34a?m6P$R;Cjr6T8jL{%mi)h^vlo(svu7j%wZMG ztLL~$FM{=9-8RoUoi9fGjijsnvdQN)q|d%EWCQ$A5_#RpQ-${k5*zVPeeo051kd9V zbs{re?J0;Gf^p~z)){A0Tg(fRxxqFLumS!hh@E05nsj_s(0|5Q)N4Gp>)25@a}Cr@ zz4Rpq<9RIBkhNS1*4$txhMHUp{b5BOash3y4_O7*$hb`uK^@d;Xam>tnqR#aU++h~ zPpjUSoH>hFI)8S7#5UxcoKy8aE_F{y*tk|ghX>L(XtxQ3!S3^l0nv#E#5UC2P~eGJqfm zf`JKE$xTWUq9U4e^OZ3`5zrCY?E7 zITpsnI6@rREBKCVOMi@UX*`T8a;%IK&INNY6W&wvj+=MYhB9{hASv5Mym8@v?0d%~ zw%g-Nj*UE(b13(nbd|ADhYx)bYl)fnt;W6f0^94JCZC>!+5l~vpnu}g|HvG+`l!$+ zb*`V}kU0+JI5pRi^Nu_!lY5gNap{Ntfc}U>9b?3|XM5rh&tt`hzVK%bM~Q>pG9Gn3 z$8qOKtS-oxSRu&i2=peXCl_SJZHS3KtZcW{ZoYwbu&#KNfjatqYEN7ULBI4zzl@J@ z?gHZn<^q<8gnePy1MG#mN4ClkN#_|Yc=j3Us^492zoRxy&m@@nd^Jrydy@Ui-en)Nubc37P$k!duM7O}XTLYaSh=UW;u3qP5Bi$9 zzsPNIrsDkdhwM7bbJm}FN5uQ1B_iS5;3x3IeN2!1<6{@$R^mcJpE zav+ZiHaG&mchH7$yw;-3sjoxE&Gp9IGv?9%?8oulhs&q*M~-GoH@5vGTaK^B#Aax} zFGA&a+`jM1Q!z~SvF(=lU_(~0nO5{6{bpy|kgV8_#9qt)EZuRD(6!Ghw(ruvZQsb+ z&iYU+U~Q-97ucw?UzJVm0Gst&Qg?$7F@gA9g!c2g7%G3m-R`OEI88AtTi0AIzti!% zxqWLa``X84%c*!bu6vU2;@H>qw{-nqm5%!q$90ys5BA%3m)-W0q|K-LPjz>Hl;@Xp z`F)FHzVoM^I{Ued4Gzzd@JYGZ!HspGSawgX*&{|srFm{X)I6m?_yN@Q@eMz zrTH|~2isk?U#+V)>!-xD&2p#?Lg$ml`AN@g&Phw>m&rLs=bG0UN9UXn*j{I-|0!MY z9q|4Zxc-NHlF+xkn=M;q@ zbhQ;zwB+deSPA$fvizW$2d&5lW(39Ytmkdd014?9d}|%$ zO09d+Z(s7^?Aj9a;WKR3g|7ph`T0#S`7O>ngnXaFJ09Kv^^{Ha#k&Ie@;ia{#P!&J z%FFZT`K)r4xkj&bE$6+C@kw|8wa#s8JoI(-K{*okfcL|Z{)G3CB|g?i?la^rI`#NX zL0>~OQS+uu9X|LIBMMtO_D;S080(TGE_Id2cG#$=jbo3jtM~LJ8-(A#5!gDv5$;L4z=yhJeGV6G0c%Z zHcG@~q zt*-JP(mCJj%)j++Xz)HseEK%%I{cQPP4HeRAk2Z9?N17fWTJeu~GPaB}T zMZCdvmbV}Fe+I-Q4)Ry{l{WZ(^8Zv9C9m6$@+WBfR6k^gCfG0Ro1WDDbGLOnOWHp{ z3^PmY=zs3#A!(X)uHzf*?{M2HZ{HekKE$u}PkQ`Do2NMcuiKcXU&V9s)UK|n>#<*M8EB#)j+zXP#;r`=PVnpds2z1qIj-S)4_`Q^OhSNp1YR{J}> z`hU`w*6XIr_Kw@v<#+zJht4aoFR=Oh-;<30{%1XMUksJMrODTve`v22_zw6)m%YMw zK(nszfSgC9E9sIDoKM9NO&qQDOgigc2%W2eGd2v-E;x6a$r<`6-%M`TGHRlF*sI34Tj-f#i1pK3i>Q4>Q~2 z546WlKhD|n$eiBuYMW)tIj8M>z%i-oS#OCAwjd5M*AIm14g10S!}7=m>>uwT+bm~k zXWNs+uM7GkuO@1~*zlpBEkbxNdp{4^i0wE?%CrIWDTviI4@>LtTJwy(O;FZ*QxpWbl2j;cH z+_2wRN9G%tn@`|_KkTF_4zbM2enOJIVHS>bawGpOLa=5>a8Km^$bHlI(IcIEY`xFA z?w0oTeR``M_&&}(eTgXC&x2E*7d#(aPaE1dL45k6|4qJv{6Ep;b7TKm+M;){G%rms zcPHnUd1rl==z{yz&b`h3Yvw(0$OcVd>!l8x>ybMNNn!%=Pr>C@);@Cm$qBpt(E&T4 z-*vU#ewJJQ;Bw0tE>j1Tn;^FH!xq>yU(6fxHw1IZoHDm?lDZz)sOuuK4Q-q78nCyC zPrvlp6LQ$tRKL|P?TAB6`WPRe9zXn-+R>I6^w|Yt0d(xthnQDv$H5Mc*W?Q|p2tDX zFhvuLhdsf5;hEV4&rsf}4dM4~=*Bj`gPVF6=XZ8n@9ajtXP^1qrb@oQRH!?Gc7ET- zJ3k<1Q*5)+jvxNxqNz+QVnTmI=(`Z`ZD=G-lJ8Y~JK}p1@GS{ynQu%ZL8qQJTM&bI zD+$Sp?UqCJw0(Vt(|19>-SIuo-}U&WSMj$#`waQE4^V%zOZ=tyd@HPfH|TGKNb>R+ zJqJi~ZaFsQn|WtFLa;`x8+hJv?HQZ+v@t_E zw1nKcSf$Uc+zmMxZ0PvXh8SDWA2}@Tf3AxyxJI~EAh=%gUdw$KCd9otqN^(yFeS&)Y@V{dbkJ!esTl#NF z+E#2ME*#O6JNe&i*@AItd?#UlY{CBF`LI3^s^d8_3)tMo_4rh<8@E65;kg7u>?9;R z&qTqq%-CjWJMtt=&(Is5jj)SEe+Rx#G}$Zo)G~Utq5s5|58!8s{*I=)O7*LD=ELQ8 zKCZtdZJASGElf`v^>DYNjC~c{b}P;htU2pHv>&(+c@HAlhsYt=x9nr~`4C(uvp~6J zo%jFBea{{TVnB#jT=m(M1Nm*`x^!;h9JNJs^~1Se=l`wW1&jAgZ}9g)OZ@O3BGi`l zFhvu@T%wD}-z@ai@^{AkL%yOX;5#LxP#%8~iBlrC4fOC5Tp`c=L9 z#MOp6Y}M|vZ7%Q2kMc;+pUSpdj?(6xZeL_%+-j@(P<<2LV@U54?9^Ab$`#i&*}?6n z;y_PAdk~fwq6zl!d;@i0zgosdj0(0-xP6U@f3;hmZ*uy^PK#fA1j+elE|Fe=EA}Pd3+C;`0ReTDa?Mw_Nfuw%yY8 z|7=M=#6*6>ulU~aJP)?J{7%k?^{6(t!-noU+rN?If}eBxBv;OrWmoxM0ncaPYyzXu zq#MpyuXC2Z-9_SWgf1gjG9;nB!S%P#{>Oi|L5G=i$%?=KJxRU^!bn1Y0|L%#+1-O%{$u6G+S zr1QHOAKDO;cqXKS%Tv0cFWN9au%u&a>EkxHjEs3p*2=81hUnFPRQ6u*{@B_dKY?!- zw69>J4tCKg_XPhT$l2qejGZ>@*CE)u5Q2R@%Ra|mK>wjaTz0CUwvSzOz z)-(4R`+)s2w10Si)_8xW=Ogdex(Gc#C(ls9dpN&~ql~@QE!B6yGxqhao#(CK8v=5P z5MA%#smCwyeR#h|-{eqnKB0cimOcbAX7*1^Abul0@BOoX@@Q|U-#*Z%=z?!$V1L>YV~G%skEB01 zVyJ(<6_N{oTiEjZpuc}k<;qx*Eypw@83T5}e#Ff2uk^ER%g#CgYqA7u#k#SMUhAXv zh9$63hfhmbV^i0fc*JdjzQ}{VoyU+K$`gsb=7Vl%Pn;0+v(+!Ta7}E%HNrjiaINT? zY2`lZ`zf}qI<6`FU?h=orX(@zSnu)DKVz(U(2iIu;}V;GV2UlA(^4Llk-1WjAJE3w zW_wp1{cQEgIxNiz^Vu|y>jurb5jE%Xb-AIWJA$pF# zr9bT)%kxKEpv?Rr@pEkAHexA1)}LI#dx8Bx-4riB^|@)Um|L-!@5tEnP2DKa4|y*2 zVVpO_Ik6?bLU~G(w@v+XQz}ip2 zUa5QNNe}sOegK{+NNm;adds_>c1Q4R@-qs#MF^f{a4uRt>n!j3TE@pr>H7`T{X{FV z-swNZ*l+s($sTHV^VDX@4_fkWF6{S7-Z6TfCswWxW#Z#kIu$T$^5*=G4_}*v{%Sw_ z%JnW2zx_bGMwxr>kU!|%1#QBHzmzYJOn=ajizVjKr`@wji$@av%Z-Tvi4_rD+4G}s= zZ81elj?Pi*qN@y{XYGE&kR4ib^b9jodZnuj=Z`;GlEyasR6pwQ8)ElAnUYN$?a3}e zdtxU;5}G(#+ga$+E5TX%3zmE;L*r5N`z7R*h3%XdGgOefI1aeX@e~*>%LMRS4Xky{R?}&^= zTjJ6Obu)cK5>}bFce%4JjLGYR93NP!tAxr>_lb4er}oiF(6J9u=d!5`*q4ZeSm@ZA zU>uBV=UCCPM*{cD5Zpt#m-;?CuY0u1TlNa~ckc5m3CVA; zf0IA)9RDZ>a+%7Bz36?&7=ZDN0%h#@8Ej23H(fAqL$ri>XZ~4>k=Iw#HFk7g;okB@ zmz{fDg?mv;(6_+vh?&XXlC}_aQ>_xS30_G{3Andw~7Gz8GTb8TWcd z@|=9&=sCFZS-SOJY4UDK@984n!w26G#L+u=-&0#ZSLv7jZ(8Q$ zyqw#e2k*IvxdidahrXIP>YM(%AQy7_i6)<^F+_d~87V1u*{Q2z-13hH=#&0O=7YpW zd}8tUdVH6ge53mh`Te{6|4n7lFXC@%BW`;w)6N{}P4S57Z-jgklg$~Z@kiJXBl<7WKS17+;5acO+P7_|mlvL)!O4|^tZ-PuQm^}L~O2-?iDzQiXU zjN~Y{nK_aNI-Ek2ZwU4XkR#=Nft~sz=$ky)ckIn2LU7F-F?9WG!L@{hA)1(~2YhKm zTNp``WMB7$@q5h7k;jds>^x{koDjsOkCV_Z{qH0s!R00$>~C4^#Os3iLp1tPj;|av zCqwhqa?Y45=BX(^*a^PqQ#653WvSc~)&MADKY}(udk8Vg{GekqJN;-&4B`#Z1p8oV ze{Sv5DUj?}Y(4h}zCHUTCUec0S)ZBfgU$WYA2dOqw587qc}&%Zpq-&?wse?+I6ymO z%es)HZ$lhxyD+6!s0)EF?HCK=U_8K@vbHM;NyGZHFW3{K?44cokZyWDODwCM=L}G1 zY^#zw%17{g0(6+tw+O*=jOW=AJ)d`}OpATu)03(UPre7PzjtehoH!U+vVMSi~guNzh03<+{y&XRj$Y&QhB*RaYGS4jB4v z(DfVPh^BW=llM+y>HXA6_!fBMI{b#94Qw%!CP~~SLjBNJ6Z{6DZ$lZo!4LKuTu*z( z@>++SbLRUs7dgjWa|{QGO>?v4!y3`qdJ&{k~Zy)r3CeEqK zRp!Tfm0k7U!n1daku*uz`ql^YqO1HZN8kNcG9+)}@9HC&lBP-j#Nk`t#ZtM_RW_mX z)XHyyTW2Teg`;!RN`@o^XQ*UL!c3av&C&B|6}og2e<+sN$&jqre#^MpoUR;17omN& z#7Nk4TkAfrwLeQ}857E+Zs{Y7t zh^h1O;&-*q$}QQPo%x-evGAuYF_s8HT>2&l=80UL#e z@gBlwiYCZ|yt*Jy=ZYO4pnm6EGA_o*SkbW)huGZD*x!bIzGNo`@tF_iW@bC$(w6$2 zF^>Fc&voN#i0cPi&v=pkNaACk31wmtyN<=a#IMknGVQ>zn_`@j-&FYKwxh!ojIHM!F;AW=Y#w)$pSf+sfGr|%9#yW)^GJG3 zQ)AnL`yp!!OGLuDa}Pa9`$N8Bh%G|+zWpSrfAXpJ_3#{^ev2WF`tE|9$ZaI#P5#ge zlw11X?>5+W`Y{L0#}LdJ^Vh`6wcuJBZ=lZL(*)NO*HWdcT&Za`Vjl-lXA(y8tWlg7v|rD z^pWd?{si^5@6vWkZ-V&l0~O_xre^gRT*kl*3`q1b#E;QIjI2@>?m z>)RdQ?-I)R;&Td3afmk*m-x&9eZdwjb08OV?14YA_!g({a`7MX)i=B@_@=kSD8%37 zbMaJtiUl zYn_h#uu&JH=ei;(@2tZw6nA95UHvl8PPcIO%DpLxNTwp)bYnjk*?0DT&Afc*gW zUU2&=SH>QaKz|i-n4$^tCr9Q9hG?QQ*P2t-VrYFLuM@8mwk_LCs6W``&zg9xh-Hpq z0(H;@Yc>RZ0lLfXyD2u;E_)qGnSR|T?LrWHle@;mxBxw58wKZsq>XbS#>!aOsKdu? zxAYs=Rat#PZH(g6^{S`OuyRm+gxT z;dha|vQM;+OYT{@sJkZ0Qx+7~Apfg1q^i z!x)<2?8rD7FXLuTn1?17QfqK>K3E^t2^ww0(fWCg9FKD)u_9w|UwN%7eW_3KAfLa= zR*c`_UB6xNeueXg#`S?a*1LXhdu?~uxx9aK?6h+X|fCwqKRE&%1JMGjeS$YhBeN^Q^XC(Z?^YX_LR%dJY5S`@5jad>ceZ-tavTR^so0 z@ps*y#4b$f?FWv&@jWqRuXL3wd=ISO1e?lJ=cS%-R)Up`JTGm|NpJA;?C7}!tI(yJ z_(Oe*T^Q28p~(+U|C1@%Md*HIR^C&GWJ@;dRp`>+!CA{J=`fPuTvp2Hwf?TdA7;`d z59e>OIKPR|nQ(|Ej?Uy}N(Yyz2Yk(zUI~?rW7je^SYP58?qg)%O%f=FxZ}EPiO)`G z(*$E%oP~96T{;hQjvbPR-v+W_N5|g8>o*1O3-tS<_hf6`+O&qvD2UX2(?@E zmu>LpS_#3m#5Dy+_*$b({Sw4Jib?Z~?e)&psl^e2w&(A1XpcTCE0L5|M(Z{(pv@9pFlWqZXU??_gKHzfHgmq4{3^7e4j*6*-3~dDCh7Zl*S!dK-jndb z*Ekk&PePv~LB~#gPg(t(pKVpT?ps;gqStn#ufCHfF8K{w227p!{|?1!0q zgLgnto_jpshCqf``F!M=*U}Fidl&c}y?gUJ@loE5^Zc9%G4Uq`&r13621+fBYCIDd&VLBO%Ta5U-j}`#K~`hoxk1FcRIe?@ePmfd3@K4>pS2#<*hbm79CrNk+JE6zW7eK#LT}R zjbPjV6*P>ZL@vny0V7!HOsm&@8mNt zuu;dFkn0h&YijTOhzI1^)Cc{L0}@8!GBU(c{zpvhzhDpY?o@F781A)PR~2m3<2wXx zw;+yb(uqwUUCw!)B(9UpvYL;Ed=OZ+3~4{8q9x9k<$k+xS`&sdhkuYF*rJVazZ z04 zcJgsQ#3pW2zJ{_HIhK%QtpFXUYr?*k*r?kg1a0aZVZXtza@SKHq9yb-OYZg$)jLo6 zJc2cvAK0pcu613x=1probDxBy8G4>r-&tR!>yY?Xmdc+vs^_^i^E|NhObgNVEc}Tn zA9JL?!#>FV1;6TFW7f9s^y>df|EWFm^aP=coF5otKou z;OwO{+wRi!JK>0}-w;FZq=NTS`QD1}enadeijIvsXa(MP(cbiY=d~1<_|Pus`)0~^ zgHN?}l?`K9g7JjL)dc5F)`NLx?998bKbN_NSp(*Y`GPH-wHa!6)Smf5dJSzqoO{(D zIn=z+otNcZ|5V;;_p1<{Zd$(cF*^)hU!>v8Wb;NHOu5zolbaQjdQ7J(b<&C%GQxVm{2vPjRS!%ELMT)ZTp}zv0_>Kly!I z|88vmEc?>h{=|^aO_S|k!QIbYu6^EhcX@p=w%aG(w84H;+m4(M?kkbcvez?;=Q?Kz zY`X7E)f>v(kA|QPJ~!B@$L|Q@(|(JlKI!*~CHqf=e4eQ7M#gWFPw)-!rhX56`WDDH zK-gl4mQ?-xH;q@!-=Ka2M6cfgSN?rq{T9fXXeC1uLeDtZVv6>_(X$5Dg`Yu|p=XiV z(knydcQnU+Y9A_Bg8eOaG9{Z> z+S|dkAsS~t&U$Z{Tt@}Uw=Xe9#tX?Oy85KwlaPn?D)BL0dgYG0ufFW-_LkW3ZGy4z zJ9j*QJp?|C6G<6g&c2iLub_4Y~;B$lQ=?6CO*~IC-A{oc$SF*O>6ZKLqXRYl{%+EskvXEquD_#YxbiCm|oln`%Q$`ihJ-B@MA51pVSiUpK@6?D*jCw#X@(@LVzO zI(F`l;~HqZt0$b7bhIk!G!1DD^`VV{C)5>~;so<8_iXlHD9f0nja>AEL5GH>Kl zy=~R+*6s5o@!d&CGLOvXNtovw*CDB!A|BYX0riG*2=)Sd0?q|{<^~^p#{=)|^z+1& zy@{^;89!^$MO|NPO?A}o0%h#8v|*1l;rl($wI-JC_2zZomyNn8@O%So)HUk#oM^mf z<2_xzn@95AZsc>Mm-k33pOHJCpFCTzorE&EA+ZhNzQ_xfAm&zl`lvLOkNT#6=TyHF z^n91dcL&3_0lpFNt$^UjS+vK`+N`1W8Z!xUW*+i_{jJ4?O^@_%^NZ-PC) z3HrMr-v;@P2l2PhRG%&1C+--uS>g!#<~!jQe4~8jrTnJyBv(t`voa@)pK&ox=5z_> zn7KZJbqK-wuuklcnXrZ-SWiF)>{B#BJKD2`#3erS%be10Fyue$bG zXwTLE*Y;)4YcAx++Ow8`?($TbSgi5Lb!M&WT%luI>4QJ*dV-FvsUFa4d1PDSY{7c0 z0%Q6H#?L&Q0%h#9M*_BY*vFD~tKdAScZ?&Qy#=g`+0u<|NNkaGttV>=N3h1MJ?kE< zcb+$fBxURsZ05=Cc1T#sDUxR$&o7>1OGLu+@EuciH%GR2+`e7D?&p)l?-ModdB~~O z|Bl|Xf7)8NBya0Tk7LO`M7zL79mEG}%v+~j?dy(1Y~#AA+>YRkGxDs1j*VFO53%zM z!yaKzO~L+Tf4{Cvu3xTco=^E)n7W7X++7Lx6@x7VK0HTg7ivGbU+9|RI%w*HefjEJ zxoq<5vi%{UcTijKo@$6Cy53_Q!S_GD1MVaw!Q~+xT7h;jMHi7ViG9;#1Nw(~(N(@V z%6SzSL-aq{l4eSWCOE4`u8Y^hYvF5@>$By$vwpOpJ$=!q^SE<-SHArzSKqC&@jSef z_Ir}^iul0-d?3KGx@#Ic@pf)ctVReJXqI-ucWAIo)mS z+th|}Gfu|4MH8LhL)s%#*V56R0rZx8?6rT`Lm~wJGdYq#oL}KxJKK@-qN{9b9X86Y zvm{p_?~ydgitSadZ1W^Tb>^x5wvFu9_Q>|m@h55jZzQpv{O&&NbK711sos9G%)9j} z?N{Yfy>0LOsCS)pOWTpo`=2S-!(JGskiSZ`6b0A~ScMaRBWkMFCE&LFQi z>L$xE(#sljOAv-+nH@6w8hxWkG_d(O71K$Lv=t=xteTXIa256e}%GS5M z%2c`1RlYg;-uDFG0bz*9v(S_T&Ow}gIQRJMvpL`7*~ibQDvzEql#ymWJgd5F5P$p$ z&m=RX-!$1OyDN{2sWSA!(R~3{VMzZ(X#FZ%WizEe(aM@1?Xf48?EMX){RvwP(FA9> zq3dDlTHtpcbm^PxLw4$bGCtH*KWx<5cgZ&j#IH=14R-u#OPo8_ke%4r`vo~HF$CxJ z#czSUe!Fz8YuXWmKFpC04XL(=>t5G2zxDkdAkYw-BN1< zJu!$KqULn#Q@NiD=6Q<{>g!%jy=6{GLSR2-@3-%-XlIwLR=p*ga*#{eSU^{Uf z4?p`L@u3guZ^+{l_Maubb=Q58)t7NE5sBxIx%`GBKh|T2Eh1sAEUizcW|l5R8HGGZ#%TcSy=N*nv6pnzA<<>(0Gh_jup$Ti*96 zV+VYA1^{i4*r*$VXDZK5&eA*2&s`GyT*Z$zQ|;+H1bYh5u{Gh?UA{mYK;NamnSHK2 z1JDnB1NsoQk;l?Ao@YMq{dngOv&bf$Zw>1W)B%2O7t)DI?2{53e!Q1_y>HZe$D?l^ zTYr~#^xchbZ=r8+e2Z%d-{%b9>psy{|B2AI!JoL>yT6lpkXu)tO*sd7lfS;*G>rv@ zSfZEjHq6Tw%mr((H2^GxcRN3JjJUhNf!`JFkZUGvH}BIdR-RE8#) zb3-}AYdsW$SnP$mN7yIy)ic+o9AJno;sM5S1i24^v`%>!C{u_3ED*!c#}Y#{v6TyR zfTV02Ig;@hfE$i8z;m;CUpV1u5ZAHm*a4-r2^&$(-wJLCCWs>280mJCUQ zjhMtXl&Rmsv00Z8OJii5tPf*0J9X?}A9U(~x$=D7wrZd9y{Z1zk&bCO)W^+|4I*I; zz4l&P zCv|Mp5igVvIk6|$M@_Il$$N;dea^MVwe9Pf>yzuWbKUA5!o8$%Kgs9C>mHNOw&33> zh(j4awIvqUDDml!y*QK$`Eg$349M?`mhYvus-qDlKJ*rGfv9Exl_KhbK9vh_LVRf_z-uC z5cCO0)cn55-TwGmM-t0WrXF@7q(ik;eX8yqE$fh`_GjBI?eio$ZH&U02KY`BsMF!tFo5$gQ@qe^Wc=p^Nai*&o^~LxlDelD&hCy#q7ZlJ97W2jdIu zYxxw@efG@Zog~j2@`oW9(-vGWN7oIsEMudt5-R@=H3qt4ki$F3@mD!&1Ham?`a6at zb8Gz@S=XZaSG#rUtt0JUZSQpZ{)$|Gac=JRL-9W0b^b}RCw^ke=O@06f!|NDp6Y*! z@3`OiMP3J==C9Tfv(YBcKCd$bXJpa!tlxU(k32{C_rc^hSAvam0e(yVQ|*?{2gF%A zBfQR2oD&qEK6swe-xTy6!uhNZ`{<|+-uaA&JwxpMk3ZpiAT)9Qt~g1Ttc1StnJpd0 zMN=8-cfYCce9%7-I^)1fhGY|*c{uC%jI(scsc`0j$Y&FB7v`lmm2aM&#XNfr&!?(W zhUkB?C1G4NmEmxoDNN~4blDB{){&t#G+yVYb-is(?cTv2gIVa(c_y(Jhu|E|S(bD3 z5S(pqwrnBT&q&G@*HI5k9MKfpP=;DZ-+)h7-Ru0W>nQjgt#f+l>^&vuMkuiwFwMwiIo!>cPNXJf0`fFl~p4at}594OM z9*4((+`mA`*RT%Ya+SNpoYHS9%$zTD zZ1~{=N6-g-nJt|h$mQODwy{sy+e>gg9KrRrMI`7jk|qiGPC;AZoE&qgU!ZR!dCUYI z8~x+o)DC7s9B>&Qe4psD6Kf<*(%2TV;pg^8I=FmG*Ei+x4(H~2x4C0|Q|`(I$a9zc zTk1pd34BAiz56;jzABw3(z)-P=aBUJvySHEe%M-HuXmN$jqR(ey@&*T2>g11GIsk} zF4>H2ekNFAAEJp_VxkilyKxx_Q!uWP`_*%SWIpO#;A5~;Ka(a2o@3^|=Nhsm!1}-t zO-x;%T(7XowTqo=7eD+XA>IxB80zq)T}!zovD@cc=~%=w#QFyO-RH@^kvE<*%b9bz zB_Ra!y~Gh+YgMt0HQa(V9%3aSY3g3VhR;ZvBoM<8vnTX(vt%0&kTbbY(S*mpH2(fT zo!g;#Yr^|tXiqf3J>K{Ep7(p?JA19e$Is`kXX(&0lrwWr{4C~Kd-TkuzYtyf&rn{; zkZgju_*dv>BJ5S-}KnisZ@Wx=+$$MiPVbOz3RF_cOk$ z4Sipm`Au;ByUN1f1-HJ(nJFE5g1#@XnVx>g^EdUEpnt;c@UcEK?v^Asa`gAXMgELK z?@Rec=y#`wR?B%kI{z&( z9-!|{{|P!a{5d1;JSVb7z(6w zi0gilGyAFWS(~YL#30^I(4i&t50tTQ(aV}JXMoK_`q`g0*uXxPwm(VxRJqj85iQp) zB!P84!uN5WE4x4$dP?ee{`fgWnL1+BIMsIBse>(uwL}P>ee>ZAfUR;==DF!-=O<}D z`_T4|h3&RiA9VX59djrLa2aVo+o-SD{v_SD%6a8`m#gos-Z3}i>17pU|)>9&#+h7x9ssDxSqK7xCRH;A@}^eZeQ1`u2=3Ai+hRS zdBJm|3BC=^gfjM*ui7ugqK~cq*puY6SxcSWLgzcqft(Ge*n;;}OLV>GI=t`F`>+ta zD?9nV4814R!A^!GG(r2B5HA!Lh;N2;XoCL9qo=e^`6$mJ$hmPYRsPI5b2~LIuJ@U| zu1V%S$4Fb_7*jEr1ALH_sl$(cVJB6$?ORD(=mpv`UXOiA|AhUJ?Sfd9yZ+m9je$N3 zweF{Qqm1j5zRH;G*RHnJ=T~&cdy+K{^)2z3yT*OA2Zms;ZNc973H%@u+OLFo727R` z>Ys2dOX63i%Fq(_@y%p!i=Kq8nabiC`T)M~>WAung&HT+_IL4Eo+M-a9gfu%jXRsOV=Kl!pZh=KfuA-~EuZQl9RIG^-)?cdpnVtiVCXUWAQ*;rWVZ;*SfhHSlJyYgISGjWjQ0y;Q@-ajDP3YON3sZWfseJT& zfb}I^emBAWL3U(itNf0kx=%FK!_mH~boQI}-P9fo_9c5%dv+HnclJDcH)-tOi>)#- z276kZi>^MfQHL+_nqm(@T-s8PA3jaxscUF(KG!*X=-kb_rY5;W*V%g}N3wBV*V&!l z*-LGvB<=8}%_!~gr!K@%n=M~{zYkr*hwEB7F>XL_1#Cz8PUXiq$a^SXa%Ud5XqwZX z7+mj(eO*SbLbY3Os;|)R4fXgh5eYgre5dFI&p#5D7=ke}UdH`etDKi130q*t2VdF| zm-t&mf{qQ)Ly!;qa0~C#DcyS$iGAvt7+roM3Y4)!OOAZ;hb~s`f8y5hlY{dh7sLGE zcM@_y$A*8`_!?u?xLeLa*Z8+=Q}rW#k&a0_>Hs~2{f_+1%C;_lE6MSOSfk{Cj_p@* z4Du%bPv8R~+}}~3p2IuGYM&YdY%#QEtXl}y&=Q?>HAgz@&ie0*P#O9k|0%8GI|@xY z5YO0#-%djE9mIx}vi7x0j3K=h+~3S`bxFoPzM#nmm^0?C3)Yvp2i5^tlUbk)k&?P5 zkXwYHJ+a^v=%eDgYD33P+lqawd}`-K6)&_Qh-jgFA{OY>VzKfw)O%V5WHsm}g zMna#voFh3;QU>Z^3FpOn(HH%*XGZR~Q}*4GPMQ5zsXpja_3UHz=St9BMz& z_FI;GhY00M&dgO$JPzuKM{N4|MC~8_hPJyv8G1_U7}F9Vd~I7|qpk^jX;;Um??2_9M4>v9pY{-lV(DCrNBz{+LV8E$dR}v}Zo{r;Kii zelDJJz^~#q_?nsHLE472Z*AWdk35GU=b8J4J=qfWEc=(e%${bCPtk<$FI>AkSGax~ z_X1tFNB58+{M_IfQo$bLXUL)VTZeaF zyeE4Db!JM3CTIssbPnmFojK0qEz`H&+yGw;kVbIW*;3q8lhxpl~&HqZoZ zXF@&W26XD|AM&NYo2R^NJMu@jtafaMF#_Y=Ul7HIHqer-xWB{9TrHnsxR2VOZBG)v z-{G5B_Nnog`m5AB>n-QW<#!T4=79O=;%NPcU{B1lPg<6-QCA6-fp)7vEWl>n5?f`c z4334|7gOa*Q`vYwbA5Ed^-hb+l}j< zjf1_Gtz-XJFn%Mh+gR4Tuszx1VO+FpV(J-q^eh}=>)8k)D8ou={isZvEoeW)N-9 zrb&mRy=10z=pwZDhUnUh{I*&;8*^SgoQF9dqv!8CpPyf6ShWtxV)K4NXXM~_wsiWVZd`3AHtO(^@8LHz=WsDoF4?xu+)MRSG4b7!l<~*6CFq0m zyqIcJ+mRDvr@s)4k^Vz42IdLo#a0;_*K5Cuxl0CZx*9g}V9Kp55wYML@ z4o%?ec3pa8ydg>K^KcE2*H*rOPA;%SBTOQ%fV zzO(4(T^d zwj0kIbJzv2Ut-GfVZROaE1-+Q9h&=j1#?`7Pz&G!Dix^7|m) z1-r)Q?}O-D@b9;HpUU}+vsvVMu4`^8Q)RQI1LZE5L(Y7(owU!h-{8zwXwuDVzL|5; zQ&P7D?O7Az5u3hPyCshBep}i@z3emg7EFP?!k%-P`Xju@kt5lX72BKaw4p7rOjjAO zKjC_E9b)J8f}~6w+MNPrx3k1{b4r^}dTqxzJf>ZAY}Db~1$n_rhGY}u-34nAxd&Jy z<9#r+cC6v+yw90mbP-x#SOwdt2Yk;1#M`20f9wnLnB=3}$el4TmO7qL9r?fZj@r&`q5Tj|xDRx|ZdTE$GqkDLjvOL{`#xnbP79n^xuH+Qmw!5C^E8ttfA75eu@eO6_ZtOQz z-vl{s&CS%jt(;FJ^T}LNhY$X=CEhByd}IUioMJzK9eUz8!~mC(lZWrW9 z-sE^P$E`NB1N0EY0^&Y#*Sl;_hTx6cu{DFiu^FZr`hi!)$X zooIUZ#XGXD_h*}Le|n!bF0fGtQ}pDh-4d3>fl$mXW^(?Jk648+9YQ&5F+>xbWhdv^ za$e=!8Uj1@_?ha9pV=BG>l+#~>&LjMqy1KU_h04u-!WW|TwnD26yuKRzH00z-R(xnAHONO zh_0MO6PtaIP&T$%Vjp59A$j9E`ebw!M%j#qO|CXF(jQg68P#HXLbuO(R%_q2Q`DCMR{D9j($=dc^J@)En{mH-D zj@ngf-8cGp)9*KS&-3rd{btRG<+|UATiZC!ze3KEC-Es)u>C~!ull=op_qpCLASL1 zUnzgJucL2%EAcl#Wayh8?8M&ypJbB{9KQ33DSCmjsrJwrWr-o0m^zpA3^~$p%+5W1+(*+g&ZGfs{0krbsu?Gp7Jw2 z_aJ+2u@^-XoAZ`fI(u!-o}5K>9zLAy6Z9tA5Y%<~;Me3oIja^Tbz}vfDQFiNi;$P$Qcp0a4d4WdLgnUiSD-?6F7xkNzJ<48Pl3nxL zRM)Z(x2z!7fWT< zn6*BFJu$@oMea6~n>dO&3q5u0Pi?T7Exi(1wybsNLo{)+k1omB7~>EvVJ?j4s>|ki zWKLoH0Bi6Q_nO(C*zV^@H@D4Y;;J)<$^+(XAsVy;xMNDEJNx(+k z&bT#}V-UyCKQZyI(8tJrhzFFLz@PR*yq^D@0Sb+?M}fL6LeK`#0s9kE_9i%EyzbwU zp?f)SPq%E+Imb+$Tc*C1ZN8)NU5&rP)3>&nbV&$(v%6VovvfUWGo-f*Z0}g5ZAdrH z!*XVRTM`<7J1F=@$T&`ZBMiP1@_kS=#wkJ^o#i;AaW&H5dTVVdfL$*M$v71lK9cLal0csF%9JbjW2+s|-cV+&M@+@JAx0EDS5?;fC!H8GX_D_C2Oys(T-Q|} z%DaL;akn71Rpw)Bk8!UbVkPLcj6Di>nL2D0Y&SzT`tzPfrwz}u`39bMz_ZT`>1IoZ zxWG2Vt1Y=`?92o6bHr2`+tFM!)sqL1(`%0KV^6WywEr4=QTIjQ{y1}QvIk#r)h^Wj z&b28AAeWu=JR@*Us9>)|`qQ4cQ_u%_4$!hoUopoo;fiW|7z|L4% zk9#fZILIByqf&ir$Ioo(23r#zN596xcnsqrpDDk3 zp*3UOw$_QYW6ivNx14YV)8zW_h)*S zc6g_z_ii`XVI^JCgxbOu#A)IvCJ_5%KSL7e6I|Xs^N~Dxp6!x`e4(Wu{$^&I*SS}F zku{qdv&YTer0%FazO;*ib8t@Qk3aM6%G=n6{A6?8%yGNy_Dk*Gfe&@aCqC8T?|Nj_ zsRP?@>H1sVes|ga9wXAWCt3ZTeDCB{&o^A#x9wedW}J8OZb$52WvLDP)UNjRE55be zJ&vRCdESuY2e#@z@l*SMRedY#Z=JrZBPlep3&6I$z^Sn~OXk@t$3dzAe>#O932nJdr0M`zx} z`IFx)oOQ)e8Jg%SFP%}5lqct8omF4wRmE5ZVn)_ANi(Fw79sEh+MdFzUVR+S+d3O_ z#^&5ScxR+{Nul>jQ?v_g)D8Kx{5R-FZHC$sqjPT8`F)EiUa>iQqpL5*6*;z+d6FCR zG!y3STSC5uIR}^DN&8u@Z0E8i?RQ}o-FAHNC&m%|fAR80SC*u(wyjJdf36*vf+?7S zDVTyOZ_Q|}aWr_bGqbAuoR2vKLI@!Ug4o&B+5<>K9oQR&I`+^D)VHX1)X_5zV-695 zoUw-9Q{=U?p6oH&WOD?Pb!RX1guGK;;t1NYW1OYfcrJLJ>a*zQ5NZ7>W%TsnIpdiL z;dyrXHN;e5%q_45$`!X)T`kjY8h!-+nNK7=$E3}+TjkJr=IN(g@a%Pdo6>$B`j%v0 zQ|GyS@+M!{8pe9V?X!&O@|MorI_gc}b0xQb>$}2YsaZQReYwtXsNJA@_WPhev^ zm#aK#H#N?gxkK)RoRZr&mh|BDU~Q&2g?k;TyB*s~S3Lwi8LtV);n=$3@?1vAfpwmj z>y{&JL(=T>th-E`ZI{a6dSqXoVV-B6Z_ei?o-^&}h<_GWvjAg3MW3-GBNqiNzac2lIbbsJpadZv?WpuRl z(GmT`q`h?_Go?C4_mM|4Rx3W+px!0awNT1xAx@?dwL0; zt(hFjiuGN36Drfs&qPz%pj!eP+gqXo+TmPSKct5y;+NcWqfXx?Jig_T4rnt&cJ5}W5nycP5? zMN8-dOZ0>?I_$8oplcWCu<=+!<5sN42HPg^kEFg6%IF!t7pR-6|5OgySFk&R@feGE zh;NA_SobNm;9c^@;5$Hs-Z{X#=MA@GOP`jKx^;}hxE`N6ZEtvClcH z^2)hkPrur?t1mv9+8BS~mptW~FfQYNf_BqY2G?&%TO_OO3FZWh3G4}CAQx~H6LCQZ z;$$x5llWOT&H?h+Sp(LLIV%_BtHpOqv7x>u8^G zH;lY99!c(z+%x&UX!;#(^Lv_pXXE#_KY>0WhWg{@$RAA6MTnts>K&K5bz5;vK}_of z$BGTM)eq(Bi#j$pwGZ}7AaBqU@1MdvVP2q%1mB;q%`I>H+FskN?-~nSM`AY$?pJMG z#%{KM_RG=!WnlKB}~jH(Bj!dC1p|+u!{3oZl_yrKc}`>^oF8TXUGA ziF?mcH?#w`uz$dIw`}SM_PcVAcgaY|?MRv=@XY=j-u(RJcXCdMUL<7`D&H*ajSB5| z-8#$K?y}pEm0rfb%TsM|gy%a-E?supzm<$r!QWl~w`17npZXau^ZulJ^IhlhoAS)u z|5n;(weRV}vbOzHK4pB$mAAfKHs)?ef9t5D|A~;zzoGiKpQ`)Q^87HDBfatZQ+N4q zr2AXqvW?k|FljKTOH*c(ZHjb90);xae80l5)JT^)7g0 ze53!=WZg}3g-o%^xAeI~yh z`rWC5t_#}vZkh?dL52GAZN+a;jr%$GcjlLKU|b+3<}ylL+{0mA9M#`w%JnC($4?g_ zm@`mEPe0b`=Jt2eo<5ac>ey6&TYhgsd&3Ns%~Bbt(`Sm7(mo>FYCB^R19NI(DxV?9 za}!%@k3>h?5nZfoXK%3nK$(8D;|ssUaWpQ^!4f==^_iqR#VJq^!Lzh^ZZd9ST>@#k z$`C16w%v1g4v6Q*ZOFT=)|=YR(Ks$+vjzEej%g=f>{<41=X+7`{dsaPJ9pUd9P`W} zsgqA~iH}kC8-8JlE<&(&NOVWkxQGqV(T2@$z!s{nEg!%fhgivx^!YW*`NrA-YY(jF z61^!V!I?h97Q7>RzB7>MOl@0fV|i3>dJh3*a9htZI@`>AcfHBm2mRv*u&H2A+(Wd& z(R^VhAxSRCNfYFW-1UT73blh98|hoIgWw9xdMM`McT4^J}P%p7uyPpK<&)#`}5+?9n&b z;g2(oahk^CY`Z}}GY(>~?;{_*#4+W!<2xnwBZ$jTM<0S%hznYglwDscW81S&?T5|I z-vyB)ZJWm13FA)nrH$VTx&Lr~;T|(|pELcOm_K4)|N$O_%OBobqo0-^%u3 z|66_;dn8K|xQjs-Q*a+6Zth||?_x)id2uJ>j`z${`5-6c$kbe+BPUCcpCiZ{`9fB< z>a6ohLJ~Xr(cgY&j)@L`^urb?1O2S;>F?lAdvWK!?d-`5_8i#8b=xhqHSJOMDSJ7t zeY{kMBYHk#TM~HgT&{AaU6-Vv!4FWU&309yH&svBdZc~c68|HiY^*~@f%+0hFvb?l ztAY-?z>YrTioB6W)_}E`0(q~CbB<&ku)k^P!+!7u)D1S~?!VOrj6qCG96^jT*EKS3 z>gYc~`_4Yh)x7oh82F-}`_doi<9^g>r;p>KPWwnGSDTP6cz*Z1sW)N$E;;CBoTGBf z@$tW}ahJx0ld#tF1@-}ZV~CZ6-b29qsN!~PZI2y#>P_IUj${9{xxLHA5T9d3(idO& zodx2kW39A757hCGk0&?E+tL}s*~Izgvnf=jj%>UubXM{X;5^+rSAlbtvvlbU#kS!` z`_^j{DsRp;K^rW5%Kq`8Gn})Y|Lz0Hng00Yor7KaBF@P5Ig*rFW1f#LW=i}MKk;;d zBzER-GCq9Tei4tbj<}cnvQHRe<(RC&lQZSbd7PPZ*O8Hs( zPnZ8UB>o}9)|_V2QlhsFa%CNTlg%c7+Ar+)5L5eyygEnN!T5ot_BX!khy4-c?oXi~ z;A0d*WwSNEDZK8TeR8pKKQ!3^_LZ&rCv4ZUFXL43-II_c*T9~ImOS|;=l=&xjqx2_ z?GOoj<0l5^jo9oR?IHG(St^6;k>w-FUZWrVSK@Jz!~yf-s9td%KgnwQR-P~ZtImC_ zyX)5fjkK?SE9>0x<$5h!hkP4vW*%yrbyd<{>z~S9cE&c;DO+0q8%ZDYW`EN6Tbuj+ ztql488`#(Mvvk?}{Y}1&)63i`&x_hl+j+t3UiZPDly9G1|1Pi+s#muD4d8g8Nmn_# zPrxj6l_T*zW9vNu^8>s?&euOINf?Dt`KGqJTy1vKcuA9d!)N}~8GqCB{J-_CyZtBm zTl>&>u=I?-;kJ?Qo}K&XuusMF{?9X;bGvh1bMMuCmpkTj&&~TfcXsZ#OK=C?V#sDC zp*}$0Lx1_f*G@?tJz#T#9(&#y#Bwqo>=<)$kJeq7@1Vy0Sl>O+1>Zqa-$N&VgX$@% zqkq12USy~rJl|20_~mZSSj=~F*Vp*OPF%#)^d2K`TaZKK8*W3w_<-&3(FAkvg7JX* z%(1Eu`qjR!TUYCp$2Tms!AZLI3AkRR+iE*~KQYt>%5ySI`CP zzS(yPW%St6f6I^a!x+TG8OOe#Vl>$ZKjZ!Df6C}-ZvtET&=()fg?Pz(yCgc=BIm`p zKp%5d2G@U*L$;Mry+RvxWQ91M`70NbyeMBG$Q!vNw?UrN_UQAQbZCFW7JPp@uaB*A z$~rM`WEbRl2xO?fg!542Ax2`N4O+pvD!1C{V;@Upb5v$6X0j!LHG*-mRQI|cttIQr zx~~t=)@0Lj-R&Pg^zUlt-NXBc_fvgeQAP(x?QbmUVH84TsPDC=G2m!CW8IVl$3FbaO;JnyYT?FTl=J`z`%%aGx&wqZol$$1lN_KJ>pqZ$7ore-uJxL*B{t^Q`K54W8wE z=BLgG&WffpV{u;V3_5u>aXxjynZ(&M3h0|`ZBHBLROdG|y_<&!l_$Taq31U-+2E6N z?iq_SPcZfkdYGaM-X-+K5B^WyEt>8XhdV*uGq`swK|82i&&DK_F;7wBl) z{1#YXJ_bED*scq7v|&rUQ{xjCcOhc!iuw5sSNENkccQ6#5O*So!qV@(pQv^&-?r9U zcKM#kksRgDMO?6wBT2mTLw>vF#oWkkXrAPj`2yt%x}N!S?#K~kAZMZVWsOhLv}Y*K z)Gyg!M?atpOGMhy=h=_!$t+X1j{RKe>?^S@j_MWmdxgEvUViqb_UGaG$$iV-Wlvjr ze-EBB!87+8u!Sb@WvIhQ=`!^te)5eCU~ihrw}0!V#_obS0%btI6Kr9KC29=R(PK~l zC59k>WUz=;_l%T%fbwCE9to=%k zBy>FkFvU(|zSPcG*z!&s>f7)uI8%|Fy-Rx94E~9gKD@VpGWwopb}V)4Y-c&k*^R_Ys310*K27!=t)Qd>(06n(^QNhh`ANE z=HVQ9e&niUzJgpTALJ&;O}-Kusz(`!Tv<;FzT`c*)&;0tw=aOH;7<{(??FRi4A!OTEhNv8A)5DJw6y~7l;8I(^C1jxKEYzt@T=NITo_U;BsxRWw+n5 z+Psy=hgg>MK)cI7No>tewr}n3Yx!nloo)VBHpL2m+yCG8^Z37!UQ=vcMtdc8{_)>V5n}k?iMf;?h>%_KEa(LlC5_G z@Q&d8uhQl7rT)V2m9Fw9PLpk78`qcgBMHejINyQu{oi0ef9tE2cZk8`wUHV9N z?vIn_@6dV7xy*flJ23ZM?zr4-yY98zX^-xjNbb4lY43RtKIQ(49Sqq{>A9QY55HS5 z#>#sK?*`K~J`l$)P=D<4!8rKVJ(xQ&cVxaxn(+4!-z(4WQRO!(zGI-N4QPi|2z}%D zJBK^@bH`@B8aEUtu@P6x_Z;s#2<6aF9|hakuFKZU%)KT0DAc|#yWRCFM;VuLZF4`y zn8}eeP5a>!wn5H+L08`p#6=!iSJs}~);&kQpYIv%8}?NfeB-d6fHFGo!IpappNzrY z<~f-8tny4RafF|FOLRc{5FzMCf1Z_-F_X_$aQw`HGVRQb@n?bl-%)LAx%RQXU;e8N zW$dsazr6$ zLb*aaVB19q`*4{$4ABbI(PIZanUZh{)VshQAH+KavBPO<6GMbxep56-uE<#zkx(}1 z$TyICaNYJ*y5B8-W8*&6_EXum$Tu6uh$LocrEJ}s+_JlUdc39ajCDxsENff5#OQYH zk6X4NWsIF=`?f@nT@xPTmW&yKoHfBS8^X^s&pPMM z;tbK*!+FB_!`TvoGid4eE8ej)f6t=Mxx_hyP3Iiqtmll94ZnTy+abT1ErBl7=kCWC zLu2tC=?P`V2g*zE-XJ#f)YZRgI|p|UWLKU02=US9B*Y1> zSBYQd0Qd#Z=VUIBD;biNP@O(a;J1qq{7%UIg&2v;aW=)d{d;k__xLUp(gF7*m*1rC zQkKY~zDH6~@Ef3X0AXl~>N&3MQjeVDUll|Gn6ess7dvoMo<-Wvo zb$+1f`Mdel2Y+2efx7jSE9j5FKhL?@D&M%R$|gJd+aa3Rxqhr6YiX!&fep4xME1we`2gz->Tbs#AI!~jq&^GOquAIEX`0I5`cfI_NB=2~@VH3gFk9u& z+5>xF6^_cle!Fqo-Cp&p`06S{2*w0rXo47n7!@~b&pzN8H`LM7jve!%FHr6RJ(4;; zX+u({4PTsHoQa&5oT)9L9OXQv>~`c(eRH0{wfKG4HS(3!1rr7=H2Y{a@m;|`GcoDSt#7pcgp=_)>IZx(0@?FLJ zS&IHmwXc8du>tBQ+m@v1Dg*T+ z+;4yVGfwUMoeg%jZ7TnkljBzTU0=p9JIhFy-BxA8PUCbD31xKW2k2YDo_-;?|4+U% zxZg`sUxK#&4fL-Z)$15PmB*L%oY*3?=X%2abNNk%+6{dHKc=Y+Q}J}cT!z@18}ox+ z%IKLhHj#bk_r{dIf=v_{3-AT4z&J23y6WV96^6<;A>B{7kL_HyTNEvHwPT%r{9)Z?x1Sf1>u^ z*Zl7I>KyL!y4H*OZS$$_zW=3UEc{hT`A-Pho7&I1PqNznw*1N0Px{;coBeI`Qym|+ zq5NCi=WpfGSl@B3cIvmT_V?fUev(^v1K^HeTHY6q?g}sy?hR2`I_pQFsa)A#@|{dc z=*emFEmlgGshgqlO{5NKw$5>wDP5*+&>3`;k^cqw`p#cdyE(j1k|kN`s>9aWHqmt^ z4bEwu+1!`8D+ZEp6Wt%VbGL*$<#X4So^KWI#enV99hkc=cTs+SL2?hJf6$)yP>;bl zBgeuA?eL5(J$5tSBS`ABHEiVXXgqu~2ID>VVeZBH?%4W1;adi}@NZQ9&C1fhTVYSX zQ2!&ihfn$Jnh*2s8t)lfafEV3zE-}^$Y158{!Lap%JTPM1CshBHOV$ zqWbcc+Y^iOrkdI@%4oqw$7dY;sFJk<^KqHYD|v`6aiW1Mx%8d60YNi4XT* z>VFc-5CWfu`Y8IA;~}x3?IcUmpu6X`H81udxo1rw^88|bZ;o`V7wgX2MUzj#njXO# z-|I{p^den8(y>P(-#p(RK2fhD>iN93v{80PlDcj#~WR1yX`cZa&>xXpE zKHzrby0EX?H}zilWLIsb>~D^AUI!%WVyfO{w{6+|hR8K%-C2J_9YWv-zwHCW2*ln+ z2y(?a#B)6JS&wpd`+VVC;e6pYsiSk8yF%y;=l3gq$C|?T2JQ|+P^XP|JHL5Nfn2I% zk4@+Ok1yU0g7-D=ge|zgFdpL?&T61O#Sxr?NI+-0$|3N9PvT~rXS|F(6bJDjiHVpE zb?7N6pBJ!aE?bZj=5&Le_G+`z4jq0OCj{}3GiU{J=R9_m`HglZ&;94RJ2l;ljPFoY zx(zv#EeRoC__TjZbPxsWmUJf}zM1h7GcXtCv;^}6<_xY+m5&&iD{@1*CA3jSKb1e$ zawI$VQP&!?)-Cr@X#Lp(KpCF>rS>H|?13_z0{Rg6W*m=!&R_#e9Kl}0KjR(2K4edB z!CoD_n-iY3&U;$$j;$Q&Dz%L=ZS<=)E~7VH z#OAmxmvrRFd7CQ##E}iK54|6~C*6jRF7WTMn##mPY{W?HtU2qyl9oE!n9mXP!AGP| z{8Fba(vNNN{gl~LIzyJuuFjdKbCC10i=i`@vw0?zL;9w3^RdG=p)Y43?-cxTCLNVW z`o(U_W(#bG>WqQ!p}zrc{uU^Z)K~spFx1|2eBz-G<4DR0j0M zI@%d$G8=-y9>qQ9|D!pXhv_6qd?>F2wVV zBR^9<@yj<1P=;B`=-1afLwde-p7@$<@;-Ycr|Ps}Q>i-2TXrxL%C=cw|1<7T8U0i{ z{)b>Z>bvy8&bBUFF13N{pX7ITP5mo7$G_#wIJfTXeA}>=+~aG^*z|-l?1FVYZ7iAp zjoXkj*~M3ld3^oP94MQmvSHn!zu>kv>Hf%3Xe#fFt4orHAwrO&3Ob;DiIdRJpmP~H z6YJauxn3C3MN-S?91H8!#L?QZhK9BLQ{w7lU$?|BwtoV@+nAv;w1jiyjVb*NcJ$qX z@rk9fREMey)&Ca80mf_zG2C%|s(0D`g!^vUdW>5#er2g{?zz>u-{orSHtSr!^hXL-$$k*4$^gGa^H}&V5&R#^=7A z_eT01mC-G=LEuL;wE^wu=#O9S$G#gQ4R>Z(v|}fG{L!Cr!2a;x(=Rb`rzK8&P2G99 z2Xi;(p3I$@?~|VJokAHM?Grof)$a&mc;dXq-x@zMUrX{6~+_0mi;zte#_1zT0VI{a82F42F2>(lZa*XRa-h zI(j&&FWIw4S#S2FdDfpc`f8lueB~~s=YeN;igUr4!5QMSgZ3^$@ND^c;<9yvaeI(piPIfx-4cH#;_{Ma0^r6<;vP&U>b>7cFy>r&UoHeGi3#5ZGvU_2!C zBZviB;`&w{LOuXrus%Rr2>Q1Rbhj`3oTSdBmOZbn`H^okR9@l;)?z31tYf{vYv(fS zc?9dc6UxxUR9mNAec3OJMI6LMyh!3<>?RxRc?a>%;=Q-7_aF5e>>$KeKcIh0j{H^7 z)4m0Lfc~(=5ncZV-%v*%f`0hwA_{fx%=Cs5HpN~F<@#Q+!2_| zEb%(dnQ?Nrh-66;J98j@=5MIO6g|P#(03%1(PK-0#vJJj9c|b~vUSHX+;@z1Rk{t? z67EX;UfV?!_$}AJ=hAkAZEatwAA#RGP94i*y4`j9xj${pZ;6voMh8tf2<77l_K2Zw zmdX|M*s+$+{w({7wcok^E>o|df1^o%xJ!y9pF89Eld*g5WAH>S@CU@G%NY<6$d@)|7ONZVz zNa|hKZe@HSNq&Ix5N@MvLS?vZX&zRlt!?PK;MoaDpbnJL^BnQq+|XwGC0pnP z>yC89!MNmQWIm5%&7;dR_s?&n``~M(Z~X5ZkFkUC6>HZs&-;mIo^u5#bH4EI;9Ou% z#C_*zWPi>K{5(EAC)uMPoMAfeHs_(>jO?6~oXh$5Ipk0sJ?)&Q*wUxxS^4;pFQnfe z_=S@;$WB@Pf^pz6hX6yd<+y}WkeyP8{HAeYf;alVrU2Sh*zvKfyO>FtZuc2ObUG<}}mmr4SWGh-i zxq_bhzCcGC9L)!oV17;W?A&uDxdYRev6cuiRS((DycZ*f?CAfr$p-t1`!)47TVh~Q#^=CY6@WI@f=QDTaE4p;(p~>buY~&dryw+{}z1E*S>CPzt-E<()udh$I@-CTUNcxmbS4(PucC3wcWa(C=Q;U2N^2AsYLLXH^W&7|t`DIg9g1_rRgM;np2> za!(aa_290^{ZKa8HO?O+mx?JmB?Z0Kp7O<~n>XG1k6yC;i zU*svisP`8_I)guB-7S0iK#%Q6nxtWk$Ze2gjh^tU7{=&@hv zbLSpqU0K5?F7`gpMpF*I9F^Jak)x z+Oat~zQ$G7ZmW;|+UJnpNIdVRxtykcqKgn)^CXYJoZ)2N z$wR*g)hlkF(*G&vrH}m~s7HHS@o1_o7=bam7zNRiJ=STz`S{0d2UA%lAHncnde;LoZ;MSoOQhSbzbrtROk0T z{ZGHd-7)kIOu71m{psU#SU@Je&C30NGrfeKX zlMS{oud(ri&VFY4vX6s5tp)4C`mle@RvD-_fz1-VK%F-1R<1YFYmPotciYJ_?S?iu zf_=jt0?O#|!Ptx&Vr#7ob@WR(j*-6bF|(*`}y zas}Oz4bSy1&l_Vh2G3xq4I4NK{VMp}FQCVce)fUv7qnNfF+FWVk{F2znmCFPD5Kji zpufQmfA(#O4yqseHo-XLq^GRoV&gvcQKj3E*c$4#>p8aTOKpH{OBiz(X2w!wZGS6Q z+gk4ORiSMs9v_L0wwt4U8G>hlea*8l3)DmUH?W5zn)>rRz!vx%VkJkCv5AA+EJ4o5 z-@HKgMzwQ$T(;El&72vJ@rh?CM&@$#9P>>3T;@5i&v)c}=mV|vZR!vBLC^dG)aUW1 zbF%08h}?p6uxSj&^tc^eIa7Uha#pUiow7gTiP1Yvv3LI=AFxDS^Z8-#cpqR-Km5!Dzw8TQVJyakE+TPWEQfLgEpx?O$-&h8 zkJfCF4>M_!N4{5~s|+Fdrt$X-add%%lh6*MFx9qn*X4fO zH5bde@E?8^a6D_0-M^8dhLf@FNDfZOp1##ZSw{6p9r-XY-Zu8 zT&ZncZSjXVR%Gp4_1~4=rc$mK40{i_a}H=6X7lfJuTh@m+&!F~q5ec)&x zfa_B_*ai9lK42yI1=rpFowRM0*tZMDCyt&FBYXB{NcS7=e$?GBR0e*h0e=fFo|_jq z8*UoE_56Y%8`yt<`&9Xx{!L>L=PcZDyS>XM3X3yBa8{U|cZBENQQiejX9{q>tP9T7 z$$85eth1Q2nKOE%q>PStAZIo2hRt~_|4n00#l$?AJ9GA0S)%)|vF}@cH_mPQUA_AG z&hERmuKfzP4Zd$(t-IZ_w*9vJZ~Fdr4D9+ZS--UVzW!lJen(e3gxKwW7%I0Hj?P&#RlafCoBYXN?ek`9o$cn!_@8udeY*Oa zP#Koq7ej2V&&;(udM1__oE4lWoO7HloH3kRI&ZelsHL-~Cx>%Ly1~7ccFr}~cKYZX ze9l7oI0An_xr@O$DZ3$ic+N8E7?bvu97*?`>JK}m>n;7#Cz38n-`gG?ZH#|3{!EC4 z*r(>eT!8YUQ=8|)SoXQIj1Ir%w!!WuGS8N@Q#K>l0%@IPZCh%u)HauE`)|wcd&{`u zar??Rj-;^;IZ8j~Ix}ZWc?;24d-6=qmHWswAphi^KKNz7vTwb2(FOYS=QY4@Pw0<- zuk}!w_NSkG9iE3K-Iwh0Z1MSmu8R;`Hcjk&rg)A*5Ch|~hX!%$xoEu4*+bGzwGG9` z9GVE#(X;QFZ@czC_V~k=eug@F+Kuh1?UvomkPaK{y9lwA2XaAfHgOiZ%7AVQ?C3+^ zDewjOHP%&m(w_eKVm+h4{CUohNBYN3eelWptmMftc@|A;#y$Y{0c!x10ewq}ehJP% z)@?M|YAspQBfQ?@dZ*Bi-IDz%P)AQY9DxtsX-7=HU-|CkZ+- oPc>n!pDv(M5=9@M>wY7*`u$rz#rEdcwr_G=xm{%qYTH|1>uh(_mo^vVhD>yUi^Uw4CobNg(2fyR#yJjSmr@nET^h<WLI{G89!Pe|7qX%qkN1b+$=P_s6;&X_&ux|5B zT9_&~ar9f`68!_TV~4$AjFnJEkIfd;VWgzZW7vkaC5~WT%)#?O2V1(OoO*8{%~Y9n zf|L4?thu318*9qi!YF;{OB-u{1bcK0hkMDp2q0 z2UGAI@*IWy@hmj?#y@+~<93Zt97i}m#JCgZ)OFgRrL^95NOahM+bx@PjOTe0A9E$% zIv>x4HqWElcIgd$vGF{eKXS&o$r)*e>YR^Ja<$VgWH&V>FpSZARFX3wm))b$058P$OTM1^DMsVq^})P5jKeY2C=*669tpM^C<$FLF9xAeYvYQ|#%BANC1; z@qYyQac+o~31o+0N3o*uXtLG4&)Q$v?0b?LWfL1pcO;kE3UwdmHE6(U}lp=}ZI46P?_W@8RcTe7Q z)^q-HzH>H*SURUG=!e>y&giMLm>h5USea+yBEFM&K5x=C$l5-y{O@ma|6jL#+N+^G zcaLQq@+a=T{}=VQ_!#%QScm3fzO!?iZT}0oH1-?+#`os^{DCQb`-bzSeJobeB|i~r zdt*0^pA1Q8KX5o-U!dQc{yW{?cFQh55Mt?_FvQjxPtlX3{Rq^N zgSyVMrt_q6e(Q``I%`5~olQvUGw;sa2e;1qfh}hlXPWwU`B?bT*)o$YY0$NV@c?`1 z7wC?_o_>ZpdfIzR>Oh@1CTF8!o5ZMCPtK!p*X16~S<0DPKsU3EIn#%EcKO1u#|X;O zMM8ON%q2ZB?!O?Ef5Tcp|G<*|6Gv^|@u{EnJ;w~mH{6a~-#~kapT}eW{)|0y?3Ml*;}fAa;$i+r^JeZyazS2*_hifiy9@f#j-73PV~>rYA9N9d zSf+AK93gmC*zc^tvo863Z`oi^U*Z7Aw@VrQkxds%zVUCG%J^Yz84nm6JNl5HB@TH~ zUdb)&A}N!1=by55*3$+{9Ko9HTPcij63TlcNfKCGio*?J_t zZfg5m`L?g|U_X8dw)Qpi*|D^~`dhL;AE0fB5cp_<=d;2y+l9}8=ULYC%yZAP&bh*w z5`y2BIOjtQykD!fyrrKgt?P#m*UF{*5ljA*#6^I$S2tllk94KynmxIOk-Z#(=U41-f=4~Ht zKe92{0(Oiy66#af&V5IY?>Z74@(6qZeUa!Rp0)aPVq#p@hPllPbU^z`9H(Q%-^h3l<0}rHN8YvO@OEuZDz*MCEsxTmP;{IM#dc(JMkOZ zs;$fDza!GulrQFO$jcI4kP|~4R&pe7@KJU4dCMs~sQSBZKkJ7vvBQt;Eo(o@*4Hu7 zeX_H?Z9=x}lc_x#!uy@Q%$~0M+Vzkgy!Y!ffsQt8kC^3I^>eUuUv`acR>piJ!Sf-1 zK)tqEe@kq7VO{?4gN_)8hdHl2OI#)gfWE7J3MBQd`S5%j>O*?^9)Vx{19^Sgl!vbK z*5@l_@5IQ3J!dTEDomtwq?q#JO^O$1{o#{22S& z(naQjHRZXtU&?Q|9l0+1Eg!^&FXHM6xvhDf8WXx;eC9>`#OFCQ<#8#G&)V@$6y&Cn zpUf3Gi*ImF=|f+9b#X+^(NUfln;3|PxTZLQxS0#NID#{pGkfq|s5>I}NxmoeZsMC` ziX;3@Vmb7E@*P|4fK3zl!Cwe}!|X5VMHfe74zcyE(hAhkFM%EQpV--U(og4Zto!id zUMxRLdir9={E!CyQG1A!_i66b^m}yZMf>^(>|I{chZw|~_FZ-KzX4l_WXs1VY;#NO z85{1tw=H$sG}%whlUy-hpnSxeFZ!W3)WKtP`G%DoNkhKK+b)FG0npLDWZ$zdCl=FHHlZ@GZ-G4w z?U!u6^)9CBaP*w>ymPj3_HtJFj2b$lU~^XK3<=ILY&ZvfMsQ9Pdiu5hyS_iffjzcA z!C33UhPmo*AT+Ut~kB?sM*yGp! zJ%;^R+V8pS=?Bb#xh!!6^YomN`~L{`aBOZ_NisJ-*R4atM%LVZ|aW0asO71 zd^VA*ly5z4720e4yRy%NPx8CIciYGqZ~a?+>RkRrQ=6$a)}1fy$z(}Fe<7r+?54f+ zONQ(!>#EnbpUO>rf6LYx4)X=KeUfi|tn207F!CKRwO%bbdIsKDJm)L+28AzbTt7n$8u@82WLZ9MR-w2>e0Fx3La6L=*I(Zx>OZegrnyR?xvz zJL3}zaRsq)PAb-;xCim;UDUYW>b~3auDo>AzI29MyjVYmc4fcYRil(Ip`Y)*+WT7wAk^ zIRx_?f^`f*e!N!XZ?UJ8Th?mkK6`Y^XOk`K+pav?xBWAQK}Y+#II06V*n($+XNBjL zvttU*osswqLekd7l5G=P{rsHy8N=sH7=y7KLsz*Hs*@Au#r#ZH*?YO?enyfn@;U^C@Pj`?9caUbKJ;%XDI0V%$6`Hr#zSna$+PDrS&!$rl3mE(6!->@<*_ZTuhM-i z-A13CkQ;Kf#1Z6_+*Zi>%JrLhetJDlw$YAV1>F(!VI5h|`1=2Y-q7AJj_T}-E%+W? z;)t2wt9-NaH#z@(?oHB;9nh}{d{ofUw#6rZ=s&fMY_ko1-e8O-7<-8>nA;G{b>{p- z5>``N(j>R?`4i;abd?S3@d?%oYP@odEg%+uwO@zSS@5cnrnVhORhR|sNf?kDpQlE6KL`LqwvhRqxHG34in zF8_@232b1Akx+&s?tb_~Z;9Q&PVtcUmT^Vui4mRGg*tssX{TQo!Ma_)5mQI+_N_WB z>85;n9DEY%A-Bpmxre2)VJ*OIlpAZKK1(2H*)RB^{RTaKC;iJF=-LPD`=%l_hru`5e;7$St{ zYKe}vB~C&ahKPi6#d;*ZX3`{=@POJlIP(SXfg_&2^2s_6#w$4? z$K(Z$aQ?{|@zG~yJiL#9*ocu>mmqdv9>K|uk}V0|4{wro+v8^ne1;%S z){3!+w+Y5K!~yf7E$+wu&L zye}Tf&7G3FGxzhRGT#|u^4%f$78%Kw1b@SPC+X7!e)xvLC*Lsm=NqPp#dzWwTQN*= zG8Xi-AAv1>_6zu{*v9tA?`UfOEhl5@l5a6@xgRl2`rZFBjpZD_0} z$nVnk1Z&i{EZl7@BqTUhI%XMO1mc+NDP zVVql>6HSuyZ0UUC?8By>6Fw^z{lzKZcM7D-)M2M|y~?gO`x)}by^(PlpR>wP=iGYY zmQBbO`*p>DznfERu!oU^)~c}&v}Y^mZfNgf^PKCM579X< zIJ0#=gwF7xvm7WN5jrnu=ZtDPFP=W?Hw8YLd~tSE@L%hco4}raU4&Tj2b9rGab7?l zV#szXUSi=4TZ)Ys8!>Zt<#!{#CG<@ZV(a^2B(0Ru)86yjW908mhr6$QP5JDcl{s&6 z#F$GAk1dJbIU#Q5O@2+N4Crr$bQRjMb>AwN^p&Ife?|4%U&g2X9;^0oxyvpD^I%@& zgEb>RtQom`^2z&0u-~3ME7!=)JBl{h9cyl&#KNARVyC1G+>3Z- zww@i%fGLi+&nfEYY42h+*$Dd0eC}30d-w##@_0?o)H+LC6r+&*!6<@VZ*c5dG7K#<~fD^g4>Y2aC9cDJX>0xUq0V^ zo-3T`z`4>n%ELUzgh7cVNW6@I{ZGr{V|sM z;cIZWDKwSAV^~hvnyzvM{SoxrB6KD~FHon=zUjXd17~Rn?o`CK6eDpmk6C`7BmPz9 z#Ju=h*l)qUC-^xZXgMxp69chShU!an#Y((}`jH+eyN>M;_`+xPi@p-7gKeAa?>LD) z#653xw3)85IVwMXiA%XA@2pu}FRvBrgr4TCb_Tlmy7z&kti zOpJswx})|k*dHVJh~<$lV!{8;SeEw6&fFj6Yl)tcI&JKyBWmpU!7uSR7M{_Q)aMsH z@omLt#)}<&=ueyNJ)h7xP{+fzj^i@=PuOOb^N0Jvb4MsT+RaYA>&TvX9O^EwoWsa@ zM5)tu63Qb9N#}<04Q(wU2cPI_dm}P`RoZT)KkBrzciIcAH_(n9(8ny5-#F6$Z^3vW zh^q_r{NAMi@S-=ne@QeRm@R;bPAU@}=E9MIGIAZ2A z!uk)f63Xa4!Tz8xw3O&X@7hu=fp>xJ!54ZSRc+S&Mn5Ne({NqrT1JDQ{Ure z=eId+J;%hBK8!i#6aP=Xv<80mSrg{VT)PO-a=wm-zQp43?U&e?%P4zlYK$(Bum$t< z8ZgF6jwEC63$J&T*kIcP{qe(G@ay~yjRTAq(gWoth@}&YVg+Ij!Q7a`(p)Ocm-&%9 zpp0&blTeNXn~^k0puPn*U0{C%V-f>#5&vX9T8F0dH*}5IYwMFiuNb`urArLlgKg_ZZgS(stIn-sKA-A^$+W-)Pbs*6vT>*@7;3u6FJR zuQT-_J#9DWLp0fNc0_@*l5-Mh!v=eNFxCxve4X^cxy(E7=zQY*ZsKrWNlt;TGO*(u z&U0**@$fwo$_8B|%##=ypLiWV{z5F?M_EP^+thf(w-U;ovos-|COd3l3Hl!4oG`AL zDnrXSh-GCw^uZrFm^!~X+c)p>gmWG}?ZoiJ$a{*p$RG1$zBfxcb5w@9HZ%8!`bL;=KzB8upq@SZI$A{e1eQAD$q4G_W&g`cB@rTftKK5bx8$VP1;3r$#A$xK--;*V2 zhRWdjoBY&{KE&1;-vl`i!QLFXPpLys+-@7}VDb#>8Rtw1&Rd-`Q)kh0R_Z($oSB?a zoIiOUZ`pFr3_)KA`7xbypwz8Ly3OtGZ)scCM~>4Zr^a21Vd^{!#nU(|6F zv#jY{3w=i%{oSwgx4#e2W~RzVG<}B*L4W*o&PwjQ%Ee)vJb#JH^O>605l#85kYCqn zzo8$XciDCKL*mD5l|SJ%|E;|Jc#JoB+rHInzm{|N+(L6CXY8|~HQdT0>v*(|?5*eh zr2O_=PbBO6th4OsLtpm57VHQ70cG@tI^!`mF%c^{>EZ~UnJIXFc$Rop$Co7E5DDc< zsJ>*wxiMvb)Q`USz*kG~zY@j^!T3NdP1Kw)ms9e={sQvjIWwOny564%%2N#fCrpGtLnn+Y%jZL&OF83VpU9Kje$N z9YIcq*a>G|zd(l#_A{ydXrm6*XZ5w@;|SI@w7#q}Yd^&nP2ZR>#Cjq0O$uGi^nsBy$x0pPyYf`rA($(3Cl4ocG9<~*ZvW(JrfiZGa(PqR zDbJU_wa=0d=pS&q<#`#i>TWyNE&GRY&_`*b3|srDGE{$~<=(4pyY#icDtj5PmTlwy z_-vx?eVzg8hIZRjyDl3OD)a13p22)pIWvG~7s>MulmWe=PFoY4WlM0r49*Fi7fWZ$ z*4fd;%5#PHX%k4;BGg7ZXAoyU(1)`K+sOBOyK;M}hNrOHF_KeM(j^bc0%$50c#m3!&I}7&=;vRx|Ft4L~ z$nt%JJ4u$&(RS0-=M8*q!F>i8W2K~Q&>8D2tta=yI>iy>cnIdf`YTw)-eCYTTNGF@dLC*)@&)UhW<_5o|>_v`bH)gGljbG=%w7n1d39f5LP zOV$({bSrIbKV^HcmtUTPE>5zt&e$FwJ7|J_UEq^3y6~PMSLB7cG(rA>Jdv}hJYu^9 z{pgP$;=w<$%#35`S>?H{&oDZkVW7N4*K=OM9z8br!lz>={w2C}w7dUOpBwwcUxhxd zW3!SY39fgQBcW{lCy9-T?8jIzQo3%59-26s6Z8EJ<_%FGAExRlTmL49`kAIOY^@`( zo;PkoVoTq3agYBub$s6a?s2M(+lKlzL43sC#qqO~dwpuZw&Z9J1AFL5?_II9qz2wVbynp*+OSGq;tqcV_uHYgK-1 zZ1BU{(tqT<9W%aYV{MrW^JMNv%$z^-J~_`N zyT?AqXRMj?Wqy%RUgC(YcH%f02Xlfh4*nDu{_nAgBgE49K%JNjb@WZJ9#8!ENvOj~ zSTDe~f)1M@Cb49EO?e@AT@XLAg3dVKsE1#kUMhfq%Yfrr>*K>b}kR z#KL*cx}1!25~t!@+@lrSW3N6(;O7Q?#bZ_7U7qS=y2>V0hNW@MP`T1nhs`$$^C)zc zfxHqwi~{u|u&MpF>c6G-qud341K;FLAh+_14jOBnZO?bVa=zIc+P}zWZ_qAV{CNz@ z73-_qvH^19JS~--%bHKOBX67jrLlzj(GDYNl8ocAd&YK{cL6;%`vdqfw+;Pz0l(zr zC%&^A>Tg2j3Tp(jz*+(8^@){jmbRl_>8eB3QMMi#1@?wP?{bqKMz*_;+tIttI%Gqy zy*Bi0hxXda^O3U)#zkjJ%@J`3nq-WNCr@xz%IV&*e2 zbcRoj!&peaUz*y1@nI2%puHuw#orN(!JHirV?Q}4axxcUkgs4|&TP%s=lf6@-B7-W zX=p6QC6$+^<1cB`?ij{%YPz!J$v*X-Cw_F%J!y7XHNU4jC9#;x18#C zbK6k2FUys_UH^@wkLfC#P#Ny?|E>?c3rthl9OV+Ga9)?zr4@$C=zww)?A4>^a$V1B zsNVD}Kj)L4Z=Q3WcbNIyFLbp1f5g1qwPY=>Mag5g7vxjBAyY5~Q!oWnFs0Xw(z>Ol z_l|S&tGe9}b4UmwK>P%8Vq|i6=q}-2q0KC8VhG}a%a*p^a-_fQ_MeK?1%1&c_Zwqt zx{tWayY8sTU6mY3$_>doUVjVXH@=ayBGLK3N-C~js)Lhs#c6!!QOr+Y>YF=}u`s64 z*cdD0U*d>RuHT>@@U0A$!L}paF<86GRvB2=H(2*4mh4dLy2`|JA5nDMYQ1f@jH_L3 zLmhKsj!m$(Wxl}Gm25ae~|{v*$PCU!m-`K*$?$(Oy#z90s`mY^#Zj9nR-RSjY@JK+`8gJM;7zlg7b3 z*yGMcu&;#A+swH#$6y{}6^LVOkMy%_D${<5qj({f^5{Ove-q?*DevT-oRfd(;%M&! zI`#N(jg#?tZ1jHw(nRUIXa7jX0$a57XHCe{5#*3u`tGZ8WX`Lsd5>HJY)^J#EfM|y z32Qq3sEuH)Lo9Jb*LTZV*r_8;(|6G!7T-ye(C>3*N{3yr&Gw$Z1ulO3`@jB0NE+Ly zhmq1{+pt%<%0S=r6$#@+_jnom5KSoX!Tq@ZTXv1hc+dS-y4@|u%UrSJd+%$`421SP%#GPP+)gft|8U~*r|stUqic|px5$I zTv#b^sxq6O_XK7r1$1qmr)^mQV%rW#9>3Xls|5Ni2$RmbqEC0{6CLh2TX5mOD zPc>KMiyV?O`ZA~J)rOt1ky{|YBXfK*&!6{^@@+YbZW|K&7R-GlTqh(<5uyov z@TV-#+I^Y}DaL9AdNrwj+q)_DgNv7}*EDH`Fb`om9d0M)k9P`wq2-{sra;%)^9q z=4O~{WvE`m>_DcG+~EbZMVhU`7r+UMZ1CARs1eTlII{oK(16h|ZG;3T)KjQ`@6sTdJeJi7nsGywr}IIbWfi zaes8~SluUE_iInMXJLvaxMz2M&hVK7Gr>-M$d4FX@#urG9{N<@jM@Fu?^3_?4d}+U zBb#F|S0EPaII}%I=&Wz3jy{l0V52>884q*(d?sj~E$7Nu2jflIC7AY>c62o|p&o{ES(1Z@Pykcf8)AU2oUL zJNJ*u`p&`kkCETA_+A1b)(g}@OZXl`d+36AmgrN2Sn6+g>FRrG9G~%MT#L^Ly^R^y z;q5ISe2<{r-#{Bfj5mhrDoy3Beqk26bi>%pk{%B<**^Kzd_wu=^9`8e;Qi0{0KPk9 zTjKmJv~Af>C?0VEoxY(-r++?IeGb?I*zlcY9JT+WxZkkvlMi_!cUzE8Go%CMs#Ct% z{a@{;r0LSZeTK#e?ZWmY`x7VamiR>4kfupzjm8VqeWSMP^1V6AeTB6M(UPV0G1#nI zKKZ$>%8~m5*$bO}`2(MQqW$9i%ib9m?CB%$of!{V+p_mQcaZMP$^DvlIrk^`?n=-% zcd8`!ZE#;}@6xB^BY}Rgw}kQy^^BbuJz*?O5dU*GsxQVwzl>wbP92~(!MG~;yAAdD zZhbBgmob||Kgp78V(=Ly|4(1^$z3h@{D3V6* za+Eu4{ZSspNa?a|*q`XKS9aIDf1vuKkHTGd$D;nWeJf9m#e{S?y2D{TFl74;JMZ}^ z30)lJdR<^M)P>mW#k{LHyS@uTI%PoTZs7jl9trLSfj(u!K2(nn?Q7euw$ODSz)0@$ zlKl=oouwuGEv;gEmH1C_ z#8#Y@-*0&T^1khPH#WwUxgjr%nYr-UK+d~zKMItAI+!9vD^SLMk{V;HEi|p`H(2+| zQn_+eegdBmP0-IsYQNN1@co9{5~~a5)&%PuxxP)gW1o>r_8e;+g8UAx`O%t(VC|=1 zA5dR&Pppd9g1%to{zE_Xf6Z?(XUMk$P7LDFFZ-6h zcga6vpnt~Uv8)^qV?vLVjCs*sv55PvPwEZhsL<{R@FviWq`{G&wcNk#;|hD4x^L9a zPCgHTnBcN)yVOw+)EV1$sXH2@nbJRF)_LlJ{p!8PzB`#4=C}m;V^0pjo@UN1{R%ef@pGG7y8T;>J039&ZH#UBuL9*-SL=`JDyy_ZA1^f7jr&K= z7rN|EI2TK~j2D{hKz)_ABiRQP)(m>$eSySA-4-=QWFMA}i?7?D!%pt@ZbQBOkl*O4 zhdRcY`+!uBlAR=7$ZU7vH^9*Hv2C5!4TN& ze>BFGW8FDl=D?ho=O-_Dr;fZ&KX<$Cd4D!>|Mv?%8w_>$wES#1e5UDM{@l@u%^J`j zd28fMd~&FA9S38XqRDq9=-61p%4gi^8}h+#XM5`KrC*4I&)zQhOyDylF0fILU(2>f za%4X}$4^c}YvH^yA99Pt#u~8(tOtw)opUwCDNx3aAM57W#3N59bHO~#l8&7*8OqeR z0y$y)%)?N|Za@3<^qZNlMh=vVA;=5TtkjW!Z8PcI8#GGQOpQ*R?;kziG6S)qIi!t$@ z9yuO-PmarDBgPGNfUlt~!~?gz$)~c_|0s0nmBaW8%%>76H}RQE&Xf7EC&@kE0r;*! z?(s9U`v!j0BX1UY%kLue%lD9`zPI|LU(U(V9{B8caxVLR8Apvxe=|9fKT-4K9Pa+_ ze4;zo=;q1hx+;g-bWz9Q@%fb! z-9Ky3`loK&Eoo;PzCA(5?zr@^3Y4J<{P2ffxNV>FUwpT8L!26Gad&W!<$cCoLTtw* zR*26&mml>@*xqFewdGs_dc}6mj_I5mog>Z?IzC4*4&s0MkWQKSTXjgvOH?1~g0_kS zEkR%6Q>QkexXg<@gmSQc!I94sPi^u0W_P`%eM9Xk*lW4!O?7YBk9zA>F4=#J+cmWz zUocX-d`o<3w;sT5p6aXZ$hSgWh*rRMM9*`+CI1te?v&q9@1gjkas7brV&v*SGvotL zzBSgZ$De9L9r-rq z9lP_bx_QG3zIz0J0}_M3Rp~oP@SP^V$4q^Hx#9bazvry{K0_OzJ#mOheEKH}lH5(1yAE27J392h0~DAs#F-L=*Hw9(#c@b@-eM z$09EMn4NuL$H(o^sl#uGRU~_Zys%$b3-U((4CNQO`X1yICZS zw?xi|F>k#$c!TgB+4)xa{QE$D1JrMU^8xIQ-wuC(IB&S^kPog(gv4?1a z@dIb3i_p0lxsS;aI`ce&cNBiKB@QteQ_s2H^IMwB5uMNMJonf_G*SI;oqGIWB}dY6 z&~3L2*^F%`+t6-`ouFf*-cSbW_5;`}U1h*#9Z9_P3x4AliUUmy<+V-q4mhe2#FxHHP)ASj>^Lck=v^1M=bgEa?^O z{R`v~h!Y7qHrhHCIyTo4^R8PO6XR!Y^&Sn~quig|<=pE{OujRVr_TrMfDgVy(9WL) zC-321NK90M^t2FAHWD6e%aoNb?Pp8@L0J-%DC9QT$lcS5}%C>!d5cvEd?$NY#z-%WUa zE$72nw_tvZ_mnZ?U)xY`@FN!NLeKn#>WNGIE}W}SzFNYXg3Czi;RxE%9=0GR{nH11 zGPWZ=`O;jOJGnXJMDJ0)8;0JsTkqY|RsQpDw)B?Y&yFPDSGM503lgw>BUI1#8e%QQ z=PiBIFYEC?dFSj<);l`n<9_KIh9Cx@SDMO(b}*AJ2}dz2*z99j{g(V8E}F_ft|~)i z=ns76%bW{SI#k=OQy=&!Co4h6zJ>cj#}*f~>3r|!bC`Xedz_pP&VcM&^^WNnL+x5Z z4B~F#{%$#P?o0B9T#>_BIH^~Oelts(kbcu-tNnQ#e46Uv=tT zUS;g)ku*v6_h64F*uXwW%J%E>gOS)ibuaN*ak!&6LwsK39g07GjzbxL?zSZ#6U0bg z{N09jjALCKm6_*Ew&dU9eRRw5vfpk$%I?$SsIqHJZ!rF!aNFO;wEu4;eJ|0(P`;1y z&$=KfhwQ|0-}@oI)Y)#o+vfUj(s@LV!jk^RQQbeK-bFL-Ez9_$xnDGu;mz*)Tdw}E zcG%G?x9?loZJuJd4mk>4I&3~`Q=-qmAmj^2`MFuLK@(f+FhzgCbw_(8#L&J9?XSUk zPnKk3&xx6mGIo4I_XKayrT6F1J1uy#3Epsq@)XqLx4DbdW~%MD;=2#zO~}>~`mbXc z#3L^8c{8fNpwE1xe%_kAIr;9Ae+$}t@3`nHAHLU!B_e5(72A*O#2{A7*h>}u&d1QX9!4q9}^1+ahd?_;BW3Ves!bivv8d0v@g z;*yJ+2gcAc7mS&Gz}%S^^JCs2IA_d}{4hV};&C&FH<%anMHh~vY*7U zt8^U_-y7`s53vO~fDlWtM&yc|0Xa0bRY~0u_2LyiZQ zY*6c(%9UF88{KU^hAL<7#V%Q4pZ+cE?+WdS={}H@vE$bg&d}!^Nphy1I67}I3n9G~ zmd+?}Uc1;jzahAbd~ZkI+e7!%=dRM-jogB}d5O>craP|rUd}t8wzLbi;jZJ(iM z`&Ky=<7nK>hqnjs5Z)w9e^>4NZ8iVi8j}8-EWhE60%bTU(MQ2`wca-5Z=pRf7Bi(Y zPR84KLkZ?E@-4-@m>=_G-p&J(yex4<%`au34qQf7KXh#PG(jId^Eor8NWf107LjeX z+Oy}_f9%z+ed)c*{${_vF;oXjoP@Ihl(7#%9O9ll3+#FL^ryW)>02!Af8;kp^)oq= zH`F!3S(^EVKyJ}x!%jbuF?>!L&k>As=DZm@^YEPRc~M_kS#FZRya9cPm7rt0>#@Ts z68#AJ-WM}-O&w6KY?Xm~%h+Z)(+BxRsO~2`#&3T3FrSe$NpjE=@_(~rgGg!J<&$k3 z!_s!kRpP`Ik2?EVI?hjWYV3fY+Y&ECEAUwW-1X3f_bU65y~!TEsrIU~ry(9V`F)k| z3efWNWaeGZeF5AZoPX-=drP;)*SNjq%6OD##w5FOp=%V0(Gt@ zhbtl9FvTf&{i@A6z7T>nIT{1wVLsfIQSMLlCO&sM_d9pJ_}u;Z`Gmd&_92!y_~d88 z&d-FU&jsSqN6Xwf7py7yKz_!e@dRTMJ?nr$6SQ zxsfC0%iO!};l*99_bl(+DURU%yG3-BMgOBZu|?B&k|oXy)WHxfp)E0jSc<#UAN?{0 z#si1(Fb?_95ABI_gZ+t?ebE-C=)dHs&nH&ek(E#xT0*X1B+RiwPH)Kd8=*RK#P^87 z`e>a1ee+!+1%$;Cv_f=eQ*D#oQ~W#w_LIV zcFQK+V6WJ2xnIW0m@54mH*@uT?>fq^vtN}@{X+xiHtQI?uVvH+%KD z^>O9&t-R#-#F708d~JWaces~s;*a*QXb-He{vW{i$&RhYxqXJ};kG}O?OSD6TjP6v z>fSaX{SADmLt3Z&#!;?di6NTU%I_4NJhMl|DiS?J6J2|3>a2%AQXazgrMqDB-pV)T z(p#tT=HdGRZ|ejd+ZIFh_#OPzt}Dh6E&HLb3jLlJ@N0r`5W{0(4D@~UHXM3~PQ4Wu z??&E`dQ*lt`c46h?3IF3I1eDof=Gb@S_P>GEg%%!PR|zghM`SFYGok^7N-z}_4Beh|uO=X;6vf}za!6>`n~T=J(a`6Cvf zo0V%p|BS=qK{wc73UakLb2_8kDKm+bN3!c}57c2_U1dR=nP>ZCoRRt88J|ArcZnv3 z^1+#y8Z-0pyqD(5IUomHxgal}T;(329r-krp(pG=AlDG0CFtLp)(}QO%QWtO>XtNtxWjNw`zVKWxF8zz|EcKk6@;lBeJ`9a`7Tcenig zi{HUa6dS*j@q5`${978otHDZ+WaMvx{Ql*SCtgbdM4B4OPvR7DBV0|M&hn-Y^^znlI zs9zUPZK{v!yV_Me21~{j!uxY+pZ*Oa_ji@Gz3okYPf&l`tIcPv>pTJX(o8s)(8TAg z>dc22dB<{J@j1r*%iY8~0no8?|8n=zW+rYQs>83#KXi|OJ~ws8^}O@B`~3OO=PRH8 zJwb2!yd7etM2B{PZ6!yN&k*8n!RLsfj2+)5=)-RTKI2WKj(h^YlX$#H;u*^L;0sL* zy(@T^G{N6h`5S8peuv|CyCK>IHnXIk3v9L%>!)~4{@)m?hb2xz%$aOS|4leF55Kc` zD=|;zO>W8Y5s^9fTJTv1tclkHA2X$6_nO%si5+Ga@T%kKR4}X!#OZZ`pLb{9tXJL+?c8RmU6eLkZGiDGwwZHe%#7c2@qDl?K|P=w+n6g5tEWVV zEt=W@I<}tDb;MbPBi-YeA>9pobi5vjk*dp=r6e55J#WUczeVZR9W z4eM!|bk0H-?5BBw4XB49wjd7uk$={4X&%gN3i2MBKlxj_qj|6Hgg5NS?}k%v->$d! z$nOQMs~!B2zu>#dM@nB=*Cb6y$8IQZ#Ud_!vTj>rVJ(`*!8;q@mfwYzq#0>Lwu{@w zn9P*^M3?=IqcMDAsU9L}lFZAHubVC#9L*6vIgkv|#OJ#MYr&c*A6;^&jGcN|sUPXY zr{BM2tKAK;hzVUf<;s3&V_WS%G@d0+;Vs9u+n084gzDM@9>Z^C&v{PG6Fg^^-(<-5 zpU~8fH5)&`8b9GP`8V=&t~gssNSdSl#CZqy)(!R}@OS$yz4A29pKK%N|IM%V;l8V+ zzLuYKe9Tk5Z6Uv(AP*Ji1{-=4oKendJ+IUe+kM?~moYx+)!+5x4ZY^iIsK=y#`;P3 zIG*Hum}|&(bNf+`?e_a^nYQL`V>|M$qg?B^%9T48HNSV=TlsEN%S&tdH*kl*+x;_D zS2-#h-?3G0-J@^d_g432pNjVzLVo7x9$sG~$(IAZetDo_q#J9o<)^tB32{Zq!?1u^J@ zzUY^?-{H-!_xRAe^XPrpMCa|O?-7Rbc!2sT7T;l#!FQG~Q2$0Rb?6n^EJ1%;W7)ia z^^OgVziA%4U&#~mCGV^OphFYnfVp?iowqN!P&?}JW8GNWov_|s`<46QCwb)eH#lBo zUoInO;<~?;OY>k(%(AIVpRVsjpSdwVfj(p#+B2V=kw47`h+%!I9KV1ys4SI7(j+UkKgsna z&RutkugA!Ew;&hfgxt*JNRqb-wj1h)Am^+dYj6bX0kg2BoAyWT#1cnLeQ(=*kJE2n z&;>R_9X6mITK-;kB%$Z;X`A2E^cx%STiYre=}>?BoBF-)rq;Vnje{N+P36j6|5RRT z1LpzkAzFdC4Z(avG(j%N3%LqGKFRA4E!q9wxv%6w>kIJ!_9N_HWBjD= zYkVFT(seaP&oNojuVEY}q(e)#_A0PvztL5HgfJdeB^xP!P?xohivi){=1@INV=ByG5lLu}n+pSz5^yWDq2cls)D|2M&B0Fv^$ zz((Da&(Y@spA(UiGIr`|cM^0Mq6vIxH}rY#_s9L#;Qe8!TMuA=qRYPXopSOG(ImJ2 zUfRXcZ);1$15LJ_zs+^Y%1FEARvCZdEO8QaY}Di13rGJy>CKW2uvcH&!cK@u>?4>5 z^I={?gyze6z!7Gu~dH&h<-o!xOZ%U-cb%=WN|8Fca<>;0`iJIzGgoiU&1T=U&S8NMcjZT$m$s zUE+wI=bPLNu>^O@7UY$@dmYfRQHKxyrz7VTmKi&kb=p;}D#!k>V=bY;)e4aVe2K!0yy>T59UlY=y ziKV%Xoa5FUnI~gVwWY&Lkw8U_W_3vnPPH z9|g)4_7817F*H8L&Ky`9=vs>{mgYwe$cb|_l_S<7c+>L6)jRr#srR?v>d37!U|+no z<@;F=#Ud{24fI1EBl{ut7W8wlGo~ElXFSvq_mAS~yU=(5`!|l-K0(ZzkPWK+l=j%E zf1=60l#7vcNpNnQpU-?USGOLr134y-BSFXJI_xc>jyOAG+6QU7$MQ|m{x`hE7&&jt zm1QK5ABYEBhu_VR&2;Hcc-+j_MA54aJGnIE4@S}?e}nz2yxVP^1DJ)+*--iDOsxd{ zzk%a@lcBz#3C`c=oJt;@L3C_pO9y=2Zb^qz@SJ`tYd)%8+t>Kk>GyA#^8arTiqiyn zSqU~n9d;yOpW?hw=ZXI2XZ!76%R}ueyOhyQ^?B2~{44Zd%eU_FRXKC+w+z|gw*RJl z)b1yU6M}W(R;mwcbbj z1=u(2ug^O&-;Q0f@$L~v^xTK+MRaUHeH6*tYb5B@;YXWq5VwLav6~>?)Vr$d-PL&a z@vhVxvWb!3D;n=bzC#G~NGNZ87YV+T=sQXU+YR-%&25+JDz`6XgP(QUca7sSCXKoC zHe@cE+txfsSqIjl335T+BIh5Pf8%W_4!Z2*2;YOh)|T~Uona(P64(pIwkK(yTi!9K zukFXx-|b(0tL}DP?G5wrUJdEYZwPWlPMG^t4yNAO?1!WM&#y*lJ37>qB6VVHlD_dnl zJIAra2KZQa-IHAMJq6|hTd)@7hdjZNPTt;_s@sCRFTpw-;We9o6uZEh)pa~t%g}FI zfbO*&Wv!{3$(HQ=7Rc{nVu_~T%R)?kHxpgIr@<&}>Cp6Bpb6=frECTTnt#@JtA&jR}wLa^5jX8^`8v454cZ3*YA3(g(q zFA7aMXLmf{GmXFfy8Nc@B<|Fu`w7rPU^kSTpnep(bndpp=ceEe=k6c6&$-h-pQ(JV zid`gnaNiebhrK80)PFut_*}?7=m*TU<_dceN(uAged9P9jsVgr3JAI}NfN?eZ*`Vq{HIWZsR!aa4^BY7??y$@YR+J-;- zi?-~op45EUMm>9Q3EC593(h0wa|+&1L+k_{x;Ux_{6aKQ&ogJ5SlFn;$9|UBpKu)7 zcfq)TF)~NSJif4V{>*z_gvy++Po9-)a_;@qRL0JJGL%D5S6R6S*^jIpYq)Yfn-f2;S^S z%AdEl>W^|yZkzH*Jm$tcJcp6<`Pi7N`X#UQ!@7LdkhivC?yh!!q~~`az6(`sza?${ z34BkXouzfkZ*vl}l24E;<9e5?r0y=;HWY((U_Ho{*JW#6n(E-=BT1Y21NfMu zHgDJmIbYbagLCp5={CrD(TVi~_js4)0P#SR?S{OKq)Q(5@B`F+<8Ct*!-RBb$>J=1 z!G6Dy-{K7Q@kEz>=e@}uf}^v?Ip+)_DNot)!~c%6uJPBgM$Y$^^TlqzR&sRfcYkl? zyWL%;oo#P1Zu`jjcgY*-R>83?pX%aE9;WIl_||gOsry!M-}5DgeTZY4ba45W)YW;R zkL0#L`9AsJ_ePYl*aw?+q+@y*?*)_2!y-Glpnl=q)()CXDh<^m}pX%{cP=iodHYeLpcr`V;t? zS#*49>o(N~znwJo3G}_hXMBvAwJ2Y?JxyVqNcc|Z( zfDYJUKR|nP*JBT{3Y6cl-;s~U&X`-~h1{4W{Rr~86Yg>Awv&I>f%RZr>Uu@4 z+mbwj-?5N@trA(L4nP0yh1{a)_b~r9c%(z*Z)RH({CgU|t*zw#?TvCIQ?g=vl}$dx zB4(wl4DS0k(rt*d65>BWJ$_v{_S76hc>d(03G(B7kw5ZEKAT`YhG1>r^ndHN*-qUQ zQE1X(X`Kygj_$gjWT<^*s%(yQQ^!-sM7{m1j1sp=2gb$tOqYJc9%lbW!air;w--Wo zSOwdj>`i_n?|}Jm9vkOXXC2TXk|}AhH8JEj^L`z=U%6+wZ?~Y%v5=IzppG_2zO>;! zUb?gS{Np{)#o@D5aGzH~WoY8_`H(t3N6?-)A&7ZI&pRIp*!>yI=f>wVMV~LL{GE!= z^^>3RAqjlmW24Sc2I@>mZ-O_&xZW63Z;TL)_lAB)`~04!-_>@~WdrQ1KpDIJ_&x3o z{+*wH>l@OceZfB8Yo%u5Fo;e{eum!m~&N?&xQ%*&{}xMoVrnn357{Tc-Wswy9x-7R z9OL7MzuK`6Kl?k+1^Yf8_C9?CeX0-Q5W6MlOVGE+$T$Ii`df@m?HM;?MM4wQ*#{jP zgqY$8kCpSnc$>ys&obwh^ZQ1vN3UhK!RE719t`=QFYW?E8IGVH-z|u>E*!tsaX0-F zxYsIEVpXWKKQVej4#%{@m-dW$wgb&Jcpw^wHGc%DEvcN9D*lF^|c5 zYwYX`_R-cJY&siHQ0INw%Nd~^W$Jc;7(*=Kvp97QVGF)H0q-YR1>3r8_yX-rNH-_@ zpdX+Nu78sD!5>D_B!PJ_ALu{8{9%a@O_0x#XRW6^$}`N|D@*%^z0%o3S_jsGJq74+ zv__wG$~EiPeq=pa-yvGUUYljVu|L==?3pguLxwW-b>GkjdzW!BR_4Kcm>2Uqf}CtY z?zYzA^A6SS| z$$PfL*U;W=(Vxn;Thhl8AzFfd68lZX+sSWo^ycFj#JlOTeZynA<~%4grm#<@0Add2q69xKomR>5;)o|Rd0QteNA%ltU*UAEoQ z_5a(_{UJx8+Uppq9lh4Q>2c-lZ_2ae_)V|zZl7BBq#w-z?s@&D>~=`&v~$_ET7DXX z$NW@R+x{o|Q|vce?Pq0fM#+)u-`dZzW?TAC)H`RCdkcLf+-c@>|LOjWU(n=ZHuvHS zLw4vFNBx^A9U^Ixzr}6-6Y0M07`7w7)h(^XD0J3F>l*A?K6498ItVYG4vwfkTfR4LN8C<)-f+CRcvtf763C|VmJGeQPJTb>dJi7niDD)p zx%loOCf`NG(RY)Tpab>_zFW|a_Dyk!Mcgfh`l>9Ip=m7PcdXupjE}Joy#>h^^Yfgi zbjsw0`G)2?HCN`f)DFffmQ$a>vRwjrNlxDRBl zujT54-V2PMxj-b$Z;6q6kX+S#k;_R=l-HAc;gc`k;>nh5^E|ZJStFp>Ezxl z<<2>D?#LVY18hH0eZ3aMG`0~3U)IBG!JcLAs}Ry}T#rAnemiN32gEc>`V&X?o5*o7 zKJa)w{-wE)FLE~pxg7<{TXw+CI&>RA4!uWrsH?%hW_8M4{{`1l6c_w zme{Drw8`?C%x$gy^LC$DgXSLQ*-vnp< zbMAFFOmW1Jjk+j4*eY(bbl+~>!TcSCd%iuu+raQS*}2p8c@X)T(DnIwbmtq&z~^d{ z557mxmN@Q%GIc|KQ|+5r`MJX9JD>f$19%ghd?O(JKA;XLM?$@Qn{?h6yfb>@cS-0y z(!}O>z5+Ib54dbUeq-z6=r=c*Nk}%~-|9yGc2{MW4W4YSv&6?bvX;?dUC_T_JTS!( zjG3`-;W;6h;}PVDd@bdzXKq;o-drc8*MywE(d3ICZKl?-YfV{K)|WTJ&iC6&KYY>g zr5?W!Q?QTNAFu`cDRMut57|S2?!9!fJu%Q}N8OeWdz|+Md!9GQ5XhNlfW1jP$6^e` zX$5RYcud4cuVuyv!~yi%58Dxpoj#G)8OLEPWt^PVmTv&gy5Y8f{I z#|p)17uZ~Xu6~~4KiRmCp5TtEJo)^_wltTUQS$MvuKHQO{c9PYp6tvGIl7r?vvmE@ zIs)tYhJ7sEwo2kYv1K=|kB9M9oBO%t$}u*{uDuxAliT|=_jQw88uAZRdOROVm|BPqny>v82#?L&q@ciE7 zxN>5@+IGo@{7u1KU?_(m202<9*XLf={XFx|=H50<`qI5#!3IWyKTZZaPlMajx7$@{(Yy6%g^E{f9p=|I0%G5J=^1%Jid20pE z9yw=!unz2{DOew1y;!@E5*=Fw8;qn$vd7u$?17W}fqel@e9lLnm(E!c*noQbB9|c6 zA-=}g%N}8V%ylQ^YKf^lZmk9H;;uJ0Z||Lwa$r|I`Tn$5em;9o@jY+)BNtoLeabl^ zUyOq`ps?*5Uo7ci7SUFGLf(2P;Em-i;wn{=Lo;o0-zL@O$zTSMgiE<+k2(yp2bJ@|2yo z9-vbnycaV+%*QY%^46)7&z3*$Mc%Dj9Ok6<_%LsBa|CO~+5ziWVQp`gY;Y3xJ-YY* zt=sRGE88Psh?bx~;rc2)ZsZhQFs~(;qu0NeJwVQa90+pB-kV}6KjfW!vo^fJyY>L< z4^vQH1$;ifYD0dBLp)$D$T$7be;ot;!x4;;@iK>_xpT*~u6BaA;1>L@aZ`Wi!0*UE z#LD|>Yi;H)?jjg&TTK?X}Z(fz9@+t5; z8StCgPW)RMlHb-~CH&@Q{5zcGRNXi1*W^b$$L`WA9><%sFYQ<2*p~F+e(=G!3t~GI^VtgPRN=-6Pzp73c5J`|IL)m{8Vp0WMsRiHr0l&iK5$Pc~ti|xLv4S zOW2>|x*O{K5P4Uy*A06g#sf{Zn~=>=#~B);i+b+RXL8$J7o1t0^UrzL9k2y@D{ZHA z{IKDRj_stx#vQ^v%$>~r9D=(VmKcK1OYZ%ZpP79AH^E&$1owUg+e++5866@eep{a# zM{fn*3oYS2(0LQ+-4LQB=todD(}yzlCiq=z2>uRwbMkMb{GBvJ6Z{=@Jn;1QRs8QZ zU2S3OcQ?cDa1i3?cRO?chUfCQe%t%0T-#OOs^fEBU|d^x>{SlUg*i?^9?2{DCD*&) zbz;p7^5AR57?jRvO{ejYDZs;$Bfh& z4`bVcGkQemymDr5wrpMJ*%BSb1Fo~A4NMV&e!zX!e%5|{zo`5AnG*R!U zp}XcMdfr(`e0PC(#9iVD#%`8xMo&Axcv_U5x{e8xu zF>;@BKX2XBadC8quM2GWaj(;c&&HK^^HeO)pZPM^sdJ&#job0@s3D!jFz+N`?` z?H$89G9GaMj0J{h7tF~pcIbk+++cUTeVY9E45*(=JNMAh-e`h7$Npe{vUaTDDtJws zd{|dQ8ULQ3zhNI@y@AhNhmDw_z8LG^-q3tP^I8H4EkP#-JGlz zd*ox4T#ygbIg81b{ER`qM{xe&(_Z5m8W(+UV$+Ad51jnoy1Ux``8WP?%U`pP&?r&tv zc(g(P4fuZJrx^F#{_=S&c3%?*SIL-)5TR_j@~VXGIo59pv^2XTJ}SK=A@1^u0z^}toEUD6O50(=###u#{m-|6>!FUYiCtQ!5 zg^xn;YiNjhD zn||oeIPZ+jjIm9=a}+2@^7?Jj9J#AuEA12 zH$yhlue_6IY?gPu%cHbIuVwonU03UgJ#+m=u3wWxuN;*leewSZd}*^T?l@b1l^Pd2 z<6eUGA+MEF){Ok$>vgn7as4*v->;Bezi9!#Z>?X@^qZLB_c55sku>~{X82vrY<^!8 z{_V}b#Ua_@ifIX*bRQr1@jHj66cK^_5%5xNl3C5 zD~UWiP4aWTbbdy{8p7%C*r)nU`B%QRcU_J1pX$VP{~^7JrMrMTg*yRu*}owjj`p@; zpFc6a*qiDqK1=l+q4(tJOmap;G{K$7oq2SJ0y;M8Phm-~P~WImn?}3*eB|>Fjy?;4 z&r;v}d^niH?(nx#(F&BSjo+!J;P0Yv^f%I#grsTGVe9Xy729jwkWViheh*BhWF=IFR^T^0 zGo%~akoK>(TOZl>w!1C;!W8ERm=E(}o=1=~^18$kUHK;OO>4HqDGcd5*CXUx^Su=Z zn`0nXf=*20v47YjfKGhu$VfYTi9LD5;EgEQr@S+OGWFhf_ygrA?Lxj??-KU<=Y1kc z`N)2=(uRzrNgDcpgYi`GqaAUUpwF3OU>wl$9C9wV7&@z*S%_px!V%m7BSEjc*=a}n z5UoJj%+!%b5ZCdL`vvY9xa(~X)m3b-@^1T9u5GG*`;W{E^8nAu^XgguO%j7XLM(9} zzz!kW13Pm@Ea<{x`X(8#seW5_AkH`N39+=7*iY=M#=gojHe)+JUGTXu1pAV`+Qre{ zHSA*u(GvDK`@R=Wp1+xAku%BJ97&f1-vPI*_1q8K3BDtS?ueFf4;k*3H@fO7_S>bs zW3F-!(YMFOn1)z_`7pOFm^bq`OFFqBN3%egI{bQp7{m%eeEK^Y1LJ@p7$@WBE^T7u zXWP`>9fEt@P(A`5-^aA${w2oDIk${WZkzh0f5yc)S$FQI&^};aus@de4f~4y1?=gY zC0m7a!TFd8Iy6DsE{H|kqxkeOQcl^Z$JgzUQ=G(asi8O3H|$rXV^m3O2+PA9DoC%oQk;6LQQt^7fwjM(@1ORc7tT z^OEn$+>n>go-KJ<>iY=xGh?70Kk|5zp)nETFb>vP@i*TA^xc5(4txh0g)JL(EkVa; z3BEi11b${nhn{T3o)@7q&`-toCyC#a-L{#1Y{}n1yQP@qg|$FZb{y*Ou~D}lz)st) z7_9G9&HVz`ScQ;o zn)IKrFY+hf#d-NJQTrlhE&o>cSccm7Ux44uk_~E~kvW`_zr}4Vf7|YF{Tv_JzTkQN z6FD*`&WmO3@2CE$znNe1c~ob<`MEAiEc8lK`3d{X^tZJA{|iZ1yPfaDmG8gJ8>rA^cX{y6(c5V9b`neP6+n-)A4&XZ$NT9B z-k*~n`KV+(NLfOE72l3iD@;*+b-{3G&kgxtfBt@9ab6 zb}Hvfxo_nES{v4g^`VaSY522eGUxO`pP|0NV;Hhw2W&w75=SuhruiM_t~_xswJy8h z9k>O*X&kW<%1ux=1b!iZ+7g4@6A#ue81iWaa(d%>e1F4w>G#NP>@zZElcZlHRC}$n zeaWYSy$NDW(FHjM=i6oK06*9l#33H63-ZfnN9&3$SO?aFbuml&5xx9Y#qU;4zhm)x z7OdnH-E~biaCu2Tg5SqrCR_3w{H|vBooyxlEso#&o|v*jt!w!ko?{@1S?MZ2QO8*8 z-FD@;-XwlqFz-mn2YG=ZT0)*-CS8)X0M;c$OISM?qW>kQtB*oRzp3@skNk}Lr2jXz z>Yl*ACnv`dl1qD&{mET0^FCnD0_BSBM|&K+*N1ddZRoaJHraqP#98VO_}p<0o8Ug= zZtNG_qu4C**@8AvpnS)n%@j-b`{!=gz0PMQpQYT{A-Ko+tRDJIhX45tm(JfL{kKHw z4mN!A-skTBd=AJS`rG5u-z2wwa{_e!K8c;bNfN8c577Br<)@7#IzE8D66%}aZ=A%c zbd^oDqub8kNEydWLejLx1l;LY;HPJ5Tc50;3e zNgCq8NQv$``>$+Y<&llP8|PANy`6^6D(Ceiq4NuzW7y)pBc5#dV5bd)aLgLtHe|Ka zh8Ux;Qb#_4Z?*eQ`mL;O(cLbtd+zpoDi5{qKY$M`;l3=f*-rmc9O1ise7V!{rA?)& z433K&iES=-*>7C;CU1YrJJ-IhKWjWh6MQCg!QNXU*n`@SBdL3TvR5xcWoXILz6Z`h z7hC6TiqF|A=XFSLd1g7U{R{OxMBR~3)nVP4_nX&{n4+- zv86M1!(5g)33Fx6umyP_C*V9GDGz}Uz9EP~tRd)!evkUEFeb(t8gmoewLS08raRL_ z-o;0fdi=Ja-4sU<2S(17e(0BRFfPV;SX=E2_6hsuuy3@l*jshau`g@6>eMa4{^v~i zEciT7kAK~-j(@kEY7Zyzc|>w1VgG>rkp04O=_5oFjNMSi?mE{q&R*sa%FhkCTR%XK z`5YkMoJn%ed1M_T_f6NjuxGqqTJD#rwYyH`TyTJz= z($q4#alPeMU4`*f>R4~R$rorpe*u1moRQy8o>^C?u-)a5%{1w-H3u`KGbdorG6xgPN3_Dy-hP99U+dfs8+xs?K2-OHeUSF4 zwrZ#TuA}TRJjq&L+m7t7OKz>Jp?qUsV}> zwkD+iH#j;|70%5NE%|&7>3-sk*LcV2GwJYol%Q{Hyzk^kJKC=TW$e`V5{q^)L>I(t zg7?qly`eWz6Px!(>O&HiID+@h7DH`l8-h4MOk!`r*rTwdSB}cS_+Tbm(qQX?cEqKR zrG6PN<6umTjknX$d#Z`9_Z9E0SqSMZL3erRO}O<=oC!K_$0hJNf_Os^+iaCh$bJMl zV=Nw1lMdJ!Ur&kch|e4}ALd0Kn6uaMXzhUg=KWXYE_>R0cRcLlT1WkFaGNS?f7U&o z$aBGXml&c6uc0NjH}IK)JcMYu-Y54}NVbH1NAAG-C!geB>%!Wxj$X^AHJrJYUPsn~ zHcK(st6hCFwvl7>SXPe5V`5weo7<5K?x-Vp*A1~n{83rIWlXUP*p})V_41o)xAHC{ zRu@RvNy~bdU2oat2S;U5BK<-(vqN(WmGM`v1+#+g)3>@>-U9yJZIP zRA~mLU<#&S3Z`I6jlPaXyGnA-wRfEFWf9)@fq_FOPoU28ne!<_Yz%eKLvX+680$cJ*tbvcPY{I-D>mgO5e3$B@f}g>* zliFsf&EIm=2D-kl!Ay>%Y5Ml}TejL&W{GLN%XZ^&RC)VQo)2JmyQR4d5rQ>ht)L0k z8(90Ege3a{-lrxVj`k1ON9Y%!@=vU$xS|y(V+Zt#?N4$0pZ{|V*zD`JLou7!nUmxt z^n|=6f5%0rY_@X!8=QqoQyDlXzlC!ZV(8q>au;&cvReS-|{X#KA8`^vq|?{VmEKm5H>EYWn&W8=<0-23wJ9Y3?};~?Zc85_Kl)}ojMd|%%-D>_ zNPLftI6!RrFtsm_`)KUsY$%601DplkjXmFvpZAX5zwH7$ED>V!CMq=P73#LYw`V&f zV5h#N#OGkow?A*E%2Anj8fSSYO=lap6P{R?9bce5ag5_3yPz-khmF|WIov1Tn5zGu zp!V?=&ptEtU=E; zV!ga(Z)-&x+5&Nam|YBwgK;r-?${}~Lw*0YyoZt4R)SAYf9_8hf;f?LojKo4AL@(# z*|#IuU-IFQ7kO8ZLykxQHfa-TUY!TgvvcM!P+tjA_; z$RXBW@9eI(ddqh`a`BdzoqfWW`-_SJ-w^ag z-}L2iY}rmz{U61;z*gxhKXKF^)(;HTo49n!{|3iHuH;CXr}1KQ{Vi9<7?N+;=cF%X zd_%CF6>LELZ*iL%=S`pL@3%Dh!pZzYnn{i%T@oT$4d!h^E$HrTPw_dh;YD;@qiOaT~ zVs}4B@qvDzC+LiWwzQk-Kh*z77)#}-{1f=wr_N()PKNp+KK-d*#qB-J0-Js8M;VUlD)^4lX7GMv%`{f_k+Cy3=CcKReN%H` zWzIOaX67976rBH@g^KNO(!Ou?)lPquk@WvWPdhRS%!7Hg2W)3OSeGu?=asN#Ay~gg zo@*_G^;G_iJP+g|P-eZC)}6ih?7wnh>zzONw}2o&$P@DA<0Ics9;q#PMZb)NaWRJ_ zNttm`$9Rx{&34LDW!h}kiFaI+p2Q!u5k0?eG=1wZGrw=(JGuANek#sU-}H~{Dq}O7 zejcE{KY$(fMcw0CSM6^);{?~cY`LpeKy2fit>?yv>lo+?A-C64I%-D>mgO5d`8?^?z->Suwnu@BPimh_74x3o{_+ZynV zZ9Z^hgQxF>mS3>38{$WV|8{FQKn7_gP$dNS3&mE%N4M83cK~9s~;Cy$PI>7H6_Ir|! z+ZEsUPw4*PZaekvAxJaQ6c`w$J^qzx&LDzX4UScd_(0ry=+hO3Q?@1!-}x@O%6voG`liIUrIwuhK87??I`qE)e_|4we&}zBlag|Xt$M)E zdX@N8Kl?WMSC0C-srBf0y~|I2Q}Mt)Nc#=NVoX!8zN|ZYaRmFvUb3(3GdY6fZ;B!C zVb6xz0D2eL-Ig}arIGp6$uD9mK5gii{(G(uV`6;wc$aMW;J=0YBA*x!W9^Cy#0UCc z;*i72d-9*NFmxt>^Kk^{WeUH6kUMSE;~RpuK1-aZmY`FQ-x4Q5w?BO^K^tuN& zRiJEaKe9pB`JTMp3QcF9^S=`A19WUnR9|%3cj4IR*zu7J_C zZvQTyCtQzws(-TKdkU1H{{s7-Y|ogjTY4Ttb+8lP!$@q@H9?8ZhzP{xRb?ko#aVk$mlikZ#D?A;@2H`H3leC(m`(ecrdu9?S>2 zY{vC2AD!DFB0+~!SZUkk%=_$PyK!Ap9rxG`cZi`5Kes_{5$dyo?a7C+F&BPASr68Q z^*M>x5gYVENC(={p0yz!eKD>sxIc$zf;*S{ko(k7hMC|;o5=P{l2}bKE6j!Y(I5SK z{ETM`@_;;RJb>L$&pFtFv)IM@3-C7~9fygQjivhWF9@~q`XKMJZP*=uNk0XT5n1g|`cgX>Nk~>~U$Vn_;jP~G zp>_~O)_G3NwL)DjH*#E-of8+G3JP_{(xUs$R$NBVF0cF)y25glLKhIH5|m&STCUgm$>?{-J+ z%w0DgVuoy$tsJkA??2Jx14s7`aQAc(V(U#Y#L?Tei=p56t>67Fj(+ozl(8?t8=%%N z`G9?=eDT2_ZhyzazLM%kZ0fLu#xn(DYZ{|ryf?11q>Zty%8@a)Bv5XGHdBOP42*}l zg=l&sE#6Ba#Mb+%CFt0Xn7pNOO=v&G%J#Gyyt@Q#mdeDX%}!{Gj?MOw@rc!POh>j) zY>kt#v#0Dc`LGMlpK7!2x^d;+QW;Lt^K4E@aQP=`f46b@Zs&fG^!vn+9pVejYleP5Y>f_C3p)?aYXy0enjXSM&~<*X!`B}OFDM^Xh(bR z%PDKi8bc&oeWGJq3HmqehddYG>TX}>3FF)a#{PtTs6&7AA>LbGZZ{NTiO_m=F$bpK7k{T79#`_T;P(8Si=2Tc$@xC!p`E*5{6;qNoL+qvHX9b09~2Gkku{*}K4TYpoU{B22pb6leHx5s4b zZ<3?Xq#Nwkk%#X?Nl$s(LiWYCAu;(*biucz^$Ui4Oh`A_s!n-{lidALcHJ!*-xfn- zWy}?9Pc+$qc7{0L=&G-MuQFcjlq=Y1v1Eu@VI-9n;4P(4aFtb z>4P`Q=S`FEQO*WuhK{p{(#;D{yo7DdsqDt?(0cXZ=b30 zaL!?*w7yj49X0db;O+qK2cQlg{I}vjZF@?4{N0{A2G$GQOBL>=C+zbU$9DT!+FqsW zdiLdV^|3xQ?skFgX36F_mbO{i-pd@PbT}8bSBXz8qjwRB$JEMrZL4FTO@;Q<1O81+ z?IC-*3f^OK0Vod(s|-c8OjD*6P&x5a1N_ZnRCA$;M{_L zcbe`F;10nSr44?RsarbtJwdN!?39ln7NBE;ei14g>XtZ3)g6aA?EL~eV`Gd*Y|WeX z@mjq}d`y$h+AZ4W*y(5MejRxqbMH;jx&I23D^ukoXcswF#%NaBda0vN`ex4<2XkOd zOLXM|c`}tZLpjuyOXL;#XPR__ZHvf!J(88B@<{NjgzPu2$L@AZI_F~x|C<=eTkIRv z*E%t#ARckyWPEHD>=k^6h{R)^Wj$KzteIKTp$qP5?*HIiYOPP^MWkd8usuOt2==TA z=MQ_${)QljD%hx7g4|~BLwjH8Dih044uS7@0K4m&bU2DX1(GuL#9^F_nX!{wQ!sCG zY-OGW>q2g@{=BpG{_c9;^Ty{bzSw82IcrB7;*4AiVxxa-8IQY<+y!E@_TCqCY_6wF zZ2F+z)+9yz`ETC--BRsu{ReOCS($g*E$?1!~0eeCSt<9U+fkJcio z^|YNylZ0AEx4qVF*?(e|Sm-Tb{J>nGKM=Cru!m1nzq@{@jcMdSfqw(mvUTh*FS^Q= zP}z`UZ{Ty+jlAEmTW`{VvobHR-PqslEtg_=T>Zq1|({G@w4w;WdvzLl5!-l+G{t@rD$n<@i#>s21v z|AwV?cw)$ITG4Got{-^var>sWu$99w3r}VHRY{vJj_x{Gg&{q}*6(=}M{kNHI&Ta8 z&chIgcY$c--$qON#Fjot%Gm8Ud3%VX_XnVFG2}zME@*Rv<7~wV^$(2WZ&_*sPwhf> zpbzSoVBRx9$7Ub=>UeA)vNyrlLojC~^PsLP#u3Eb8JjY8>hYU`7*Txjw;%RO&-Ru} zWyZ_?v8U`Yd;J@>{G40l+A4(3AoRkNZmRuBw;$5=wz>RGhT7eDe2ldfn8zD$>(JE( zj@Ar@V2@bKqcvq+pXjW&_KVylmnUy%z14@_?1#5|A*2JgDezgm>(!34K^*#u?0>zC zBjiKAkSop?#zs5l$GWhVM-1I*(PR_tkLm^AGL|@ew-r-@Mtkd`)P+Pq=<$T*~;dK1&?ob!BZ? zze;FLVdVO<#z(N`Q|zSayA<$^3YG|cy8^ypy)jjHW8ZjakMDVbx*PlYJDk7IZGD?F z{)Sg&{l>TSjnAA*r)==4^^|)Ol0csxLzV4cd@E#bT|~l~d;O73us`fmOV~SLA5CXJ zMI?3qpY)*|xoNVU_TNnDH?%wn``mXncjw_Zn!8u;kEMH< zyLjskZ({0h_FdlcKIh(sBd{%jkD-kT>D=-7fBtU7-*)tOr7rkeV`cHTN5MBDz7+xA zd{)ADoHs&smCd&@VH^4oN8g3`KGc(tg!&!qO&|I`_C`}3Jk`6-^7dJ32aIEimSbhS z2HQ~`pc~geN&CD>V$okuLh=dfry$PV59_-Gdo@L9|9ZlHR-LkKyX^1D_SmQ=KSFsz zzVJ6dAb++Xm&hq{4qKBi{hu7W*Ah9tP}iHbTXQ20W1xTfra$^Rf-}L{IAZ2Iz3c4k z0%r*u=V^(N=ZbedaK2~{#Hvs?)35q=`I#dfAI>VVVTmK?m;UP*YoWqj8IM>gX=+z)ozj0P(m;9sJ&UllFU(Q|-H;Kh6*5#$%`N zP~A81p{;2dhZw{%lq!KSMh>nMW(*5<61I*0K$8j$qtd_#7~wo^vIS$T8N4^*!2y ztv7bpySwQ(lKYowybt+Jz*@BrLq~)qrm#qGWD>;5spcGv!z$Q&CPypi|h&U>1T*Y9UJxd z?H8zn{s8S^6*@5sA^i!~;*FuYN>ka80~Oofqge_9^dzvWi>C!X%9dYApAuW}FG`gy3k!qeedyiN1n(Fm zY=Iv>P4Ad31~GWY2I*sX6E|ul-?Wm$$RBGe4!=iJE5K1Ea`AAY`Z0X&c}J(eY>1{pVOst`h@E(Z=cY) zF?Zcvc8n^U`US=W_gK4Zrp}%Deq*RU1Z&BfHo@L?5u)agb#j7SA&0hd^pmg3Uvin; z4zcuZ|GeGxhM!`KAzN1+J|ADT;k*!oSj3;1J4nW`WW!FMfDW5n&D=*WY|KIHu(VD` zbglUoqp6=i|1c$+zGZB_Yv{YjO3<-U*X2VS+D<_%Vs6E5;ykP=HtOiV@>?5i_5-1Q zzcF%bjPE2>$HqDs<^x+a%@LUED#V|E)_`@Gg(DqUBN)k+tc2F`hV_M&97*6i)Z|;# zFYvc4aMSn`ksU&toDw5ki9Uaf5W~Z zzskzKh9qz^L?}{!y_bzV@!Mo$=E}o(nmhST*n%Lag0^QuU>f7bZZ$Jp{ z|D(ShEzvJR<*mOz4*mwIZ!}#5-;4CkW+aX8Kzt_?e9w7e$_~8{(qZX45Dd}yCZz92 zm3ft^!}dm39i0FAKUuQf*pD{E+y%yCmUK9x&g0gn=JbU9Xzy5-L$*q&eB=71x+5H~ zO8R9#*rO?qX#7n%_m48vx@zmHt5C=OlN;m-`9j{z%(bO_i_A^rA*bZa7#J^W$(lk- zPT7}LbldR_wPl{3&r-b4929$~4|3c6&g}n4@;;wBKaIC}!kN0kr{_6~luK=46l%H2 zzUf1t<3~O1i3L*}G1PbHOn2Ts2|6}&m$BQ1gd-a7xFon!et@1C_UmyVY1A>Z%@v{NPq@sAk#HwORS zh$bI#MEmm(QxaUZ#OF7xjMU5 zysD%g|1Lrhd*oQ?3mqGE_$+Y}^h#IRMA5NPHpByBn>_m98?xzkqxLaw9DAytUAW8CVSA&$)X~n+79Cl^R;lY! z^>MAO%S(2+_s#q1J-y4fKjrFU-F_~=N&Edqdd!{!vK8ujpwF^KRlofx*Es0JzFD$; z!*h6>Yqd|U+c&B&I=+_ZKY{Pxax}l+@+~$o?)rI+d0%DfvHcd`S(c;RXa6^J_k$$< zN{({pW@hf)^7f5|G4vbGn`%D1sd%e?-mAP>1@BYf zjRhgL-W))ACbl=;BYKzg1pT~#?+{DSo>=thG3?Zly!m>*|5}cnIq*&~%*)v3dC~@s zV*D2Tr`UpcOB}%(PQiKty6Mslwk=ruCD;e{<7O5cKKL5S_~Uba03Z92Z$s=t=Uo zXYZj2a)$gPH_21-mfYrD9)kBe?{>r69~*VN+wmpGo7ykMA?_%B(Fg3bGmev>hj86z zteVr-8nAv#9KpS{xx1RmV*Zg$-!hiq8^L^u*;`;$Lm{f09eF&I`;R82c8?gZaP`NAy4c%#}F<>jSKVS<=mIV;!GhJ#S`N z?;{=fHZ{JmRR?^}g8smg?Z$r9e&h#D-^_0Ax+x#Cr9=F`|0hGX3g7i^j%+_s+l-7E zlJ47Mxuxr?q@CO1-xB7yLMVWZCBOdoO?7XkBnUId%bxUR15f{); z`s2g>--Z9q!{2rI`wm3H-%r=WA|Kk>Q$nfne|Qbs4X!<5ZC>@ z$-8gbE^!3$91}Uk7VO0leB+yZ+k1h%^nT)lZ%@#%f&Gv}kP|Kc<~Nl)QRep{yCURZpK5a?YTfiG=#fwF1R@q;P4hzE!}k|tTfz69-PKgB6f zhAu+b7uykx!BFmkm|J(zOwe2QLp^@X!E4iVT`iZ&23rXFm}*D=#5=n0xPw3UdH%Nc zyx(Wuhl@M0K-pmLWqib(0$Z(P+{`D`x1nr*>KWJ37!SFVcbjvIT-q8tZHT{UtMZb4 z$+zBNd+mU%2nWw}yw55+D$Z^_{^F9Mxy5XD{Y~!M-4A{58w`Uv5)G;Q;N}u%2 zI2hN?{*UV2o;DTiH%&I$OhIgjLbWaROW#v{9rX*0Aq2lItTAVlTp_Q>mBXG0_NFHx z$@&BP0Zp)%-cRe z&73uN)`0aOUk+=jy_@>27(-m)u zBibLe{RQ~h*SaNf%#_{>l)qu$ke^|WBj>rb4y;858*6|MzHaAOmb3JIq`y)9y8J4k zGT1hypM?2aN6zBcQ;#CiZy3Mvd?=$o(PTHQgQ@F--V^fVX36%%ko|86`2)H3#FV`W z@)4Hk1fRAyV{X^wQ`{j1V-PZGZ;`)#Z7 z&}VYnDc^1H@+xB=IUi&QuitMaZ7RgMS+bc^blZ^FO`V_T`8RTF4SvHZ z;H`l)yh)DUCtWPPSAaJQHtOu()n+A4eGzkCeIE4%htVHqg@_pPhuxm>JT+ zV)?d#`G z%Y1sut*!khU&xcDoFJdc@2NZ|-+8MyvGsm8*r><%(@yax=TiOd#ABfUD3IG8$EQwX zW=>1%!Fsp6*OnyjKklq4ct38@^&R8zJtMz^Y)O~``w}6Bd}%Y)HU#mAO&|AuxqLEb z7z?=UdfL8WpWjHw*op^?3&zFDGLkuT!F*1_{Fvt!%o+L%OLlXl->{yBwS@gZ@U2f! zXML!=3P*aS>w6YVu?v)+s6MyOx3cjImVCh9+K~NXs(f=~tF-^6bxDRKxLo~eoURyA z@K|nHeP~m0-OwDGU>#U1)|Is$A};EFz3m_NmN*BT7hQP~f_yTRjcv%0G|AKccb;t3 zhx$sbtNK*jn=Tt1oeAJ0Ufw|tBzmGHwyPI@CL!geGQb6O?>X>61yoT_xCIe z*?PvU?T>8O>7O=TeEybG{?60%_Z|L@=)X61>5=ev%aJt63V+XpDfoM4q_j@iEWZB; zz5{K251NASLU8_{@7T}2Oa3q}LghEA@5=T^l9*F`zE5dvbq>s<;<;4GykH1o?2JcT zppS2OESC5(9^w%f&|RkQDcA@0g?(bbR&pdw-Pe$PXB*m~_w>cDvdIVXgM3l$kWb|3 zajwcn?tS8GER2mYGj`8oNH-z9f_;f2XxG%9d9@rs{`$L0n*)qQ=>Z1H6-umqr9ct${)X{yy9dpB-Q~6f! zI{bH1b?&kumf(%a{br_gIEi)JU8erl?L(ch?Ut_p8_8W<{qLAv{r-g8oE#%DM}czf z$NtEkge0(j6|bo!b@WfQ*?23zMi1YxPIkX9Fmc6PXK4OLOp(~K)DhsLygfC$8ZnbEZLw7?y6Cs zjNLv*I{ItJcQW_- z%DX?G7DV`grQ$75v<*pS#6Gx!= zfi1A()5T2CsbgHsVddFypPaGw&=37qhz%|I#8lk*0`;!z(vSLKZXp;;6Ra0!n6a>i zpS55wv@V~u(cYbe@(p#YX&3DIXH6xY6Ue4Ab@W%#oa84}f`3v9t!P$7hXLU){~T$$6jiZ-XQF_eA{B*agyT*_&AM z*?Q-X1l|9x=&A#Jj=&$L*ut^KpMT<8+Wv1Pv7eZ-n_BOFs?B~)Z5TiEfGF6uWHU!P z&<-5uO+NW-wfl*0W2P_SLLE!Bd7P_^y-R;1RCmK(*S&WRG}$ZU)6JCaH+1<`g4}#z zDSs;?-x}og=03W>4)|4U|Ba;G`X!9}C*QZZ{U`Q*t)a_P_R3NDjk=%S=PLJ0E>s`% zCT`nP8NVkyzB=w+<`>ex` zzO#4HtNqrQYZP2Y-YnT}@Ts<=a)mm|$iHF8pF6LKo_o{sP1Jc8<$GxIRusGkc&qJv zA2i7dcIuCyO@;Q@m%zs{W{Gw5_UODn1aA@~^UrPQd;jikF&6)gKb@U1^Z6gb$4L5%|1`k zK2P%Y#|PUKjGcKgM_B zO>+;e=Mvn1Q*6=NKP<`qN3q1=@^tN}0wXieiymL&6oE@pBhpJ-Z>3Tw0z*3ES3W=TI6*bH?|->Bvb zTXqxrwl%&`>zDlCJka&6%uMNUm$B`n>ATw#L-utM|4ZxeLci>8OPor}{@g#3@l>$c zkGAMY>y+_xGd1jny>vzhKDM zH0f{nOmIFbU1i`Lajr%Zl7_Qfsb{>NbGKa?%MyDCd}-@@n!DOyYpSbIH+0u=_a5Dm zQ}-pmu}JR3uDcP?8~17cc6Z&gpZl0{`qKs<%3XxO|7OV6G7d3diV&aQbN==sX0o&F z`+nMnXSKb!W3PESo$XPjluUZ zz8hU^eNU=Pm7ymt@5_%k#9QJB#=&^DV4Nf8uq2tMp$ydDG}&km#4=qvebCnsalzQI zQ3tDVq|@H9kdBAkf^o1PN3dt?9s9_hHvUGJP+r&mdXL#>gRQbvrX9ILE|FKgJf?4;o-$)3 z&+$2e^RPJ&Iwzc?DNf2Rxn%b_!ViD9q0IRLe3qb%V{k@Cf^M*F5uAImQc^b6j<4I` z+jLLxe%c~<&pkk$x$A9j@;QnJ=(DsLin+uQyeDB6wshW|+-a*o8M~njON8z~=s#e; zzm@dy24i@l$!_lYmGR!j;dCA^>cl#!$$ue1O7*NzW5RoKgPOr z=WgA5ox4wWUL=1fKy9wD~s~#WIq(8x!U?p7= zLNG4+Zepr`#?Tmp){Aq>`ZU2Bh8Vf7Q)|-`bT|om2=;Sad&(MDSX2DE{E1B;Jwb;p zJ9SME8(-qXQrjEs_#8nS+PXcqnYezd-u*81cLZ||&Ho7YgneYqw$}J)?RkrLz1xo% z`ZvNB(KHs({-_TT`nQkSX`}AQ2bSm;k!9+Ra4h1zfsdih6Rt;6@AB=tRM(S`1ecfe z^8j}Hgml&dSQlssYXy{Dx1}4}f@3|&rTTBs=B?g!Ria1Yad>R4jQvUXe5#z9U!|-3 zM9BUdmhx=;K>c@N)wkY7Z%oyhp6@5}dWj(-A>VJP!{^3rEU90~k!@ID`i0iZ*zWS19N7l>8%@*7)qe?!ZCe`<60(G~k-K74+|^Zmoy zhj&lUw-4_hbZoqdsQ<=RJ?|*q21}d*W$YpFBhJa#^bvwLDDMksV(Z;Cd8_iy6_fXA zzDcU&ed2e@mOkXW1nqzr#3Jsxz-BvRU@TLdl$2@HL{|*r(#OiT81JSKe%o!{aH8og zhMoHQrV6#={e`^?{Eq%@(1icp!M`b%7y|ucS4{e#4Q+|TdN8KY7+Fu&mvshomydL4 z*ZY`y+O^z|r84&fxj~NHd9!6B&niRZB|_)4f(@uQP5KQn@3{82TvrUYv8*xO=9cz( zYC|3NDY{_(H!IJ}kc3D$KQm!{Swq$}3Y4+KDTH)#jr{xUBl)U*b&ilHH`I~uY@}(Uy zHZc{y(q#kNR_<-&9v}fboWhf>*8fI#|5IatebF>-)&Q0W%>}xc;t1AcB&RwrQou%z9&k6hN zJm}IP9&k=khdxC61*d7eVqSEWO-KjwyXJVcxvt8Q=K|RZ_Zgzh*{XDv4d>Knc@*6? z>H&Z2L**z?UdfSsg8CssaJO^cb-|s@Jv{i`)4lk4GjP}H_qFMF)_<=<^4rYs`P6Uj z)ZN>4@3wqT>>{W13hxf$a-VmxuDhIj9QzbUaHkJ}#1?|PfAf3KTO!BSHJ&K&Hh{t3 zAcfx!UGD~11?m7>7m@hyn}cshNs|P=2YtQ?@m(mn%UyPe!qPXTae)o+!+(k++!uZC zgt0NknH))Aj&InUc4HqaOJ#D4 zJsml(ElHar$VEWE!9E3fK}_fi|4(S8ww zc*Is8Bm3?phskGhm$9rIA7i9EbDZS&$4~hlIxDmD(bpTHM}2;sQIgO>eKe+2QS*a`1SbmS1+ zVN-CQ-C#FOcb*y2e}nyz_}4PJ`*R;i+n!{tM~D6e`J9+x8Maq{Gs>0qa+> z9Ur%w(!u4B4)(3>o8kfc!=81)9>NkMiK15<_O1OVC&&XhnHM2B%2}8?2VDf`LFWei zo3VACrs$k2oxLSEgB5I*qq4z{ZdvWvMiP>+b&i4aY>94~bl}``2ZY!U_u0rhtfwFT z#xZU=v!5ED*u-+Yr8u>oG7#JC(P=kzkA>)><#_CiPZz|+Z|UCS?rRrYcOv&9RK2Tw zbY~)A3fdEkm~f<12KP_DjQ@tQZ{dBQ-s2-ys2y$bAs2X0kR#*i(Izn?hjgK;sAnd2aDyZXX!Dd&k#yH7uP2Iv>?IoNUx z^f$#x>OEu|^*3F%N~jFXYb2cAn~)9YpD{MUc!04(7a=BN)x4M+gy77wj?8)Gx^Aui zXWh&BWRF-U_K$trnGZCzz{V9al@_ohiVw*5xB4e}_TfV_gL+x8^8d`(C|l~?JoBc?-c#EQwNN# zj@!D&T>Wgr2i$-|QC~ z_FCt%Ci~GoL(lzhx|{gj?g=`!kvK;vbKg;4=_+#{u4G6;2<}VXO-&rV z6JUwp&A{6s-%cS3yeXUD&2hg)wroH>ey|d^ujQ_IQ#9VH$(E#V=*g0VlM;If+MFC$ zQ$M_WJg%X45O1j_c(d^y_uIZn=UwM_oa=e>hw#5EcK#cIe>3p6mhms39skxKCVet4 z)`hV%C)S=lU@w+nueM-+k(9B+DfR625#-2xV9N&N6Odm^gw8Q=rf*z_Uxl{x5%ihk zm>O3dLsvcE*W^zdBxS~l@5=FR*{EN_eOlgQs%89XM@(Wd29KvnkL=@QKV5zm?6eyq z#8w+14m3f$B|@+-FbYjNc}NbjhvYB&N$&CXu8@n^$<2@-xw^<%wIx3N(5L&i+_C}V zWc@~*-t3k7e#f_|O;-%!5+7PZpJqyjDDN9Cq(Zu5mL`x9^ewl}pSc7=Xxx$4wGe}TI51?oevPOM!M zta%q9*bnx|`*KVB*ssagc<-GP{mK>R&@IU?Go(L3J@!a;)3`61%I4%b=e$?C$`$Hd z4(Y~chI7;&@OiuIZ2z0{N*nuk={M9>Tc`}&bM=nrj^>W!uI;)zxl6n5Sboz(zv=vr zcl};(F{Dpy`S%m<;Vu8AyZ6&p?RQ~H$Iji2AO4ZCPyTHONxYG4Nx(*37mMF?fixjI zP;P>E#nJCSe`~zqT|k|oJ}!8N_$~5zOXNEwBw_1&&=a-p^Ua9wMi+cfS_wMXhc;$E z^x^(3m&S5L%XgE<{Um4lAX~v@%exM{p^pB5zF`y{`;FsmwPmfzANGj-I)c3>4_1QS zsbh~6kM(FtSD85MulL@2PEH!)kHmhIsV6Qmi2aF|vFpBc?TLw^V_Od}K4MMcDn5P9 zoE!6M%6HDvVQzw)X5QpAlJaNXs;mB?v*Po_S^B(TdB-NfyC$&>pO&Cwx6du9voG<{ zk+Xn}c1Q3w>-mmC`mM$r&0q`ekPB?4)?r&}GX#Bf!MKJ%PL-*n%@HHtYV|$I+imIY zYI?g(u?v)|Ep+F>_yOOGNb9vtWSm>pzO8!5$OS{$U~6J4AIJ$fnI}P>WX?A7 zSe!gNpYy|Sk_f?X&k&qR;7nF*|EbP?ZaWp{i7q=tVddH9j^M5f!9B&j0WCpaf_nU5 z7TslR`vO}C+7ssn`w>X$xEp8Qp_IGYQs2(&?|s zKB=S6oCi8K)`z~hrw(~dzL#@2^NevGI18MCPkzgvanbM59CpTGY~(QUx^kO5AIf)d z-rLscPkdr3Hhs`PXOe#Jad<4$e}fpr>IKTTjrvG-o^Qs(xI(Nvr;EAfdE`toKR{=` ztUqhm#VPytSy%Q&YvWvt%sI-e+h_e%&-xkG6h`7Q^|mk7MfOQQ^b3tW5sb+?c6|CT z!52>Ac*KM)$iL0}wZH5u>&4o!j;u3##qZ1!n>V`t?ZCe;_;(5ac3I*G{*B~+FCi&6 zK^;E*0-HI~jcq;sL$b2f?-SMMw|b2;6!(o*>d>D!S`#y+KjC^y`?&lh>Fde6#+-557Z)xd^1ahykRKDr5nIKp5Zkc&6a5uFCo%^Db z-{J`0L+F6Lr$onQpTU{<0)F_L+HR}PFiwxT%2nofqCq}6^chfE# zW!AhWzF&~GeUtdD!jKLjruLQJeSZIW<4wH>f;V5jnY!KxycL3XVZKRtS9B3#>m6f8 z>eyFuB!M!rmG{RXrs4x{5!2K+{WA{6V|d5m%Xo%B_H2(%?4~g^y~{rD+wf`SXtL+MgTtj5^+L__2T3j%eCf&Mj=gUN3P(Qy%Rklv^;8B?+fM zc?jCft1b3QsEkh&^hf`U`!f&C=d)khryw6R-w>XAmyLZ5_F40x9pfezace);vG3B~ z$6tNcHSqkqVlgIUB~-?Tu|muCll}#3%-UA4QD^Mqd6LVA{7iy*CA`0ZI{bR>U01v# z7{eBf%VR@!aTqIW%R1*B%iZdGHFW3oygN_cf81wVaF6w5NuC1b5LPp|#Hbw`^(; zj$P#}V}G+(AM~Ew_EW}>?)s3<+OeLjE$iI{d$4|BsNRHh@AaJrF1IUZ$fJt$3)wGf zoppRd?DpUJI#O?vKt7Y#JqgJQXX3`^rK!%;GiBZ9?M>Pb`Q+1I&hgQ`1>9dSl8^-M zHkc1I*|-O}b9om-ghpNJD5H`ern^p z{BvjPH;UhBezVMx4qf)0;6vF^Hu%zZ6hit>@J{jDWa-_&-vW6<@UGx}u^!$Tu5&#; zH$yhSj^C57eVTlD^UNe9Z>YnMc=SKT5kq5T%oS`c!N=G}y?tCaiyt<8?RQJ+%w6a5 zmJe?|*3@q?_Raf++=90m@0KNr?XzEM)0vy*3LjnZ4*R41@}8m_+p45)3dX=#7&CJt zUSxdMbZc!5>s-N(4=|RM(0&pl$2F8|jGwnMbE+^mV4l3krWm}}bjD`VCEIS#rMj}x7^g-#T_<&U~|vuF6>`esxwc1)IHfl_Mf1C z^EOuAp`E)pCFQ1eW~yvl=UBVMf79)YYzc8Hwmj+Z?dxkv01P=Ou8g6Zq6)yuN({G^msfbpXs13 z&m`+UbL~TtwFCA7`$;Gd**|-#y=9MP?)fNt&c3t%6>K+8^*w)wE$IukXzCjngLNcz zfL~AO%kf9iZ9^V`KkbMUInT&F?YVxe8GFe(vmfMA%YT>i?~TU4Lz?1={YR?)Z8XG6 z&?`-4ga6G;8?w?>hLe5W()G8TYGdxYI|k*I49QBUe6w@zNSG;W8JoH7lftDd{)Q=k=t)SLrJRJ3kfX569l`xT z4o89xN08T3;16z#jvad9@>cl?*H_s~dvw>)&k)SR^Lmm~^>8kJyO!0@`p7-nk`?c_ zWzQTz+IGv@9{sL&xtCbqbo(P~?2%)>Ws@E5^|{O6*3dC5$4g(fZ`J>XdoFj`@hrc^ ztZl8o`MdtNlK#8$i=5<4Z2nD??_YjHfiiZV8}beO@$` z;poi-=vxr)5Knz9_0`oUeGlGeg0Xb57*m0Ah$bJ}(DoBUv4}l*2kSlD^)6m|ABWhy zqZ7(U4EZ#CdC#dG&^{>Z4c=6bpW9EhZ{q0hFMq?5zo8v5fOuOh#;38;=cjLtVKEoY zi+z~6E|$!jHCnkwtVI_yIjoKLa~Zcndk>*J0P^Ap=gyn__$dyt>1Rt4 z2Y>Q#lzgSUGZ*psj7xJO2M%*hx+HUFFPJ~|_l@q( z;GPm)tmH_V)--3allUWlF$$Eo>YE@&7sMXi&4RvWg8mb4zSy7I+TXIv?+Mo(! zaf{HHfw7w<-5lx6ZwlsWJa;4vv0gZ`L)X7)U|wvMpJ@8V1blM}eWL=tRn6o`R`{l6 z2H&?5zIpXz@%`&x;P$s9)=BuTH-E|7cR~qREs!n|^qr2}a?bB7yy0Lz& zEoy2>Gz?(Q4zc4!Z{4!M$QyLJ0idHb}hAMQKu`YyQtj_$~*`*rC)?fKi! z?>9PkEWeY~5AI?4;?JGR{mJh^PZqI6%lPzxjxEHbt>O^R_c(oWZ}aXr^q2il{dVzp zxShXg{I(t5B9bu07Q9ga->x>a<9829ISTmT+XQWi!~5p*X3)EVHwy5M=nsT!H}*TK z|rh1?+e6jcRJ(6{tE9A7GoNPzi3UR0-*1W*>W~Uu-7$4(h z{LCo?>%#gp!I~lg8|;+SP02pK*pZ7lE6*G6Fr5M3Gfynp z4`)PYWpiG1?l^nv0vmNVw4p6Ah&4ort$tw&#_&1sI(x_?u;V+kPx=d;SB<1yh{c4?U z@k2cOSyq4R9vkCi++9RU&x1PCq=U;_y1{1sP2%&!?QfqZ|2G&PbiuxyD<8Vb*w{B1 zDY4BjP+!5fixAGCro7mhC*;d1c|?wr-{ds84Lu3TH~0;Lw|d*{_ayCOiBA(Vf7^WL z-S@-M{jm~s?hM}>+#TrHpZusd)Q4!g7rB?X15KA61-|DLEo*!yxZaPnX~Rr-Uo*DL}zVB?pe>hK9X+?;19JKE2Z&$2OUUCL>7RJLl?7Az=R()`G zl*8mL`O0`cx^kJkUTMGQ!I<&?)RXs$!P(*ra!#N6rawcyY0@iupvwkNb#FGexn(GhS(-y-sN4$7^Np#x zH=f%3`Ioj_E`|onqZF`dV-E`T$5vm7r?q=j& zF_n`*ZnkoNP{!ULz)n5>#BnUQx9v^hNBbun!xEnsZBwyM z;(bMqWJ?<7%r|M@-^g!q?z$Rpl<`%)$!mfsupv9zD((RJQn zU0@60b}i>XnHbaoer8=d=OIF{{!Oq?751-|yUHON|9;_3#5+mvprhaXAvW(+y#u=VybJU; z;Jq*lem}HQ*Hs^4>HRrGOG%kJ+7OS}pFZeI{n9_wfS=3gunNRJ z#8-cet*d|5YHHl9D~y!rtRJ|}(stKv`LcJ7x2)EJHDP_G)`>M_@5zUuT-gb_b7!g! z@B!@9!xrS4>C#t$GIjX2#O;tk%n*!)eKhPPVBe?^r|2RCv4{yx& z)+G2oHIl`*Dse>DcPuldn=QTRTh~aIB!s@-8QYMf_|l!-pUQWr*;EVFa=XE1yeAk=UhkXQb)(io%__e z<37wGA%yr>khy}x9r2!cXY&5!{hHi0X~FX?@_c)A-X4i{2ePek_6ypj1-6k{=;%^y z>e|eAEASh0%!TG&a_9708Q<~4x56B2&NU~Sqmz$+_JbP7b)4XuO6o1v+I7gloacynDo@N^>HM7Yino{N ze{!#+d%wI7Q9elDKi(7X)`|P&9g`VSyXYewZ09@4xW-&T)f=|Ndi%{jjgx)YkL(|? zzCC!erOzqjbgU!BYyXKh=VF@;7lv=Ol1n!E#N73bQF7cxH}56p!Q>P@ZNHWM7j5;a zJY!qu_+-HV=a%!+_T*yw6SdFOYnvKVdo8CvuuFUJw0Fkq{WSqG zwp%}7y?**nXhHUK;|zIr=HtwJwkxBY`^~qrT`G9qau(;4oNi7te~EgFe16Sqkyp))awvPsi;#C5`foxi}x|6W74`PaZU$V~+Vaheg?bTo3(h|A`a*+2?Pt|0e^RT*f8FozTF3 zhU27P*KmUC=r}6Y&$#~``SD@Qjj`W2|9lY~HhTVE&%(zEN z(Pmv@S$4!a#Bdy3~;aypQy0-MSy%=x91^adk&fh)uY`Rvi z*=C)OXMuG;>_?s--~0m>^!QtS@OR0D)$xV)&%f~Rn+-R}zk|L>{c2sdGltAU+I}l* z+teHJDu=Pl`UM>yao)S&TD(zxQh)Q))(ZZu_r}DY^zwJ$WnaYoNe22i8g-Jn!F`tg zg{)7x@%u*lyvZK%txMf=CMy_7@eHP3pK#W)61ET{gTl*WDX7Qe2e{yFv; zcW)!Vp|3F~`_TgR9s6Uv6ETfv9A(ncD``);!?*evPmDd_(wBX?PFyR?jzaGB& ztBdx^rfl4XPjVb%U~F||V)?gZ%%`+{+G?zcy@P8#Tx*!@MF+przKuTbBfr_ck-%?w zgT?pUH<90V-%OY9yx;rHcfZFwWW+nfx50!4z8x0$j_^G(f>w0x`siyreAn~8?G_v$G5xh^~D$*m+L6%Sys+6-X7)ZYrSaO z;QqJ=pLx4)tbdEOAFPw>v{e-rFm97~mHpW&xWn=;4#C%GPTO@Ct;^8}75$EVzI!MQl6 z%As7zwV3}9GuTC{-=O|IviSWbIp~tVD?R1pw~5%w?LS|K{kHpN=Er@ z?V^8UKk6+1M8@g--X<$J$R}BsvPYYrWX4ziG!E;kuli4Mf2uQ<>*sp9wgXy_`|u{+ zrw+NNDOcI=e@7psy)o@0v79XQ@deLep3RBgg41aa3p#lAy#wQghRu89{psMH%6m4@ z+aJ(J7Tmx%m9#7MlL0N**wVoAVuoR&?ub!Sa9xuJ;PmwHtR5 z-@fd3!(bfOu`oX8*g4~k_sNA5Z%5zN>I=LF)+x0$?uKs{?^@ok#{H>iw_fyJ-Y3}c zUeEZ(t7xCT0ii{^Ui#T_PrdWYeDDL->*u|;-IIY$8svEGOLELPZpSjpcx-cFM*qz> zTX~yq^c!^Fat{_|InYYZGyi>K7Qd&SZK}QD|BdSR8{IgLRUEgd*Z7uyL$*z6n;PG; zb^R~Uzhc{5fA^(r`@Y%d8T)HfA-0}jdSE#QkManI``E*y|It( z$)T;NZ!nHN2h=|{edynLIsTlJYvnphdqMk!!@Qk~^K;C`b$s51&Asrh9Pkd#An&7h zs@^62y-(gH&;2IOVt$@s$1TqD!e{RI=xaM;)qd>bN!DlomY=K`Q%7Fn)-lZ(SG|MI zbrvk=zBumVdcbAWG z#5s!d9S<7iH#Tcjam~Kb<8Lw7_Kk&o{DAwC?9i3TK$rGQ(*G%U#Hsf7%b5CkhGw9i z<&G{hY})_-;@f;2kK^m$-N^f4Iqm9lf@iGpvR2yO*w}x=4L@aKyVReubB;Vuf8yZt zZ}y>qZ=%chdBl6o_mAIg-)Mfr{gyAj={w#Ye)l{07Vu3lpanbL z3;DK4S>F)0HHIAUJ(KST`^oo%{fcGjF$R5YlWnv8#-@+H*D*7P`nFSl&TZo-_G5eJ zs9Bm8UN?Zvd*4m*W7dH`YU_f6ZhdlJ>QEti*t8fa{hyPdgsj# znXAlG<|lKL@6ipvefQ?O);41MN~dgGv5#b+n6n^@x?5%F{yWTf8bx*2qr~ZxVqpnZN9BYnUISOe%F(=z*dt+prZ}zOWy>VU3 z2ClJd-N8L@FTOFTlNQwdbboV?Q-8`+_P_e3J>ynsoxVv&&vRDK-{Aa73wEbX(2?R< z&$B)oij_h!D_+lI}%xPp$9OU@BhCv8bL$8G0AtnYigc1 z4@v{S37O~3_3GLZZ71@3=IScV+w#e=S1zayWIkmmLi zsB8C)r4|BiC;^jG!U6-*}kNgX918f`ybUr%y`T$%Y!wdfQ#zA-;DG zZ;}=GKB=T#sr~-ff0KUI=cza1C-zm@lto)BSk`{QaXMb-A?nUo`v_?{eKz&Z)pcBO zq5DPxrDf|Hr0+tvzOfhXhkMo7vwSP%e%8Gl_}Ru-#+flTr8et6d+Qz(%eKa;XGZ&> zKF@*uAKG=e#4t`XR>bZ|=PD~0NY}~rYH--2#Bya)zF=EpZCEkZ>wFlG#I zEbu;5v`^pT`049C`PjHSBkqxRNi4T7__i@_#xsr>dq+&`R`lQTukWGGo%UW!EBF4Q zzp*1HOk@jeyS{MC9PC$SkmGP}&T)hO#xk~jUC2D8T){z_&&+E(q2<)@x)l=ffC|7ra>znr7?-(nl-FSwSuo^SQpHf8!`yG48V$^B~X8GG2k{T*db zJtyC=U9IyB^uTz@MwbT0@8H>P;Cc7#i}y;Y&AI{lJ<;AkKmEOn11@)X$2V#s+YeZu zKA-$Vj%wtAMLziCA##NIrv>H=^U{gDVb0Rm{2}T|+ESY5CYbXEeVG3e`?Sus=FS`a zWuB|@MDA0HV{=T~+(`^$uE@8}y`vjD<4*i#!v%AobG5x=)z|UJK{w{WZoM&-mbKfL zeYj_nJy34S_J64pb#2zUU+&`#M%h2>SD7-0u;0)bR}1_8F+Ruv?e)9lQH8W8EFEa}ej? zp1Kdp8(4M?-E(Eu>yvfv+X!}?k&bWj*;nk=RjgkTw-HZW-vOReY3QP@qHR(y{lPc8 ze%9G`!v)7MA81Rv1yTet#-wU*F`P+Hc14 zCni40)AyG3_>*4SHriz_^S+3-3F&tqvA@l6#a=1(spa!1Tc_klRJHMgk??WCJ-?$r=c5^u#{bD@IRO}c{TgF9FW>e4UE|*bypKoFLTXF9@9GYN|F<;Q{9mjxDJR<1m2cSI zI145*?azL0&>4?7E~R$s8#oTzh`P1`7qZQvO=DipZNm-JWk3tm<$`|`9BBUS1Ma)~ za9J<+Y{mP|ck=PPaBe(17Y^sB$C=tl&sm^m;D@p zx^?1`R~HTiv<4uGhhc7>F-niPL&*V@i(jq{+=bb;O|_re!;;fdHW45e?QB*KS}-n z)Fyp4zDe4Y9db-LhY@p9I@elOUoj8cR*WarcEUHMZ3noH6I@@{yMud>`=K1AtS;>b za&J@4$G&IVjP(|;)@6OhOPO&y@$JOUUY5NJ$ARbG`Xf z)U)jOz`Sk#y(L$hqccxW^0YbJTy0Jlea+wcT)vxrJAJbxmIrk!xRe`QzO{aHKmU{C zJG|nX+>o;Qo+lGM>F7_gPPrdsyp+~=Sn-C)w?*?^0f+a=6W=VAPWk@V|I2={J^gGe z886FKW}WiDWL%Edam$IZU+gj{Td&>v3C?!}3;B&pUA}=vyn#AzAm5+vVeXy#>mKI* zecnm#P@T z9c}BAtlQjT`V`q{BR#*u48P}S-%*Y zZEU;YLLJAU|DP~-iFs^8=EFrUOwyLJQJ19cPcr?L`qpyl+JDRX!Pj`s#W^J%T`X_V z=ZSXFM|$A;trxVX&zm&H8yV02a<4sK?qye@C~f5EZY{xFV=q3-xTajkP~pd6I#L!V85=1p<^ZJ%xQ>*3SO zp1QBn0{7cJ6m{*^uizp@oAvf%T<1NQk89yRZTBi@$V%Eb_6x41>$l;=8h++P?9ch) z91qU6_pUwe(WabuzpQ`a9lLQiwrAG4u{NLeU|n3B&pKhVZh8N_PZ9ef$4idUF1s+% zrNJjBMBm;`@1yt3wX~lT`{Y>MKgaV~OXO$ouwQVE7wF$;XWP#>Dce?G+b7w!M;qIz zPy7~spSgNw;tW;Uu*--w(f2-ZjAE=LZ7FSITN%*6GwT`mtcZHYCf5DLpgFLA$N{Tpxon>dw0`ANejC+6k+lFNJ(=Pn~?$cpRr6Rzo#j!h10 z{wLg*%6PexPut}l{)TM#^nKc&<){DOl#QkTpJHx&zG3@sb@PWh+x>=&Z7gX)mN(^Z zT_n>;OkO9y1^ zj5i{tZH#TX1?shIn^+lM3)YS|Akc+S?m8OQv$UeKOoy=B`tj!z8YF3#X~z2JBbp@H|odm`!=tRJ3P+BsHZIPMiVml&1@rkg-r8im6MZ@s$0QqW;J7Q!B{}Gl>yXm5ksajv4gMyP6nK@ZBQ5VP?>fa(Lgo@u#|sqtv&e zZ9|WDl4IO(19fc;>R8paS??TI;JkCKT%Qe%J#Zgn!RL+R8xaQkz#)T=()u zjD|FB#x|z0HgUS|rr3YusvE;t8wO{{bELGaU7v>UB*wE3=O8_Be$I7(bDyy;uHR)H z#r2ddug6*$!?EYM9p_{m$FX_VeV2gJJ0RM<2Q6g!HSW@i_ls|s860Gy%{wda9Du?nL)knSh)p!Ys9U2 z)v;`FEVi+K*TpvGvJd7rAh zQSZIecg6k6`|+7KO@`i{9eXU~Gnkf~el zg<969(bo1G?gMSsJ!#mC>73l#S+JZm>Ltr52R7U0xayeHPmIfc#PXYut3 zcD+3N8_bIXE_enGZ1&H+bbk-DI0OD2;NK|zO>?1l`h=tW=U;5(eL;IN(BEj-O7KlESy-t?dP`>Ad**5oHY>(cKh`KK6fV^7JU=b=Z6ejvzR_>gX*2iQURq#Yeq!AQ`>~yI zT>rZ6mW`e9C%V2X+NeL#C)v@~I0u}!sILdL-Zk2Rx^46~p8Z|MJ<8mii?&An$4`IO z`v&e!>dM@!iCul!?h62NU9c<-tY5*Wjc*#_P5S7(fw%8rXu;wCCNc|Y`^lc| zjg_P=+h;lJH@`L=8FhZAk{N7d()dlQGAJj@-@AUG^ShY% zJK2H8Z+0>sGVN9B|HL@bLuUC<&bG!*HhQA11^SEaJG{-oxg}Yz?~|+H7nMQz8`ZDs)j#(+rFHtAKs_1g>9@a}m#kOnGlPco{CO5T-2UHebQwWICZ7Gg z2R(G99PW~=FL!SIV7E<@ewQ5dWcvmSCbIp3LoR#s(O%KVc)MU(yM7DIjpoP!4Gy_; z#{0;8y~(Q`%(Ld(8+li`p+P4fU-Egy??ff*2KE-Y|C96iRxaN#d^=Bk%|1b_3_{n#{+#`NmA#r_Ni^_vUcl;6UfwomjR`pM$?` z65DRD-Hh-3MyhMGt_2fm9OL>XSl}B#IiP`c`q;)k+-Juxt>C;??6LE9{<%)BPtwrE z^;BNV_~+VeVt)FeKga!fzi^g4(|N9aqsV+gyMCXy7Wc+GbslJw@q+dPzPr4ajr-{x zl?6A*x0m-gIiaW2-UHj&URq%MZ|IZ#SD#KB$LBZ)m>(wiUNlcww%+#kb-^(>CNck1 z%t@6^S?&v2_a@)`JGt*0>HphU>Hkd{XZ!{F%l!rEJ0gBV=6X)nRI>bAX^g}+9o#SX z+C9$wb&tieSU17_9f5jcd+YT(rJZdpi*=PvIhp7U^nWt2-8f^Ov!ojnEKl(6-Ix1X z?`-Fuiuop4k$22hH}cIOADCOr4`MD|18s?+!8md( zpB%(^4)tO_bbQ(ye#+EskG*r>Z?KUk_S!xtK90{m?azLktFd$b#xw3`ZP|A%1|b}t)yG|FDNhvFPMeSOxNaXHT8z2W}k{aoCwH?qD_uYcY- z=WQI<+4XR)&U;~7#Gd%tCSzXox4foZ^cz7#Cia>A4&oZ$zCP!Nc{yL_{#=6$jlFO`+;jJT5LX(wUmdJdrk;B^ZReT>>WQ{quxy=e?9UiAZjC+SjOeFc z+0m!t|Lm#z8}n3l$|v?)narZwZome|=-3)@Cm8>}pj{@kkd__W0mr|=braWc!3EET zcfqr9!F%G_xa{%dTj!hKzXN*wO>&UZ;_sb}lv}W@&yBwqJXgjVl(nsw7`D&9v1+WJ z>`y<-yD-tE6>|JlW_`82>5Fmqzp#I^pOa_2w7@MNZ)3^54QcmEOhkb(BHDp7pYAyfdR5c~30g8L)jWBxy~3LU+2|BJYVjs7I- zZD*|WK-<%9-{LqN&jQCL>I1Bk2@NuC#(RsYuWc?EBV$>%u01ejjhDK1Nq^gR;`#T( zg;n;%HFm9CbN9jXj341M1Y|hp3IX3%sJodNf z&ovUu7aUu|PhU}&1cWHj$L+e zk(He9Pjdb9ueEaxUDFL0{9Q==o#+JqUX}R!Rb^9t;%{9~2DW6OU&!CnCcmwnAb+Pz zsXcXN`q@VM52Vj2F$OW@Tg;5-_#Ed58uBO7Z6{@>8tpw`n(nhCG}X<)6^cv(@4Df1S~RlosTK6)eT zQ`+W9*1bu64-EQW;8>J0f-GOkj@NNJ7w6?X7aVZSU6%{FhOT4f#F}o_wZ)tABK>Yp zaBr2;!8-R^`SjCQpGKW=H~l&W$Kp6f^fjnA-tbMp8Cu|3Qd-vjiOra7V;s35jg@8l z5&fEW*1+{}ZN#-oy{?mMWDL)feQo;m++X;dd&beQIZo$vKp)5FIOV{^zjGhFAHG`- zc$bv2V8Hcmg^xDtTzujZAF!jr4hw*=Cf!pMLsin{eXX zZSKwt)IEQLI`szL!4cmi?`U$(CBZ^g2IV)lxrv<6$tm}dPt3Om26pSrZKLEP+sJ|w z((*OeAe;FHmhsIg_7&s0F-~QUUpXV!w8%{txtOo>p5PkXKwah+vOcALEl`&Yu5F$L z^&58U^pV3Da@`$guA6go{_@e8>t~%Ax9yDU-sZlmO9$(2#5eXXd%9x38gjC3uA%W= zfA^xrUYX~Ujs8Tx5iDf)`}Bu>oUvz)!EqhO$vO2tc~8HwsjH-4!`^AT%02K-XtOTc zTy)8@GHp-$pe||XQt!L>%Dr?=-7Dwh+BtvMA-U+H{lvO>esfRNZP#Ffaoi8jsj*!j zx<2G(8H&VKuY!#+MqALF{;18kpn;l!O#YU>ey;iJFU*AwH}*JaGjJ&kjE;;d>@ z*WO>yZoPgRMvUFK_GP~V)(hILPb{yX>Ia+iab7t$<%Z>YdiU5z&&%e#T=seL=0AnT z-wHBd2bMcNlQ!PB?Oh~ZH!gkJul=}Z)4NR{p3mi3DseKN@&-NrhjIK{D*pc3A!kVK z)-TZiiLs11f>!jjS)W|=Z!z>4K|@MipErG$^;1vuOYiA?qRssxbIr3p%Wra) z^Kj55ZKt^h6-Aiq0H^wL!SG3tizYQ(y z7nwMY%F{lnf8$`U=+hr)t3K-5vX8|$lC<^k`AEime3S4UIDU8h2JwwseDivsezASN z8GbKpQ?UGGU`t%%H#YXlq?|N#-!uf>*Kr4Shmd=sW)tkcd`E$8Q}k${J&Gu(cif7Kd-mCYCTgm=X_87lDwzIu)jCVrb5-Z!KwA~x_pYd!j#+59m)VAP&Ywn(H z_QJhszO6W4o;T0kMIYMSH+}GTUy_Ti{j;C=T=yCO~pXFm;&?>g{I z+tzbEp$DIs#BG7+NLFx>`r2;O-k6`UbBDZB8@ykl-XQ;mYxvB#UyjW&b;tfPmKxu= z*v|Ie)r^xd9D}heI~IK|9Pan9|G59x96_FG<~wtm-;ItBY`B5HEr`D}$a#>zS7@*G z>QD5Mw0HCy{p{#>AR8RNVc@u=zmRn)4?bf1C*SN(zh`+6OVXa@Kgms-`!K z1Pz&ZHe~*Sj*r~VxPyUA8oF%mO)}A?7Y;c>R$-w3r|^9>!JO2={4|1%w7js3_1b*P zn9pQ_{@ShA??8)~>VLvE_G1iVH88I6&Eb_5`P_HYG7pn$FL`%E=4kEa`yHs8kM-4W z`u&M~zvG+Zx1~Y8R}SwLzj3kxZIWfBW#2b`FDu3w#5*wkPJyz6eO>DH>5wrm-_;2X zd?(3*4t{rK$G6yTx!?8t#w%C62^w)_JJ2*a3*LFak0X^O`-is4>Z?1QwU|ApQ^sD#Eca8V4-cNOH z$+xqFrgoxiRA%~e+JG=n{~$Afo-&<-LaW_%tPj*WTPh+ z{okdN@8sWY{~SY>s~n841&6tqPencL`pABe_LNzF@k`RCOj}BA0~*+8hdc-B+74J> z(PrH@I(4g@8~3p8ow~LSo-OxL|Ky-2#**4s+R`W6J>`g)=~v^ZYuCrIc&0jd_MQxE z&9li}yxhsWzj=4P+sQzG!}=ST56mTl9AfS;Z=A@(O3NFTb&=onykH!XzY!oZef{SE)8G}eLxEzX1cAeOaD2V=SR zpLJ&r&L!t`oD<_8_^c9Z6GM!bw1}&paUGNGjO{wO9`30z<s+ygtT1it@FNlw?6lS{q){!_IAa2bpIDznC0&W^?}W^ux&>S z<8Q`X*XJ@XSxGL=vfsD(Z?+mMWBjeG zebzpVeZFAbZyER|wmAjMmwgv~ELTqWR$J;>mr{G`RerPAdF!uV^|gKm8~GD?2J;+x z7XOw-o8+QP=YJQKN%?QE?ceyn`5L3fIh3XGe-|0B;X?2KFWRjikZrTQcH5l7iWvI4 z2CjRL^|xGOs87iCdXtMbBjOlq2I>uaqR%Q={$^{@?puA@FMd77=r|K?)qW`diN<<} z>muqi;utHj&lO{TjzgbpqmSo)_$?vl`(5z+w)hr|Kz(BCd^bMdAioFP)y3LD_7)ZH%YbwwB^h>GlgvFdLV{MMtZy(>Y?RSmu>1Hm+xx!{{EjZ2< zI$YiXGNAb;;N3HQdj{9{k?)~kAX~6`M?IOot>Ey^T7hNlwzK^O`>;Rzb{rLL1N2$= z?h@0u#?xjzF}8he-p0+pYxqBv^uHk69NLI+W?Va@sJou7vuo=bANPU%`0Nem$a8f$U)}o@%s6-2 zKlS(xePe%D?DzECS;rY2{+-3y^$g2^!#VD8mK*iPwI9#)vQ3=r=3R~RZM=$SUaXe` z`cK%7j~K?bFYnNf`=@N`j3e)pelozhIu^%qBc3r^kp0+)KKk447FNWXF(>2c=bW8? zj>o%h&e&j{xiHCPjoh})gXG0ye(czTjl6~N4{cvq*xoqUrStp76Ybe%px@|cBV|AX z$0YNGjxDkN1hz|zQ`wZ0i~hvAQE2Fru~WX;>KM{5Z7I{P%-DnYvf)Di=fBs+b@I2N z2^U8EM&)l>{dgEJuyf1!Xj^(zszHDBcEwM zkiP2LMxd_EdSi;ZHhqlM;KW<1$2-a#E+caJA-{Jpx0+*b$= zd@^6#ZbA$EhIAO@`po<4=KcnqZ_eghmv0&0RK96GW$cS@mOi$%jo%An??Bx?v|s86 z=xaOU9>1p%)7XBa7Yw+3w|9QOk%#ZO-}FlN8xQ&IZ+!Rv&s%~w(0sg~F251>u&KXc zy)j04M;!WivZC*epS~4sa>4QG+hQ)x$@w|Y#+-Mor)$w!6H(8y>y+y}Sv%>FZw2-9 z1$`xbjWeLd`cGrimt#0FCdZ{dv0bphAAezmw?dC%(IwEpS0@xNcNF55TsLwhk!($Vd|j?wXr7?*YGZ#3*V7xe|kwXf{g z@{Ky>p={lSMIJII$s|9Sr*6>AXJ1Gk{hwsrQ&zuPSMBPxPCe@fdV?JAH>pp>c0aMH z{~L_;S+B_9E7saIRd-#x>-mMZI&ETGV>EEzj{5~2-LiJ;FJzqBR(saf`Wa(+`Wowv zjA6SIw3xSL?fMMJaSq1$Ig6a#{1*8=>fpWfehz5hy`A7aHph6cr4>DG3!7|mz#=c1 zTRJ(WkvGg4nR_R?HuK4~P9Jkra<^j^PH5+c`KF z=Onvu(Ve&RKYu`<^lOo$>iu_J;5*w?yepEE+g z_ALWi;F#Qt&)hh_-s8OY>Xo!#>`B^wBS(qX(DM#e=^c_Dcggj4ZJTT9IcI*kMy`{% z4#|!+Sz&9)MB9a2uVyUh<&4{h=UnNT7x(H|j&tw+=`*p5K9)On>nc0ixaO5yf9sR9 zDYbR5U#Yr%IVStH{|4^&6YWXXt?<)+p^n==a}1-Lk+d&ta)T_Vt;accuf#Jip0VV! zkJRmgcf~X4-SIwqr@UXD$;JMAK0GhtxtQ4X{lsD{u8s2;knEwT-MV9PGy5;9a>ed21YK{N0pTPOO*y2aKgpmFlZ7 z&>PslV{xoG{+wTyFLQDp`c3?8XZsag!V-o&wjH{rR50?I=;4P!6znhJJdb5 zJi&U`;eu^f!E!~Pq}o$|)-Bqfg5&!eoRjQ=_@*AgY*v$C@pHI$;HQKg+Fsv&ybUE&CFp=Nr)Lr)+8cg?|@r}Yl@8-^6BP+JE zY<&mYHs6?jD>-k&ZyIyD&JC8|bH42Zn%{WdHPg2ZT;7JVd?P{&HgBP1qDv2%<+N#k z!+PTx+y3l(!+p@*hcZUTiURnY&(dt z;_Ylqu}{ge^)f=HZrKSG3o1J>Q%2UKy01Sg%c) zy5%<-_9us&^kia_r@fZbN7=()Ddsv^Frb0C(42V4jpoN2x!WA>yQzLh2KjWFSINtl zyez{Ujm;da|E8TW63a57fj;JAxIHNP9QH+P@N`Je9$-=4|0aq*qpd^dgb_?9_* zOGVx9s6O5A731lV`;Fyzm37R1OOmD@eHh1hi?}l2^8McV9Y^|Ie`8^n4!#4nZ-Jm8 zCEp7x-$}le3TapBZ~F_zH1;W2)^7bS*yf4#7xcH?0pBIg%{d-$U44uAX7nx6Sd%BN z(_)R?7uQYHYk8Kn-RPFn&$b;daVo}bQ1{s}t%7Cu#&Nn&EB1WEnC!|n(bS%=r@#R>RIbLO=T`b$?fPL@aB6Dmh zwPn4w12f)l7b)hk%F~v9{}Z{R&!6PPM_R%0R%%`Pf0O&A&xuHvH-kNDf%*RA$_4H}I)!%9Zmwe#fwHMq^{cV?Mt4zwqx1RwyzRnn(lXD#4{9TX1y10J1_O7jKJlq@iD82{o z;q?0nJ;<_kJNEVHPdnSYU(;BzPxdA5)y3Ap{zMd(+e1-T!tt!vs@Kga3#ou}vAb-J+*1L>NaSg%|sr8et!aFODj z$hBLsZlCz}0qTkC<+_UcqRzE*U8TdtmRQ#A+F#d~eU}c4vi25i{A7adjV2ckoO&Ki5svwHs$eANK7S#Bn*k1-IbXYgxVeIKKmfv$O+sZPxWby`sIA zTeS7wcqZNd1D=Jvquo14oj&?Ijs|u7gR*E>u0UN{puXUOV>#fsuW=t^uwTcrg4B1p z>xVYdusMF~F4#tYW5^6PQnK99tDkk=3Gaw^=|blnA}eH;YhBvjq<%lKsh0sK?xt(+ z*qq0Rxj1I+j?eq+81+wD#4t{>(dCBB@;5miezyA!&il=8KjK+_^V9Y%M)e=~HE_DjuH{0U>5^{dv6PI~9ckiZm)%)M$&S~3}MVo8q8ZFwiGNxxg{_kbNhWx*c zWyjJ3+eiz)(TN}QfAE7WFYKdmX(KzBNXfGDzMy?V1N(L?pLJy}&L{U|mVH*&ZvBWk z+s^jqfwrWh8(Y*R>rx(U;&|T3vA)%5%l1{SANr|&HKz6so3S=<&Yu6_J_qUxdaTEI z%*{2jU7}y=-=scriTfMuf5%<^oLTcfm_rX}+ktvgZ4J9^hWDRy{rNuetvc}zIPp!t zyor1Viti)weKP~!JHC6?gF(5{D9etwlv1=UFpjb9=YsEv&zr(`1!xoNrIrWf29D`Q zJY$YPJ-O&&xj|Joi!V7mj;7!lLBV!45Hv|f5(n+3KP^;*vODW84^ z|BRJ!wY{-k;#Zsc0Q=fsuudO+?bkk?U+$TE;dyECF3kPf*u}DSowMbe(6`Zy_s}Gt z`W@Qv?F{F+eaguh_8fcW#rB`vOf2Jiu7`P>zB+u~WAyJB8hF-4-7)EByBv!#jc>o^ z`xDaTb3V*B*CN+v|HHUr&6T;H3q85$ z()s=A8;?BG_iCWGz&+0WcJCMTg5^rBOFhq0%3aRcPx|ouz0k1b znVp>L)T>Op@;7w+67$bbEb2v{)Kk9M27XCH-{d*-T{q{E|3%PFsUH8}W~>+?HsiEAJBm8|I3Z^Mc{jb#k;xB1#{(T(rVF5gVQlM_ySFRi=5 zitp5?U3{xP->dkp&Byo5as$7)Bl_?=J)rqrF7LO)w|s%$^vkzh)U&+fo8OQp-T}S^ zzR{`6w@ZCjs9R^d1I9CUWtP6IPxODo`fRh&FF2P2z6YIezCT>c8SCiUj9??Bv#xT{ z<*6^~PQh~e*nSkU?EB)UJ#a6?ams=`2aWN%cXiyxwNJ;JChtz#E_Kq&ee+%o z?v?k``}M@T`-b=HfcHDGtgkUfVWW$7>)%)}_G+{J2pTeR{N8cNxm@aPd%#?Wyzk@N}+Bt^AG0FHs)>m15tNp2;(vNzM(#Aq;2!2)DiiJ2Ke62|Se6#pPBKo)wCkt;hWkL9b^3PLoHNf{2k%+~?_#~1 z>ODBzU+?gZeBhlnf2_d#kfiNR>NChI<`?sb-;);K6!VEW!#tDuDq||OnNMun%ulul z^#KcR(DD1^9@^NSZEuhE~!2ZU;B3)b&P|tI-lmeVxL{V2CnG@*V?uJ zd;=o?_sIVTDY>uiv3|>MMeLdF-6LbJ*cYYo-NO;_>mF;@f3jEIv&i@yzjGSpym#)p zcl$hOt@-0tygx+)o0;%0{6iEbX`a6Lw9|Hi)}@n^;K__-`K=6 zu6;PBP564_de%_zNa^4}^$pmfk z=C4gQJnw7X+c&ANT-qg1+n~;V9YbPS8g-)2h8tvzXKd@Wt>7Rt2mP(IZIba)rag0; z<{De-pl6>yGM*|JmO^Bh21e4 zV?qnmpIG;1(_ZT~+FZ*8+arG5h@XLNX1Iirg+FhH65zC z6P7RNuhcg9_H@51e76qog6-Rb?|Fy8`=t3!;XNZ0I$Yj48;0+l;PC!Q(q{cGEcC=W zv209ZXZ-ZJ)FtV2@N-N#uGF(^jPx1!?Lb``7;6xFGUg69{(m))(!Q{;>um>BPHvFx=Sv^ys~pj;+TL{iq`i=JDObciNc(r)TtC;buBq$pURih4 zsrMXR&y(lM_mOWH&s&^D&*6c_Ii7*KwguMr(6v|evCeb6p~ZP##J;|#z_VS^_Ox5R z;9W3|aW{Q=zs?sHc761<|Ll7@FEEz2w0Fw(G3>|p8@Lwg+V#8Od@9;xf5Ey%n_0&D z$qkW<8aefvTg_YMJacNN4IJqI&^Fk}O8%ztO{Qr4BRQoqQeV?)sCvlx*{SV1F#(nm;%RDZ+)OouOav;~p_1ZzMrR%#27hU|F%HOLF z1(@6+S5;&?bDV%Rc76vWVRWPel73d7Crs4 zuF9-y_)CtnGv-|X+>^mxNCWrIJ?wD$zxH<^bN}m^NImQRB%l6Sev`AD$A&I>R-fm- z+N-{PxEr0i#Jp2U`%~((VFV3%;;p2dfp4D$E!ez)q?XNn=4|tGMcX%QQ)5}~VEYlM zCnsX*XWx!VjG>R;D08?uyMg(An&->=%Ky8xz;EO%SeC5US9VD4*86>3@eNjLOFw;P z_zdbc-(#7D!?)UR`Snc#yWn@e1%CG>-vQs`z(?NdPsC7W+^4KQsi)6dyBXuX$i+OI zPmg)J9?swOSiC(NYyWwF_y&Q&w^#W@`|`UCexs{Yw{C#3CbYo!js0}E^tr*l-IETv zPws_SPAs=TT^88R_Qo-$@kL$Rv<^S_)VaF`;u_dqAIEoL$2hb7rq0;*(d>_Qu4k^Z z@r*xh%R2ae*)Mp$S8zS+FrdNHUz>FoCila8GGG@hSFF21=WbQfuFrr5hqx7Oi9Q={ z!FZNy+=Z=Tz3pT`3!eR0f5BL`KhVet<_U8{f+ZmRL7~hLkLCbg|sP zeRjVS%SqN}`Ar&U2Nx;M#kuF4(mts(MhDN@0MB6u@0Itffp>4h;oGy|a)*xni7-QLJ~)pEYrR`lcfDQT8*A(O z{%>B+?b?=92dsQL4En=)5DK9X3ZW1Rp?J>KlCNB@wfE`v9sZa@AcPR&RcGzdE!KCm z*)9!SC)X|g>QCQ?_eCsksQ&e{^vOCIdviX_Z8JyrevfnDS&$Q0)<)YNWWOG^O8NN0 z*1TymOCS51N5?g1o)u#qmd5 z#IzVkM!~Tw*|x&=BHfP%@~n5(&ovaw?kn3y(2&}TWy!Xb7hU$Xtgpt|x!xO`CC}Un z+&A6xMcXN3>Z?H=Yg5;sQoZpz7p3(PZMIM9+q_2(-2>**+zsrtF)n4(N91-zdqeLW zv`MU2)aSfToIU4Oaem^wou^nC`L!*XlqKuh zi2ZiqQcm(-$a=N2JfML-`-2?Ic5U;nIovaiZ>!(XPFB!MIooQx<@7n@J8vW9{zBSK z>>8xsj?ZLKR{tdJt6tjYM<>i|N)4Lx#Z7+21V5GWEo3@ia$2SMAZxtNgoy8k9d2<%; zPlL@H)qU^%R`HvK@0?wjlp7qrht><~ZMVEAK5v0j^#kpZL-ST?u^#5#{BQS-`-gqxesW*AzZ!dO#~yWmx(D6E?nfC9QvYVN zu&q4%T5m6;-KEX*;8|+kyRgCYl~^A^LnbrMk-m)0c_cIDlzPvYbFSRT!74de_{x~{ zZ(Nn90y{YQ2`Hf6IHYsow|cQeUOz2@Nuq#dsZd0WSe^uV(0 zU$AfXueRU%s!uv)$vyff^4ne?err9r+&O=W_tS*-z`JV&9qIQ^;@y@E%FVluJ5MaD zyI?%MTm7_+g9 z^SI!ioS;;9%vJ0q-Ukqpd#ds~=;kaoHvpv{hO+?hekw`83GAV}8t2VyV zS)3d9&I~LMb+p;nJ_B3pAAQ(eY_|PrOFN}#Z$INR2kT|-7yZ-*+XMEoZv6J^Asf0I zcG_xZeZzznSiVr>Y;iU`A7cFwzjHKxSs~R;7}#9!jL8YIuIzz6wNIZ7U(RI{hZw8z zs2`vd^QKBb>ma9$r=<$u1 z^r4)E9rJj`{j3k>Qd!jR3!UF2<`)inY5WHAWTUT4>Kz;>W4y#_4C-vtUOxj?aFKsX z@AxT9$|taFn|>WcmV9^1*;lO6d?ne82Y zQqNA7vrVaP!2r*j=dO9~xTn0oyw7gjo8E~x?n~vQK4`N&?>+B9@ecIv{M@77#rYQb z9tXa`;v1z@@7wGf-F&0t+rT%!`R?=Wa`?WfsIyI*Pe0!~#_~P#4N&$#z3*O**BNh@ zu^iL-4YJMe4Zl$wV}ajLerNgp)xdAF{D!kU(fK{5O~=+4l^M7C0ov>@=tEi|zX7YX zUArAzWRhcK{U^Dj|3p5?wv@MwVcBtH!6{f*pZ#X|wyxgxCkLIm%DkC7*Vnw)?>_g6 z>o{S9-zz89{j%PR{psFwkM5U!n)}-QYrA$m@H@c%GD23Jdh7OakGtP}=Y88fKeB_Y zH|mn_f0c`V(&6H3!+78rHRk5Hf$%3hLjWWDp#RXmb}NE<4+9QlScU)hxaR)l)uqwdn4^n_;DP^-7j2pPjX&$t|#WL zOw7#;Hu47M)*PP)>Yh}aXIs{*&9}1aJN)ezI#r*zXK`0#mdXuyaUV6cH0{G#`~R+-vMuOM7wtV@m*luJ`JYdR}Y@w zL)wkNesY8A`{393CK#*S7jn*i1DX%N4>ND(Dlw0jylohPWw|e?AJE{$x|nkrg+|$N z-I_jvz!H_3e=+a_(_XtX`4<7CK%zf?tb}w$u zmuKyH9vf$Ia(+Gk;y(TCRrhN!N;%upRvX*ju&r{b+wZ_AcfqD?J!8?%ehoZ3-ZS4g z+)J`x1eWE1{Wc7I>DTdR#9$m1$2X1*<|*%{N#10KwA=#kFsXIR8@yucsXA`b%nG+Y)ULy!laYdk6hAFs=^H%{flyYQD^y z`Rw5OneztbUnaOF1AIRwv>@NUd|%XSb3uFi* zwhd_DoO9mh%y~P`XHDH7#$jB3Z^XO1`L6kI;9EUjSm@+}Z@AwBoAFn?#pb9Jm$8}m zWsabMx*1p&^|h^|TlmnAQq-lbz82iTvJ7Z2u|4#0y#`T)n&*8V&EDXr_rcI{TddU9ioiPYYH#$@a@&h+V6+E)7R;^cm4l`^!2ivyV2~I;LcKQ@)V%JIrg6qYd6Q3odvk%^x_l{R#O^H~79wHs#8t z{zkRQ@+jYlj{hyj8e1!UEN7d2v#d-z$JhnyPg?j)-k`>}2D= z^3+Y*jm!8($gDfpLEg-xd)mG2*>`V$bmVqNUU%d~KX2$evHd`2Y{&mo_G2#gOI?xhl%-mBio;{VK1I1_qDjEZ|v_@*2X)?yJ&*Qe9bf!_&!Yxv#J;qooj{9T0aD!;QD{PaCHXxHxkg8q`U zwV#X!slUZ%_EpZ{l(IV8?34Wm0HE-?i8<4c3<5ezMI^G$#}fc?iKrr&x}(K{FC=N;lbO{K^ zm;OomvTmC;CrBUG)%PI#DzpEfEDd)550i4zDZky@i~GDXs3(`4B<4ougVg`2eD=?} z{VzCn#&cfY8t0MZ+%`Jr{$!w&6{6#HPdLzFOeDB`wJMqMlsJ z_Ic~4O}1sb_KEEST4BdG*Yf_1x1b?qmb!QB@-7DN+zrjUmwULwihJC*r-RaaU43F* z{fW2C_i+Z6WzhC%$5y}kzxqJ$yVrSVM|}TG-rq`o3n|r~c%QZRjsCpT(bxCew|w)a zOD+4(C$(K0^&R{+7~r?V3>vbce!~UrPhfxj$Oiq&2rlwDheq6W9_GPsLf`&QURvbu zMh=z9j<%WmSv9N;#WT37RXU(lpT8p#v+#(zp*2J=is`yKF)E)+|5IW%&R%8 zn8zgb=0EeAxip9Rbqr%MZs+NqbpLJSrGC*b+KnUgZ69^c?UMuSavu-++CP2h$F>gc zSFx;Kn*|qU>|xK!C}&2!`&%1r)86_UX)|cAH0p`^3;K2}&#Gs(p5@J%mir57r<~9p zsQbpIZE&A>r^pHX_DuXv^X`#)A9+6|i}E+p=1sP8zggBMbvdTvzwul9Tc4+{sIuX8kx9kCdX<*XZvF&!{xGJ~uuZ5M4vU|F5^8@zK) z+&P;y@t#Y(gVqZdop;j=4pLm>@eA5W_R)vslv%c(sMGI;`+|Dgwd-Ka%@~=_ioMmC zYc~hv(Hy&O=6t}3b@J^P&w1mHus$CU~Vot^E6`}%$v62Jk)2~KsU+liF|L$>ZZ+J3>cHy(Ws(El5@8LRg8t$vI_A6Fmb!Sypo=11m()N7M0${oz*j(nO^ z^PrRwY~+1l-^4a;wbQTTf6k6)D9*d*OWb>&H|3MmKhL83)bn`QtM1p2{}|u#4&)qD zYO~>jePo4Hm->TF|DJhcoj=5Bd>bx!mMZFOYqmL0=Z)VSJEbwGpWwKXW&4c4a<+AB zM7zqSp7u)XwmXJ%Z-HgUKQXq_dfJ%34w=h~y?*q0gN3x-f*eb`M4dL;-oRX#(?MR$ zkvTho_tf+L+UTBKbmF(%KVf12#wq$vS>KL1paquI-G4y(s(sY0FWB~MKj^-(Ui{c! zjMcoFXLEjBC(7#VH~p<4@HdXiqW;G5_lgJgca+KREc=D0zIDcf6u)AfPLTB~ zv+Xxo`((MtI8S+rK^6>XVB8&?v-ucc&dgnd%>N;u*0(xr?2~;hSJdyoF~spIi~2vM z_OIpk@Lk(#S-T|Tt1{cN&-xcJHu5z7A@)g*dm1M6AGq#uXt3Gy(=*`S=Nu@lZ?OGI z>IZFNyM4UB8g%ZoC)c|Z8XWG*4x9UP1PwWT3w=ui%N?D1If3OHHpg!h-*w)1rS;Am zFRQd|-~XT?kKYx6W!tn7%N^a}8|(7@HG+nGV%r73#k6nn`;29Ee?rGD(O06*{wGM^ z%GBB3!9EKHGJyI{P=?>vUz&cQ{> zC->yvwQvom-)ylL+%N7O_mF#OzytIG{1)7SWyd(M*s3zxw{Wc?(gkg&mFzsK<|GuksI1yw8Mf6{{6$hgUBiPcar+I z65G;F*}?u#)F;{{se9V5h$ZEqo;1p`i94B;lTO(=K5_2JpqyOazz5m(Cf64~Z?@{H z{h^*1r)1p99poIxU;ml+in8*baIt@qc~|CID9?jj@05e}|3=y<^^?-FzLRXL_9OhJ zPMaik+U(#Wzv18a{5#))_CNnm)QRm$r!46&$7qaU3==wJ&PQ1j%dN2c-)lILJ;?P| zt}j%ZYB#X2)P0)zRrb`0vbtnYuKjl02UQO5hoB)7?~jUl>)D=iy!6esl<8+ae5zL` z9WHm5SWf++oTNWx)n$2xpZ<{gT+qPptrKs7_h#OqjdI?vtGo%@J2~(zINZ}Rp+nx^ zmep(X#Qy5+qy2gz{b-YRzMq{p&Ue-K)pvIMf;Jsn?Gx+v^ZUs+JKt{0Ss%RT?S&oR zEr;)cCw?D%z7e9$?}ia*Yukh!=-+W<1s7=yGdRc=b964Z%%_nDbJ6{#^xHDt|HXUz z6Zszdy)ePP;y1(s{fXZe$)-G@1&16~)Y~q*V7)=wF8XzFE(01kN9Sw~8knQZtvURO z%&R%IuMAkAPko!SPfX0wJ+$GFBj;-@7hC%~uIR(QwrQiSeH#08#9qz)xu~n3w2OAy z*zUePk27FhY|~~z1N(_(bv^9r*|FcVuHD5x(Qn#q&e($TizlS7=9)Tf+br_6G;)qYvFEz5r@)1Pv}exUA+j{Zp;io#$#NGb+K*1#a4U!b?~mL_vVZ{(|b%-!MgX9HV3?G?W>Oq`qQuT zag6;yT~gcBwHMTDcft7?zw>ZD&Tj`7>6*AUa=`O1(>nlM#}O>#eb8C&8MyXI>Z;5( zrTQM|Q{Rbo+q97bE$*%nQe6l8G%#M{b`CPYc{VT)4eGkN4z7pmG+^_VOy83S>TJ^{ z*Rj*ja@O_bI-2uOZi&OVJrkaz_BgB0Gqh>1*xsOfE` zDzmN+QD;oXXUv|-7P&D$74@Qxc}qHFx%AKZI%lyi`-O{6ACB?aM{!21pYWOewA1Eu zuH1XNTMz_pvPhL+Wh2b;QF==8P!CSHAd-W}tDV;aW= z-y+Xkp1UloPqNK(mZ!5$fY2f|h*yctKY<0GUeUQMbT+m>2US=4yd? z^RCJ~Zpw0l=Qm%iukuZ(z5OP%f;#(X`-W|Dv3p|Q#I}yzld4}|#;E%9Vx#USYJba5 zTXj9i_EvsR*pzQ!@LR$Pj=w8>q4QgcOxQu|CCST_F#K(I5$6$_V!ISXWV1Q z`wx0x-L@UrcDMrv_i2OU8w104$#0Ot@jIfhC~v<7c+Wd5-*xIAJ^l32Kpz8J+nViR zt31(1KiVAau-~AxuHN=%-|8pJ9e?8QB$e&AA&f$!EGK?T>Bz)yJ2?;1zV@|`-JsZ0jNAF;Tv85lVm@cA$!GoC5AF;0 zi0`KFse7u$KDx-ny)}b|bdPP{SNbjb=U#KaS(XJCv}wN8;omVtANm@8U-0g_*Tpw@ z2I>>ffwsnBzY{+6WBG~t4vss(7{v018|7|%#OWE?VB8sVwY87vv&L<_lCft!b?OJK z;37TWo_X&D?}!VFcd&7nPq;9+yBC~bcE*9heRiSq_DtU^-zWY?BBQV=zoAV7``N#P z{*pymtlK8qN!HUgrS?w-x;Gm7WcR=4G?CH+=WJPN4if9B%kochmA+XX=$>So@&t}= zOtK3Z`}pfW^IMcFmwJ-=QEakJX--pGHqXhSTyfpra836Eb(I!<^_TvivW`{D*|++u z<^34j`h6k)*5}^?C+r96Y&-FH!Tg(GmFaij-xxcXzXjt#qb|Fw-=v&$%93mTC-SY2 z`$k6K`At%nHYrc^$@=)xUq3sT$OebIBJtkHveNb^_IvWQd8?VGoEpHTbMvg2f3 zKRx)|fpH&tCnk-uZ0^hHy&3nYcj}FIq9K>>NWprd{_Dt zUGU%YH@;(9e9J7pWqSFZ@tYQ#e4NXL<@aH{rRL1{^nmZIn9C$}wre-w(sx4hEykCO$or0bHKe&( z1?%c%!iG-21Nzgi{&SpN#<|@^x#;SkE za&E?BEGMvTyxN)H6}~$@7vFY%<0-8>j=O1!q!x@C1w4mzphzLmAH z?*R9gd+W1D*i+|mhCCmhBk{b{^EB9dp85U2eJ2gPyAtc!wkeDC25IA6P)U82_POBR z&$Db?k}-D5wrMk9$2cATpYsy;>JFrh(mt}`f-xt~;XuyuPo#Np&0N0=-W9H`_t${+1>3ZFvgteZfiL~)-#C&wz?9j(<6a!}v;Pfp zT{nIm!!eD?{0z>9=fv~UILpe!dNL`?4lFmY@5M(0&zCuM9h_@}IBBlb!A z9&2RXeJ$GlhW3fJ_Uo|l-yDNnxIX6N6ECuXYxxaxCg0lC>m#wQz4U_m3j^B&#`1}a zINc-e(H(hGChC($S2|>NDOcZ4PX7o-xl_o;lBFo;&+A&^G(7n49yntS{TNtJtU3tDhTv^&t&1 zPv*$nGNPymka%`zgfs{9NLn`Z%+SB(ywww zEFF0xzA6{G0h@W9n0w}6P&ZGm^#=2Lshbb&rO~&dum2x7X2vjyL$a>?)0h^1nU_EK%@lTl8RW|h}_o36iy^v#f;*iVTvSE0~1c$q42A4Z&z~l}) z+-WNqMPA;b2FrKDyAcL7INX;VE_bbRf_iN(boyk!5%$^+*t}2EHwspKb8O%J@~vQ5 z-G$-z035JSMV;;1*+*J%_@+n}VpYn9`@lSy8}nrj&A;p5nzmSD z*V;YeUO4Q9>32=+C+mrOqzBrHI``d%+>h>0%Zc^WDLebPv7g=7vcUTGjSegibWhSw zA9BLCQl9!7b8!yOIBiq!p7%^-4kr4HvyL$yvG+Ki%EU8!z;o<*mJJtH+&kV28!im) z@Z&x1{SF%%cjkZvy`zT%jeBhrHsuTcon*oB?c#4IGJhfM>|-VB zrR^8Y)qHTMSH899nkmN@cF}96-|DYZPt+gKE*Yi2gP+KK01vYxt$u7P(&vM491(?+yUtZV0;(;&Y;l-AYv@T+Z-KJ9D!4O?X` z+y5JbHfe=AewJr^3-uT2-TApck>iDiF4=J>pLkbpyeF-^6`gzByMMzdG|H3rz;`X* zgvHxeJWPBOFv5&CPPOR_1az*_G{X3TDZ-d`+3p}5GD|ue?%w{>|rA@M) zGWGQ?ux&z~`;LFl{%1_2<2z4dbY7plkw5b|$)~wB$Clmq?kDqL?8fVuhkM3*YQIqL zw`W=Vs>}ZR`i+jwpI~eYl;5!ZiMCIj^{3wY8^)G%*pGSEIX}zVR{N~a@Yj*@jAIeU zfZPMQ?yh~JF1eJox1XFsj#1@|@s+8!&j5Ytx4~ymxSzp2GQs`jp8xE7q3aI)_xs7`l>Ov@SkHSMg5!@UHLR8|zy;9;g#-8uo*}=EC_qcj;hV zX)Z1}{wLqY0>(4~>+Xld_35!jX?Mw?=wq5s(5I{*>s8ljbA0D+EHys!FoK2@b0W@3 zvi?nK(|^F+t_SMuZ-OgeAcD(|6Z&AR%udG@nzn>M1oSXOU) zvz@UQ`7##gKEb>hpZVM+@6N$_I$!7SdYDJ&b3uFMh8AeEB40OhV1L=+M_c*oGU7__y#=ersRFUK@QSGs^1IpY{io zGQN=Qhjy`^<^P8Cw}Lpv{Y z$v1trr#$qL=I>_@sZM5*mJ{0~>nT&OzZG<(Tw<0D4V<6z&3Rjw=UlZ49@j%z;?X@_$3>?@!2>jPC?> z4p|<5{XM@bH@XYvLoz?-C7;=*v^}L|ajjg#U9kS7p-Zx_z9j3m+iw>xwlnGv-d&Jet>5II$KJDg9sI`e(oAURW>t z?P*i%Py0dt_CQ_b+5W717l`*l(kcH$+9~%7>iYwAPtwNwY55%;?y`w&aN>^h zu9M5Xm!wX)9;B`J>!&@lCb(&gqn`t54fOIqht}*opQ7HgE8g?z_zUe9L!{-$U(zx(zq5tgZ#V z^GWJ#-w)JPvi+j>n{E32X1vU0`EEwuF7nBYT$)F7co^v1B~z#CFlP7p$w-e!rmJ_S(m?^GmERIN(@f`O{9^hg>e##P8Mk zhTDA4z2WzrHWSvSR zWqo_rJcpjg1NGa;Z=@aMSym2MAuT73@;9oDJ{@Z_wlNq>Galw8IX7dyfx5=pxvxBH z?s;?Ueplz3HT>!O5=Y))ll!Z3sEc>tZ=~&Saz4h$dPo0;@x7t#pXlFwR$ZOH`WYOg ze4F3x@mY3!5{Yq|L=vANoq4?pb|Szbkx?koK>>)N6BJNMBWI zXCHkW&xU6q?%~h<IxaV+g@cl<^i6?OB0w)U~_2x(cp zwi}#}tiW;u+q9{67ky<#A9b0h$r?!3ZPTuSzKlmKt3TN8SVzkPU8l`>Wr20EpLO-- zZI|4e&ldU2K8|5O>)JoDzkL>cKkvEs9B+`n11uOJt*iHYVR#S12K#jS+Fw8V|BP4W z<^J~^cqW$jFz3ZH;c!9^}b+ko~XCI!Nj(M z`^0@=|NXKTHvW@KS+YJUC+aLWFb5r6L-R1en9awZ;yk5;IsA!1yQIyfY~KTpZ!E?( z$dh}>c{*q3E|!x<+4lL!g*mprTzrXT^#}C(C+IiP-oEKa{bhXD=CikAyNM{ERQ>{G7MrYHwfnll~pY@$y_Y&dvnS z(G%NzOFdT=b+*mubCI6O^FZAI?VRsq+`;&pSd8byoJPouu}40Q(SBk%8I&7nr@vWz z-N?1R9mn<+G^Asg5AD*{x_a%!vgku>7t88rv9Y|tF+TGnt_$YqGjHZ@4$O^r+6KRM zp5H(Vous}|e#8EvuMYZ6tlO6T($@;#Kgo+trTaVNKfu1#zWVyDY`@=ZYWs@$3}of0 zZ?spm)n+o@_BW6CP3IurSm?DG*tQ`1Y2P}&;KKR~9|eD>`i+TA@@AX<^qmaKKd~dG ziL`u!XCM7Nb#L|T|8L}u7$)*J()LZ-H}&^1ALsFnLtFC9Z|1$q&fZ9-`=r#>U(QxV zUD`e6Pi=3u+NbZuIKy)coAX_%y6)NMeyLR5^4{eh+HkxtVQ^5s6R`QVXfXNK=s$4%_JiTK9BhAEfewdnz7-6lJpVTLQ$72<$?Rv_ z`hi26u}H>MW3zwyO8MsVC%YU&|8)$@8_bP49OTp7n&-y8a7|oW*V*-UZ@I^s-v+VY ztUq-No%>|O9Y{!JmTjAX?Y616T-(y-64L@@58vANh-DI6Qe(}y^sn#qr__&g zbuYRH7yC}GXT*JKJAJb6d5o*?138}37%mLI4<2})CuiF`;DYx>(jG!SU z<5kW=r(8MARaP*N=Hd<8o@Bq2w%4-tH(yn!P3>p@^M#DvIc>=KDhI3w>fX@q2If*S zw}*12@wc^F&+<2YZTIhT`xmrH8s#MY%!nuTJN#Xwe*=8OHthy9(3gIsgL5|r0~(l1 zbJ~N`|Iam8)cZf6u5ay^`W@#^IZCb?MxTy6+=rigv3wVKS5|h^8`8UUK?m+u{4+TXFp6iEkO-60z=kT2YsL-kGq?K8^Z} z@1N%PPkj6MMt|OE-|FBX2R0r11B1Sz59|7|O)hrHqAXc|v$0M61brHF^PPmnoSnP$ z$b)6|18&%9e`5X@ANvuTzLP^)j6E6A=OR}b-!UBPfa~D8E$FPTxXu-IH*B=CoLC=_ zYpOrXqTco!SXS4-_72A3d}m=(R%e?wvygqX-^|NA`nD*kKvps&<6%eFmrskcoU_qo(^>i((J$AS(ym(9FnK!eGe zR8HAbwriJc%5q+GWsed^n@rQFaU$H;LUE3xl}QLwIF8~Y^LN11($L1xgAi*xWfAKX3OTi!#X&?tLe z#IkJ*x_1>!@ZJ*rc&?PP(MjqTI=SH3#&Mt#ztXz&2in_j6s)V));LZX|usxz2)zqcWvtWXnW}++0XVFSXQs!@t{$!u{e*MOD86=-oX4#=wRN>|7R^& z58oK?Gv5%|A=NFgyGwHajyoCKc%8F*)3e~cn|QBY@I2<3%QLp(T>m7~ zww_gW*7Y~a*>oJo_0Bh+H}Yoe1DuQbaGry?Pk#Tn`M&zbLJR!uB+E+MHe9e@qrW~b z^q1V3`^=qVugGEM%e6MfZJdGS2@MwQ+Bk;|pB$5G`#T@~$$-tg9IHQ2Cp)C=uBkb2 zeRr(2`S`}7E$NiSIafCIWJEb->XkQ0Kl(k;A`as+#uoFs$RzihQr!xhQDmbm&bOkj z+GTm6m;0dV&A0i_{p}s!+2hK@J?}nWptSCq+Q+^P^mD+nI@i~=9^r4($A0=$w`sFo zA2<5gS05dWaX^Ed>x^}-(l%|hyG_3Y8BOPf>5sjJfVZ)n%Sc{OmJN!Fde zIT=v*VeVt~NsIDA+Hc@f|Bml(CKndJ>-am;hVd8m7cRPQbbjNiq+R;S`kQR{J&eDJ zqk-~Mk9a!L_|#e6FzE9e`u%^wapdXaP4_L9MGS9T^zxRoXWO%0`%LmKi*@a;t!vDE zbiIebvbr5ut}N;o?Cak6Ms2rTvA^g`vi_UYc0hx=hrM%}v2zD`7cCgxNpTmhxa+*{ z#QW_H+qCs=v+o7{=u^C-#j^SxcG^k<`)~X=7%|QXpZd2=K4t9nW!#d!d-zU&H+<<& z8XWIw-ihWL7vDG2Z`kt9;y20R+bzHQR(#hrq-?(VWWLbRJvsdMf>QOh?+$-I$+SN0 zi|aJlYwo%83)<~iTl*!K@{=7p`{=iG4&U&6y7xT?o`)NFzOt--1Pv+n)35%G!+n?g zY%n+3fpcB%G4|GtZ_DL33Hcc4ZA&J5+P?Z)pkMtvKgU|&xEZ&x7+c1b^<|9Ap}sZV z?KAH8i>!D0?6koyH15X%3+f%1ci!RNYy7Q6wtsUebp9suZ>fIX%8oUHh7@Blu3pHP zjsJ}i^Gn&NS9bqTJ|^WS9bLuttov5}M%t#dzwDsKGXDC{94_S~=bQC6sg1cgV6HNE zn{wq+Pu|up+rG){v;2Epq1G)+5A?MoriL`e8mH~rX1_yO8h;m*88Y?Sb+DgoXrRvt z=4S;1DGhf2JNL85PC5Cumf8NN@|&NkU+kSX(&pJVzSyg;Jo{O%?cNEM9e2eyS#4`M z?NgWaiNB;#uJ|pSEXtKmJ*oOEXPfete}`R%&bk^PVZ|sFcoo(72 z?3Dx5CDv`rb=JRYnpnSJ`;0NQx4h6h|7DJda~mrdzjDHexV5!Uix?c&7?kQhZH{F> z^5Gi$o%Z==V=uTj+zRL{x7&Dl|x;gI@fH%4leQ=p8Y3joBfp&9Lrb+aXAOqrLK?pa*oc$ z92xUAZs$SUHOH)jYca{WdDhnWjlnyrlJ{2CW!bi$NV`9ghp`jwzxjRh_06{GH$H#D zySld9x7Ka1WLwpZhfQs>YG&xd#x8hCaVcn-E_!Fz{0$hYZpRyZ%CuqdmyeS)@$eMk7wmt5#E zhT}Xr=#0a7cF2s&wx@m8d$grI;U|5iKYfbzO4T*mvcERAnJaT=K6k-&sOyq#X=mGo z$y)w|Yi+wyd;Pdhj&Cdr`U7>gWsJrt)}?{%iFNfGoKI(r##jp+cLy1VdfRg@mJevR z*rT44S@7%(&QRw}dA?el=Z$=0M!R-mp9S{+hG+FN24i_nJ~1+n&U~DgbL`ByvCrJ6 zpYJQ~cHhhx_68guKBAIs`aV0lMQY2u+`$NY?E856(dv`#x*=zpOP4)QmezYPZdPF7KG|Mb^OIsInao77%zi7j<0 zvrV~RGavIjowMiK`I}qU;s-zTufNAize;s-Am^wYKTz%dP5J5jIu97oLRwaLp|h?^ zQ8xn1atqJ;v(0uHLDp~aQOnt`96>`Ka}JCAH<;cxg-uy(d!oL>;jX&eTMZ6(+Y|N4 zq%0eTcOvLxA=TM;1=UaLI&GW#`)~(OXqL67|1f^$;+({ChlO5jZ;&xNzS#HEADJ-> z{9f;T-vi$UzAFabADi!#28(aE?l&CYa~p==b;04=Pv(Q}H(_x3yPR0p?!dFZx@@yt zN!y=f+Ur9)*UYtZ9bKKl;Dm*qa!Pt8rJ(2kH{rHVp3A1>H9U zHnjM=z(U@jciJBu>eBxDn}OVLp>wy&f&=~y#=phfVDL8@@$WTH4!S4Wr0(s%2Yb_L zZ@&>ZwlPd-U|eF1{eg3Mve3y08uE?Z8K*EQf8$T=(#ElJY~>0rQl4{|%<0KN=iJSM z+!wMf+dKIabNQznZ2kLOMco_ev(ug!*8t;po+IXJy`itM+5ddOHtB(W?K_}>{>+_Y zG}!&`wd%+xhqI7)HpF_CpVGE}i(~&u-x}jL`}Go|wpCyIrhZ@}mpkGA1@Dw66Wwn# z@1h5XyGp#XlGLTXd!GfDJ1+U$cX0=LkLTU({asn!p)d-KvUs<5@Lu;$Z{YVq2k&F= z^#R`LvtV6)(kM&Xb=p_l{o6MYdMOSSzBJzA}#{+2!?o`cMJ=3LcP{bo+)&K$~(IaYhi75h)< z;CLvw{1dqCWS<#5T#k_P4CQ zfqrw&`!PrBJ=)X895`p=bxxOgI$z~y?!kZC5amH_u6)n{wZI=M89ZxT~iBo_wHA z4rp7kpQzh#p?mLvXS8`f1M6a2(kYAmL?1F9r2e9p?3aF&C#d6PxyngA$)n9ve%a!(H5P~X@1raJyc5?p_W@i!FeE3iFj^x63x#@oF0gTLJGmen2FR$y5i zQzm?3AQtzi>-UK-_RS*Z%&B=5>*`y~$MqbrnO6gStb%p*vtV7D&zOvDtP6~#(x^YN z{i$=F&gD0>OZ1^HSzqYHW1iZYre>_xlT4~ za@|`^&Dj#9b*KR9jh^p`&KNg7wZ@7uapfFY&Yf& z#(yF=Z+Y2|yja)HegitbK7NVS`5FKCLe9~4=kGl2lX@U5)w1^X-GOD>PvF19Evp+~`x~}vH?f!f3-xz_-)ytJ z_IZ{EzcYN}Xr%1{p%E(zq5V8-^^qOSy#4bQzq4KEjy<4;1Y-I2kO#BX?p|b&>`od z{uXMVpUUg6|I9z*-!Zpua>hKf{XXoU^|bpYa~+k5>+8BDoAQ%vYqY(QzEZB(FDZM} zv;FBK^;ItVxTiL_zjDuY_FKjMCnvDn!F$D zJGXK7dMEp)H1Ll8+|RTveG$)M4$h~?yez95&_EmGKG=)p3F?*VhQ1inJNz=HV>p&? zMdOV)d~5g(;&+(eeFOZao6rmS{kQlAe3EUy$?W&EtK(!jZR|6GhLp|U{={;&UCK{Z zw71eH`=+#QL|?}~;CE~%AC0_R@O$j@eH`zqx$|9h-3DCqNnTx3-_-8<#$H%uU0gHQ zZvBAmZ#49-Yhqoy1s5iJ#IxWz887E?hu(JWwC`Yl{YZn{JDst_J>(oN^KtLGk5=Fu z)orkxZRWszw#dsiSLAKE*T9^ar;j~;9oJYg=FVHNz&GLZF2uXiinQ!oBpU|r#)1QR z?|S#{cuTyKWdsZPi7ruy8GV!pIBF4QTM0RewO!t0b~7!{%xzWj#>K+?2_!G?bJt*d025bDAlF3 zob}WxlOAPdVq2Ck_fD3TwXNzcYx}vUysHY0vgBPix$`<)?&t;H)p<`J?#VY6`pTd# zm%CebFyk)I`@H)ufp_NjY+;1;Z_w_Q8w zaPW~V%A;UCIh3`x--dxNeHy24iWu+5r|-4DCBcaI$G7`}{`I9F`G zxb9iUPi$KymT3(5(EsEc&F{6zcUxk8f#0}}Z{Cg7xSfMz?!u)kRX3O&KasZWWuDpgl*>$-M0+{V*i#kvSf#era@wR$UmbEh>l< zCgp*>@BL!`x(!m_7ca1w}=N@dqr?343>y=J@ForpB%m&+B{D3wx zg6w159a368%*Py5)QRois5biBkYg^!{j3==&qCE5^v)}3ltq1IQt!dV?-Sd!x39h~ zm`ig!;!IES-rP^%nz%Ne1^2vrPTYSo;S|&@+H*bip)Y;9mfj_^tf%E1*Y!TwXeXA{ zCDsQRYXjHa^>z*$8aTHZ^LMV6)eYL@g7dZi)rbBVW5irr%+0g5;RfCBgz`2I^x8Oo z=bU-5u6{?qTV(oJ;Y*tn`P#An4{?~YOH9Vqf*Ez=6m8@LmIv6!IXSlhlesSDydxLp zL?$$I0p{?4_8aWm@v*>h_3b{GL6*(cC|FP3rJT7iAI@_`E}Z{?##+k#uLuHG>m^MBEHu4j_0N`gcbS`$P;6huK3L_w4_%j>@5 z6B}98XU^;o`yBj*^9Uj;`#!8;vX-5F^c?3NyU(_-I_%bIjPbLh`$H`32bcbn?(cAg zv_E6*kI1J?8tucF6FSuS*jFzH^v$@Z)K<|yW2!#e<0aP)oie$!bAF3?e&hIC;ujjf zH&(v+srR{i!{;xlc6GIHdlcN`H#Yi;KD9sTC#lbV!zQ15k+amnyJSC5r%hJzQ=k5W zc4dc6d&U`izL0s%;oax`>V4?_>fJa3+a0~O4fS0`c;|W# zJBH(VPkU$kK0DmyzUTd>@H^x3-Q~BJ-(-H9b@01xLJJQ6j#gRpr594CJP+P<`ego; z>hzUfuzv!5C+y%Nt=ZbMer+RYNU^_zi!|nd-=EHZ!*v~46Ti7yd=KTjZa2JXH4BlZ{QGasl+{h@8n0XOiR_zZ05FVA2;liF_h zi@U&$6naayKBY0Hjv(P-gh#g!HK)O-qYTXhdbJPx(D{v-?%%|zxwK5 zG1mKbfp`4oe)qmkI_<-q?A?8NALYBL(e^H1zU#p6h*7YAL7P7M8+!}(&m+DOlMLGX zk;6O_l*y+3dACrb_12p8b$s)fuZ666t-)OSX08?URi|BSU;EfxAJ=OHwjI|vb2RA0 za zD~@r&IcYbiOmG}+O?%AKdF1?ZZqCnk1K;x<4)1*BhNT`K*U9xVpYw1$*HoN`GRJm& z?TLN)%$<1GZ;*HRZQ=JCC_jD1v>wNE?RJdk9L2GnS7JYP`pOI%(z)N*GshMC>b2`{ z{DK3|nm2Vzo$F$*w_tx18f{74qAeHPSNGdJcCR;F@Ge+|6YZ4R_0xX@w$*7%`zNns zF)!CoY&!?XHb&o7IJBSWV=m)dAIThz9M7?9zHDbs`}0AkFX~g;esa(~@y`7X^9<+8 z96s|#Ui#*G#*Q!fp5y-8ejWEM*Npjhq_r+^4lShp3u(8lZa{<09B0h;a0ZfGgQrZp zGHu51;4SC!Y;h(g_`EFee2qA7lk@h(GpN1eYmBj;C9(b1Z|Kc!OwI4JJHR{1yGyxY zq3bcW<2A-htVf$Z#x^iUjPG!`bHui~4f;vOWM5m7c70a(D!*}ypE(?J!R6kY;JsV% zE|&Em^(kA#Z{#fuazD|o-y7QWKhUXf##~#OTU}?@T4qSse87eaxqr6RC28|KxYv0O zs@&*r!F~hJMdmYq&7sfWjCxKz*J67V?5EzgV;CnhuzkTeeJbj-RlCoR&yCN_lM&D8 zQ!ez0w)UXvp1JK$I8mFmxDJCg9_*F-czv((o5=9D5q>Z6cN6h9l=YY7?=8m7M~=)_ zr9S5ijXJEQQ`4q4&!6YZ^JLxb<%<1uF1FRV*V;X2`ijp{wo~S_G-uS+dH_(`dq8m zp4d;+H8|v~sMEH=Tq~Y?_vCYiSjWj)A8`HM2lr&g*?&G0J{O+H9%s|@XjZP&4= zVN?5x`8ltgr_Zg=>IL7;^*ydF=YNTJ9a5^_Rwwr_FxLzYQW}5z{}VE<${9I3GD+Q2rakMe^=nIg zN_92Ip>Mq-*hslS_qUl~A?5r)>*RZ3K6K{k;8@mRea*FJU)|@;%t z;nE&|{=-6^U(hH0mDyK5(O$WXl~ny(JN>HMrEc44d&aLn|9#xFtFIjT;%|pD*#3?f zJazuoC}~%w?l(EzhX?1;@ANB|XH=HYDs=XEyRSaq+ylyKpM9U&JOkmYw69(|9Q^cE ziaJSq_EVWtDn9jNWDHY*vLd(2mOq>%*FXMILu9)qhvpA>UzQT5Ov!0Gndqu zgH7Frtn(H>W6iO>XJPs-@?MASdkU8CG4E~Pv`}jdL})i7Y5JKf)m(Qr%m*$O!}>0z7=OXYj?~A)?-~Gu&-XbbZF{a8@~tq zF7UgGbyd2auHPr#bzxj<^A4G?3ypWcfE8S1qAuIY?z<&uyfcRH8@NI5_`nJdQre&Y zV$x1FZ5iB`3l4Pd(aNMR8yf$H^c(qaOjR!bJqqf-Vg1&gkAL$rUd)vY+LcECjp}3j z&F6or%iPcS)K|Gm?U#0?+On8&#^k^%^HLti2$)GJQ@NV;t6Yu8B z-IsT-cW&Oho%Y~v^gf+&ui96yPu|t(ztH_g$LHod18Ke-!#jI%XM1mN-akqAeZ)OJ z;Bse+ZSQvVGklHv+~?l=!9|LC^XYH=gcj1ixg19yeb*yS+lbg*;>{txm&Nvk3+8k@ zWwy;T1IMbU@5N>vu8C{kVx5$SeqAf$%)c4a@qOQQP}W*%?pEfQ`8T;QC>_T!tYJr8 z&LwkXys<;yFK6N{m-UKyGtY|kc1*Fa&GB4I*VZ`=-rK&*lh5~2yw?Zs_7<#o&+B*f zC&wp`>qO2$?i}+*TU`TdbFL$BZPjT%u&K}W5!+es#Yevm#$^1Vz10z`ub9XF0OJz< zYJ1W*KJz(;#QfUyF~*#e+#9TI6jo`gw|~KX`$o0fHf9v;tFL=MxJ$fGy5CZ;0^900 z=yM+9+E=en#$`-HH?fOteaw5HafgZRzri@^fw`Wf?SCrG{~HINzopL4yw*ElN9{ev zd*;*kiM~nN4!UooPnFfT>a*R;7`9WdOq+ACe&^A^{m(tFvZMEmdDas9-_T!bj&E)K z%rn4e#^=Z9s6pqs%I9pyGpDXTZ$7`qi0uK!?+2-y=#4jzIR`YzyKQk#iTBmyuG2@~ z71TImGu|9x9^-n!zIyHYOZ83v^sBxb-wU;dLoKpEy*>ks>tOxO`u)4L;NM(wxR=HI zIZ0j0^sRD6u8pjuzDNG-r;oLaz}n=3wN9|^7ISbO&gX)2bDr(7m+r0msQwLY7d#7| zi4%C|%LLC_i?3r%*x+;O^D3*f zQ}>h&ALESwjgHTW+N{%>T?g0Fv*up8x0l~Y{LN&GK)U^E#$8$)no}ppw%%PRN`0Q=WYhxGvl1AHe=lT1bM{=3ZGwhlE z>bFZk=rmwknaFLE7D>%rc zF%OwwT{omU{YPLsV^XT?fo=6QkMm#P8hVDB`vMCtxX!Nmb=}z)&)o&jpwxEu)g?Rj z)>v6VN1AgN?5o%31h&&}Xty5fI@Fx&pp=}8Hs|6zoWmsMfYQGD7T8{3UVR)R>rmRy zSbb*rSZ9lQF4n>4H=plA-@5g6oE^0oZ@!E*ZWDL82WEU@*dOql>)6z?1MA9~2DTkO z%QeB~IdKlorPmGuBEA`LVj90JUga+2~ zsmr;s?#r{uo_5c)`yXf9{n5`D<1d*1K>ZDAc5(+BDecdH7|6;~zp(#AjlJZR4Kt+e zXELhs}LSAKN?j(7n5{hXX0cTB zxBm0r3BURMZ|I-3<{bX1eCDxV>r4AH@4)YEE!98KZ^wQ#qQbgYrTN9NWCa%~S+DK14cgKQ_SY9K`rmMz94qq-;+`~gm7K>nIb%)!sqDl_ zuDR0nP6qA7`zrDNnvnPO<-YT-^`7mp;?DG);rm1u3yzB>;{u6yh(2&NP z&%EBho4ebwI#`2aTZ81gE&B(Ze(7s}g|9yPr_9*TJ&%6zK1dpU^~RcOL)K#7SbaY4 zVBQsn_eh83yTkW1*l)f^K)>p%f7*NxIj%C>IlkjquXV_Q8MO@7MrvL97&GCNyymyB zUZ2ddh`$BbeLtA=^>G~MK8R1ubpmq^d=8i+_cWzCb7|M_;=cp?3)1$l!!Mysv#{i~R)`26S|OOZkm;{f_jz3cYzY`CN}p{?0f#52gK!et_ePzR9AU zXxm_%H9IHg(V1uBoYaj#pAGsyX~xI+<`wHvm)KX|U{XuB9_EwSKQJQp;O{saE?7s_ zqkMAES-X2B`wQAS)Oh>qci{8v{x7&MXlsyptj}6ygMI5Ur+((}TWo^&mDsMN{!Qwq zURhCBdD?2fKVqxxr}imh=$HMsn6y>dxT7oM(bqR~*{;4dPxaHkN8C?7`uz==8w69J7 zjNP<5>*m_ESg(t8U6$+2n!5)+bMBMp(sL@dJLLWi?Czny?xWaNr)|RxY|AJ#+WL36 zW#2b7bnBoV_tHA8x7MuQnord1d9iM>ZM^npe9d#o@$tjoI;`!&hDqN#%<;)X`%^3P zZ#>5r@)`d;*XTbv$Tjgb_F4)04|j*Y4YHD@y(F!t}K5qg&SnQ$`LV#HJ23(+6{aQgU?aw}jZ^owFkt_Qx{GPeY=d&-~ z1Ih3{2-IE3vFo^Rc~{h^U$#@KOH$X-RUFGP2Q)a`SJOM0yUTkl?@#Z)yvOqnyrJ`c z?7>1d?|L|KN1DTSvf}>S`rtpfV<&g5cklEaUA`-Jd^h<0Ajfa1U?D4mzTaK5(EJYj z3u;VlZ(`-X;CNDNIP^tbwkLHvj|+pjtrwiLb8oDH@1D+jxF*Way9k}OjZY8sw?86A z{{yb4?=APl_5G|fYrcIK`c4A(SJ~k64vRe*$i#g!Mqkf_=fiWdgNyWBc`iNs`!Bfo zBk)pYDUW zC$6vS+VK0tSTA$%uJO*1&V4W5`-%N(SHHn`i|>XN_>SnjA7*(^jE-+`kx$ZAW%~3# zKw+ioQ~H*-qP29{elTAl8#?+Bpwp&ZFw| zNiyyynZDm-&23x1%-3m){cKlR=kwNHu}*&@$IE)U_LBRclv!xBy`Pr%SKz%iz`OZ! zpDq}2x9+%85BH#WzpB%&?*nXmqvF0_uHDr8;hqiuY_ z_uUMd?-lSJ;HL2@Sd2wM|>YEeJ3lWhg8?_8*rhQG0n3; z{Vq1^u!cGp$F4kWC+bb9uWi?6L+)FTwRX)D_rvyt4*EK0b2#4?*jA^l<}}WD^Nd2~ zbzd9w$Z3vbn2)@!OLL8U|MDJQzLVoS$nT*fzl#obzl$3Fl|kPeu7BMF*VnantP75- z+@P)nw$&x-bDxyJN6&FB!CDM;`N;x1vsF@#*+DhXJmW=PFUB zO`jDtZelMumSZ1q{hwIpe$3%gzvn>doGR+mR_(UaHsd^;I1`?OL|euUVjSC=tj{{L zR_kgpwt3Cv+1hab0s6o3Q;s+PZ#wf-+n_I1_ix(wk;{JSvi+0X5xmaF1-;SSmmv>kL??JJxE~z%}z)HrXOq($a4mgh3PEz-# z-}qXu`}ZX0G4YX%ALN!BbFdz3Y2e-M{VpT$uFrN#?FWo8wvu_2lD6!>$@JOyUsy4J zYnH4(`>u^X$)GL9nj^8VKK-gZ=*{aoE%5w%wtNPjsGl#S{~%_^bK~>SUY-NbZ!&4? zBl<7!Y@5TeGjHb9E+;TZRrp z>*<<`ziCy}^}scEz5|?pu7Q2^avx-@c`Ap#>$YK)d%(TU+Ag+4KWTyS&wQi!cXDQ4 z&ye#z;5xV_gLU%Ejj~sV{qcM%6Z@XqO4VI#Z|tJCJ)yxO_h!s`=2z@}?veXcYjr>M z@tL+>w3&}_9K(7q_+0B}KkK&^bBX?9 zdzM)5gzdKi-wX%*+jN${QS)zAZTbwj_*bU!W&BC4x6I!$SJdFxE!IVSQy**N8JK}> z*DBX!;CBM&v7JkqzvF+_f%!XrckRIS7_gz^qu;SsYIZI;U+26F{=NQ0yM7&9594Kl zebKI8#@ZJ3iM9a??q8yBGH6SK%%9RRdSE@awdrfjdf}pz&Tkttu$}s9JLns~vxx2V zPubClF$+dW+v?Ju{s-SAb^7U_)VN-9H+-DiEb|@A*}YBd*L@kpn9s4C!-fmJGhT3z zN&AD}gGs-#>HoyjH~a08xAxQLpUVAG%S9*oTV|Du-!bI`{#Gh8*hp#o-ugz`t6c0y zWzd%vZ1?CdnfN_<#-?3;whw1Omd`m{pZQ>hRF~)@`ae17M7{nS8XWGF#QqH1MLyYK zt8$_^D;PBn>WTKN^uz$dqiaKr5AMMMx%7PIzWU~3Ln||B;e0oQGSYnWWuGWNptcfU6K=Kj?^T)y`T>E{{PoQK7^@Qiq7#>=_% zJSK;>be;jpzV;E^$YstJc>ZiF6UXgvsX?rz!Hlyxm`7#N7j+GsYtC2Q1!K)&F2}Kk z9oV)8$JMT{@ve{S=h^ja=bC=jjGP1CPaN~}x%YnYo>_1U>a^b&#~9aSyH>ml8t;M) z-S+@2IMDjvqrY*|;rgB`XrJV}u(*rm6luGA2P3aO_*;`XHhpc)dl<%pMPKfN{I{^l z-Te~>`~Pq9-v#S9*3<)QAJAYj*IWx_qJGCZte9)YS81-K>TRdq_yOiKzx(03kH9sz zuRd{aq*=p*Nk7r1pK+HQ0~&Y+l=km~9eVAbvx092_kR@z?Mk&}JMGQ0CgH$e;3M{ES*UGV9D*Yd>}RRo@kJPpMzFl_O|K z$^M&6AJIQaWYfNoV#@kV|HC}K(+?Nr16Z-JlKmU;8M8PGatc1{_SG9_ z9l36`#zl>Z>wEMo`7^KiChRZVFZ%S?{{Mr_|4(GC!#MjXvu*!L+LYgT#teMlsDAfL zzUuRA4`Ny{-Dmdfu$RsA$658c-Qe>r4gB6t>~D3P?~A|Z`V(t>!LxE9&xm7qPCQRL zXpAkM8|%2xsp*M&(Pw};%sU-}8mxO#Pq!v&6Wg`F(8+i~{RRCe9581^opya2c+VZ~ zyA{}$j$WHGbx)}+{oZ`jw()bUCj;Gr3)VCPYs{KQsb76X{|?5RXMkf_%TILd&Sk*m z@7;>JRpxKL7P-xDThu#-xqd^x4aP0FVC^}_DsxUlA7ZT2daSFW?gTl${<32%$1~3Y z*Xh7!Jv(Q8#a-^X7yFge=d<7$-N8i;&aKZveGY8*K%1<<7;~7ngLPPswOOaqzBc`G z4D)U9dGXnKK2yK-jaguPd%@f@YIZDZ9+366sCmWuSl^{~>mM5A z7k_{8cNl(`SulcD+Nn#aElFLK3wv^Di(^c1yrePyrv_^A+_=VX>+4))J$SA`U5DI1 z=ig!t%st(|zd*mrp`WPtEO?H6E?rB1pAy>*-2`QlJ~!q(kcn%MT-u!+zp=_VuAR?W zK3ko-^0`a7uwUxgr8aAFoI0jDYo6gdO76j&Jx9*pHQg{dYp(kUZ09~GFC6yGGkn2w zUh&MUSE|pxx~f0eM4ySRdnU}y_=&aL*l%m>g-v^4>-gyR^gYbWJ^jp)^;3$v1Ln&d zj&U4|@ja7`dc^jQnsS^*U!C=4&BmLrN6uxReERC+{G6-b4i`Fqm(G8I@ryj0_q};< zsrNGG0LPZkyr{>T#CfQzc54;&#$IslPh67?jwchG-^O2TA829I$NH>uJm!9wgEanb zuMGO4PP=n`W0v@5j5cGG>33+GQ{N5u7xejC2ENh?_BUMU{Jyc^yr904KF!~L3aNie zV|!pbIkd$bGuX&C(%!IVT;}PKJEik+UJYDR*SD8-RxhJ)X`9ddj(=hG|8S5=|3j|A zq+Qwc{|On}OHSLTjA=XV>0jl>Kgr)JNBm}~tStJ;rCsU#Mp~KlQd_69!Z6MEc7i)UZF1JsH3^FW>S2dP({ zz;=I-x|EKWIDUiaUFN;!z2`m0Jt(%HsP~@q-QauT`tE=h*iO`K-lyGnD(~0CzIy#u zU_1S1#ONo+Ef{clUr*oJaQJpuem?}Z<%G1|gX=e6u#qx?+E&-#@C`Q$J=#jS{O86N z3}}JvWYJDir%avl7Bc@Kcdg6X66fNaoV#nV;ezYp8o5>*TuWJSp|eko{kq_OUg*A$ zV$Uai_q4~}>f?FXzOOtVoT>dF&y#jpg+cogYrJRckjs3ImsrDw5!hDuW^d?a1RHq^ z_S4pAdq$sVzuID6S)+B@KH!{uR-NmokL?pfzR%jRey*i!I+2UMdCj?qH`cqryT*G* z2H2nIy$keP_&4Ij*zS1P7w>`1dtmvVf!_a%N&7eSH*UZ*5BYA?(P@8l+VI;j{=C!B z%fzP_cEnt4o%{RAM3)@ePa1c5Wzd%mw@BNS&b=<{2cAB)pY6=K$v=XIv`*`GUIQ9z z<|;F=tzMrVb2EpzD(YInzU&9;8aTH37I{Zt`^iQ36XuwKZG8u9>drH9ITMLzWPvv2 zlYvg(9Zcl$I|V#To~y*ZoS@-bQNO}Rzl_^)HkInM=_lufn$voAaFNz4&v_hl|Ac3J z?T^Txvf54=Te~%7eZR@-JBfQEb7*Ve8V%OWd*ySt#QM2z+ADP}jgua@?ykSN8h8(N zxZYK97kWQ=Pd4!G67MbVcJ(4ppBiT2uJ#f4Z)<3{GIk#4@zu^Y@$$|sMTbtNcuRZ-!nnUbs*YA8mn?8yC1CFs@ za9r(){>h;2xDy(Em=>Ft*`OsF<;H8 zE=k+QXTXg2ukYK$_p#qAi9RQ25odhGT-qdkQ>xQ$4F{}s2hK~fubj|e;kzT&c-uFm zee=&!+jBg9bKXk(HQpRai+s-Ab!ab)SQmZGcVaHqD)uX>S8llAnQD&vKYjJq*B)bJZgU>+nV-L~v5R-#H`3=f zneqC~U-&6k#(wijyE1*Uoi_EUvz@5>TQcTvWzJujF^<*2{cvyH*Y4S3A3M)Q<2!oz zcOHMg`1eeM!{0cG{Q(#B`S|kvZQM0B&bw#CvoSauyWm-Iyak>a$#Y{Zvf+aDiEWwC zz*uvB&K~1zpJ&E(Y-`wo?L=LJ&);Co@5bL|O6mSx2HFqIh&AVWKS|q&IVo?%o2P+!&7as8ZHayD z9jwnf6Z<#n)kohqv~|$enDK&o?fU6|z#3+-k%_tn`Wa(B*U@w8d3~aOf<6u34aPZE zC3TMN7}qgkZGD$`{=_>z+v+Q+SL!$5LOvrtCt`mF-DeW?TM^Tc<~^}*Ip-ZU<(S&V zzOgNyJD(q)BiV5O0{xy$bke|Q*k>~7wB<79j(ItL&MoIOtP8p|Qmb=l&VzLx?gRU> zy?f$!q04VE8)jfz?hmAoa(+SE1%KyRfn!Q%d}(ptc}6}pS}%29>UUlv=IVSa>ZJv_ z2D9vKu7i8!o(;IHgX|a5rl0Fp$yno>IjGn3?OJ;NS3C#y=L2=x2Q+Z)lSx~$uU)_2 zxcHd&fO~2^`E1$V1?>&=onY*O29BHK+aGYj-1-`0tT`@th9>jxtdIM2*r&~!yAMkH z7ri!FVZYcXXZL`zqR#U!Khoh%2?)i%+A=k_roz6y9E=ePG9AvukJ7hS>Sx! zfA_bdPP=}_7;7HKOHSlgZuEotdd$(9t$l~gcIW-O9<(ptEcuP0yfFCgkOk)j^)f(z z<7Uv1C&s-|({oK-BU#{_oTt)$lKNTJU7e_NkIZ`l+a1&=_SL&4u7`D5r}bX2esL}b z{9As0LA!nXqg&mmFyP7Wqd0R=KfPuD@Ld{zh6^^poRnr;xwxKIP=MVtMLo zf3vX|F!&(?>ZTv`4?%^}k<8n>9G@ zDPw01S%)@#q=PvJG??5;mpiL@mvMh}?=jyGh2_0p7_>L#I~d3Yhj-?L z?)%esZ(v{jll0s84d19Y|Bdv?^Jp&5pt%RMz_#ol`z`EG zX?>pG9%uMQ-PZm@AN|eU8DB1KQKwXAZDPNvCs%`fEtNjU9Yv0gJidn_k1_2J z_JaLHz3qdaW1DA?XMuS-dhPnjhWmx#9gj^}S@bK1zI5+;n6L|tJ6^^Eb#H#@U+v~~ z496WIZD-$kH0aDPS+ozRmkAq;KVZ%oJoD>gZqYZ08lXU+JEgf;#OPzsV!>1@$)=5of%ZbHXk( z<|*UBqA&LcI_s2Fzl{AR$78HUU#!de-uPxyUu~U!lJicPdS$Lh)qQL0yMlpqeO>1X z4ZL?2^uRl8c#m->&4T?W4L|*jHHVxB>UwbT6=RY?yFE~s_LLW0=C|&w{W)It(>rphM&>sz;|Fcgwgdf*Gl%Qn zV8-{g-*JiG-0Ghkbb}b<8@bG#V>+%l2XQ`cZ1nDldvhW8(zA8Gpsmu&8Bl*7_>B0h z?1KH&XWQ|u(RFf-oR4#`K5gdJcYooc&u4YTb3T#Dq5Tu-_rERcn9uvE_Mhz<_tsAT zv}OOBOuujPTijD$<7eEB9qIZn7~na$?4^6?8QuJyvibYv@OMo2?-~Bq5!(yY`}fU= zziaYuneFr7vk_<8^WmBO zXRyQJ`IH6LD$dcr$)vxaeuKWo9WYm7U$nh3(7&PYfy|Rqz5bUm9lOC{eG~hlZNmlq zjgtfBn!!ej>yWrE3vOUL8MLK=apvh@-kLjYPrEf(leIO-x|Q}PT+q*a%28sp>zBO6 z>+@uzyARaqXO0E^!R73}p`S60SaVpPbyn0R>JPY1u7_*4z&qaaC_W3xQ#X9xf{naE z_gw(1piLhc(7=2XdSE+sH}=xntg+U)(GBeSU-I}Y`CR#Y`JC;7{ol|(eL8K)=eSD8 z@GjWU95bF@=j42>r-N(QtS@T6?1g*co{hMdCU=nck@wOnj7QtPw8G|h9GM0C`slmB z+>Rlq(4q#?%4t7H<+vQP3%j~f8rzU1#{%uj98nxuddH{psu3bzOq5C z<=`w>x9jN~Jo92(-3)A>(7Wy%oO`Z??aBzBi+r++&9TgD{tdTae?pF{?<&|g_W^J>+Mr4Ei5v0xeNiTl-baKqdrMwYm9_@}{-9FEL7x;Z3r(l1; z#YaDxA=PR3`@(OH9ppF3;BP`%!9`Y5U*(9Hh7|KUhGTYW9c677a}ej{{9I4%;@rCP z3m@CsH{6i6N6?VQ$OQe(oB7on=YCs@b9Y^=E!V*LJC_Tczw2jkkcm3&`cXa)urL9ieEiu{N z=$@oaX`JyV@V7{rf62zL()g`Z2CN5{{*%t{!GFudSGs4raCw$0K3g)N!Qr`+8Fb|5 zKJrcqhR-N8*gm&`?L^(-xn6M(T%Pj{6AsUR2k%So*Xwtb@07syf(sM-!0&Tk5Np0( z#<7N^j_vq~{?9tBC9$Sv?!42YK5M*;E!Jus)@x1D1M3uPPMWp*z36+H_viK<3WxXV z^4XJ#jqTaqTv7dC> z-$)EB2s`IM0L+!55b$wVr*U~kevA!2M{Oh&CuvhM%dwRe< zcmMB$QO=iV{DRLweGWWto;AqMcHm3|NIryYoJ%O!~>8E%rO;|BP2x8T2RPJ4e^++qwU3~1o`=K5duQ{2}94Lk>8Uw!4$7xylu?aY_@>Q{A( z*Z~c4oRsRc-ybknGGkojd62eV{B5URd4j5Yw)II}r!Cef0~%QWESz|!45Tbj*Nad3 zt9z2RDyxsV#JoxE5A-wMWxnfu*UWgAUEVzll)l^4SDSiKcY>$gzB$e9JIt{h(;Bko z&AeP2*Ju^&s~3Ir?_jJsF1gItqW;f&)^}~ead*MK=#%ZIJ^KT{MBNQ+t8amG?}76b zbx*r%IY8;U>0_P?S+ngOdCjArF`I9JFG1%UZu>nK-*zj$=>|Uf`90T&TMx{o z-h4_?cOmO>eCt`kL5k~~Ypp%&)=&M0+*|i4_iB~>%6(J5p>NV@n{z%;mo(arU+Yn) z-S`77&dw}y)4m1InykX1U8y$PBgj12e@cD7p|7@-womAkN!};Ve(F=E{hO?Q*{wdA1i*wlh2If3_w*5PB`S(o0zi-t0_l?g$^Y2K{ zy|JE4a+$NT=u7_rz-&<ca^}OI2^|_J*p4(*7mL0S>+sY9vW#{W8vc0~*YDj-5{f z=Thh3+Bpx`QaV_xb?2Piw*fv29lWCkTz-ey{`M5ow#=X*CHqggv0X6NiM!7HVt*BV zi?d{H*6bQOC+|V$=vs<%uBg-AgG-ELKV|9{wgC+`W4K<9m1{O)?{nQ;yH?0`T(Op$ zdK+}khUeaSxRw_@FWM6O>K8Pq>s{9(*J!d%`Xm{XeWkh)IjzAlPGDQzit#4?9hAnY zmjUL|W*wCi`<5~KUT}=T+*}9O=YnhKn!3gZa_wjAQA+o3!3EE@a)5T@P5zWV4}{ng(xUW>W5_-43B z*)ZU+4l~~A3*TLG8hb$>WBe9re7ndjY}%D-tL>*xBhK%j4%X8kYjIpzFrb089p>j8 zUDKq_x8sxOEA}UB$UOGdugIrV=XlQ7dUC#5_oZI{u9x+Kc70pS%emQ}p})wCe@bm{ z==Y3!j(zY+-hSUm-Q>59|0$c;#NT7YzWF*he%3W;i~SSmlNhrh^C-uIMPKe;(mVP8 z0p>OTC|HvfM;Xn)%0zu)le5A?DN_78D0ur2M+e@8#rg-bi>{I;1a z+JDQzR~o;c%7p#GMOPX8TVGQ3+1`;O`zbr;S}xB*vVGoR__dewAJl@y5H-_(y!1su{j%`B2Pu~s3EokI1mpKoZe+G_M zQQu+Vr+-DA+{ibhKF4sZ8)I98wJH<))+ZgTS*(9R3nuSI-;oP^Uv8MtVey_FaCx_G zn1x3B@XfS>UZm}c|CW|ieYVr~O>W{xq0vs%zqM1R^xyY>!hZvNlK#ezU?b%QS^r|b z=Yi|vdR=fG54f(f;lhY@@9e<__vM0nc3`ugd7j*R&s1`0dsaLj2RuU+b=n5#JA)Hv z$J}y3oqnE0pBJBx`;h8n6jFE5`7HWOp1|ky8|m|u_1QM3V>Q@OmpWqxxo@yz9BtO` zTpPG<;@lF~z%|Kvn*S3QHQVmw`1r?j<6hK#Fuy*X81Jdg-9NbVkM9ER{26Ry(mFK- z>h$TwPuqYMT;y-0ukttFH+#kp;*-=}bo~!?{($zU&p{{6_XUh#BPIKdwtmJh#*z#6 zGrsyx^g}<_Io=P-%i1^AQJb;Jp)ba5FxQ3~Eaa1cPV^UJlTP~^C&pSx8A0|>v1`ly zw{G#=e9jKivot+Zp7A)(o>kk+vx$C!_C(#mrcYx3>6iXKV?IACo*&P!VQ-b`2)%;3AV;%YP%ymANPFR>-xl(%Qb!v32~GPg?&&Q(^(>}#{eW}VElKTxM_1W!M|XEJ7%b*eEL`zAA= zvF0(~0KWsSza#m365nnuu*rj|X%5FVhy9NpzXR^a6Zh#B?8}4(j#YDaa?1qAT5w@HHZ|N*pRx8odVH+Q zIP0?R#rc^p=PS?O;QaklGWMUy-^N(f@#LVZK^DW zYc4x*52S(5b$4IbpU*yVjy>C+_sQQdJ^mh4+V}ix_uPBtkLQ6s_YAqe=6u6(D*AS~ z#2Gt+hO8Xx^6WSU=g9F!oUO*$@^7eZE!4EYXVV%_)N9SQolj!FgMX_f_9tjdKjnZ6 z&e<^^%`%^R^s%jn9Kh90qy#|G11HULfX^ET1M1m zZ9j3byH>8*lY`Fn6xVhK7wH*ZFL=JiXK{jO-upBew8g$QeL5Ipz5$No*d46T`UkWJ z>STkpH{z_#JmxjOV_on$^cmf71E1d{bt(0$4EjllNbu(;^+o_?&8d|G!a9+7)&f7Ux)TKVx z!u60|WNn)>*U7z+zge$bH`mT{k?Uv8*1nj7TyQ;I*9|wYt*(LgjA{704$?zzr1}vo zq&)knzu3f_GT{WawKXuWjEDaU`^4t{xsQqc{)jWD^Kee}%=`SfmY%`7w(9JwU*P_@ zZ=U-ZT>1-k?45gLoH;!Y`+@sWN&7`FKEpnP##gK(Yj7-UIZ>B-+v>D6>Ji%=u5&Ox za%X<))HWV@jH#IG!eP$!{cc}eiw^n>&_7wUwHvqL7P{X>@r^Rjo9p5)#;D5}{Z^21 z_768!xD@w#=6~e~QW2Do>23^mmK-7i@egHKyw8?=H`_eovkKjDO~Q^Zlk94`6 z(%$(UR90}1Z#4c5FPY`v1oPkW^56e{lIg3|ulC>C&F9BwtT1Uyhs(3JeBPi1hi7yK z8=0i;o9sT@aJl;y4A|U@-Mf)Ha(j1rm-7xCelLaW-sjrzX%U;blrz{!vCeEOCv-Sv zT*t0l@Ajy}v2Kiid@s}9piViUfwsiH`VAMXSt$c1zSnmnbl$1UckP4A`!w0KWkL(; zzlr&8WJ#y(zoF&7r&Xyw?cZd^4t%9S{(D`@{P(=hzwITdJLTU4jm>ETHpQ!$E@h5 zf9AX7Np|Ga-)G8oaNa(X&SwN0sqSOb2j51_h_P}ErMiQUYu1S|&UN|dur2M@&O3ek zZsxt#x%cN6Hu@xeIy$kxp>@U$7E*2@b$=sAhhfWteC$s z&J*PKdv?9Q;q!b%TfO!j z=r6{~dZ4a_E!+0R9OfV3bJ@UmhtICh@PG!BXWRG7=e-;6srGnh`QA!Kv0d86nj^_v z$^)A*I%7Jvb$BP-z}l=a=bdv|)PDop0~$D|3HlqiAag0rmpD!X=j-^+S!rKRLA`a> z^{{@KfpZ!7h`vf~i#2HQ`F`QM>BP5_{T<&>u9<$B&s>dorA+8>!_R!3Sii%r-^Rg4 z`i&;Nv|F?{au8!&)?p2fZ*IqNTx%EGN$OH>U1E$S*3o~dBJ%*&SzZwti`-5>awQh{P8o+@8c2Q!-ss< zY3+{VxSlc3n|SuVQGNc#*E|`MXW6k5b@I%$h?Dyl+47kKk&q6svo)7Es{EBDIGd<#*cWa=QOHH1~7Bz3`^~`nn{0-*03+$_(pig4I z2l`Y$ZRyuM`>`IbiL6)~*GKFpopz$`fORJJMVq+R=64*)ajn6c7L1U!MtzOzke6kaHWr#vJYT%r#v4b^luzu&^IT+xx>^p{N z6WaqWSc`R8r|Y$tgL8BKuES60VD^^}25TXJc?@n$z~#kdjZuk8dAslBOZT<^=|(6+9uM?d|o z|AObo^||1BxqhzYPwd!N_bT@@_b&IxvpZu?R_uji?6Mav+BvRy-Pa^@Sc^HV&79gD z$1!)DDccomlNsoLBe&xu=Ifw*;`j@UGmrT@@9oCBeuLj0t6=}4pWr+G;-A<*piLj+ z4t)DIQm0jlW z-!n)055|)nT;$)||w_tl8dUtvkb9b+x(ZA5Kx5#b2862cot7Pp;_4;+laRxceBaSH( z%X&jHUE9hzEpd*Q)bM>Pg=o$%X^|deXHE)D^J_SF8XL|1^cq$2G+M?&aQ=P zv%$5ij9Al)be%W2_7^7mpSZU8+|4H4$tiZpWl3jH@Onee|tguEoA&5kGUO7*~TT(3_1vZi7?`;Go#&D|IGWXB#% zzgw`G_kv?8H<&ZyGoQMFO&Vxl#E-ys`c7iX68W-TSxJmu(&`(Qo#>8s2C`Tf6gZ z&YO4o<(p}NvZ8JT4VkE0#kTqOI(&l-_$nH(jr*DB!TlEd?w{kE z({*rNE^D+{BiE;ueEMZh^NPC9c&v+Sxxjj?qlLd`qmDmg{H!C-pncmA;$OWp-<@^5^eaT$|x$Cv~88x5aivrd&sUwUxZ(~4^^H=fbsSz?d;8^z~D zIqAD!?PaerzjDV|Rrl2QQitPu{yup*FFnr8b}X3G;@R_@odW5I=>y*R+m_RwtMkyu@<== zN>N`)-9Vpg+84}ce(SKFT4&a{(Kpa9v0q93!bh@SeI_xo;eNp}pXi&qh5dkg;J)ptoo?>ik7%|_RXPw`OIZkSK9?r+J;M|~ zo6FkNHL@1#>Vflej?Ptb-r0ZZs@=GXz7t%-8|&)Y=K890jaQ&wlDdxWjT)OVEyi

hlB=^ERxexAz?HReN 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
(dINmri3yrq(IA0HJC#h4K-zTb1 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l$HvYpg) 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 literal 0 HcmV?d00001 diff --git a/Tests/images/hopper_emboss.bmp b/Tests/images/hopper_emboss.bmp index d8e001e2bd7a3345dc9fe53c9441f5869962ebd5..01d48fa3f48ee75744ca75acf8cc7ef5f414007c 100644 GIT binary patch delta 18 acmdniz`U)2d4d}25k3ZnBOA534*&o^HU?_| delta 18 acmdniz`U)2d4d}2VLk?i!yC1^4*&o^C#_#UhnLx%N^lmMd5; zC{QVafJG1xE?iKsNVQ0%+;VxT77MM0o6=}7wZX-WSyV_j~?G zc4p3;m*+g^InV1EA&t+g9DoL1Mw3K%g$ny&sL+IiVvJYu5x_-Zc$H>+=5qm?nMxj? zpYtOQ1om`(R!Qel*f&!kLt$$H@KY)BizpDN&|6+Rm zz<-fTt9_G;%v*Sz$+XceQ_V$e!0;dwgG&V;&|n7|2M?ceR8q&TLP+A_F#y0c+VL4; zfC9x>nYqditw;fYGI@xa^C;Q-;k`Y;FwjaH$C{(ewQ_;E6M`pjS;h=}I#^&Q;o-{S z%KiTSa*sMm?vnlG5nIcb>7<8M9LHPC5Z|DS^&*UL*#rnEPcZBnlwt!eQ=(#jYx}=w z`wEa}1Zc)1MGh}|PdNmHxU5!MurGv#D){Kw({?;IGeDUyn>l<%zF^Bw2D&+(HvSlO zX=Ok#o3bo7ACea5y5Q~pU%?cZ92>+i=W(8ao8)OTU^^Q^Du#M+UK14|_znNVakP-7 zK#5V(c;syc&{@Nh{mKoU)!oo1A)xRm8Av*oh%Gg*$J z$zE@zn#F*$#fowD#4xU{MRhW2DFs?+2JPDx0 zFlkzt1h_N<9vTPV!2rdmGS4DFQwB=7`1YAbFEPoir zrGyXUDO03^izaYT(QvCM0BFVqN+hXRu27_ePX{eL1Qf|L!ceTi5->!8hwZh?5F@Pg zA2flzbC;UVBdn2G(h}Tl4mUqyJvXXX>6n*g~E% zpm7>lV2wt~BylOTiy@LYlqoYp8J7e`p@3nA?Q6Rk1t4vQ-NKX@%g}gKaDf3*bdhIk z^lS`Bk>>HJ%L5d^{8zPr`hohexWPyKk5xJul1_P^9A6Ip;eR1G!xp(${fF}wCWi6^ z_li`QWYJNHsJB|)k5Et~kB?7*JfmpRw9-n36eyf%@Bsbn2H+?%0PJEkYTCzRH=8H} z1s-4s&o*D}peOU`-Nyhhhnaq}ow)$PkAiob?F<4dI5i^UubZ;^xxBKE3fu|q`8ZTJ zL7!yZ6W{I!+V;;QO$wJWN`QxlrlLT9yAAd&>E#l& zKP%W0?H$X=Ql>8|zm7f8vxy-?+EOP}siHbXI+6jxfmfkM!zId;qa8&fiY5lt5#yRE zNt^vuh$`JdhITTv0bAKhiX%%(D9`hOQ1X(6oTmHLs zjI@iA+@a4_Z{-EMq7l95iS}46#E4q3F3>u z;!HgkRa3*{F#T+%AAoHPFvJvkVsA~RLWsEg@mM3T$RRA`M0Fb-xTLw)tT6Yp+1$%a zo~4H*Z7htvzt#LO_`KZUoacN%-6m<~On`tqgY1s_6bdwB5x@6wW1K*fBuOaMXk5Y# zHGzX?MQ2gGC3tu6!8*(Vbjg@yMuocj1GHGQv{7Iy1*XTIHuE@Z)H{G7X3B2qHj~Ye z|NY=6?4XQCmdP@Mbxbl}lG~-3X8L2~516C;S1K0-74?LZcb4iVxu6a~)nSca?KL8= z;6%b)Dbf#$3{xW9%v~ygg9b$L9XicN8&)n1kXAn(*R;!WS!OHWLy0_Y$ki>B%~tu7 z><_>&!{!6#Gv+D|w@ctw&NFA3JLR+F*kXF5sGd_ll9R+`W>mvHra$;WWyRjs$`~pA zdFL?wHdc&7K)PYV9LoiKD)=$LZ3qdk7k-t@XMxGoRyKP5`lWV`nPci zNQvm1>RRrtlf-(}j}jkq)^a1z%3X5$xEhf6=(ni7`UHKG{;IRIX=BsnO|8yD75u>aI=ku_GBBwNQAYb7$|$T0_Ui96hj^)}|n6%Cap$G<0SPJx?i z*Uo7Y&YnjH(560^I4SWb^&@7ofED^1&dC!G4a8-)9ISq%{w|NoN&6}VO&7X9&>yxJ zcSW?)Nk?4|y$Of;+TfAmcsN9TF43kR0$HYh&5bOOOXLY&VZk`TBMb5lr(ebPapzLG z#QvNHAp5Jo=xwTrHWu)p`bXkpiDUE$`YdOK{-QHg-y#<_)VWZm$oUNOELQ^q>Juzx z%(=-qRnCaoxF>Ojx{6JVs6*8L%m*Mx7x8&_EehN>iHJE4FPUC*ym=d#?;4HIrShCU zR80wfAN6GK6wg8Ez2XU+4K6U~!Q>C@k2yDT%O zm{wUK33EFzXnA@S$_@YHki@q<&b3N#&CG(3mTqU4|M%dp0HoL|OVlsazo;p}VU;_` zG1t7$Reo$ERZL zJ^KRJDoRz=$*EcaA{5M4xa=@P<}pG&u)qp4c8bX0lw{w8r^*Sev?0-`&)z zzallZw~^gaU6n;3MVUbgbO6vti5(1ZRB*JpMOuPOD?#OHe|B)LS!1-xaI%?XhRoZ7 z1AB(&B&Cl+7R>4^tou+-XjspOb9uqYdiMcqrS!S9@Hs zcdJUY4}!-Ib*@R9A6g2S%mCAHomS>oW`O>dy21Zb&;&ADZqR>}e5E<~gsrgGnHB5} z{?%S3@ffz2x*i4?WjcFkrpz!IW-&;CDXe17zo5Bt49v*+GZQR?o-?8FOcF zfSF2}JP$FGGV85nU`z(gI=&vYScI6x>b1ecjiefnP=WuH;s8z!4mT%Q=8<6z8jr;! z%r^o2XDcJ-+pGrjaB#l=UeivA-ResKtY?%{ZIzmKe4s=Q*i4yb3V4)&G+SAVW`s1$ zIFVk`W_lUU15Y^KNnDX=^ zJJ``M1Q-Ik8DbF7xOCvLjUt1zTYYq5m}nQ6=$?;5i88)}(1%OW;xWl&wlPLua2DHX zWe){%);?6Ez(1J|I@n4-JRC5|@qmGYR)g-K`6q&IKz&ao##}Jvrc9?Npkf?MB zkJcbXN(06F@i^TgFyZN1>_@@=58`E_3O=xzcmQA zLZuMKBJltcWPt$^_NwuvYgl7ftjid~=$JF3#3=ndMDBI?@|%Yt^0tRhla-o+?CkT6 zHwvPtl30k%qQ&(X8gHE1nI}avhXYV%h%zNMMo+6^$#z;B>_q=p@NA3l7*Em`-JgiP zy*bbdMV>Na(NN2Hgu+FMVM?*)LJsjTgH{XE z{jaqMUVnkOC}mw){ZS*Ky)9HW2GJy~H7$fa_zYP2D@&WqiJ8Po6yyBj|0f9d^K`@& z_GJmE%Kxv0hBBf;H5RNt07h_uJ`!}{k+U*WwfeEhgzYm(5}#R7buMkMvHDXn+ss6r z%CT@C#*2DLn>~bbUD%8IWze|K_^-n!9EbC`52&rDeB=t-YOBbx$39Epk+kA1z0fg1MWUp}`#gTJTzY!X}10m z9{lS;Ig|t9a2d}w+$oTc2&;@oAzJ0FbkZ7`rIK7L8|h#z{W6&$`pDz6o+Yd_-{pEP zaw$S_;Emp<}lEsJnudCU-#Yv_-*?dKZq{_jAkdJ>Th`ZwbW z4MjuFhojY>I8G_nL=+`kmo_kAfx@b8iBitDcIn zHEdHg`Mi%j!wj$+k5P&gSZ$x2EUPGS4L;Lpp@$YaSisdRWgZX8P8pUBEMlfy&L$R6 zWQ10lqgum7)+bAG%)`M4gLj%CHgc4N^ay}D4IkW}NUslTi9)e-7_ z>TBvZ>SyY7b(Q+6ya#}`*uDVs>aJ=%n!||9lxHHzVs-SWjh=CuVdE~8oVZq7HpyvF z;?`Jm&HCf&6AcBk`;hCBEOZk3N_%y!Tx=a6tJS;J|EMF?FZ5gFf`)e>dCvny?S;4cG2CAJ@K@hDP&6*d))Zw>zhVferExeE?5e({mM) zbOIAOtrzLP>2KNJt7Uei7YxgD@*eJ|yRLNg>Opn1`iPv%axUb)2EWg+yr`8vNE!8M z_225>V^7xG?fUiL&GCZM6uZnd!Pn$ZO?i0^^hxT^Qsnl!LPbVFHp*Xt#mtC383A_L z-@DC;k&QNE-et~_6?|Q$%a7%3`GfkTdP3i=cgbbIM}u?BB!=Zij$>=&W=YyN4+`Gq z|JwWq!{(3X(AZS;vi%JW>CLI!3a$Z=X7xq&6S>e?;a=<<#)+{ap?aIfS6`DGq=lu? zlQw$wuF!EMpB^u?$WP@qw&2l&$1FC<+3M5kY_*^KK%S8^SiSF9l6^ocaF@LFCf|B3 z(0mEIz#rOIr1 zR^9=)?B;Mhb~0Jrpbxb#<^hL3a%Lw0OL&r_Y#d-$oeJFu3$2{7%3M=5x5*84F``vy zlmV+7T4*cm2D0@0FWjOjQqgwlDDqrMnQkkTv~fG@fHlF9{&QwBX_7p~4m!-;W{;UB z=s8eh9;NrDD#;{FDKGrJ-i(xSsluA<~ljXmfR?=H1jAq zZj^)THpyN&y)I~G6d1yz^-b7?sz%yTG#)M*ffib6jj(&4`nCKgL`rfM(#&KbcgXkD z6|&gw(rql%H`yid1UtFUhDOYni(~Rvnrqo9pJO-oF)ya@zQWbD>Sb|U8UdiQVT0^d zN2|v8vf7~DDAedhRmSS#fD4w;49yk^$Qf>wuM$Cg2rSS_`L2+jkq~!F|CQ3PS=NnU3-l8D+OM zffUHcNGuuiAA|;pR=VV70J94iHirRlIFnc@KjnG=&SbIN76~*-x`B-nMr;q&Bz2Qf z-&en62UpwWS7sR4MGnY?Tc){toWaeF*)pMOC$791ap*v@v@+9TvXfpq>5een8e>G$ znAX6V=V_(GhF=_4?+!LuUeTitlrvaLjzzq}^Q@5lrG;g-=7?KJoBEYHLLDf9ENI{e zH(E8T9yDRJQ24t#_whK9*QbN8Dqs(J8zKz}#%v^gSZyh0-_29PjZol`q!RopcmY5r z18tU6Lj~nHF5+;W1vVQcE$S2UguE#2lsMMB-xSzt=PV3c1WbSMo#0c!Kg`?A;XKZQ z7$Y7fKTb(c;Zt+2)l4?kP>AsE>!Cnl4H{K_ubXZ<=(N|GbZqNsB}ax%QnuP@_%bsL zW|U^Ii51xx^Ju%WwE z3W`euUd)I`^n4By)1`M{}H07o~P4TJT4TE7kTh6s{1LHxeOsLx2ERw)1 z+ub&1*?1qwSjO1SH2bTITdjKRh7K}$2RFwEts3jQl*_rys=ae#jc4W4>S*=Jx@~f! zep$UNQ@Do}wAIBScd{h!1H$NtHNb;0h)^<>)oTg4#-dSDXK}P^As1_&9hM|4dXoBSdXfS z5lPY!ZN$MPMTR6Aj}$4ABuSAb%@WnHWBRP4Jtl4xVlIgKk{RZZ#3K!4EdlP4gX_5U zAU$9cobt?KJ=b zlOQi?wsJQGkhx45S13u!N~R$~AQXQ}b?KD#NT~iVQlLPQQ3@ENkek!|dvjg_Mr%BQptQO7O^#B})b!DLDQa9`!XD6%awpfqyzvUQpx*Q9jf1>;4)7%{C z{r6F12ahwx0C#Yi9HWkqKgp3XgL{^H7-JdKYkkrg3#hNxEmJX-J=F2*2kX{dfhI-D z21=$#vIJg^HMFdO)G`s9nJdT0W#Y>7>XSSbJzvZ`D@Vk_;N^SjKjjKmsK4v7JjL_U zR?R4XgRq=u>)((wVm77>9Wif@7c;#8k(gf13yd#|ko-MT_DPZyOBBC|8M#vC9H6G5 z4U-{_#wAOFlW1lYb0v(_)pU`x+G1Q_#+(y;#{8ZwoWgclK$7BwPL()>3RYzElU_F0 zJ>S5#NGly?m@KV!ZcF2wq^X#{y19-mmTeScff7D`m~gN7x!mknVMyBKXdoVam!ynO zf*j3^k}xYtNvJ)9d79PekGGVsAos}y z@@Za)B#0by>!KExP+}yei08@UN4%YYt>rim2`lCF;efaFgqn#dRjB{s( zkmN<#pNp*EJ-;Doav}HCd3kkgt-l_OM?h#655?of4gqC6)&Ut)_WwYd-?5fX5^SN3 z5=q*4lC6B*oWfd?W|4V>Yxw~y7{nn*3GiuW2hhhxO7xJi9^G5bd)UELN^GZ%js~A& z9Eu!ds$QxNvN1vTM!w8iHjtt{;tURxa41a>X*@o6VElD8VWNTq3S}ejAkQvyi@BFh zk__RKrc42!shmqY_ppk7;4xDnDZ2rmekwLrIE*pd!B$duu|0g4b=+wq$dkLzD0iGre_-tYor*I9;Hi_D)&n zL_BziF*A+%@&Z{vGt+K>p~lwFTKb~|Y(Pf1pFXzBUEGStYVKkl?PM8|Hm+v|SyFV; z8%Z2tT`n!0Y1`ZuvE+f+c@-rfH^Fl9BNItAB7A&_8ck?K;gE`=fYP+nP9H<|jDQjv zaM&GfHbr2&NibJth0RGw>|s~3^wUQsCb>V11|Da&EXJeAi6lwUj;2MvC(p8)wXEer zwy+#*Xl}1uF017(rU9KSjVU)?v=3XtOi!u!QPI`waj5A1@4U*yDvBEfXCP_&x*)e5!p!xc{ZALBv>Zb%WZPC zERxIRS#G739;T6E5lc9h>p^_|vsg58O*t>Hhdm@-mlZa0fC&=p-`Y?~R~=z>(*{h7 z#R_(_5aeL>H@Oc$u8vmgqukGYdVo8^8|ugE`_+o%x9SS@eSM(1O6IYiG5NIoM(rn0 z$@B7}JSdOKU*%jLWdU<|TBb8rjR$ci=~LwV2?v*>cS5IXb*SzHX_So$)7Kmn9`UnS z{5sj{Nm6JAqv311NTbc+vPSw0!1Xo<1V+hGrjI$C%xRSAl9S9fQXI!UU_O8&$C%x6 zJd5OfG-;mZR=I;RS*CFaJGhoLrUI-pi|DP^>i@U;lU^RYD+tEr2oBb{ZL0mNohIa< z(bS(a)3_)^VPX{E0=w$MP<`^GDKcQu;^E=ZMV~p7NlYUb)9R;?V-{VMB}FgD73?-w zvO%nc##Dl5f*`ok^wLL($9aG}Ge~hK*Rz>h&6iCzGG#s6s=YAp3%+T3)zRwn6T}6S zNWMm3ekl4j+J_nzg~E`su5ph^v@*qV-zr+N}snig_}{yB4STnoM1Ca(&WgPm9#Pk zPtJ^-Q?nSP#4rn3!x&nc0_j$y|@C5&_x?7 zd4X@x$03}+sld(hclnFi&eP^pcHuFLn?6jlw$E3Kb1~xZl z8Xv}^LLWXww#rO2v(+W6;y!sddl+Spsc;?rY$h))miW6SJPV{AHnWaCU?x*&rbtgT z%dK>U6llKTALw6UGHg))tUsXM74s7m?0`z_i1JzX9WfY3ic;v9qIoQfW2Co@Ea1|^ zyjYnnI5<4alV+Q^a<1x8|D&Ff-CP<>3jWP(;M?5801vapF-F$ZxFH^sy#ml8vf1N@b7Vfl^F zV`G4~;ge<-ezfWBpcR({8kbRg%I0t83RcU%sx|5v`GfhndC2^kdt-G~d*4heLky$w zC`XB*MT?s7`~ef5>Xt&2VVQoP+H0dU;>R1dGmmMc(ahl$c~Bj#4v|OYYWbzyCI_>g z%jm|Vid_bIth{aHWh9Hze^ z&rWD{YNg%!2CMdiDvM89SJ_g9CR{@~y5vvtiY4|^WZm)T;3zI(BY%*sa=Xl91}Bo@ zcC*Khb=&c3poy$OG}+eta=EhO?R%{ zH{S~0;RnGxnXkX7jryV-8FQ%v4A6&9=nN@Gsk1Jwr2rRdkpT;N(K$>%Y3F`Pltguh zJR{G_Z{-JaBqQQVN>aSSecWtWTU(V$S=4!?=wU9Mdb``C{~bV@RrQR4dRS&zT@<6n zOyxxd726*^H9Wg`bpKr4V5TUB2V7zGD@BTFrzY8 zZZdys=uVDV+)JL&-Q5pln9F>#YMg{09+;M-nF1~;d`9cChEEG1AxFqpWl&n=FI>Ux z=I3UT8H$}?8P-#k5{iUdp&>FZYMyew>~s(s#ye%UJjFf0T<+&G=Fv%#dS`k%CXiI) zy{h2~DN^d~P1iaz2+yN>TK#u}`6A0@;;@J=x>;zcXN$~~wtdVVr0JlIE@sd{J6#hB z!AUmB`RX0&8T}I{qt~fl$pzzrYpUY~SORa2Zf$gqccz8nhdRVLR2jLts@F{jp^v^w z_~W)3d7^2zb6Rz#^gx(lp|1 zW_jcpx-}N#5tcZT>$yYTBga@YUe*voU%%e9jSTT126ycVoj@ffoUu9BlbeW2*Le5he_Q$dIFr9BEqVVJ4mAqV(R0SmcO$*_o#M)m4(J zt1C+@OTygY5*50bM2R`9i5f3ZqQcA=`IH!@56Fgp@ z5GuqaMj0bX8x?j`bzON$e2@R``#8BTI&u?QGs2E!)Q$Vxzo3?3!6lO#b)%m!bg zj6)$NmV{O$7l#rBTFB7L2zm3t;8uBF?lPLmbcF2H+#&}zY>45oM)BVC@WU*J5)~U9 z>rtjinn@(=`KKL>unQmX&4e+)VZDeSxlH1J06~OwkEpOubDxkKi{38e62)PH_(w_8%p>o8weLdFUy!0 zy|Y^XyZU=weP;^4uZ2m;eatttcma=$)ydLisgSpa?Zl6xNzqQJE|#p;)f72-Ptz{5 zsWQV%WdkFm!!%s=IXPGEB1h5u&YY}HaK6Kay-WNT#zli=Vu{-mgQj{e+(hMEa&(a5 zF-xte=j49>K+ALi&Qb)m^5#i%6jg&Vgys(yD6oscNL(q$BC!hWVibRzbvA4s^YP#z zc~;J543`8YV4?c4{vY)Vxr_PSX_CP~mAm$CF!wh-u0I|Rv!ERq-iJM>23055#AvFp zhplYKt?n;oGP9*7M7wB*y|EWo+3Fk;l&xPVyi<%`X=GHxr0lV_;e2?cYzRW03Y*NU z94g2140jU7Z=}svDwlJunaN_t_!++@<#!m}bY{~+GJcKK_0b3#2i>q$dgHdv_#K}j z4n7($G`N{EeezOt5K{%eK1iwt;|E&3KR!vNde61N5aF@-J{p%U^)Ba~=Ap{-W)j;- z(nX0aBx$EaKH8N%PSyX@G-j?TAHdXT5Mh{BJ@-%B)2gZ{N#jyP+eKNzBRmNy?*9wZ zqr$d2^x;hNpW_-^^BHPF;;kpN`rnOb3lYtK2)?y4hoNY`88r*#$1a?> z%%+P@>xC+@*8j-fo8@Z#9~PyxRo!UUk4FfHZE3;-bR8Uu(T4=Sgbd;oIHSjedpvVXTe@T&joj$B(rPJuqeVm@~~0>I3RGfkPNlTf}FOJVlbx z;cI1lR`|1JBX5DoJ{;DUsB6uq`fZWw>Mum7+Sj>)b)K)_yP`zfSSXkM>X@Lti5);S z!p5}`SK)oX!S}I- zYItUn3?42HDLh>3=_}j4s5;~fSz4@$l(h<(+YlO)Bz`*fVdfwy*>=moL?^Z2MUgb& zRk|Sx+ogWLu10Iu-%VK+#dq$}LldS-$5oe5<_I&zx@-;DdTT9*LU+6!j|>@7q-moq zDj5%-%S2XX0OV08AysSgInIP8ZE}%LM$ZxyhWO= z&y>xr@`Bw7{E2yhPZ$O_370&L9^-JJdfaZ{+l3Q0Te2EO3dcN7#&#eSnC8X4BvOL{ zC0a=`MuIj=?A5ly=R$R0*5 zf?eCI+Cm94ovPOrOG&k!RmG(prj=PX6J9kYOjIlL@Tl1Ryc&^RTlqd?_FE(4&+@7@ z8Frz_fYlm9WhkwtOP+<*t8oY{lV^xMj5KU#W%3L&%rHep2|tpNB-?NbP9mDH>NJE) zqe3~#E5htzhxJyOi9I$x<>-+5a8Gv{vN5X%6E{+ULvzGD9Ux7TJQZ@lL!^P_jtpB!(ZiXh#A+V1h#gOVHP)vs6Rrmx&8=@89#tsd zQ;A&t4sq$kCCrQqxxnolAcJAx{l0iWIK(P`YsyA4nw2nb$Em}wZ^^zCl_*i79azsW zDfZCMLNm#n%7 zt^*}1cx0%qRZVlY0<{ zXETf?fk7tGjVHIMf0eoNI8*KVFOntO;0DQ1|5YiMA|5S-U$M$FXwMqP)=-0)bkky4 zU2|kjs_hq{<}emAO|uqXmIFqOQ#Rtc_nIu#FOk(t`ZXw6(I%XiX)*MK$!V>)43MT* z9j(8oUSd5LM%^ng*Jd16eUv4tr+x!Ase912&^X)m&_^Lc$q-|^v<%2%xr1b#1mIXpavNAV(6ev1TRUO~7t}NIv3(+7fB|;IOm#lPq`@lSwf=-B zpI^%@GM5~_@LN7GuClm2=tli(MuW*rHIrBbv@!KH&w#2w?K|A++DMY(VR^5U!SxR| z+s%^`wwSUcTBa&-bdf{b>;t$*ogbVcot$RYkPdDB>Px{tcx}+6>d>w6LH$tH`$zKv zJlRmV9y%qZuhSKIl=*f8^yPDs;wi4?MCMHBde(Im6#jL|GLv5YaAJRbsP#Et9eXDg z3tefnQ8jQNnU2p=c1jy_Svbz;JQ38k?o{P06Je5i6i zebUV1=AY~vt@w1XlYYwwoQCgPg+x2wy5BY_hE1#Bmsz>s&ktK8kJ<+@;tgE6N z#qW*ck)uF5*kCF$lZ!y+#FAr&DUoLq8CuBN0H%6)QC%#@M!jkW&Y+!6uH{sa#p*w~ zI*KLkiOqN#NXFDE4PlK>^KtzdeTaTl{ocM4o>U%c-@7Oh9(YlvNI#zs46gbfnEB>7AV-Q;vZN?6 zg3Bnwl&Fl0{y4~#Bfs(>DemVQrm{`8a47@_m=k3)t7stit4A*{3%y*S0ZME7p zOkzGF{5MC(8uBR9&KN`1rjo}aM~OkgZw&S_U5;WlC(1$=bF$2#haS2qF$scSMaIU2 z{epuV8CJtX3pypGjx>{ls~BJ#;4+zZLjU6^CCfm&NK<4F18kyeBA>tfm~%`4A9ZiJb*)vZ^SI)$Wvyr`5D!9-AXf~6e*!8;ghC| z4!UIl3)Ow{15@DyvxHk1W0v@I`bWzu{UK@dZwr2{uOw+o94AjRpKF;cCz-FCi6MbV?jPZ#vx^kdHZ_22Xt^$(?XR7a9*OcL~Ht$rj@AEdv@H&uf@phSiYEmXhU zm9$-Ii$s&MmDj7)cd(EwP^O0@Rh=O;p^Vm9bxM>NMYF|BH3#66iabO0-vb|D1kY5s z#~c+LPInV#G|9tR3@O%H+O4@%RIL!Z~f3*MO;7R`w|Bm1g{|JA- z-~|6l{{Zt1JKPLu$}vgMWsK(V%3Q}RM0kXMu0w%KnsOvKR1F{*(os5`rrB>&booG*BwO+5j6S2%2pMK`94Y$Q$RJb6v4d`U*}~fB?DJ6IYU3zi zIuEd(B%O3K8Mr(a&XKf+-D=G~o%AsT>Ogtc|Do_Gd$^6?+KxL1Rz|37TjkU@v9H2y8nJ2^19! z`{7d{iHAc8gCfaZUZsRau^;dX3Sln>jY454<1&V3FQLL`2-u6Ei7^hKjAE4OeDePR Xz$>lA&YbB?b8<4H$uMbZm>7m4BD7|OR)iGtD?cOhD>lpek!AU82rKJH zyU2!!q?IiqLJXy0QW}Oy!^u>m)0{KszOU~e=bkg?%#X0U-&c?7-1mK5*L_{@`}*}VqFo3N%fQ>^g2^^ZqBaXp`OA?nPpb#uHHVy^=g+Va5xVQ*{ zLSb+K5Heu^K@h-TP-y(vXz!kjA&J2tRyw=!61xZn;}t^sI+emDK@K)a{Orm~UZ*b) zQOE9-@fACW;3tV9$PYZldPZ49b|NrDJX{6qjh0UUB@C4eSI9G}-TADSc% z2A$EEHbN-x9ta9I-Q-L{w6(zU>h9v8yj%(wl_CH{=*4iZ1)zdD)&o$$^*qE#?qN#0 z$RcL5jFF6CnLRQ%qsJ9JR|Z~FhXYV2VbF&KpY`4A)${3`Cwdm``lo_;_|x1{do88u za>mdZqoj!PF7E$#C}k9*sbCcE$tm)POb|cMGKmWB`AM9ta502w0HB^IbpSLH#UTOs z)5ZCy!(u2=noYU0uk#mEZx*<315jdCOFsz{GDoKpyJsX;?YKDpuVf8RGFE2zhU!zj za^`hW)di-3$rN|nl;D3vfO?{YDMM4u3Ysb9NEyqe+{E>aWh{V&ix8t0fF@!jz05U) zs3)3HT$l(A1Sw{PJC#Sc-w4O1pYCp!5s`ODwB2x@GkL9Nx&z(26Q{NM^zG_#bA@}~ zPO5v+PM-OR{o2+8J~j~~gu^u4-}(Wh*%heCXWCR!Sa5^Xk-)3=yY)o zaWqjZ66PQ7Q*xqtiK8=Kx02!1(A0Km=9n$XLN}3E?d}WCljckPIrZ)aR&`fjwXgeW zH;>{3JCPtrh%Lms$q^vDETjcePau)iuV*OjfeMT_DHF-sbZTDgLca_y6P`m$>8? z62Kuwm?E|i#77=FYtCvyr$0v%O%xEP8HWVgn^cpafF2o{7I+OWrV)T;EY5hEz#>W% zC?sx{m?rvVJZ+TG^rL^bwSCSVsD4y$sJ{Z3S35Qz%t7*@y3U`ca(B|`N~(yYul)E3 z5g{KxI%B>9hx_(575hF?+^=%(d(TZVp$G1p0{c}ae?QIluBLJHS5=B#z^};^* zI4D!B(JOI0F*J5;e@=aC{dd6 zd5g7`wOne7Xlq9>O@b&5tfPiiR0E>0@!6f$U-bgvj`skl=N14K;m{<7jL3Rnv5Z!O zyg@KR9?_pz{q@1VB9FJIF(;Z3je4Q_jXqxW>|D6f?onddV*=*{ez4cd8(r3*+WU?v zG*C+eG2+CCp()DXTY?mOOI{lZnu*dx3_roP`-%W9T;7QQ=joTbczI0*w>E?MAQw@U zz7rvjm-M@K2ta+Muhy^W3F0tWDFAb_yQh1h`a{iG! zELiP(i|L)}u#OK{pV7G;8fo;tZ44hJ=_Xqg(BEUOQUr^$jTmu)gb1Z114Ry(x~dZa zzOt)2-h-^6@u0che84CsW;|Xcb^3C9i~2KAAQmUKu5Z1^9pLT(K!anuADW}h-R3aU zUtMNb+GDN7`k;(uY(?wjmP2>^U+V&Mv~{8Xt(@(-Z>b+URKcM-Yqlrdz!=RW&=h#R zw+SdrbC)R+Y$1w6J`sv3NUeY`^L=0+Ye{x__Kv`t@?59aOt*3}t{w_J?ca^Z($|yh zSMr|hwR>KJ??@g~z3gkPP7|WuEzlIK^Dp%G4Q$W-F0@aNt+{^>4wVkWO@3>g6P%Sd zF1&r0f*$`=*LA8#X?hOmjMUdnp@dOXc9Eu-kxXVXCol^BMMKZBoD#{{RSZ)LMD*Y{n=rU9s<1^ zoO?UJUXD|*tG9JeE2_UVS0=`^Z0u4*LhY4%t=+t%YDWct9L{L7jaz`R_^4;uFCam8 z2vW|jh|m}5-4Z{xWoe>89$^mEc8NYf4s-`NzjOM!UuNg>@6D0%ciXsm)nZ-UYMC|x zXPA25o^0JI%aVny*SH5*yV!O5_vTIKSy$1l4s~tjn{|%3wX7q|vGRoy>r~rPVm?SN zY`w6xogsfr4KPV_6o5V=ccVBh_r;H*&U#HAavpXUazA0JNOn!MUqOPS5y+>sXKChvnL zhvZqIz}y~3<=x$LVqSgTs{EJ2@9*LZ2YTG#|G^5$H$aJeX)VjSC3IBJ_4#-8oY-Sw zk4tj%0(;5Dpg-_`;y+bB2Jp4^=p7tu)#;~wrT(YwhvX{&#&UB8L-e0v6r(IGTAk@m z&^;a8Z_SPRWeVIi`gV1=xy9s|Ls|!!HL299r?t$w(0yN*w@J)m$yga|+KOvtq@$fd zT*ExzEJw^H=WLm#ThzzI+*icXy_u>0YVWB&vi5KuGH>&gYLPxXqn?)C+#>xu6Xgm$ zl|QyDaSs>QT4^=JUs@xHqYTWU0E@0a!vl;VjnomqWKNqY6L9>1|$3 zX>80o=jo)A*emf*rx!7)l-mNk+V6PHU2WI-&aj*GA#Iu6aoojSSw&Y8qnR&z-s@9f{YB>u*Z^ICqBcr#J$9BzKk zYCXUjXyPrew$9~FyG);t-}y8-%n7*fy2?Cn&Xi{v0Yq7juN!u)YqU?AenRbxP!mb# z|J-9e7H7Hnhx1ZvJdq#2I(fa5>pm_A`j**Y0Qr|%XeE7>_C<27M}XUmlC#pq7U8Fs z7%N#y5tTg3JZ6|5+#{TQoPClhA7->)w&PIC)Rv-|P*dyhfM4y%ci4WX=n?tBJ_fQKaX(Eg__3Q^g4O@QIAZX_a{1nok z2nuD<1Lf>t_T*{+8VH(4xz-%ve&L*w+?brs3HmPSqrP>IbKh`2cCM5Mq@TUYQyQ$3 zi}^UMVH?dFR??khXo--=~#Hos5KlKmMa?JAK&h|tUm zYVZS3QbD7c!V+^kg9w``jFeH-Q%nJlsi&SOLs`iNb2%Xyh#zed&OCRT`G8z=9*bGU z0;-8oie?0U;=ox_VczDQU)At7G^M4*7(PPy*^XEUF*Az=Y2D6z%2@3#G$TxZt_IU& z)-j7sJc^&i=0LNXbA|f|F@3BoQfp*(#ySyZFv}^V*^|QWCP*`Jj$3}uYzLw_3B zk3(2aGmX>`r2+Y5tbn&<#n@@`%G(v`~!UNTLS^8l4k<16 z@2v2YU;p1=rkZKYXXyW^IE7N!XvnbE)Kf+?5FtttWsG8SMwV)-+vqRDsFG=9YL5Oh z3*N863?4L35C_VC6&u_&1W^p`Q0+KxQVYyt6{~2VlBx{zN2tBao0a&f;GB%S7wu#X z2xZuZyT)jivYyu|>OP4%(@bX=;~~=s_aDQ?QcT6|aJvS8D9zL`A5Ce7?squ*r7QR_ zQ>gaL=>RL`bondaq#7ebr9$$=Vv9_W@714mAAOlH^JaqZmmsuvjMW6pzVOaUaPDW2@I#!F1d>}Ll5j|c$3Ph{HSx|&`>X(x#iHd4b(uQRXo|JdVjYnu0L zmipYQMc;-e%3UC+kdI^faC~j{29KyMr?>@+B zjF3DzQkHV7Jj9#sVa}({Wn5|=?I895a)AUHCKf?cU{_$4?8QT&u)Ou}tZzf&J|O4!cqWC|_SMA2>j15(CtmK&e>mLuf?^_elah}nYm$-Vx$fcaQ9PzU*C(q3nMQGEuOQRH1lhtE_iJ;oxy1RjwI(?y zxzYShR;JgN;b|@O0t%??U=aN^2m%IT7)i_5R&8*m&)*?o8PJuT=$XJVxzAai7_APq zLh5}cg8I_;O-`R+NzS~SW9-?oH-OC4f77?<9{O+gt^PLxd)l+r-{lOpbT(V4FRjz8 za`}7aJ-JQ&K_2;!8`?e2=4@ET4&O}e=~ny;dt4ECvE9!HfFdGNBqw#serw?5+?m#V zuaY0Duh|ShvEI%8*uT(!u^gJ)2Y zKGSaYf8qPsE>c(gkDJ>W=Wq@@!Pox%1D6Ei>S_Il9@m9W3MuQDtYjl7m#6eW>Nc5~ zb^rRnF}XikUWb%PUh`83>s@tHpWJ46m$2SsueIy+?b2!u^gZK0&wfvBWn&kWdQ8u? z-}9a1KiD44qW@V_yT*B(Cp^f0x<;L6-R>TxTKu=#38#0<)>x)jz+@WK0R1`BOe9&J zF&``noM{~rzpiySfVQ1~W_tjt7nSR_m~Tx8zx2~rr~yRXtDT1{*}2(0jV)Hv%r||wM$fT#v@WNuA5^g%OA@lbIW0@l??(Y; z@-K$60^|j`E?Y`>Pcp9?k0gd9tOibC35#6J1@cjb1lkoA{eN#h7>sZ-TUbpLjiYV| zK4}?K=IltGB;mv@&Nl#3Wag=bRuBE7Ii%&=3}1rTfqz;TC6cY$oXp3*`;&XSJL4%k zKyqc6$v1OMBjU3(~K@rn4 zxdeGj8#$$m#*}n%@8_Wj667K-f?h;;tc}r8-|2to57eL2%7%9LsZk`K#$+po*RX)pFZD9>DPOy8#1cCqYGaXWV3vl;ayUs&g(>i4p0I4&kQ2n<039n zfT5s`DRq0e+g;ndn)dv;mSNtn1svmE=Jav`?osBC01Tp;No|s1@?3X+FJ;_pU^P3u zt5;-pCoga_Cr z)YQ6L%}w$EK!(FLV46&f$Gp5Js^jd5cBR!{|6X69{-ypcdBoUg*0V9g!)R`Dt$Tp^ z!OWppR!S_x?prCPbgu}Hd8)DYI{$J~2$E8ZFJOr$R_05P5XEUNV10)CSIBUx+WUZ9 zZ`L?l-3R0%A|TH&iA+pooNVT1a6d{8ar>C*<`DO6vxai1WtmiRzZ8nYdYap2ar2t{ zsoBNknCIQM&6o^>OdaFZRh?9z1^5XA!A`1JCxZk#Cx$|$)Z$A#^D~4A;!r?;hEhaD z2m8B4C1-hy+-P}^q}%E~?JnV2N@bGNvr;CtSH}ctQg_HBUAxN zZ`B%W7wc^OiM~P)(ATLiRWE{6$#L?GJYvpgaF(jInvtxOGP02%P7`E!jb@Ud^RKRu zwhXm>uv&>OulR24J%r5**t~0-rUE~5a!|c_8Kbpf@ z8N#7PXBc7&DEviQVZY}wo`=q8O9oOyp#4ELiz&ci8$pT~Mpb&9tsu%G-lT>CVyt8% zi%q#Xixof|%yv^w4H53o@Gn2#6qqS8Hj`H~N3=fCdRS{w@($CNVe0SdW%`?w-Cf=O z?ic0+b*NqCztOr|W~QfM!2QU%(wv{=w3qjbG(jQq36e)4#T1ZFglt`hAVP?|3{Q&^ z${55p<`D(PGl|I?4#Q`H#zEauN;hz8U3Bk^|jOGYsJX)8!TJVZFI7W5P>e zl+4xxxZccldg6Ji>RtSsihNo#3aO)#ZOr$+*$OtL%^9ivWVIRY^NYELYsj<*^`oMLj90<%kUbMji}!?xywvpGK3-9^T#pJalJW@WdNm_q>_X9Yj`6lW}~sZQTS zGfgxTC*ci-B8n-dm@*7T;dJO0&oY*N-psraI=!a7_z4tKPlhU8?ooHCpW2g%#e0-8 z$9!VqTq*t3XL4gVrM0(gY~nO`9M8&P22sq7Qq*B}Ng+<66LEroCNDkUVnk`CnHW*3 zS~5xP#|%F=0ZiV?7(Z z&h4=67fFUO9Fq9)r~CuTGWHC0)CV`Qg&6(hJ=U{U_0m(V!_-HfobVtqR^kB5)5GLM z=Q4AlJi;tvn5VzcQ`MewQ(Ix?a}#2idQ2$%t)jdEqt5Z4*gk{taMN>oxg`QpGmvewg?_UEJTiPcv`HF!iTT!)~AW6cq zQD&%v^ei5eR>TFEPcwyPB{uWgwaCs3?nXIWrkWP!$ar<2+vq%CF7OC2hLecMO39Ti zlyRIog(i1_?BhO9jX8^PW`>MWz1+495@}Z|ccHtBY>`^Y2B33(eGh2&&VmVRTjz)^?=?>wJ2e2`uP+Y%hHVP+8)%`))Y_V z>WCtSW@rS$SqyW>U1TXj6j4SgrBpDK%b5(@X>*A$*B8niow8_Pt$M?b_+IyK&pAJE zysagrv42u-%r;=OLGD$3?R$dHgf7hW`|{LnT~@t2RLITpY4&Cfb%S1$aXBojnVHOb zkka&Zgffb|P?v=$rkDbXspN7dTP$p|49Wy9GaUubBFb6JHp)1c)!b-CQ^1RoYXAz_ z+_gT&Txn1bo7J4b3aL>u&2i?{U(oavR#7N}7?RQ9!?Kh?%udS(wTz@b%@$|+i^qr& z@mOAm5Fs3If^0`Kmgg<*04QE&f-mI^-B-UWqv(hrDG{j;bBr=^D%Ds?`HnR)T*gq| zwNf&#HnoEcrn>9ScY@tZ1!n{mYDCz&YM zQ^rW@%>fx=50rAZ8JES7Rnxc=523a%-bgL6EGI620*WalLO%JaC=+1{b&lFD@9Vq$ zZv+N**hD#uDU>o@Ued?wr?st@_$6nYzppwFKn|DxBU3Humz%S4ul64r*v%fTPgZ?p z1e(ZBqC|}6F)Dx}92q5<`vqU~&wfem?IkV;M)L~G_(m$^G5M>ERbmefYzmxejbkG4x@3Elgw<=ZHS2ws`);rr z)ko@Zl`FkDs}rZu73Rq!Sppjhp% zKGJ)tKkzYQnZ#oZV-kxbET@q0mZ5j_)&AQ8uln{@y@7k$_Yb9@a%F3#AzQw&3#_x$ z@6;9QI%Ufhy#DhfkUVvP>e~g&9LiA0O=k-=UG53|HJHR@iXG%Jl@&m}d$l>+-OIh2 zZ8WilD4KdK1~F2~Jpa$b%zQVWb7Zu9$}pOk&h$**0dozHqT4Gf=bG7On>j(=k}YhM zVi`n~0@lk{>aQxmJ9Kt;st`-Pp?YUHM7P-{nlmP}s1(TP?pWzx3_%oDhT7;Xvx_Mp zpW!TK8BoYMGTbhQagDpHIZW28 zpY+T6YaP%}E2U|tl#T@=!f;w`ZayL8XzYzjVhTaMKV_YDQ#l)lj^JV1lTT# zEH~9eIPSl8%`~b5zf?+5*0rCwS;i|S?MiDNHGgrBFtgHqywu10jddSDH7u}F~>a5hPE40X=a#X z%{Vn#{n;(#9`k$8i?EhjdBu#BU3i8N>shCITOt19+zQ~H?%385YLgtPUUt8ATRC0! zS3?;?6^ebR;%JjE8=Sk$tEPYr=5aH{T?k+vR`1zWQsEry3^kcP3vf40lyq9;G7-i{ z^Ut~NTV(GXZ|=0X%%h&=X`+OwpxSIO&znc(2la`0$mDnq(tnXw*_(PZL{?I5<`CBP z`uFVW?ww+_omP{;RjE%{o`m1k(-QKF2b$$Vfgq{zHWojKP$E)&iBsTa0R zw=G{~^0$n1!X5a@M;T6Gmfu=7Vgxf?d%J+NMd)f<$%71MzYadVjpVVCC%mgEjHJ{| zF>A~~mdR)to%ZorMj6*jLavqbWTrYvw`!Xv8Q(5kx!=W4Z@s3q%6ZG(?oM^Db}u*E z%@JlF_ht9@?xXIV?h$Sab-)v5pz)=19p9?WFng76koBNCHDkU>y)#C6x4cE1DEz$f zycfN_CPx^>3?A#m-w(?uWSKlC-^omUyzd5kv%S=+S808c`qa9`T5g5xiQ1_D^F5P0 zC~#%qcztG@i7W$SoC@qcbz zs?NxWYXcF+W_Y)TI!%gtrv#YHL(4ZZ7908LJML z@r(xG8~uqoS^q{^OlBjs`cJAabKP<7`|_dsbB7Y=nTMT^lM@q1C*~(_P5P5VlB1k& zT#tp4B4)|O?TOvrCD$fKCJW^hTl;qSmih|x!_wxc5#&25C+1$;;nHu{y+Ns zdUth{T*d3bSv{RV(tZcjZ|$|6O6`c*JV^vtD}R;0%FHYdGbLX%s<-W11D6LE_1L4w z!XEqQEb?8Y-LhrU7AVcIkbzKMU9;V6rs&D(`BX_9B)r+d9 z9rnGIGcfnw+#7Q%0zK`s?2tXm8mJGJh1|u>R0K=&ZwNG5^Fdwh@A%`#kOWwFRh@iUSFqULM&)YifAZHeuztDKj( zMjvPu_#X24?WjH=Lwssmvg-*_MloRuI!%QI^rr+cLLXZAkuf~$8>>$xn%vUbVZ_P^ zDWt|6zVG!KIP zjl0zu#4o|Ea-c+Isj1G2D>t4vjWiQ)i+U#&>PvN<{!(p~jm+R8cUR{;_Y&t5b7k7j zokGOZ7^O^PeuDVeLO%V-Cq^X*yYmxGRLUv-t=7NUn0PqRVMB`gQoTTsYs?~m7 zhyuctQo}mWMDm1-JKnijmFO?@f$AC6V796KOn>u|DKz!&A@0HMO&z>V+YF};5khDT z2w{XDLEZ}NZtr6zC+>_3$Qd%zJ)Olskv&rK%s|s%8qB5KPK=m&mM6?qrf|R4dw_ z{m7@8W;U_9-Jl9`pc(uISAYn^thspwy;kJE zV|kV#^{qNEE6p?NPwHE}mwYTAdP>%E)zd1`Q`7!s9m^cgbmp;=^(^BtuJ5qFX^MQO zU$iIsCD1c)U(V6N^5C*yO)wHTz`j+z*2VH3p@b`#EcBw4W{^Dsy{w01cw$w{Ptwc2 zNuOu5b4v0nb2S$E%Ijf|g{x+71SKV)*5>O7gpMoysA zl)LXT7nCid%;&DJo1mK&45pMm45EN$nuxNBl~i}I8NR13mcwPD`cQqZ9+5#j#Xx88 zHMkU}NAryBUA$)Rz5arggF&Xtp~6=TGLr&Y{*ip^_rv)Mt`F zN$@3K1+TR9i?w&atT8Wza+36;iRmWl{>VU(GgxCP(}Gu5@2RAOA}R>cNR(nUD_E3v z*v-U4>SJ}DzD~WWPv=&*r}K#OmUEkVFrBuTQU;}hs@kNy0G1LmCFX73GB`)LeyYqZ zWDlVScOFl*pCq$M{6haTs%)PUp}t=A-9N%!AN5tRxi13bu+?%D2bW>@cF9e!3akIPxX zh^(lAO;l6OGM1BIkjG@kSjBRpkGzNQXbsGs%p*4yrqbx0o)H9DJN?FMv6j9=ynk;uG$aHn2E=xX6JSzrq zJ0{wT`GkXey-a;sycHz6%yBqbb8G|fp_o|>VaB8K2kTk_&8V`m@|?Zf_@m+E?4unv2X2l<3m}=i9$a7A3YNZgqxdS$oQNy7aci18XLV zABRTbG@@}lC#Yt^gv~$XE~c}bCU8G=lS<1*70IUMC(KS)Yn<9Hd#9fir{(I=%;aW9 zWdtZ~>yG8z#XVh^H(&(M=s$#hA9+2J2wkn;6syg^As6^}>(R5v!rZrVoWQ<0@%+5P zd3~vD(PN!#`XW|BGsAmhB2oVa1(1(6RfT)?{PH^9s;i`xA2lW&ySxnU3 zD>)(l!H%LG;g%cY$0i?h`XvuceBP25(?tAxS&w!qKl|;49qbkb45Ex;`g9vtZVAg- zliH(bs@=m_BZX>jFA|PTtMX{r5#6~W=XK$k5n~(iG|9HnNDPf8#tPRs&zVEiTAfsT zQ${0C810_yzU6-F&NVY=ViSIrICi3H$IUyQbz7}%S^LL2T9V_0DC?l{%csy2&x*Sw zLW~BYUcXu3erICxy*fyJ1%QjC5PV%^3sIET(?uvr>xzRJAv97|EK2)er+ji_r1qRx z#98ECl6=L@ktg-O>Ts$_MVkuY$08=fWi%710MC~!!$&06lB?oVj-l{(cUXu!hlU>r*!Og_Oh?_NaMJ8hx~ zjm`+D5GR=?U=jz1IL*Y+gvrBaqRtJiH{mx=QOSKkl;^3%;Z87jQOot-Qd{G^oLIx{ zyvi%!`S>?@?+Ipvz>EQ!aC$+mCFRc=BS^5z%@aWTCpO2sVKYWP)Q>nr%m)YvHEc~O z2C`605t>zA*BOjO6Aj)~2tV=kWaKAE+^bkSyAq_BVjPOqZ~gCB1x6>nNW_>6j9^8# z9N#-c8ODjk@Uw!{TCXW0gdaaChpjl!n8B3!y2)d0;C4w>@9uNd z>F(3t$Lb)E-slrY7fA|^(Z7=B{u-WjKs5Mhc5;Sll&5F?ZpzMH56 zZ$ZcXja#VZ${?NV0yqkxE0uMOZEPdY8}CsXvivg~f?Z5FPI@W?*hq{J&E}%yQ>~TB zG0p&wP%GuUZe8#pPCcv8=#22$F;-DWjG(v5q(rVvG*ize5`;3Q=*;>y5T`IzW3$-Z z-5kdK@)h6UqPv>|eE5lZU%Juj;{NnlOnKdQ6*jMZJ#M0!CgLnNaW+uj7K=!>s6)+- zu5`+oLNy`$1PN12EMu^&B1$<$Xrf+iN~tG_pEkD4A)kKe^g%(fjGh@IhTjXq+u-&v zP0~*ts*>)}Xv;f7t0RK=iLwa`pJ#K`==5PXF`^U@?t)M7p)+Ft=VMV#8I@_SJ}7zm zN+vjOJ9l&wA7U8=)Kh>(Q&#B>Gyocl5D>zm55?rs#7g2oGh2WNrJk;!Z7`-zo^aZS zs(X-oqJEQQayQ#B79G#&N?{9m=~>+&goA^V@y(+^kOCZ%B+_%TpFDzW^Q8J18wnDl zfhw+NB@GlaPwmGGAP>uC)17NWBaPG&?q*3#5j{%CTLRk}wvJW!NDwEOMYuRYAnB2= z8O)2O7Z1xpGL}WS7Sg4l8D|3#uS3W1d)+Q`+(!u$!3QKrdLFqN@DWa*{u9N=AewoP z5;Qf$FaA?t`!mfEysB1|PJvoR=@ z3h&V+nu*a&2w0V#U9%UkD7B<)ZrLwuL)&6GnErB=eopVJZnP9Z^I%l@wzUX0&?Dy43fUU9G<36`F_wQBFyR=WK6&<`-_nVO<8RzBBLOAmzka zL6jzHGK98@w9iP4HN+VNoWmSqGLamN^3LPSMEh$RAzOp?tB_BGO8S#e0~IpC+Rr!E zw})M>zW3B_an=y+z)!@pYR zs7`Z0u{Y(xP(F|=)KAXGzc^NCkmB^{t|v@Cg1|~Fc9kFXZfb?O$+^y~bsk_5u(E?| zL#ol4lExN}#^PQ|+s16%qyNAvq=x`j0h!Rr!`g;ckTE9zvA3rKl;_T?)^EaCM z^x=NNG)Fm@NtDpY0-gkQh1p=Tt^Yemh$0Hu!Z4axoF>jtYQ^$7>N`g7*dI4_(o{A! zrW4mOSPikJ+Wm9x_d;d)1-IHaudSBLO?FfN(cgA9mbxILv8Pm*z z+~g@W*O>+`m#t*Rw%bX}ku;jJ^p$;pMP?1xFcJ%;a672EN-_kETB(r_C8GbVKGA#X zJps5&?vTD>$rc$V2mZ_#NqOjmi4dmPt#xcSfL~oKjSTCcNsd!S5QtEo#cRyxkPgPu zYPm`suLh_C)n00V`ejpe=HwYB1m<0Hf`n1{Qs=j3Vo-)e6=S?SDZSNZ`zGHz{;T}o z`H%AN?$;%bch-2>bo+#aSt^|UtFgRS%JldX@elkL&IBmH*ZFM;m@{d4vT*nxfR%x&|9 zl+q8s7n;Q)Mm<3@$NfCH)ZK?$RYHBQew0dYbZe?Omtj;U53YMKgZw}^RT(n z{oMVN8*sk>hRHj+Nyc`{(bnWl7Cw&x27&3%O{_=}Alab@BnYR&X>T-@PQTVeTIaXs zCu>|EF`CQ_<1-O1CnmGxIMt$V)O+f2me2Q)-5!fn6C@wMcNSa&jjSU|vHP&|&t$?q zR<2;S`GK>%O~nvM;l}r z=Tenc_k)jSd|-N*J6VpMB0x53Sw(Z3+;5a~cJh??G4VgdTN6LD4tMT#`Z}%7S5B*Q zg!6yykM5bK+8oV;)KIF9)W>^eq>eMxgozWQo_cCoz?-JOvo<**S!YUQwtJNO1gn55 zA`D|28>pj(T2|pOLEe!`@{L|#Z?@~K^VRWs7yX>|f&GnjzuH?iTO<9u`3I=m^g-5P z_BYlE_W9N(dz8Ol;9dXYzOBAV8QXtH0O5>dA@k6Lfzy~lGL)cu*a*AnWzWJK_2z%%MKuV@-_H;UJy=fImHkgU-F*1?YxFyZn zHgsTdR}rL%l;f{Mw*AsAv}rgK47ll^JIc^xrjr|WUl2d{y;TBmdQK@ zVR59{Q);}u#`CElNT$ z{uu9XB5gZrJMX={ad-t9V&vi@zz*+JcSH7>@oo7Hezv2rXvISCld?_O*toQM%@h~X!JrZrs)@jgVWcjfRs`u_pE WC}ig2^aR-e0000wfJGnLOlm7m*dv!o+52RoFIQj98`eRqEHs)NAR(zbFUz!D;z5Y ugOr!JdGrzxcxJBn`^-h_`WGNGAN=>LVdY3T$Fo)n#PxLbb6Mw<&;$UZr9Vjk diff --git a/Tests/images/imagedraw_rectangle_I.tiff b/Tests/images/imagedraw_rectangle_I.tiff new file mode 100644 index 0000000000000000000000000000000000000000..9b9eda883a371d9cc88b4677b09d2e351c42e609 GIT binary patch literal 20122 zcmeI&F$%&!6h+Y=Bch!)+E`e-lQgcvRk-;$N{W=PF$6gQVHmDR4zF|=?zOL~Lshq} zuAQFk)6LGCuK(U2+kHQsKizJ2K280r-JfUQy-erZqv|^ATjn7^fB*pk1PBlyK!5-N z0t5&UAV6T10^=gj?_Yf!{YEOlDwV2Y#VQxbOS@u~3*@C;vC0MV(ymzL0(ogyta5?8 zv@2G*KwjDvt6U&2?TS?{ke7DFDi_F0yJD3K None: # https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8 # https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/ test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara" - saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png" + saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff" # Act with Image.open(test_file) as im: diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 334839f5c..ddba4b5b4 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -102,7 +102,7 @@ class TestFilePng: im = hopper(mode) im.save(test_file) with Image.open(test_file) as reloaded: - if mode in ("I;16", "I;16B"): + if mode in ("I", "I;16B"): reloaded = reloaded.convert(mode) assert_image_equal(reloaded, im) @@ -304,8 +304,8 @@ class TestFilePng: assert im.getcolors() == [(100, (0, 0, 0, 0))] def test_save_grayscale_transparency(self, tmp_path: Path) -> None: - for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): - in_file = "Tests/images/" + mode.lower() + "_trns.png" + for mode, num_transparent in {"1": 1994, "L": 559, "I;16": 559}.items(): + in_file = "Tests/images/" + mode.split(";")[0].lower() + "_trns.png" with Image.open(in_file) as im: assert im.mode == mode assert im.info["transparency"] == 255 diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 6e0fa32e4..6a0a5a445 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -88,7 +88,7 @@ def test_16bit_pgm() -> None: assert im.size == (20, 100) assert im.get_format_mimetype() == "image/x-portable-graymap" - assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png") + assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff") def test_16bit_pgm_write(tmp_path: Path) -> None: diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 6a10ae453..47f9ffa3d 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -148,9 +148,7 @@ def test_kernel_not_enough_coefficients() -> None: @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) def test_consistency_3x3(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: - reference_name = "hopper_emboss" - reference_name += "_I.png" if mode == "I" else ".bmp" - with Image.open("Tests/images/" + reference_name) as reference: + with Image.open("Tests/images/hopper_emboss.bmp") as reference: kernel = ImageFilter.Kernel( (3, 3), # fmt: off @@ -160,23 +158,13 @@ def test_consistency_3x3(mode: str) -> None: # fmt: on 0.3, ) - source = source.split() * 2 - reference = reference.split() * 2 - - if mode == "I": - source = source[0].convert(mode) - else: - source = Image.merge(mode, source[: len(mode)]) - reference = Image.merge(mode, reference[: len(mode)]) assert_image_equal(source.filter(kernel), reference) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) def test_consistency_5x5(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: - reference_name = "hopper_emboss_more" - reference_name += "_I.png" if mode == "I" else ".bmp" - with Image.open("Tests/images/" + reference_name) as reference: + with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: kernel = ImageFilter.Kernel( (5, 5), # fmt: off @@ -188,14 +176,6 @@ def test_consistency_5x5(mode: str) -> None: # fmt: on 0.3, ) - source = source.split() * 2 - reference = reference.split() * 2 - - if mode == "I": - source = source[0].convert(mode) - else: - source = Image.merge(mode, source[: len(mode)]) - reference = Image.merge(mode, reference[: len(mode)]) assert_image_equal(source.filter(kernel), reference) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index f7aea3034..274753c6c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -753,7 +753,7 @@ def test_rectangle_I16(bbox: Coords) -> None: draw.rectangle(bbox, outline=0xFFFF) # Assert - assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png") + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff") @pytest.mark.parametrize("bbox", BBOX) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ba81a22c7..5d9a3fb21 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -978,7 +978,7 @@ class Image: delete_trns = False # transparency handling if has_transparency: - if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or ( + if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( self.mode == "RGB" and mode == "RGBA" ): # Use transparent conversion to promote from transparent diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1248fb785..35f38d67c 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -62,7 +62,7 @@ _MODES = { (2, 0): ("L", "L;2"), (4, 0): ("L", "L;4"), (8, 0): ("L", "L"), - (16, 0): ("I", "I;16B"), + (16, 0): ("I;16", "I;16B"), # Truecolour (8, 2): ("RGB", "RGB"), (16, 2): ("RGB", "RGB;16B"), @@ -467,7 +467,7 @@ class PngStream(ChunkStream): # otherwise, we have a byte string with one alpha value # for each palette entry self.im_info["transparency"] = s - elif self.im_mode in ("1", "L", "I"): + elif self.im_mode in ("1", "L", "I;16"): self.im_info["transparency"] = i16(s) elif self.im_mode == "RGB": self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) @@ -1356,7 +1356,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): transparency = max(0, min(255, transparency)) alpha = b"\xFF" * transparency + b"\0" chunk(fp, b"tRNS", alpha[:alpha_bytes]) - elif im.mode in ("1", "L", "I"): + elif im.mode in ("1", "L", "I", "I;16"): transparency = max(0, min(65535, transparency)) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 99d2a4ada..2654fd40d 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -878,6 +878,18 @@ I16B_L(UINT8 *out, const UINT8 *in, int xsize) { } } +static void +I16_RGB(UINT8 *out, const UINT8 *in, int xsize) { + int x; + for (x = 0; x < xsize; x++, in += 2) { + UINT8 v = in[1] == 0 ? in[0] : 255; + *out++ = v; + *out++ = v; + *out++ = v; + *out++ = 255; + } +} + static struct { const char *from; const char *to; @@ -978,6 +990,7 @@ static struct { {"I", "I;16", I_I16L}, {"I;16", "I", I16L_I}, + {"I;16", "RGB", I16_RGB}, {"L", "I;16", L_I16L}, {"I;16", "L", I16L_L}, @@ -1678,6 +1691,7 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { convert = rgb2rgba; } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || + strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0 ) && ( strcmp(mode, "RGBA") == 0 || @@ -1687,6 +1701,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { convert = bit2rgb; } else if (strcmp(imIn->mode, "I") == 0) { convert = i2rgb; + } else if (strcmp(imIn->mode, "I;16") == 0) { + convert = I16_RGB; } else { convert = l2rgb; } From 07bf12f015cd418378bbf753fea3799904e4493f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Mar 2024 23:36:19 +1100 Subject: [PATCH 127/157] Build macOS arm64 wheels natively --- .github/workflows/wheels-dependencies.sh | 16 +++++----------- .github/workflows/wheels-test.sh | 3 +++ .github/workflows/wheels.yml | 4 ++-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index cc8d7e085..9645e16ac 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -62,7 +62,7 @@ function build_brotli { function build { if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then - export BUILD_PREFIX="/usr/local" + sudo chown -R runner /usr/local fi build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then @@ -75,8 +75,8 @@ function build { build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist - if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then - cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc + if [[ "$CIBW_ARCHS" == "arm64" ]]; then + cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig fi else sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc @@ -87,11 +87,6 @@ function build { build_tiff build_libpng build_lcms2 - if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then - for dylib in libjpeg.dylib libtiff.dylib liblcms2.dylib; do - cp $BUILD_PREFIX/lib/$dylib /opt/arm64-builds/lib - done - fi build_openjpeg if [ -f /usr/local/lib64/libopenjp2.so ]; then cp /usr/local/lib64/libopenjp2.so /usr/local/lib @@ -131,14 +126,13 @@ curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-de untar pillow-depends-main.zip if [[ -n "$IS_MACOS" ]]; then - # webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb + # libtiff and libxcb cause a conflict with building libtiff and libxcb # libxau and libxdmcp cause an issue on macOS < 11 - # if php is installed, brew tries to reinstall these after installing openblas # remove cairo to fix building harfbuzz on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64 # remove zstd to avoid inclusion on x86_64 # curl from brew requires zstd, use system curl - brew remove --ignore-dependencies webp libpng libtiff libxcb libxau libxdmcp curl php cairo lcms2 ghostscript zstd + brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd brew install pkg-config fi diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 207ec1567..3fbf3be69 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -4,6 +4,9 @@ set -e if [[ "$OSTYPE" == "darwin"* ]]; then brew install fribidi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" + if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then + sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib + fi elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi else diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1140aaaad..e4008489b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -99,7 +99,7 @@ jobs: cibw_arch: x86_64 macosx_deployment_target: "10.10" - name: "macOS arm64" - os: macos-latest + os: macos-14 cibw_arch: arm64 macosx_deployment_target: "11.0" - name: "manylinux2014 and musllinux x86_64" @@ -132,7 +132,7 @@ jobs: CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_SKIP: pp38-* - CIBW_TEST_SKIP: "*-macosx_arm64" + CIBW_TEST_SKIP: cp38-macosx_arm64 MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - uses: actions/upload-artifact@v4 From d3a394fcbb4832d78c39042db56ea22ae88bde45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Mar 2024 04:00:51 +0000 Subject: [PATCH 128/157] Update dependency mypy to v1.8.0 --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index ed3269460..3673e1d81 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1 +1 @@ -mypy==1.7.1 +mypy==1.8.0 From 1b7bcfb6e71bc2c8a02ee4334b8dbd3b5db6e6e6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 Mar 2024 21:34:48 +1100 Subject: [PATCH 129/157] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7adcf1b40..44dfa65f7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Open 16-bit grayscale PNGs as I;16 #7849 + [radarhere] + - Handle truncated chunks at the end of PNG images #7709 [lajiyuan, radarhere] From 33d92819ef9e6327ec8e3fc2b2b68e9f05d65fc0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 Mar 2024 07:59:36 -0700 Subject: [PATCH 130/157] Include SF_PROJECTS in f-strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- winbuild/build_prepare.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index efffbf5ac..865e08c22 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -132,7 +132,7 @@ V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "") # dependencies, listed in order of compilation DEPS = { "libjpeg": { - "url": SF_PROJECTS + f"/libjpeg-turbo/files/{V['JPEGTURBO']}/" + "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/" f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download", "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", "dir": f"libjpeg-turbo-{V['JPEGTURBO']}", @@ -177,7 +177,7 @@ DEPS = { "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + f"/lzmautils/files/xz-{V['XZ']}.tar.gz/download", + "url": f"{SF_PROJECTS}/lzmautils/files/xz-{V['XZ']}.tar.gz/download", "filename": f"xz-{V['XZ']}.tar.gz", "dir": f"xz-{V['XZ']}", "license": "COPYING", @@ -244,7 +244,7 @@ DEPS = { "libs": [r"libtiff\*.lib"], }, "libpng": { - "url": SF_PROJECTS + f"/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/" + "url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/" f"lpng{V['LIBPNG_DOTLESS']}.zip/download", "filename": f"lpng{V['LIBPNG_DOTLESS']}.zip", "dir": f"lpng{V['LIBPNG_DOTLESS']}", @@ -305,8 +305,7 @@ DEPS = { "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], }, "lcms2": { - "url": SF_PROJECTS - + f"/lcms/files/lcms/{V['LCMS2']}/lcms2-{V['LCMS2']}.tar.gz/download", + "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/lcms2-{V['LCMS2']}.tar.gz/download", # noqa: E501 "filename": f"lcms2-{V['LCMS2']}.tar.gz", "dir": f"lcms2-{V['LCMS2']}", "license": "LICENSE", From b3fa754e971722f31c4c75271d58062f3b15997b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:11:30 +0200 Subject: [PATCH 131/157] Don't shadow stdlib names --- winbuild/build_prepare.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 865e08c22..657e2f6ec 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -87,7 +87,7 @@ def cmd_msbuild( file: str, configuration: str = "Release", target: str = "Build", - platform: str = "{msbuild_arch}", + plat: str = "{msbuild_arch}", ) -> str: return " ".join( [ @@ -95,7 +95,7 @@ def cmd_msbuild( f"{file}", f'/t:"{target}"', f'/p:Configuration="{configuration}"', - f"/p:Platform={platform}", + f"/p:Platform={plat}", "/m", ] ) @@ -567,7 +567,7 @@ def build_env(prefs: dict[str, str], verbose: bool) -> None: def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: dep = DEPS[name] - dir = dep["dir"] + directory = dep["dir"] file = f"build_dep_{name}.cmd" license_dir = prefs["license_dir"] sources_dir = prefs["src_dir"] @@ -579,18 +579,18 @@ def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: licenses = [licenses] license_text = "" for license_file in licenses: - with open(os.path.join(sources_dir, dir, license_file)) as f: + with open(os.path.join(sources_dir, directory, license_file)) as f: license_text += f.read() if "license_pattern" in dep: match = re.search(dep["license_pattern"], license_text, re.DOTALL) license_text = "\n".join(match.groups()) assert len(license_text) > 50 - with open(os.path.join(license_dir, f"{dir}.txt"), "w") as f: - print(f"Writing license {dir}.txt") + with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f: + print(f"Writing license {directory}.txt") f.write(license_text) for patch_file, patch_list in dep.get("patch", {}).items(): - patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs)) + patch_file = os.path.join(sources_dir, directory, patch_file.format(**prefs)) with open(patch_file) as f: text = f.read() for patch_from, patch_to in patch_list.items(): @@ -602,13 +602,13 @@ def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: print(f"Patching {patch_file}") f.write(text) - banner = f"Building {name} ({dir})" + banner = f"Building {name} ({directory})" lines = [ r'call "{build_dir}\build_env.cmd"', "@echo " + ("=" * 70), f"@echo ==== {banner:<60} ====", "@echo " + ("=" * 70), - cmd_cd(os.path.join(sources_dir, dir)), + cmd_cd(os.path.join(sources_dir, directory)), *dep.get("build", []), *get_footer(dep), ] From e2c267f0484d81f10b636ffeb810676db31448b8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:11:59 +0200 Subject: [PATCH 132/157] Explicitly import urllib.error to avoid IDE warning --- winbuild/build_prepare.py | 1 + 1 file changed, 1 insertion(+) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 657e2f6ec..b9df8be63 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -463,6 +463,7 @@ def find_msvs(architecture: str) -> dict[str, str] | None: def download_dep(url: str, file: str) -> None: + import urllib.error import urllib.request ex = None From 91eb69c6b2235b01aaae5904b33185deea6257e9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Mar 2024 06:37:56 +1100 Subject: [PATCH 133/157] Fixed typo --- Tests/test_file_spider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index fe71435cc..9b82a962a 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -152,7 +152,7 @@ def test_nonstack_dos() -> None: assert i <= 1, "Non-stack DOS file test failed" -# for issue #4093s +# for issue #4093 def test_odd_size() -> None: data = BytesIO() width = 100 From 14f6a3445c674fbe1de609e7ebae0cc660c1e76c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Mar 2024 07:43:40 +1100 Subject: [PATCH 134/157] Removed brew packages to avoid inclusion --- .github/workflows/wheels-dependencies.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 9645e16ac..581c71253 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -130,9 +130,15 @@ if [[ -n "$IS_MACOS" ]]; then # libxau and libxdmcp cause an issue on macOS < 11 # remove cairo to fix building harfbuzz on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64 - # remove zstd to avoid inclusion on x86_64 + # remove jpeg-turbo to avoid inclusion on arm64 + # remove webp and zstd to avoid inclusion on x86_64 # curl from brew requires zstd, use system curl brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd + if [[ "$CIBW_ARCHS" == "arm64" ]]; then + brew remove --ignore-dependencies jpeg-turbo + else + brew remove --ignore-dependencies webp + fi brew install pkg-config fi From a4ac095a4972d1fd7d9a4ee0b3c1b5de3eb9fdf3 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:06:02 +1100 Subject: [PATCH 135/157] List extensions alphabetically --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b7ad57084..40dd4d758 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,8 +35,8 @@ extensions = [ "sphinx_copybutton", "sphinx_inline_tabs", "sphinx_removed_in", - "sphinxext.opengraph", "sphinx_reredirects", + "sphinxext.opengraph", ] intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} From 71018badc0d88cb972c04f8eec4af274820c438a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 4 Mar 2024 23:35:10 -0700 Subject: [PATCH 136/157] Remove unused LIBXCB constant Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- winbuild/build_prepare.py | 1 - 1 file changed, 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index b9df8be63..0abf624b7 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -118,7 +118,6 @@ V = { "LCMS2": "2.16", "LIBPNG": "1.6.39", "LIBWEBP": "1.3.2", - "LIBXCB": "1.16", "OPENJPEG": "2.5.2", "TIFF": "4.6.0", "XZ": "5.4.5", From fe8f829f5802c242252b8163351967ff07c52cbe Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 6 Mar 2024 07:35:11 +1100 Subject: [PATCH 137/157] Mark file as orphan --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 57659ba48..b4bf2fa00 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,3 +1,5 @@ +:orphan: + Installation ============ From 7aba72a48f1b55a3716409f66e772af740f40616 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Mar 2024 08:44:07 +1100 Subject: [PATCH 138/157] Removed unused sphinx-reredirects --- docs/Makefile | 2 +- docs/conf.py | 4 ---- pyproject.toml | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 24fd05aa2..3b4deb9bf 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,7 +46,7 @@ clean: -rm -rf $(BUILDDIR)/* install-sphinx: - $(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinx-reredirects sphinxext-opengraph + $(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinxext-opengraph .PHONY: html html: diff --git a/docs/conf.py b/docs/conf.py index 40dd4d758..97289c91d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,7 +35,6 @@ extensions = [ "sphinx_copybutton", "sphinx_inline_tabs", "sphinx_removed_in", - "sphinx_reredirects", "sphinxext.opengraph", ] @@ -351,6 +350,3 @@ ogp_image = ( "pillow-logo-dark-text-1280x640.png" ) ogp_image_alt = "Pillow" - -# sphinx-reredirects -# redirects = {"installation.html": "installation/index.html"} diff --git a/pyproject.toml b/pyproject.toml index b09aa642c..518facc34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,6 @@ docs = [ "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", - "sphinx-reredirects", "sphinxext-opengraph", ] fpx = [ From 128d0edbd6101ce7b8a38755624186d59a9c0fc3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:22:39 +0200 Subject: [PATCH 139/157] Move anchors back into place --- docs/installation/basic-installation.rst | 8 -------- docs/installation/building-from-source.rst | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index 486e6863b..01aae0406 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -87,11 +87,3 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: The `Pillow FreeBSD port `_ and packages are tested by the ports team with all supported FreeBSD versions. - - -.. _Building on Linux: -.. _Building on macOS: -.. _Building on Windows: -.. _Building on Windows using MSYS2/MinGW: -.. _Building on FreeBSD: -.. _Building on Android: diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 8f4ef9e3e..4cb116a6a 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -1,3 +1,9 @@ +.. _Building on Linux: +.. _Building on macOS: +.. _Building on Windows: +.. _Building on Windows using MSYS2/MinGW: +.. _Building on FreeBSD: +.. _Building on Android: .. _building-from-source: Building From Source From a027f698d2dead6dff22686ad8c78072d4768611 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:28:06 +0200 Subject: [PATCH 140/157] Fix ref link --- docs/installation/basic-installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index 01aae0406..44e0f3052 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -68,7 +68,7 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow - To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_. + To install Pillow in MSYS2, see :ref:`Building on Windows using MSYS2/MinGW`. .. tab:: FreeBSD From 3106446dade24fd6ded018ac6492057589747bf5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:29:16 +0200 Subject: [PATCH 141/157] Move short 'Old Versions' section to 'Building From Source' page --- docs/installation/building-from-source.rst | 9 +++++++++ docs/installation/index.rst | 1 - docs/installation/old-versions.rst | 8 -------- 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 docs/installation/old-versions.rst diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 4cb116a6a..628acfc38 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -304,3 +304,12 @@ Build Options Sample usage:: python3 -m pip install --upgrade Pillow -C [feature]=enable + +.. _old-versions: + +Old Versions +============ + +You can download old distributions from the `release history at PyPI +`_ and by direct URL access +eg. https://pypi.org/project/pillow/1.0/. diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 9e6e5eeec..a94204b6b 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -8,4 +8,3 @@ Installation python-support platform-support building-from-source - old-versions diff --git a/docs/installation/old-versions.rst b/docs/installation/old-versions.rst deleted file mode 100644 index 445a70d4e..000000000 --- a/docs/installation/old-versions.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _old-versions: - -Old Versions ------------- - -You can download old distributions from the `release history at PyPI -`_ and by direct URL access -eg. https://pypi.org/project/pillow/1.0/. From e9f82959509192a96b933ee344baa580f4bc7a49 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:34:47 +0200 Subject: [PATCH 142/157] Consistent header underlines --- docs/installation/basic-installation.rst | 2 +- docs/installation/building-from-source.rst | 8 ++++---- docs/installation/platform-support.rst | 6 +++--- docs/installation/python-support.rst | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index 44e0f3052..04d9aca02 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -1,7 +1,7 @@ .. _basic-installation: Basic Installation ------------------- +================== .. note:: diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 628acfc38..6b24eeffa 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -7,12 +7,12 @@ .. _building-from-source: Building From Source --------------------- +==================== .. _external-libraries: External Libraries -^^^^^^^^^^^^^^^^^^ +------------------ .. note:: @@ -227,7 +227,7 @@ Many of Pillow's features require external libraries: This has been tested within the Termux app on ChromeOS, on x86. Installing -^^^^^^^^^^ +---------- Once you have installed the prerequisites, to install Pillow from the source code on PyPI, run:: @@ -262,7 +262,7 @@ After navigating to the Pillow directory, run:: .. _compressed archive from PyPI: https://pypi.org/project/pillow/#files Build Options -""""""""""""" +^^^^^^^^^^^^^ * Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use multiprocessing to build the extension. Setting ``MAX_CONCURRENCY`` diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 602941c3c..9ae97d70a 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -1,7 +1,7 @@ .. _platform-support: Platform Support ----------------- +================ Current platform support for Pillow. Binary distributions are contributed for each release on a volunteer basis, but the source @@ -10,7 +10,7 @@ general, we aim to support all current versions of Linux, macOS, and Windows. Continuous Integration Targets -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +------------------------------ These platforms are built and tested for every change. @@ -66,7 +66,7 @@ These platforms are built and tested for every change. Other Platforms -^^^^^^^^^^^^^^^ +--------------- These platforms have been reported to work at the versions mentioned. diff --git a/docs/installation/python-support.rst b/docs/installation/python-support.rst index 8d7db8d3a..0b366a8cd 100644 --- a/docs/installation/python-support.rst +++ b/docs/installation/python-support.rst @@ -1,7 +1,7 @@ .. _python-support: Python Support --------------- +============== Pillow supports these Python versions. From 6eb6b52a68992720db3c605f33fa27ac9fce19fd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:42:01 +0200 Subject: [PATCH 143/157] Fix tab activation per OS --- docs/installation/basic-installation.rst | 8 ++++++++ docs/installation/building-from-source.rst | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index 04d9aca02..c2f889f0e 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -1,3 +1,11 @@ +.. raw:: html + + + .. _basic-installation: Basic Installation diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 6b24eeffa..05e657272 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -1,3 +1,11 @@ +.. raw:: html + + + .. _Building on Linux: .. _Building on macOS: .. _Building on Windows: From d2d6beaf34b1775e5634bc1406e264b3b0c88b23 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 Mar 2024 19:24:37 +1100 Subject: [PATCH 144/157] Simplified anchors --- docs/installation/basic-installation.rst | 2 +- docs/installation/building-from-source.rst | 6 ------ docs/installation/python-support.rst | 5 ----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index c2f889f0e..01981aa4f 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -76,7 +76,7 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow - To install Pillow in MSYS2, see :ref:`Building on Windows using MSYS2/MinGW`. + To install Pillow in MSYS2, see :ref:`building-from-source`. .. tab:: FreeBSD diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 05e657272..349d191a6 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -6,12 +6,6 @@ }); -.. _Building on Linux: -.. _Building on macOS: -.. _Building on Windows: -.. _Building on Windows using MSYS2/MinGW: -.. _Building on FreeBSD: -.. _Building on Android: .. _building-from-source: Building From Source diff --git a/docs/installation/python-support.rst b/docs/installation/python-support.rst index 0b366a8cd..dd5765b6b 100644 --- a/docs/installation/python-support.rst +++ b/docs/installation/python-support.rst @@ -12,8 +12,3 @@ Pillow supports these Python versions. .. csv-table:: Older versions :file: older-versions.csv :header-rows: 1 - -.. _Linux Installation: -.. _macOS Installation: -.. _Windows Installation: -.. _FreeBSD Installation: From 35b803091bb84ed920f26b9d51c2b97f89363400 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:39:27 +0200 Subject: [PATCH 145/157] Simplify rf to f-string Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0abf624b7..b87b828bf 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -255,7 +255,7 @@ DEPS = { ), ], "headers": [r"png*.h"], - "libs": [rf"libpng{V['LIBPNG_XY']}.lib"], + "libs": [f"libpng{V['LIBPNG_XY']}.lib"], }, "brotli": { "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz", From c4067b08eb2baf2c12de36d3bbbdd3771b5fcf8a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 10:46:56 -0500 Subject: [PATCH 146/157] Update "What about PIL?" section PyPI moderators gave PIL project to Pillow team in January 2020 --- docs/about.rst | 5 ++++- docs/conf.py | 2 +- docs/resources/css/strike.css | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 docs/resources/css/strike.css diff --git a/docs/about.rst b/docs/about.rst index cdb32ca5d..73f7ac45d 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,4 +35,7 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement. +.. role:: strike + :class: strike + +As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. :strike:`However, we've yet to hear an official "PIL is dead" announcement.` In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. diff --git a/docs/conf.py b/docs/conf.py index 97289c91d..d1505078d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -167,7 +167,7 @@ html_static_path = ["resources"] # directly to the root of the documentation. # html_extra_path = [] -html_css_files = ["css/dark.css"] +html_css_files = ["css/dark.css", "css/strike.css"] html_js_files = [ "js/activate_tab.js", diff --git a/docs/resources/css/strike.css b/docs/resources/css/strike.css new file mode 100644 index 000000000..1de4c484a --- /dev/null +++ b/docs/resources/css/strike.css @@ -0,0 +1,3 @@ +.strike { + text-decoration: line-through; +} From 2078eb4e4e96b298f6ff5702f06861604c1d32c5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:44:38 +0200 Subject: [PATCH 147/157] Update CI targets on GitHub Actions --- docs/installation/platform-support.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 9ae97d70a..a380c9012 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -41,13 +41,15 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | +| macOS 12 Monterey | 3.8, 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13 | arm64 | +| | PyPy3 | | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | +| | 3.12, 3.13, PyPy3 | | | +----------------------------+---------------------+ | | 3.10 | arm64v8, ppc64le, | | | | s390x | @@ -55,7 +57,7 @@ These platforms are built and tested for every change. | Windows Server 2016 | 3.8 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 | -| | 3.12, PyPy3 | | +| | 3.12, 3.13, PyPy3 | | | +----------------------------+---------------------+ | | 3.12 | x86 | | +----------------------------+---------------------+ From da5de3c115c6558042ef2861023fb1f7d4a71827 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark (Alex)" Date: Thu, 7 Mar 2024 11:50:22 -0500 Subject: [PATCH 148/157] Update docs/about.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- docs/about.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/about.rst b/docs/about.rst index 73f7ac45d..9cfda53a4 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,7 +35,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -.. role:: strike - :class: strike - -As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. :strike:`However, we've yet to hear an official "PIL is dead" announcement.` In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. +As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. Although we've yet to hear an official "PIL is dead" announcement, in January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. From debf5565251539623b1727a50084fa5c48ddc198 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 11:59:50 -0500 Subject: [PATCH 149/157] Remove strike css --- docs/resources/css/strike.css | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/resources/css/strike.css diff --git a/docs/resources/css/strike.css b/docs/resources/css/strike.css deleted file mode 100644 index 1de4c484a..000000000 --- a/docs/resources/css/strike.css +++ /dev/null @@ -1,3 +0,0 @@ -.strike { - text-decoration: line-through; -} From dec53f10d1273dbafb11144fd10b81fff3ce7322 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 12:02:22 -0500 Subject: [PATCH 150/157] Wording --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index 9cfda53a4..3e717e798 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,4 +35,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. Although we've yet to hear an official "PIL is dead" announcement, in January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. +The last PIL release was in 2009 (1.1.7) and no future releases are expected. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. From 06c8edb98cbee22302da89b0b44bd77487cfb82d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 12:06:04 -0500 Subject: [PATCH 151/157] Link to #1535 --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index 3e717e798..eb2832475 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,4 +35,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -The last PIL release was in 2009 (1.1.7) and no future releases are expected. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. +The last PIL release was in 2009 (1.1.7) and `no future releases are expected `_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. From e734dac917b006bb8cded1dc12cfdf453b6b5d77 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 12:18:11 -0500 Subject: [PATCH 152/157] Remove strike config --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d1505078d..97289c91d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -167,7 +167,7 @@ html_static_path = ["resources"] # directly to the root of the documentation. # html_extra_path = [] -html_css_files = ["css/dark.css", "css/strike.css"] +html_css_files = ["css/dark.css"] html_js_files = [ "js/activate_tab.js", From f16600640999bddd7fcb6cb5c555fe93e4105edc Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 7 Mar 2024 14:43:42 -0500 Subject: [PATCH 153/157] Wording --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index eb2832475..ab1a3eb4a 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -23,7 +23,7 @@ Like PIL, Pillow is `licensed under the open source HPND License Date: Thu, 7 Mar 2024 14:54:23 -0500 Subject: [PATCH 154/157] Wording --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index ab1a3eb4a..58be7676b 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,4 +35,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -The last PIL release was in 2009 (1.1.7) and `no future releases are expected `_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. +The last PIL release was in 2009 (1.1.7) and `no future releases are expected `_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. The Pillow team has no plans to update the PIL project on PyPI. From 5e9ab05b039b2ba1eaf33b605807bb83812e6de1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:55:38 +0000 Subject: [PATCH 155/157] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index 58be7676b..98cdd8e5a 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,4 +35,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -The last PIL release was in 2009 (1.1.7) and `no future releases are expected `_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. The Pillow team has no plans to update the PIL project on PyPI. +The last PIL release was in 2009 (1.1.7) and `no future releases are expected `_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner `_ and the `PIL project on PyPI `_ was transferred to the `Pillow team `_. The Pillow team has no plans to update the PIL project on PyPI. From 2833e367f57df53b865a9d78b70eb86d4176f7ef Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:31:55 +0200 Subject: [PATCH 156/157] Remove comma Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/installation/platform-support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index a380c9012..59fc312ab 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -43,7 +43,7 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | macOS 12 Monterey | 3.8, 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13 | arm64 | +| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 | | | PyPy3 | | +----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | From 984893576f9038cbaa2932c3ef493b870438137c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:12:47 -0700 Subject: [PATCH 157/157] Remove unused pillow_dir Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- winbuild/build_prepare.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index b87b828bf..99c7228fa 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -639,7 +639,6 @@ def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> def main() -> None: winbuild_dir = os.path.dirname(os.path.realpath(__file__)) - pillow_dir = os.path.realpath(os.path.join(winbuild_dir, "..")) parser = argparse.ArgumentParser( prog="winbuild\\build_prepare.py", @@ -745,7 +744,6 @@ def main() -> None: "architecture": args.architecture, **arch_prefs, # Pillow paths - "pillow_dir": pillow_dir, "winbuild_dir": winbuild_dir, # Build paths "bin_dir": bin_dir,