From 61753891869d75bff3ce9256b684d3da0762f01a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Jan 2021 12:45:02 +1100 Subject: [PATCH 001/133] Only read different sizes for "Large Thumbnail" frames --- Tests/images/ignore_frame_size.mpo | Bin 0 -> 4405 bytes Tests/test_file_mpo.py | 13 ++++++++++++- src/PIL/MpoImagePlugin.py | 8 +++++--- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 Tests/images/ignore_frame_size.mpo diff --git a/Tests/images/ignore_frame_size.mpo b/Tests/images/ignore_frame_size.mpo new file mode 100644 index 0000000000000000000000000000000000000000..c4d60707a47f18ffe644a27443b974b3c3c613f2 GIT binary patch literal 4405 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 h33pE!X2e@Fx7){P#9i~X->y}7kfIL?53 Date: Fri, 1 Jan 2021 13:00:01 +1100 Subject: [PATCH 002/133] Changed MP Type to match #1631 image --- Tests/images/sugarshack_frame_size.mpo | Bin 120198 -> 120198 bytes Tests/test_file_mpo.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo index 81d58e64b8268eac4210f1506b4dbded0b92ffda..009280a79a648be15d6064c96f931e71ce9fde8e 100644 GIT binary patch delta 22 ecmZo$&EB?}eZx_4W=00a<`d%EPlz+_+Y10*> Date: Wed, 25 Nov 2020 08:29:34 +0000 Subject: [PATCH 003/133] add raqm to thirdparty directory --- src/thirdparty/raqm/raqm-version.h | 44 + src/thirdparty/raqm/raqm.c | 2069 ++++++++++++++++++++++++++++ src/thirdparty/raqm/raqm.h | 185 +++ 3 files changed, 2298 insertions(+) create mode 100644 src/thirdparty/raqm/raqm-version.h create mode 100644 src/thirdparty/raqm/raqm.c create mode 100644 src/thirdparty/raqm/raqm.h diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h new file mode 100644 index 000000000..4fd5c6842 --- /dev/null +++ b/src/thirdparty/raqm/raqm-version.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR 0 +#define RAQM_VERSION_MINOR 7 +#define RAQM_VERSION_MICRO 1 + +#define RAQM_VERSION_STRING "0.7.1" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c new file mode 100644 index 000000000..27e59b5fc --- /dev/null +++ b/src/thirdparty/raqm/raqm.c @@ -0,0 +1,2069 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier +#endif + +#include +#include + +#include +#include +#include + +#include "raqm.h" + +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif + +/** + * SECTION:raqm + * @title: Raqm + * @short_description: A library for complex text layout + * @include: raqm.h + * + * Raqm is a light weight text layout library with strong emphasis on + * supporting languages and writing systems that require complex text layout. + * + * The main object in Raqm API is #raqm_t, it stores all the states of the + * input text, its properties, and the output of the layout process. + * + * To start, you create a #raqm_t object, add text and font(s) to it, run the + * layout process, and finally query about the output. For example: + * + * |[ + * #include "raqm.h" + * + * int + * main (int argc, char *argv[]) + * { + * const char *fontfile; + * const char *text; + * const char *direction; + * const char *language; + * int ret = 1; + * + * FT_Library library = NULL; + * FT_Face face = NULL; + * + * if (argc < 5) + * { + * printf ("Usage: %s FONT_FILE TEXT DIRECTION LANG\n", argv[0]); + * return 1; + * } + * + * fontfile = argv[1]; + * text = argv[2]; + * direction = argv[3]; + * language = argv[4]; + * + * if (FT_Init_FreeType (&library) == 0) + * { + * if (FT_New_Face (library, fontfile, 0, &face) == 0) + * { + * if (FT_Set_Char_Size (face, face->units_per_EM, 0, 0, 0) == 0) + * { + * raqm_t *rq = raqm_create (); + * if (rq != NULL) + * { + * raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; + * + * if (strcmp (direction, "r") == 0) + * dir = RAQM_DIRECTION_RTL; + * else if (strcmp (direction, "l") == 0) + * dir = RAQM_DIRECTION_LTR; + * + * if (raqm_set_text_utf8 (rq, text, strlen (text)) && + * raqm_set_freetype_face (rq, face) && + * raqm_set_par_direction (rq, dir) && + * raqm_set_language (rq, language, 0, strlen (text)) && + * raqm_layout (rq)) + * { + * size_t count, i; + * raqm_glyph_t *glyphs = raqm_get_glyphs (rq, &count); + * + * ret = !(glyphs != NULL || count == 0); + * + * printf("glyph count: %zu\n", count); + * for (i = 0; i < count; i++) + * { + * printf ("gid#%d off: (%d, %d) adv: (%d, %d) idx: %d\n", + * glyphs[i].index, + * glyphs[i].x_offset, + * glyphs[i].y_offset, + * glyphs[i].x_advance, + * glyphs[i].y_advance, + * glyphs[i].cluster); + * } + * } + * + * raqm_destroy (rq); + * } + * } + * + * FT_Done_Face (face); + * } + * + * FT_Done_FreeType (library); + * } + * + * return ret; + * } + * ]| + * To compile this example: + * |[ + * cc -o test test.c `pkg-config --libs --cflags raqm` + * ]| + */ + +/* For enabling debug mode */ +/*#define RAQM_DEBUG 1*/ +#ifdef RAQM_DEBUG +#define RAQM_DBG(...) fprintf (stderr, __VA_ARGS__) +#else +#define RAQM_DBG(...) +#endif + +#ifdef RAQM_TESTING +# define RAQM_TEST(...) printf (__VA_ARGS__) +# define SCRIPT_TO_STRING(script) \ + char buff[5]; \ + hb_tag_to_string (hb_script_to_iso15924_tag (script), buff); \ + buff[4] = '\0'; +#else +# define RAQM_TEST(...) +#endif + +typedef enum { + RAQM_FLAG_NONE = 0, + RAQM_FLAG_UTF8 = 1 << 0 +} _raqm_flags_t; + +typedef struct { + FT_Face ftface; + hb_language_t lang; + hb_script_t script; +} _raqm_text_info; + +typedef struct _raqm_run raqm_run_t; + +struct _raqm { + int ref_count; + + uint32_t *text; + char *text_utf8; + size_t text_len; + + _raqm_text_info *text_info; + + raqm_direction_t base_dir; + raqm_direction_t resolved_dir; + + hb_feature_t *features; + size_t features_len; + + raqm_run_t *runs; + raqm_glyph_t *glyphs; + + _raqm_flags_t flags; + + int ft_loadflags; + int invisible_glyph; +}; + +struct _raqm_run { + int pos; + int len; + + hb_direction_t direction; + hb_script_t script; + hb_font_t *font; + hb_buffer_t *buffer; + + raqm_run_t *next; +}; + +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index); + +static bool +_raqm_init_text_info (raqm_t *rq) +{ + hb_language_t default_lang; + + if (rq->text_info) + return true; + + rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); + if (!rq->text_info) + return false; + + default_lang = hb_language_get_default (); + for (size_t i = 0; i < rq->text_len; i++) + { + rq->text_info[i].ftface = NULL; + rq->text_info[i].lang = default_lang; + rq->text_info[i].script = HB_SCRIPT_INVALID; + } + + return true; +} + +static void +_raqm_free_text_info (raqm_t *rq) +{ + if (!rq->text_info) + return; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + } + + free (rq->text_info); + rq->text_info = NULL; +} + +static bool +_raqm_compare_text_info (_raqm_text_info a, + _raqm_text_info b) +{ + if (a.ftface != b.ftface) + return false; + + if (a.lang != b.lang) + return false; + + if (a.script != b.script) + return false; + + return true; +} + +/** + * raqm_create: + * + * Creates a new #raqm_t with all its internal states initialized to their + * defaults. + * + * Return value: + * A newly allocated #raqm_t with a reference count of 1. The initial reference + * count should be released with raqm_destroy() when you are done using the + * #raqm_t. Returns %NULL in case of error. + * + * Since: 0.1 + */ +raqm_t * +raqm_create (void) +{ + raqm_t *rq; + + rq = malloc (sizeof (raqm_t)); + if (!rq) + return NULL; + + rq->ref_count = 1; + + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + + rq->text_info = NULL; + + rq->base_dir = RAQM_DIRECTION_DEFAULT; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; + + rq->features = NULL; + rq->features_len = 0; + + rq->runs = NULL; + rq->glyphs = NULL; + + rq->flags = RAQM_FLAG_NONE; + + rq->ft_loadflags = -1; + rq->invisible_glyph = 0; + + return rq; +} + +/** + * raqm_reference: + * @rq: a #raqm_t. + * + * Increases the reference count on @rq by one. This prevents @rq from being + * destroyed until a matching call to raqm_destroy() is made. + * + * Return value: + * The referenced #raqm_t. + * + * Since: 0.1 + */ +raqm_t * +raqm_reference (raqm_t *rq) +{ + if (rq) + rq->ref_count++; + + return rq; +} + +static void +_raqm_free_runs (raqm_t *rq) +{ + raqm_run_t *runs = rq->runs; + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + hb_buffer_destroy (run->buffer); + hb_font_destroy (run->font); + free (run); + } +} + +/** + * raqm_destroy: + * @rq: a #raqm_t. + * + * Decreases the reference count on @rq by one. If the result is zero, then @rq + * and all associated resources are freed. + * See cairo_reference(). + * + * Since: 0.1 + */ +void +raqm_destroy (raqm_t *rq) +{ + if (!rq || --rq->ref_count != 0) + return; + + free (rq->text); + free (rq->text_utf8); + _raqm_free_text_info (rq); + _raqm_free_runs (rq); + free (rq->glyphs); + free (rq); +} + +/** + * raqm_set_text: + * @rq: a #raqm_t. + * @text: a UTF-32 encoded text string. + * @len: the length of @text. + * + * Adds @text to @rq to be used for layout. It must be a valid UTF-32 text, any + * invalid character will be replaced with U+FFFD. The text should typically + * represent a full paragraph, since doing the layout of chunks of text + * separately can give improper output. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + rq->text_len = len; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + free (rq->text); + + rq->text = malloc (sizeof (uint32_t) * rq->text_len); + if (!rq->text) + return false; + + _raqm_free_text_info (rq); + if (!_raqm_init_text_info (rq)) + return false; + + memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + + return true; +} + +/** + * raqm_set_text_utf8: + * @rq: a #raqm_t. + * @text: a UTF-8 encoded text string. + * @len: the length of @text in UTF-8 bytes. + * + * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) +{ + uint32_t *unicode; + size_t ulen; + bool ok; + + if (!rq || !text) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + { + rq->text_len = len; + return true; + } + + RAQM_TEST ("Text is: %s\n", text); + + rq->flags |= RAQM_FLAG_UTF8; + + rq->text_utf8 = malloc (sizeof (char) * len); + if (!rq->text_utf8) + return false; + + unicode = malloc (sizeof (uint32_t) * len); + if (!unicode) + return false; + + memcpy (rq->text_utf8, text, sizeof (char) * len); + + ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + text, len, unicode); + + ok = raqm_set_text (rq, unicode, ulen); + + free (unicode); + return ok; +} + +/** + * raqm_set_par_direction: + * @rq: a #raqm_t. + * @dir: the direction of the paragraph. + * + * Sets the paragraph direction, also known as block direction in CSS. For + * horizontal text, this controls the overall direction in the Unicode + * Bidirectional Algorithm, so when the text is mainly right-to-left (with or + * without some left-to-right) text, then the base direction should be set to + * #RAQM_DIRECTION_RTL and vice versa. + * + * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph + * direction based on the first character with strong bidi type (see [rule + * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * which can be good enough for many cases but has problems when a mainly + * right-to-left paragraph starts with a left-to-right character and vice versa + * as the detected paragraph direction will be the wrong one, or when text does + * not contain any characters with string bidi types (e.g. only punctuation or + * numbers) as this will default to left-to-right paragraph direction. + * + * For vertical, top-to-bottom text, #RAQM_DIRECTION_TTB should be used. Raqm, + * however, provides limited vertical text support and does not handle rotated + * horizontal text in vertical text, instead everything is treated as vertical + * text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir) +{ + if (!rq) + return false; + + rq->base_dir = dir; + + return true; +} + +/** + * raqm_set_language: + * @rq: a #raqm_t. + * @lang: a BCP47 language code. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets a [BCP47 language + * code](https://www.w3.org/International/articles/language-tags/) to be used + * for @len-number of characters staring at @start. The @start and @len are + * input string array indices (i.e. counting bytes in UTF-8 and scaler values + * in UTF-32). + * + * This method can be used repeatedly to set different languages for different + * parts of the text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Stability: + * Unstable + * + * Since: 0.2 + */ +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len) +{ + hb_language_t language; + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + language = hb_language_from_string (lang, -1); + for (size_t i = start; i < end; i++) + { + rq->text_info[i].lang = language; + } + + return true; +} + +/** + * raqm_add_font_feature: + * @rq: a #raqm_t. + * @feature: (transfer none): a font feature string. + * @len: length of @feature, -1 for %NULL-terminated. + * + * Adds a font feature to be used by the #raqm_t during text layout. This is + * usually used to turn on optional font features that are not enabled by + * default, for example `dlig` or `ss01`, but can be also used to turn off + * default font features. + * + * @feature is string representing a single font feature, in the syntax + * understood by hb_feature_from_string(). + * + * This function can be called repeatedly, new features will be appended to the + * end of the features list and can potentially override previous features. + * + * Return value: + * %true if parsing @feature succeeded, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len) +{ + hb_bool_t ok; + hb_feature_t fea; + + if (!rq) + return false; + + ok = hb_feature_from_string (feature, len, &fea); + if (ok) + { + rq->features_len++; + rq->features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len)); + if (!rq->features) + return false; + + rq->features[rq->features_len - 1] = fea; + } + + return ok; +} + +static hb_font_t * +_raqm_create_hb_font (raqm_t *rq, + FT_Face face) +{ + hb_font_t *font = hb_ft_font_create_referenced (face); + + if (rq->ft_loadflags >= 0) + hb_ft_font_set_load_flags (font, rq->ft_loadflags); + + return font; +} + +static bool +_raqm_set_freetype_face (raqm_t *rq, + FT_Face face, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + rq->text_info[i].ftface = face; + FT_Reference_Face (face); + } + + return true; +} + +/** + * raqm_set_freetype_face: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * + * Sets an #FT_Face to be used for all characters in @rq. + * + * See also raqm_set_freetype_face_range(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face) +{ + return _raqm_set_freetype_face (rq, face, 0, rq->text_len); +} + +/** + * raqm_set_freetype_face_range: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets an #FT_Face to be used for @len-number of characters staring at @start. + * The @start and @len are input string array indices (i.e. counting bytes in + * UTF-8 and scaler values in UTF-32). + * + * This method can be used repeatedly to set different faces for different + * parts of the text. It is the responsibility of the client to make sure that + * face ranges cover the whole text. + * + * See also raqm_set_freetype_face(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len) +{ + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_face (rq, face, start, end); +} + +/** + * raqm_set_freetype_load_flags: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * + * Sets the load flags passed to FreeType when loading glyphs, should be the + * same flags used by the client when rendering FreeType glyphs. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.3 + */ +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags) +{ + if (!rq) + return false; + + rq->ft_loadflags = flags; + + return true; +} + +/** + * raqm_set_invisible_glyph: + * @rq: a #raqm_t. + * @gid: glyph id to use for invisible glyphs. + * + * Sets the glyph id to be used for invisible glyhphs. + * + * If @gid is negative, invisible glyphs will be suppressed from the output. + * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier + * HarfBuzz version, the return value will be %false and the shaping behavior + * does not change. + * + * If @gid is zero, invisible glyphs will be rendered as space. + * This works on all versions of HarfBuzz. + * + * If @gid is a positive number, it will be used for invisible glyphs. + * This requires a version of HarfBuzz that has + * hb_buffer_set_invisible_glyph(). For older versions, the return value + * will be %false and the shaping behavior does not change. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.6 + */ +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid) +{ + if (!rq) + return false; + +#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (gid > 0) + return false; +#endif + +#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ + !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (gid < 0) + return false; +#endif + + rq->invisible_glyph = gid; + return true; +} + +static bool +_raqm_itemize (raqm_t *rq); + +static bool +_raqm_shape (raqm_t *rq); + +/** + * raqm_layout: + * @rq: a #raqm_t. + * + * Run the text layout process on @rq. This is the main Raqm function where the + * Unicode Bidirectional Text algorithm will be applied to the text in @rq, + * text shaping, and any other part of the layout process. + * + * Return value: + * %true if the layout process was successful, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_layout (raqm_t *rq) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (!rq->text_info) + return false; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (!rq->text_info[i].ftface) + return false; + } + + if (!_raqm_itemize (rq)) + return false; + + if (!_raqm_shape (rq)) + return false; + + return true; +} + +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index); + +/** + * raqm_get_glyphs: + * @rq: a #raqm_t. + * @length: (out): output array length. + * + * Gets the final result of Raqm layout process, an array of #raqm_glyph_t + * containing the glyph indices in the font, their positions and other possible + * information. + * + * Return value: (transfer none): + * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * and must not be freed. + * + * Since: 0.1 + */ +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length) +{ + size_t count = 0; + + if (!rq || !rq->runs || !length) + { + if (length) + *length = 0; + return NULL; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + count += hb_buffer_get_length (run->buffer); + + *length = count; + + if (rq->glyphs) + free (rq->glyphs); + + rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); + if (!rq->glyphs) + { + *length = 0; + return NULL; + } + + RAQM_TEST ("Glyph information:\n"); + + count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + rq->glyphs[count + i].index = info[i].codepoint; + rq->glyphs[count + i].cluster = info[i].cluster; + rq->glyphs[count + i].x_advance = position[i].x_advance; + rq->glyphs[count + i].y_advance = position[i].y_advance; + rq->glyphs[count + i].x_offset = position[i].x_offset; + rq->glyphs[count + i].y_offset = position[i].y_offset; + rq->glyphs[count + i].ftface = rq->text_info[info[i].cluster].ftface; + + RAQM_TEST ("glyph [%d]\tx_offset: %d\ty_offset: %d\tx_advance: %d\tfont: %s\n", + rq->glyphs[count + i].index, rq->glyphs[count + i].x_offset, + rq->glyphs[count + i].y_offset, rq->glyphs[count + i].x_advance, + rq->glyphs[count + i].ftface->family_name); + } + + count += len; + } + + if (rq->flags & RAQM_FLAG_UTF8) + { +#ifdef RAQM_TESTING + RAQM_TEST ("\nUTF-32 clusters:"); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, + rq->glyphs[i].cluster); + +#ifdef RAQM_TESTING + RAQM_TEST ("UTF-8 clusters: "); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + } + return rq->glyphs; +} + +static bool +_raqm_resolve_scripts (raqm_t *rq); + +static hb_direction_t +_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +{ + hb_direction_t dir = HB_DIRECTION_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + dir = HB_DIRECTION_TTB; + else if (FRIBIDI_LEVEL_IS_RTL (level)) + dir = HB_DIRECTION_RTL; + + return dir; +} + +typedef struct { + size_t pos; + size_t len; + FriBidiLevel level; +} _raqm_bidi_run; + +static void +_raqm_reverse_run (_raqm_bidi_run *run, const size_t len) +{ + assert (run); + + for (size_t i = 0; i < len / 2; i++) + { + _raqm_bidi_run temp = run[i]; + run[i] = run[len - 1 - i]; + run[len - 1 - i] = temp; + } +} + +static _raqm_bidi_run * +_raqm_reorder_runs (const FriBidiCharType *types, + const size_t len, + const FriBidiParType base_dir, + /* input and output */ + FriBidiLevel *levels, + /* output */ + size_t *run_count) +{ + FriBidiLevel level; + FriBidiLevel last_level = -1; + FriBidiLevel max_level = 0; + size_t run_start = 0; + size_t run_index = 0; + _raqm_bidi_run *runs = NULL; + size_t count = 0; + + if (len == 0) + { + *run_count = 0; + return NULL; + } + + assert (types); + assert (levels); + + /* L1. Reset the embedding levels of some chars: + 4. any sequence of white space characters at the end of the line. */ + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + { + levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); + } + + /* Find max_level of the line. We don't reuse the paragraph + * max_level, both for a cleaner API, and that the line max_level + * may be far less than paragraph max_level. */ + for (int i = len - 1; i >= 0; i--) + { + if (levels[i] > max_level) + max_level = levels[i]; + } + + for (size_t i = 0; i < len; i++) + { + if (levels[i] != last_level) + count++; + + last_level = levels[i]; + } + + runs = malloc (sizeof (_raqm_bidi_run) * count); + + while (run_start < len) + { + size_t run_end = run_start; + while (run_end < len && levels[run_start] == levels[run_end]) + { + run_end++; + } + + runs[run_index].pos = run_start; + runs[run_index].level = levels[run_start]; + runs[run_index].len = run_end - run_start; + run_start = run_end; + run_index++; + } + + /* L2. Reorder. */ + for (level = max_level; level > 0; level--) + { + for (int i = count - 1; i >= 0; i--) + { + if (runs[i].level >= level) + { + int end = i; + for (i--; (i >= 0 && runs[i].level >= level); i--) + ; + _raqm_reverse_run (runs + i + 1, end - i); + } + } + } + + *run_count = count; + return runs; +} + +static bool +_raqm_itemize (raqm_t *rq) +{ + FriBidiParType par_type = FRIBIDI_PAR_ON; + FriBidiCharType *types; +#ifdef USE_FRIBIDI_EX_API + FriBidiBracketType *btypes; +#endif + FriBidiLevel *levels; + _raqm_bidi_run *runs = NULL; + raqm_run_t *last; + int max_level; + size_t run_count; + bool ok = true; + +#ifdef RAQM_TESTING + switch (rq->base_dir) + { + case RAQM_DIRECTION_RTL: + RAQM_TEST ("Direction is: RTL\n\n"); + break; + case RAQM_DIRECTION_LTR: + RAQM_TEST ("Direction is: LTR\n\n"); + break; + case RAQM_DIRECTION_TTB: + RAQM_TEST ("Direction is: TTB\n\n"); + break; + case RAQM_DIRECTION_DEFAULT: + default: + RAQM_TEST ("Direction is: DEFAULT\n\n"); + break; + } +#endif + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (FriBidiLevel)); + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + ok = false; + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + max_level = 1; + memset (types, FRIBIDI_TYPE_LTR, rq->text_len); + memset (levels, 0, rq->text_len); + rq->resolved_dir = RAQM_DIRECTION_LTR; + } + else + { + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + } + + if (max_level == 0) + { + ok = false; + goto done; + } + + if (!_raqm_resolve_scripts (rq)) + { + ok = false; + goto done; + } + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (!runs) + { + ok = false; + goto done; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); + + RAQM_TEST ("Fribidi Runs:\n"); + for (size_t i = 0; i < run_count; i++) + { + RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", + i, runs[i].pos, runs[i].len, runs[i].level); + } + RAQM_TEST ("\n"); +#endif + + last = NULL; + for (size_t i = 0; i < run_count; i++) + { + raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + if (!run) + { + ok = false; + goto done; + } + + if (!rq->runs) + rq->runs = run; + + if (last) + last->next = run; + + run->direction = _raqm_hb_dir (rq, runs[i].level); + + if (HB_DIRECTION_IS_BACKWARD (run->direction)) + { + run->pos = runs[i].pos + runs[i].len - 1; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (int j = runs[i].len - 1; j >= 0; j--) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + { + run->len++; + run->pos = runs[i].pos + j; + } + } + } + else + { + run->pos = runs[i].pos; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (size_t j = 0; j < runs[i].len; j++) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + run->len++; + } + } + + last = run; + last->next = NULL; + } + +#ifdef RAQM_TESTING + run_count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + run_count++; + RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); + + run_count = 0; + RAQM_TEST ("Final Runs:\n"); + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + SCRIPT_TO_STRING (run->script); + RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", + run_count++, run->pos, run->len, + hb_direction_to_string (run->direction), buff, + rq->text_info[run->pos].ftface->family_name); + } + RAQM_TEST ("\n"); +#endif + +done: + free (runs); + free (types); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + free (levels); + + return ok; +} + +/* Stack to handle script detection */ +typedef struct { + size_t capacity; + size_t size; + int *pair_index; + hb_script_t *script; +} _raqm_stack_t; + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = +{ + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static void +_raqm_stack_free (_raqm_stack_t *stack) +{ + free (stack->script); + free (stack->pair_index); + free (stack); +} + +/* Stack handling functions */ +static _raqm_stack_t * +_raqm_stack_new (size_t max) +{ + _raqm_stack_t *stack; + stack = calloc (1, sizeof (_raqm_stack_t)); + if (!stack) + return NULL; + + stack->script = malloc (sizeof (hb_script_t) * max); + if (!stack->script) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->pair_index = malloc (sizeof (int) * max); + if (!stack->pair_index) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->size = 0; + stack->capacity = max; + + return stack; +} + +static bool +_raqm_stack_pop (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return false; + } + + stack->size--; + + return true; +} + +static hb_script_t +_raqm_stack_top (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return HB_SCRIPT_INVALID; /* XXX: check this */ + } + + return stack->script[stack->size]; +} + +static bool +_raqm_stack_push (_raqm_stack_t *stack, + hb_script_t script, + int pair_index) +{ + if (stack->size == stack->capacity) + { + RAQM_DBG ("Stack is Full\n"); + return false; + } + + stack->size++; + stack->script[stack->size] = script; + stack->pair_index[stack->size] = pair_index; + + return true; +} + +static int +_get_pair_index (const FriBidiChar ch) +{ + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(script) ((script)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static bool +_raqm_resolve_scripts (raqm_t *rq) +{ + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + _raqm_stack_t *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + for (size_t i = 0; i < rq->text_len; ++i) + rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); + +#ifdef RAQM_TESTING + RAQM_TEST ("Before script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + stack = _raqm_stack_new (rq->text_len); + if (!stack) + return false; + + for (int i = 0; i < (int) rq->text_len; i++) + { + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + { + int pair_index = _get_pair_index (rq->text[i]); + if (pair_index >= 0) + { + if (IS_OPEN (pair_index)) + { + /* is a paired character */ + rq->text_info[i].script = last_script; + last_set_index = i; + _raqm_stack_push (stack, rq->text_info[i].script, pair_index); + } + else + { + /* is a close paired character */ + /* find matching opening (by getting the last even index for current + * odd index) */ + while (!STACK_IS_EMPTY (stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) + { + _raqm_stack_pop (stack); + } + if (!STACK_IS_EMPTY (stack)) + { + rq->text_info[i].script = _raqm_stack_top (stack); + last_script = rq->text_info[i].script; + last_set_index = i; + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && + last_script_index != -1) + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + else + { + for (int j = last_set_index + 1; j < i; ++j) + rq->text_info[j].script = rq->text_info[i].script; + last_script = rq->text_info[i].script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + for (int i = rq->text_len - 2; i >= 0; --i) + { + if (rq->text_info[i].script == HB_SCRIPT_INHERITED || + rq->text_info[i].script == HB_SCRIPT_COMMON) + rq->text_info[i].script = rq->text_info[i + 1].script; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("After script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + _raqm_stack_free (stack); + + return true; +} + +static bool +_raqm_shape (raqm_t *rq) +{ + hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ + HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (rq->invisible_glyph < 0) + hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + run->buffer = hb_buffer_create (); + + hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, + run->pos, run->len); + hb_buffer_set_script (run->buffer, run->script); + hb_buffer_set_language (run->buffer, rq->text_info[run->pos].lang); + hb_buffer_set_direction (run->buffer, run->direction); + hb_buffer_set_flags (run->buffer, hb_buffer_flags); + +#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (rq->invisible_glyph > 0) + hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); +#endif + + hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, + NULL); + } + + return true; +} + +/* Convert index from UTF-32 to UTF-8 */ +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + char *output = malloc ((sizeof (char) * 4 * index) + 1); + + length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, + rq->text, + index, + output); + + free (output); + return length; +} + +/* Convert index from UTF-8 to UTF-32 */ +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + + length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + rq->text_utf8, + index, + output); + + free (output); + return length; +} + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch); + +/** + * raqm_index_to_position: + * @rq: a #raqm_t. + * @index: (inout): character index. + * @x: (out): output x position. + * @y: (out): output y position. + * + * Calculates the cursor position after the character at @index. If the character + * is right-to-left, then the cursor will be at the left of it, whereas if the + * character is left-to-right, then the cursor will be at the right of it. + * + * Return value: + * %true if the process was successful, %false otherwise. + * + * Since: 0.2 + */ +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y) +{ + /* We don't currently support multiline, so y is always 0 */ + *y = 0; + *x = 0; + + if (rq == NULL) + return false; + + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u8_to_u32_index (rq, *index); + + if (*index >= rq->text_len) + return false; + + RAQM_TEST ("\n"); + + while (*index < rq->text_len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], rq->text[*index + 1])) + break; + + ++*index; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + *x += position[i].x_advance; + + if (run->direction == HB_DIRECTION_LTR) + { + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + } + else + { + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + } + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + if (*index < next_cluster && *index >= curr_cluster) + { + if (run->direction == HB_DIRECTION_RTL) + *x -= position[i].x_advance; + *index = curr_cluster; + goto found; + } + } + } + +found: + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u32_to_u8_index (rq, *index); + RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); + return true; +} + +/** + * raqm_position_to_index: + * @rq: a #raqm_t. + * @x: x position. + * @y: y position. + * @index: (out): output character index. + * + * Returns the @index of the character at @x and @y position within text. + * If the position is outside the text, the last character is chosen as + * @index. + * + * Return value: + * %true if the process was successful, %false in case of error. + * + * Since: 0.2 + */ +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index) +{ + int delta_x = 0, current_x = 0; + (void)y; + + if (rq == NULL) + return false; + + if (x < 0) /* Get leftmost index */ + { + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = rq->text_len; + else + *index = 0; + return true; + } + + RAQM_TEST ("\n"); + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + delta_x = position[i].x_advance; + if (x < (current_x + delta_x)) + { + bool before = false; + if (run->direction == HB_DIRECTION_LTR) + before = (x < current_x + (delta_x / 2)); + else + before = (x > current_x + (delta_x / 2)); + + if (before) + *index = info[i].cluster; + else + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + if (run->direction == HB_DIRECTION_LTR) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + else + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + *index = next_cluster; + } + if (_raqm_allowed_grapheme_boundary (rq->text[*index],rq->text[*index + 1])) + { + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + + while (*index < (unsigned)run->pos + run->len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], + rq->text[*index + 1])) + { + *index += 1; + break; + } + *index += 1; + } + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + else + current_x += delta_x; + } + } + + /* Get rightmost index*/ + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = 0; + else + *index = rq->text_len; + + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + + return true; +} + +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B && ch != 0x102C && ch != 0x1038 && + (ch < 0x1062 || ch > 0x1064) && (ch < 0x1067 || ch > 0x106D) && + ch != 0x1083 && (ch < 0x1087 || ch > 0x108C) && ch != 0x108F && + (ch < 0x109A || ch > 0x109C) && ch != 0x1A61 && ch != 0x1A63 && + ch != 0x1A64 && ch != 0xAA7B && ch != 0xAA70 && ch != 0x11720 && + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + if (_raqm_in_hangul_syllable (ch)) + gb_type = RAQM_GRAPHEM_HANGUL_SYLLABLE; + + return gb_type; +} + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch) +{ + (void)ch; + return false; +} + +/** + * raqm_version: + * @major: (out): Library major version component. + * @minor: (out): Library minor version component. + * @micro: (out): Library micro version component. + * + * Returns library version as three integer components. + * + * Since: 0.7 + **/ +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro) +{ + *major = RAQM_VERSION_MAJOR; + *minor = RAQM_VERSION_MINOR; + *micro = RAQM_VERSION_MICRO; +} + +/** + * raqm_version_string: + * + * Returns library version as a string with three components. + * + * Return value: library version string. + * + * Since: 0.7 + **/ +const char * +raqm_version_string (void) +{ + return RAQM_VERSION_STRING; +} + +/** + * raqm_version_atleast: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro) +{ + return RAQM_VERSION_ATLEAST (major, minor, micro); +} + +/** + * RAQM_VERSION_ATLEAST: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_STRING: + * + * Library version as a string with three components. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MAJOR: + * + * Library major version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MINOR: + * + * Library minor version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MICRO: + * + * Library micro version component. + * + * Since: 0.7 + **/ diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h new file mode 100644 index 000000000..1a33fe8ba --- /dev/null +++ b/src/thirdparty/raqm/raqm.h @@ -0,0 +1,185 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _RAQM_H_ +#define _RAQM_H_ +#define _RAQM_H_IN_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include FT_FREETYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "raqm-version.h" + +/** + * raqm_t: + * + * This is the main object holding all state of the currently processed text as + * well as its output. + * + * Since: 0.1 + */ +typedef struct _raqm raqm_t; + +/** + * raqm_direction_t: + * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. + * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. + * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. + * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. + * + * Base paragraph direction, see raqm_set_par_direction(). + * + * Since: 0.1 + */ +typedef enum +{ + RAQM_DIRECTION_DEFAULT, + RAQM_DIRECTION_RTL, + RAQM_DIRECTION_LTR, + RAQM_DIRECTION_TTB +} raqm_direction_t; + +/** + * raqm_glyph_t: + * @index: the index of the glyph in the font file. + * @x_advance: the glyph advance width in horizontal text. + * @y_advance: the glyph advance width in vertical text. + * @x_offset: the horizontal movement of the glyph from the current point. + * @y_offset: the vertical movement of the glyph from the current point. + * @cluster: the index of original character in input text. + * @ftface: the @FT_Face of the glyph. + * + * The structure that holds information about output glyphs, returned from + * raqm_get_glyphs(). + */ +typedef struct raqm_glyph_t { + unsigned int index; + int x_advance; + int y_advance; + int x_offset; + int y_offset; + uint32_t cluster; + FT_Face ftface; +} raqm_glyph_t; + +raqm_t * +raqm_create (void); + +raqm_t * +raqm_reference (raqm_t *rq); + +void +raqm_destroy (raqm_t *rq); + +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len); + +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len); + +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir); + +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len); + +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len); + +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face); + +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len); + +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags); + +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid); + +bool +raqm_layout (raqm_t *rq); + +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length); + +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y); + +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index); + +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro); + +const char * +raqm_version_string (void); + +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro); + + +#ifdef __cplusplus +} +#endif +#undef _RAQM_H_IN_ +#endif /* _RAQM_H_ */ From 8bc1ff35b4d87003e54d7f8cdcbc687ad3a62762 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:21:42 +0000 Subject: [PATCH 004/133] use FriBiDi shim in Raqm --- setup.py | 16 +- src/_imagingft.c | 225 ++++++-------------------- src/libImaging/raqm.h | 156 ------------------ src/thirdparty/fribidi-shim/fribidi.c | 68 ++++++++ src/thirdparty/fribidi-shim/fribidi.h | 104 ++++++++++++ src/thirdparty/raqm/raqm.c | 7 +- winbuild/build_prepare.py | 20 +-- winbuild/fribidi.cmake | 4 +- 8 files changed, 243 insertions(+), 357 deletions(-) delete mode 100644 src/libImaging/raqm.h create mode 100644 src/thirdparty/fribidi-shim/fribidi.c create mode 100644 src/thirdparty/fribidi-shim/fribidi.h diff --git a/setup.py b/setup.py index cbc2641c5..3e0ec5576 100755 --- a/setup.py +++ b/setup.py @@ -267,6 +267,7 @@ class pil_build_ext(build_ext): "jpeg", "tiff", "freetype", + "harfbuzz", "lcms", "webp", "webpmux", @@ -656,6 +657,12 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) + if feature.want("harfbuzz"): + _dbg("Looking for harfbuzz") + if _find_include_file(self, "hb-version.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.want("lcms"): _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): @@ -850,7 +857,14 @@ for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension( + "PIL._imagingft", + [ + "src/_imagingft.c", + "src/thirdparty/raqm/raqm.c", + "src/thirdparty/fribidi-shim/fribidi.c", + ], + ), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/_imagingft.c b/src/_imagingft.c index d73c6c2d5..4a4084e9f 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -35,10 +35,6 @@ #define KEEP_PY_UNICODE -#ifndef _WIN32 -#include -#endif - #if !defined(FT_LOAD_TARGET_MONO) #define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME #endif @@ -56,7 +52,8 @@ } \ ; -#include "libImaging/raqm.h" +#include "thirdparty/raqm/raqm.h" +#include "thirdparty/fribidi-shim/fribidi.h" #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -86,42 +83,6 @@ typedef struct { static PyTypeObject Font_Type; -typedef const char *(*t_raqm_version_string)(void); -typedef bool (*t_raqm_version_atleast)( - unsigned int major, unsigned int minor, unsigned int micro); -typedef raqm_t *(*t_raqm_create)(void); -typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, size_t len); -typedef bool (*t_raqm_set_text_utf8)(raqm_t *rq, const char *text, size_t len); -typedef bool (*t_raqm_set_par_direction)(raqm_t *rq, raqm_direction_t dir); -typedef bool (*t_raqm_set_language)( - raqm_t *rq, const char *lang, size_t start, size_t len); -typedef bool (*t_raqm_add_font_feature)(raqm_t *rq, const char *feature, int len); -typedef bool (*t_raqm_set_freetype_face)(raqm_t *rq, FT_Face face); -typedef bool (*t_raqm_layout)(raqm_t *rq); -typedef raqm_glyph_t *(*t_raqm_get_glyphs)(raqm_t *rq, size_t *length); -typedef raqm_glyph_t_01 *(*t_raqm_get_glyphs_01)(raqm_t *rq, size_t *length); -typedef void (*t_raqm_destroy)(raqm_t *rq); - -typedef struct { - void *raqm; - int version; - t_raqm_version_string version_string; - t_raqm_version_atleast version_atleast; - t_raqm_create create; - t_raqm_set_text set_text; - t_raqm_set_text_utf8 set_text_utf8; - t_raqm_set_par_direction set_par_direction; - t_raqm_set_language set_language; - t_raqm_add_font_feature add_font_feature; - t_raqm_set_freetype_face set_freetype_face; - t_raqm_layout layout; - t_raqm_get_glyphs get_glyphs; - t_raqm_get_glyphs_01 get_glyphs_01; - t_raqm_destroy destroy; -} p_raqm_func; - -static p_raqm_func p_raqm; - /* round a 26.6 pixel coordinate to the nearest integer */ #define PIXEL(x) ((((x) + 32) & -64) >> 6) @@ -142,101 +103,7 @@ geterror(int code) { static int setraqm(void) { - /* set the static function pointers for dynamic raqm linking */ - p_raqm.raqm = NULL; - - /* Microsoft needs a totally different system */ -#ifndef _WIN32 - p_raqm.raqm = dlopen("libraqm.so.0", RTLD_LAZY); - if (!p_raqm.raqm) { - p_raqm.raqm = dlopen("libraqm.dylib", RTLD_LAZY); - } -#else - p_raqm.raqm = LoadLibrary("libraqm"); - /* MSYS */ - if (!p_raqm.raqm) { - p_raqm.raqm = LoadLibrary("libraqm-0"); - } -#endif - - if (!p_raqm.raqm) { - return 1; - } - -#ifndef _WIN32 - p_raqm.version_string = - (t_raqm_version_string)dlsym(p_raqm.raqm, "raqm_version_string"); - p_raqm.version_atleast = - (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); - p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); - p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)dlsym(p_raqm.raqm, "raqm_destroy"); - if (dlsym(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = (t_raqm_get_glyphs)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - } - if (dlerror() || - !(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - dlclose(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#else - p_raqm.version_string = - (t_raqm_version_string)GetProcAddress(p_raqm.raqm, "raqm_version_string"); - p_raqm.version_atleast = - (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); - p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); - p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = - (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)GetProcAddress(p_raqm.raqm, "raqm_destroy"); - if (GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = - (t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - } - if (!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - FreeLibrary(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#endif - - return 0; + return load_fribidi(); } static PyObject * @@ -359,10 +226,10 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; - raqm_glyph_t_01 *glyphs_01 = NULL; +// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; - rq = (*p_raqm.create)(); + rq = raqm_create(); if (rq == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_create() failed."); goto failed; @@ -376,14 +243,14 @@ text_layout_raqm( and raqm fails with empty strings */ goto failed; } - int set_text = (*p_raqm.set_text)(rq, text, size); + int set_text = raqm_set_text(rq, text, size); PyMem_Free(text); if (!set_text) { PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); goto failed; } if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { + if (!raqm_set_language(rq, lang, start, size)) { PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); goto failed; } @@ -401,12 +268,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; - if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { - PyErr_SetString( - PyExc_ValueError, - "libraqm 0.7 or greater required for 'ttb' direction"); - goto failed; - } +// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { +// PyErr_SetString( +// PyExc_ValueError, +// "libraqm 0.7 or greater required for 'ttb' direction"); +// goto failed; +// } } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -414,7 +281,7 @@ text_layout_raqm( } } - if (!(*p_raqm.set_par_direction)(rq, direction)) { + if (!raqm_set_par_direction(rq, direction)) { PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed"); goto failed; } @@ -446,38 +313,38 @@ text_layout_raqm( feature = PyBytes_AS_STRING(bytes); size = PyBytes_GET_SIZE(bytes); } - if (!(*p_raqm.add_font_feature)(rq, feature, size)) { + if (!raqm_add_font_feature(rq, feature, size)) { PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed"); goto failed; } } } - if (!(*p_raqm.set_freetype_face)(rq, self->face)) { + if (!raqm_set_freetype_face(rq, self->face)) { PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed."); goto failed; } - if (!(*p_raqm.layout)(rq)) { + if (!raqm_layout(rq)) { PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed."); goto failed; } - if (p_raqm.version == 1) { - glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count); - if (glyphs_01 == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } - } else { /* version == 2 */ - glyphs = (*p_raqm.get_glyphs)(rq, &count); +// if (p_raqm.version == 1) { +// glyphs_01 = raqm_get_glyphs_01(rq, &count); +// if (glyphs_01 == NULL) { +// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); +// count = 0; +// goto failed; +// } +// } else { /* version == 2 */ + glyphs = raqm_get_glyphs(rq, &count); if (glyphs == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); count = 0; goto failed; } - } +// } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -486,16 +353,16 @@ text_layout_raqm( goto failed; } - if (p_raqm.version == 1) { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs_01[i].index; - (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; - (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; - (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; - (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; - (*glyph_info)[i].cluster = glyphs_01[i].cluster; - } - } else { +// if (p_raqm.version == 1) { +// for (i = 0; i < count; i++) { +// (*glyph_info)[i].index = glyphs_01[i].index; +// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; +// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; +// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; +// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; +// (*glyph_info)[i].cluster = glyphs_01[i].cluster; +// } +// } else { for (i = 0; i < count; i++) { (*glyph_info)[i].index = glyphs[i].index; (*glyph_info)[i].x_offset = glyphs[i].x_offset; @@ -504,10 +371,10 @@ text_layout_raqm( (*glyph_info)[i].y_advance = glyphs[i].y_advance; (*glyph_info)[i].cluster = glyphs[i].cluster; } - } +// } failed: - (*p_raqm.destroy)(rq); + raqm_destroy(rq); return count; } @@ -607,9 +474,9 @@ text_layout( int color) { size_t count; - if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { + if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( - string, self, dir, features, lang, glyph_info, mask, color); + string, self, dir, features, lang, glyph_info, mask, color); } else { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); @@ -1491,12 +1358,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); setraqm(); - v = PyBool_FromLong(!!p_raqm.raqm); + v = PyBool_FromLong(!!p_fribidi); PyDict_SetItemString(d, "HAVE_RAQM", v); - if (p_raqm.version_string) { +// if (p_raqm.version_string) { PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); - } + d, "raqm_version", PyUnicode_FromString(raqm_version_string())); +// }; + + PyDict_SetItemString(d, "HAVE_FRIBIDI", v); return 0; } diff --git a/src/libImaging/raqm.h b/src/libImaging/raqm.h deleted file mode 100644 index 5f865853a..000000000 --- a/src/libImaging/raqm.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016 Khaled Hosny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - */ - -#ifndef _RAQM_H_ -#define _RAQM_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifndef bool -typedef int bool; -#endif -#ifndef uint32_t -typedef UINT32 uint32_t; -#endif -#include -#include FT_FREETYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * raqm_t: - * - * This is the main object holding all state of the currently processed text as - * well as its output. - * - * Since: 0.1 - */ -typedef struct _raqm raqm_t; - -/** - * raqm_direction_t: - * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. - * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. - * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. - * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. - * - * Base paragraph direction, see raqm_set_par_direction(). - * - * Since: 0.1 - */ -typedef enum { - RAQM_DIRECTION_DEFAULT, - RAQM_DIRECTION_RTL, - RAQM_DIRECTION_LTR, - RAQM_DIRECTION_TTB -} raqm_direction_t; - -/** - * raqm_glyph_t: - * @index: the index of the glyph in the font file. - * @x_advance: the glyph advance width in horizontal text. - * @y_advance: the glyph advance width in vertical text. - * @x_offset: the horizontal movement of the glyph from the current point. - * @y_offset: the vertical movement of the glyph from the current point. - * @cluster: the index of original character in input text. - * @ftface: the @FT_Face of the glyph. - * - * The structure that holds information about output glyphs, returned from - * raqm_get_glyphs(). - */ -typedef struct raqm_glyph_t { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; - FT_Face ftface; -} raqm_glyph_t; - -/** - * version 0.1 of the raqm_glyph_t structure - */ -typedef struct raqm_glyph_t_01 { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; -} raqm_glyph_t_01; - -raqm_t * -raqm_create(void); - -raqm_t * -raqm_reference(raqm_t *rq); - -void -raqm_destroy(raqm_t *rq); - -bool -raqm_set_text(raqm_t *rq, const uint32_t *text, size_t len); - -bool -raqm_set_text_utf8(raqm_t *rq, const char *text, size_t len); - -bool -raqm_set_par_direction(raqm_t *rq, raqm_direction_t dir); - -bool -raqm_set_language(raqm_t *rq, const char *lang, size_t start, size_t len); - -bool -raqm_add_font_feature(raqm_t *rq, const char *feature, int len); - -bool -raqm_set_freetype_face(raqm_t *rq, FT_Face face); - -bool -raqm_set_freetype_face_range(raqm_t *rq, FT_Face face, size_t start, size_t len); - -bool -raqm_set_freetype_load_flags(raqm_t *rq, int flags); - -bool -raqm_layout(raqm_t *rq); - -raqm_glyph_t * -raqm_get_glyphs(raqm_t *rq, size_t *length); - -bool -raqm_index_to_position(raqm_t *rq, size_t *index, int *x, int *y); - -bool -raqm_position_to_index(raqm_t *rq, int x, int y, size_t *index); - -#ifdef __cplusplus -} -#endif -#endif /* _RAQM_H_ */ diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c new file mode 100644 index 000000000..64ff7e115 --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -0,0 +1,68 @@ + +#ifndef _WIN32 +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#define FRIBIDI_SHIM_IMPLEMENTATION + +#include "fribidi.h" + +int load_fribidi(void) { + int error = 0; + + p_fribidi = NULL; + + /* Microsoft needs a totally different system */ +#ifndef _WIN32 + p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); + } +#else + p_fribidi = LoadLibrary("fribidi"); + /* MSYS2 */ + if (!p_fribidi) { + p_fribidi = LoadLibrary("libfribidi-1"); + } +#endif + + if (!p_fribidi) { + return 1; + } + +#ifndef _WIN32 +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == NULL); +#else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == NULL); +#endif + + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_bracket_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); +// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_unicode_to_charset); + LOAD_FUNCTION(fribidi_charset_to_unicode); + +#ifndef _WIN32 + if (dlerror() || error) { + dlclose(p_fribidi); + p_fribidi = NULL; + return 2; + } +#else + if (error) { + FreeLibrary(p_fribidi); + p_fribidi = NULL; + return 2; + } +#endif + + return 0; +} diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h new file mode 100644 index 000000000..c79bb170a --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -0,0 +1,104 @@ + +/* fribidi-types.h */ + +# if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ + defined (_sgi) || defined (__sun) || defined (sun) || \ + defined (__digital__) || defined (__HP_cc) +# include +# elif defined (_AIX) +# include +# else +# include +# endif + +typedef uint32_t FriBidiChar; +typedef int FriBidiStrIndex; + +typedef FriBidiChar FriBidiBracketType; + + + +/* fribidi-char-sets.h */ + +typedef enum +{ + _FRIBIDI_CHAR_SET_NOT_FOUND, + FRIBIDI_CHAR_SET_UTF8, + FRIBIDI_CHAR_SET_CAP_RTL, + FRIBIDI_CHAR_SET_ISO8859_6, + FRIBIDI_CHAR_SET_ISO8859_8, + FRIBIDI_CHAR_SET_CP1255, + FRIBIDI_CHAR_SET_CP1256, + _FRIBIDI_CHAR_SETS_NUM_PLUS_ONE +} +FriBidiCharSet; + + + +/* fribidi-bidi-types.h */ + +typedef signed char FriBidiLevel; + +#define FRIBIDI_TYPE_LTR_VAL 0x00000110L +#define FRIBIDI_TYPE_RTL_VAL 0x00000111L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L + +typedef uint32_t FriBidiCharType; +#define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL + +typedef uint32_t FriBidiParType; +#define FRIBIDI_PAR_LTR FRIBIDI_TYPE_LTR_VAL +#define FRIBIDI_PAR_RTL FRIBIDI_TYPE_RTL_VAL +#define FRIBIDI_PAR_ON FRIBIDI_TYPE_ON_VAL + +#define FRIBIDI_LEVEL_IS_RTL(lev) ((lev) & 1) +#define FRIBIDI_DIR_TO_LEVEL(dir) ((FriBidiLevel) (FRIBIDI_IS_RTL(dir) ? 1 : 0)) +#define FRIBIDI_IS_RTL(p) ((p) & 0x00000001L) +#define FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS(p) ((p) & 0x00901000L) + + + +/* functions */ + +#ifdef FRIBIDI_SHIM_IMPLEMENTATION +#define FRIBIDI_ENTRY +#else +#define FRIBIDI_ENTRY extern +#endif + +#define FRIBIDI_FUNC(ret, name, ...) \ + typedef ret (*t_##name) (__VA_ARGS__); \ + FRIBIDI_ENTRY t_##name name; + +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, +// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, +// FriBidiLevel *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, + FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, + FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); + +#undef FRIBIDI_FUNC + + + +/* shim */ + +FRIBIDI_ENTRY void *p_fribidi; + +FRIBIDI_ENTRY int load_fribidi(void); + +#undef FRIBIDI_ENTRY diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 27e59b5fc..c796f645e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,15 +30,16 @@ #include #include -#include +#include "../fribidi-shim/fribidi.h" + #include #include #include "raqm.h" -#if FRIBIDI_MAJOR_VERSION >= 1 +//#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -#endif +//#endif /** * SECTION:raqm diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2531d5504..fd63f4f1e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -296,21 +296,7 @@ deps = { cmd_nmake(target="clean"), cmd_nmake(target="fribidi"), ], - "headers": [r"lib\*.h"], - "libs": [r"*.lib"], - }, - "libraqm": { - "url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.1.zip", - "filename": "libraqm-0.7.1.zip", - "dir": "libraqm-0.7.1", - "build": [ - cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"), - cmd_cmake(), - cmd_nmake(target="clean"), - cmd_nmake(target="libraqm"), - ], - "headers": [r"src\*.h"], - "bins": [r"libraqm.dll"], + "bins": [r"*.dll"], }, } @@ -511,8 +497,8 @@ if __name__ == "__main__": verbose = True elif arg == "--no-imagequant": disabled += ["libimagequant"] - elif arg == "--no-raqm": - disabled += ["fribidi", "libraqm"] + elif arg == "--no-raqm" or arg == "--no-fribidi": + disabled += ["fribidi"] elif arg.startswith("--depends="): depends_dir = arg[10:] elif arg.startswith("--python="): diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index 47ab2c329..acb614bfa 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -93,10 +93,10 @@ fribidi_tab(brackets-type unidata/BidiBrackets.txt) file(GLOB FRIBIDI_SOURCES lib/*.c) file(GLOB FRIBIDI_HEADERS lib/*.h) -add_library(fribidi STATIC +add_library(fribidi SHARED ${FRIBIDI_SOURCES} ${FRIBIDI_HEADERS} ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC -DFRIBIDI_LIB_STATIC) + PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") From 9e5fc136b90e86a4cfbd437455cbe60e4aeeba4c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:23:45 +0000 Subject: [PATCH 005/133] add Raqm license and readme --- src/thirdparty/raqm/AUTHORS | 9 ++++ src/thirdparty/raqm/COPYING | 22 +++++++++ src/thirdparty/raqm/NEWS | 89 +++++++++++++++++++++++++++++++++++++ src/thirdparty/raqm/README | 85 +++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 src/thirdparty/raqm/AUTHORS create mode 100644 src/thirdparty/raqm/COPYING create mode 100644 src/thirdparty/raqm/NEWS create mode 100644 src/thirdparty/raqm/README diff --git a/src/thirdparty/raqm/AUTHORS b/src/thirdparty/raqm/AUTHORS new file mode 100644 index 000000000..bd5c3ac6b --- /dev/null +++ b/src/thirdparty/raqm/AUTHORS @@ -0,0 +1,9 @@ +Abderraouf Adjal +Ali Yousuf +Anood Almuharbi +Asma Albahanta +Fahad Alsaidi +Ibtisam Almabsali +Khaled Hosny +Mazoon Almaamari +Shamsa Alqassabi diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING new file mode 100644 index 000000000..196511ef6 --- /dev/null +++ b/src/thirdparty/raqm/COPYING @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2015 Information Technology Authority (ITA) +Copyright © 2016 Khaled Hosny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS new file mode 100644 index 000000000..29c9ae0e5 --- /dev/null +++ b/src/thirdparty/raqm/NEWS @@ -0,0 +1,89 @@ +Overview of changes leading to 0.7.1 +Sunday, November 22, 2020 +==================================== + +Require HarfBuzz >= 2.0.0 + +Build and documentation fixes. + +Overview of changes leading to 0.7.0 +Monday, May 27, 2019 +==================================== + +New API: + * raqm_version + * raqm_version_string + * raqm_version_atleast + * RAQM_VERSION_MAJOR + * RAQM_VERSION_MICRO + * RAQM_VERSION_MINOR + * RAQM_VERSION_STRING + * RAQM_VERSION_ATLEAST + +Overview of changes leading to 0.6.0 +Sunday, May 5, 2019 +==================================== + +Fix TTB direction regression from the previous release. + +Correctly detect script of Common and Inherite characters at start of text. + +Undef HAVE_CONFIG_H workaround, for older versions of Fribidi. + +Drop test suite dependency on GLib. + +Port test runner to Python instead of shell script. + +New API: +* raqm_set_invisible_glyph() + +Overview of changes leading to 0.5.0 +Saturday, February 24, 2018 +==================================== + +Use FriBiDi 1.x API when available. + +Overview of changes leading to 0.4.0 +Sunday, January 21, 2018 +==================================== + +Set begin-of-text and end-of-text HarfBuzz buffer flags. + +Dynamically allocate memory instead of using stack allocation for input text. + +Accept zero length text and do nothing instead of treating it as error. + +Overview of changes leading to 0.3.0 +Monday, August 21, 2017 +==================================== + +Fix stack corruption on MSVC. + +New API: +* raqm_set_freetype_load_flags + +Overview of changes leading to 0.2.0 +Wednesday, August 25, 2016 +==================================== + +Fix building with MSVC due to lacking C99 support. + +Make multiple fonts support actually work. Start and length now respect the +input encoding. + +New API: +* raqm_index_to_position +* raqm_position_to_index +* raqm_set_language + +Overview of changes leading to 0.1.1 +Sunday, May 1, 2016 +==================================== + +Fix make check on 32-bit systems. + +Overview of changes leading to 0.1.0 +Wednesday, January 20, 2016 +==================================== + +First release. diff --git a/src/thirdparty/raqm/README b/src/thirdparty/raqm/README new file mode 100644 index 000000000..7940bf3b6 --- /dev/null +++ b/src/thirdparty/raqm/README @@ -0,0 +1,85 @@ +Raqm +==== + +[![Linux & macOS build](https://travis-ci.org/HOST-Oman/libraqm.svg?branch=master)](https://travis-ci.org/HOST-Oman/libraqm) +[![Windows build](https://img.shields.io/appveyor/ci/HOSTOman/libraqm/master.svg)](https://ci.appveyor.com/project/HOSTOman/libraqm) + +Raqm is a small library that encapsulates the logic for complex text layout and +provides a convenient API. + +It currently provides bidirectional text support (using [FriBiDi][1]), shaping +(using [HarfBuzz][2]), and proper script itemization. As a result, +Raqm can support most writing systems covered by Unicode. + +The documentation can be accessed on the web at: +> http://host-oman.github.io/libraqm/ + +Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. + +Building +-------- + +Raqm depends on the following libraries: +* [FreeType][3] +* [HarfBuzz][2] +* [FriBiDi][1] + +To build the documentation you will also need: +* [GTK-Doc][4] + +To install dependencies on Fedora: + + sudo dnf install freetype-devel harfbuzz-devel fribidi-devel gtk-doc + +To install dependencies on Ubuntu: + + sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev \ + gtk-doc-tools + +On Mac OS X you can use Homebrew: + + brew install freetype harfbuzz fribidi gtk-doc + export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs + +Once you have the source code and the dependencies, you can proceed to build. +To do that, run the customary sequence of commands in the source code +directory: + + $ ./configure + $ make + $ make install + +To build the documentation, pass `--enable-gtk-doc` to the `configure` script. + +To run the tests: + + $ make check + +Contributing +------------ + +Once you have made a change that you are happy with, contribute it back, we’ll +be happy to integrate it! Just fork the repository and make a pull request. + +Projects using Raqm +------------------- + +1. [ImageMagick](https://github.com/ImageMagick/ImageMagick) +2. [LibGD](https://github.com/libgd/libgd) +3. [FontView](https://github.com/googlei18n/fontview) +4. [Pillow](https://github.com/python-pillow) +5. [mplcairo](https://github.com/anntzer/mplcairo) + +The following projects have patches to support complex text layout using Raqm: + +2. SDL_ttf: https://bugzilla.libsdl.org/show_bug.cgi?id=3211 +3. Pygame: https://bitbucket.org/pygame/pygame/pull-requests/52 +4. Blender: https://developer.blender.org/D1809 + + + +[1]: http://fribidi.org +[2]: http://harfbuzz.org +[3]: https://www.freetype.org +[4]: https://www.gtk.org/gtk-doc From 5cd688fc82e875de25979af800642f905cb92cb3 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:01:16 +0000 Subject: [PATCH 006/133] add option to statically link fribidi, version info --- setup.py | 94 ++++++++++++++++++++++----- src/PIL/features.py | 7 ++ src/_imagingft.c | 74 ++++++++++++++++----- src/thirdparty/fribidi-shim/fribidi.c | 6 +- src/thirdparty/fribidi-shim/fribidi.h | 5 ++ src/thirdparty/raqm/raqm.c | 8 ++- 6 files changed, 157 insertions(+), 37 deletions(-) diff --git a/setup.py b/setup.py index 3e0ec5576..0afc6330c 100755 --- a/setup.py +++ b/setup.py @@ -267,7 +267,7 @@ class pil_build_ext(build_ext): "jpeg", "tiff", "freetype", - "harfbuzz", + "raqm", "lcms", "webp", "webpmux", @@ -277,6 +277,7 @@ class pil_build_ext(build_ext): ] required = {"jpeg", "zlib"} + system = set() def __init__(self): for f in self.features: @@ -288,6 +289,9 @@ class pil_build_ext(build_ext): def want(self, feat): return getattr(self, feat) is None + def want_system(self, feat): + return feat in self.system + def __iter__(self): yield from self.features @@ -297,6 +301,10 @@ class pil_build_ext(build_ext): build_ext.user_options + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + + [ + (f"system-{x}", None, f"Use system version of {x}") + for x in ("raqm", "fribidi") + ] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -311,6 +319,8 @@ class pil_build_ext(build_ext): for x in self.feature: setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) + for x in ("raqm", "fribidi"): + setattr(self, f"system_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -335,18 +345,40 @@ class pil_build_ext(build_ext): raise ValueError( f"Conflicting options: --enable-{x} and --disable-{x}" ) + if x == "freetype": + _dbg("--disable-freetype implies --disable-raqm") + if getattr(self, "enable_raqm"): + raise ValueError( + "Conflicting options: --enable-raqm and --disable-freetype" + ) + setattr(self, "disable_raqm", True) if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) + if x == "raqm": + _dbg("--enable-raqm implies --enable-freetype") + self.feature.required.add("freetype") + for x in ("raqm", "fribidi"): + if getattr(self, f"system_{x}"): + if getattr(self, f"disable_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --disable-raqm" + ) + if x == "fribidi" and getattr(self, f"system_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --system-raqm" + ) + _dbg("Using system version of %s", x) + self.feature.system.add(x) - def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: if extension.name == name: extension.libraries += libraries if define_macros is not None: extension.define_macros += define_macros - if include_dirs is not None: - extension.include_dirs += include_dirs + if sources is not None: + extension.sources += sources break def _remove_extension(self, name): @@ -657,11 +689,27 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("harfbuzz"): - _dbg("Looking for harfbuzz") - if _find_include_file(self, "hb-version.h"): - if _find_library_file(self, "harfbuzz"): - feature.harfbuzz = "harfbuzz" + if feature.want("raqm"): + if feature.want_system("raqm"): # want system Raqm + _dbg("Looking for Raqm") + if _find_include_file(self, "raqm.h"): + if _find_library_file(self, "raqm"): + feature.harfbuzz = "raqm" + elif _find_library_file(self, "libraqm"): + feature.harfbuzz = "libraqm" + else: # want to build Raqm + _dbg("Looking for HarfBuzz") + if _find_include_file(self, "hb.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.harfbuzz: + if feature.want_system("fribidi"): # want system FriBiDi + _dbg("Looking for FriBiDi") + if _find_include_file(self, "fribidi.h"): + if _find_library_file(self, "fribidi"): + feature.harfbuzz = "fribidi" + else: # want to build FriBiDi shim + feature.raqm = True if feature.want("lcms"): _dbg("Looking for lcms") @@ -758,9 +806,25 @@ class pil_build_ext(build_ext): # additional libraries if feature.freetype: + srcs = [] libs = ["freetype"] defs = [] - self._update_extension("PIL._imagingft", libs, defs) + if feature.raqm: + if feature.want_system("raqm"): # using system Raqm + defs.append(("HAVE_RAQM", None)) + defs.append(("HAVE_RAQM_SYSTEM", None)) + libs.append(feature.raqm) + else: # building Raqm + defs.append(("HAVE_RAQM", None)) + srcs.append("src/thirdparty/raqm/raqm.c") + libs.append(feature.harfbuzz) + if feature.want_system("fribidi"): # using system FriBiDi + defs.append(("HAVE_FRIBIDI_SYSTEM", None)) + libs.append(feature.fribidi) + else: # building our FriBiDi shim + srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + self._update_extension("PIL._imagingft", libs, defs, srcs) + else: self._remove_extension("PIL._imagingft") @@ -814,6 +878,7 @@ class pil_build_ext(build_ext): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), + (feature.raqm, "RAQM (Text shaping)"), # TODO!!! (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -857,14 +922,7 @@ for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension( - "PIL._imagingft", - [ - "src/_imagingft.c", - "src/thirdparty/raqm/raqm.c", - "src/thirdparty/fribidi-shim/fribidi.c", - ], - ), + Extension("PIL._imagingft", ["src/_imagingft.c"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/PIL/features.py b/src/PIL/features.py index da0ca557c..85459063b 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -118,6 +118,8 @@ features = { "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), + "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), "xcb": ("PIL._imaging", "HAVE_XCB", None), @@ -274,6 +276,11 @@ def pilinfo(out=None, supported_formats=True): # this check is also in src/_imagingcms.c:setup_module() version_static = tuple(int(x) for x in v.split(".")) < (2, 7) t = "compiled for" if version_static else "loaded" + if name == "raqm": + for f in ("fribidi", "harfbuzz"): + v2 = version_feature(f) + if v2 is not None: + v += f", {f} {v2}" print("---", feature, "support ok,", t, v, file=out) else: print("---", feature, "support ok", file=out) diff --git a/src/_imagingft.c b/src/_imagingft.c index 4a4084e9f..fd5530642 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -52,8 +52,21 @@ } \ ; -#include "thirdparty/raqm/raqm.h" -#include "thirdparty/fribidi-shim/fribidi.h" +#ifdef HAVE_RAQM +# ifdef HAVE_RAQM_SYSTEM +# include +# else +# include "thirdparty/raqm/raqm.h" +# ifdef HAVE_FRIBIDI_SYSTEM +# include +# else +# include "thirdparty/fribidi-shim/fribidi.h" +# include +# endif +# endif +#endif + +static int have_raqm = 0; #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -101,11 +114,6 @@ geterror(int code) { return NULL; } -static int -setraqm(void) { - return load_fribidi(); -} - static PyObject * getfont(PyObject *self_, PyObject *args, PyObject *kw) { /* create a font object from a file name and a size (in pixels) */ @@ -474,7 +482,7 @@ text_layout( int color) { size_t count; - if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { + if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); } else { @@ -1357,15 +1365,51 @@ setup_module(PyObject *m) { v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch); PyDict_SetItemString(d, "freetype2_version", v); - setraqm(); - v = PyBool_FromLong(!!p_fribidi); - PyDict_SetItemString(d, "HAVE_RAQM", v); -// if (p_raqm.version_string) { - PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(raqm_version_string())); -// }; +#ifdef HAVE_RAQM +#ifdef HAVE_FRIBIDI_SYSTEM + have_raqm = 1; +#else + load_fribidi(); + have_raqm = !!p_fribidi; +#endif +#else + have_raqm = 0; +#endif + /* if we have Raqm, we have all three (but possibly no version info) */ + v = PyBool_FromLong(have_raqm); + PyDict_SetItemString(d, "HAVE_RAQM", v); PyDict_SetItemString(d, "HAVE_FRIBIDI", v); + PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); + if (have_raqm) { + const char *a, *b; +#ifdef RAQM_VERSION_MAJOR + v = PyUnicode_FromString(raqm_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "raqm_version", v); + +#ifdef FRIBIDI_MAJOR_VERSION + a = strchr(fribidi_version_info, '1'); + b = strchr(fribidi_version_info, '\n'); + if (a && b) { + v = PyUnicode_FromStringAndSize(a, b - a); + } else { + v = Py_None; + } +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "fribidi_version", v); + +#ifdef HB_VERSION_STRING + v = PyUnicode_FromString(hb_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "harfbuzz_version", v); + } return 0; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 64ff7e115..77a55b502 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -51,13 +51,15 @@ int load_fribidi(void) { LOAD_FUNCTION(fribidi_charset_to_unicode); #ifndef _WIN32 - if (dlerror() || error) { + fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); + if (dlerror() || error || (fribidi_version_info == NULL)) { dlclose(p_fribidi); p_fribidi = NULL; return 2; } #else - if (error) { + fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); + if (error || (fribidi_version_info == NULL)) { FreeLibrary(p_fribidi); p_fribidi = NULL; return 2; diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index c79bb170a..b7c6064bc 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -1,4 +1,6 @@ +#define FRIBIDI_MAJOR_VERSION 1 + /* fribidi-types.h */ # if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ @@ -93,6 +95,9 @@ FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, #undef FRIBIDI_FUNC +/* constant, not a function */ +FRIBIDI_ENTRY const char *fribidi_version_info; + /* shim */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index c796f645e..5a0b2078e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,16 +30,20 @@ #include #include +#ifdef HAVE_FRIBIDI_SYSTEM +#include +#else #include "../fribidi-shim/fribidi.h" +#endif #include #include #include "raqm.h" -//#if FRIBIDI_MAJOR_VERSION >= 1 +#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -//#endif +#endif /** * SECTION:raqm From d4403bec46a22d0b8cb8a8fde816519effbc4f2a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:02:21 +0000 Subject: [PATCH 007/133] GHA: fix windows build for dynamic fribidi --- .github/workflows/test-windows.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index db1675135..c5aa133cb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -137,14 +137,11 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_harfbuzz.cmd" + # Raqm dependencies - name: Build dependencies / FriBidi if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_fribidi.cmd" - - name: Build dependencies / Raqm - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libraqm.cmd" - # trim ~150MB x 9 - name: Optimize build cache if: steps.build-cache.outputs.cache-hit != 'true' From 3386a9ce0272d92c1c1c20037c60022aa4e09ea4 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 15:18:43 +0000 Subject: [PATCH 008/133] replace tabs in thirdparty libs --- src/thirdparty/fribidi-shim/fribidi.h | 2 +- src/thirdparty/raqm/raqm-version.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index b7c6064bc..aa446fbef 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -43,7 +43,7 @@ typedef signed char FriBidiLevel; #define FRIBIDI_TYPE_LTR_VAL 0x00000110L #define FRIBIDI_TYPE_RTL_VAL 0x00000111L -#define FRIBIDI_TYPE_ON_VAL 0x00000040L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L typedef uint32_t FriBidiCharType; #define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 4fd5c6842..94b25ada7 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -38,7 +38,7 @@ #define RAQM_VERSION_STRING "0.7.1" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ - ((major)*10000+(minor)*100+(micro) <= \ - RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) #endif /* _RAQM_VERSION_H_ */ From be0d0a3a4895aeef5504a78440cd08dbee16f99c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:27:12 +0000 Subject: [PATCH 009/133] fix finding raqm deps --- setup.py | 38 ++++++++++++++++++++++----- src/_imagingft.c | 12 ++++++--- src/thirdparty/fribidi-shim/fribidi.c | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 0afc6330c..7275dc6ed 100755 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ _LIB_IMAGING = ( "codec_fd", ) -DEBUG = False +DEBUG = True class DependencyException(Exception): @@ -228,6 +228,19 @@ def _find_library_file(self, library): return ret +def _find_include_dir(self, dirname, include): + for directory in self.compiler.include_dirs: + _dbg("Checking for include file %s in %s", (include, directory)) + if os.path.isfile(os.path.join(directory, include)): + _dbg("Found %s in %s", (include, directory)) + return True + subdir = os.path.join(directory, dirname) + _dbg("Checking for include file %s in %s", (include, subdir)) + if os.path.isfile(os.path.join(subdir, include)): + _dbg("Found %s in %s", (include, subdir)) + return subdir + + def _cmd_exists(cmd): return any( os.access(os.path.join(path, cmd), os.X_OK) @@ -689,25 +702,36 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("raqm"): + if feature.freetype and feature.want("raqm"): if feature.want_system("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): - feature.harfbuzz = "raqm" + feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): - feature.harfbuzz = "libraqm" + feature.raqm = "libraqm" else: # want to build Raqm _dbg("Looking for HarfBuzz") - if _find_include_file(self, "hb.h"): + feature.harfbuzz = None + hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") + if hb_dir: + if isinstance(hb_dir, str): + _add_directory(self.compiler.include_dirs, hb_dir, 0) if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: if feature.want_system("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") - if _find_include_file(self, "fribidi.h"): + feature.fribidi = None + fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") + if fribidi_dir: + if isinstance(fribidi_dir, str): + _add_directory( + self.compiler.include_dirs, fribidi_dir, 0 + ) if _find_library_file(self, "fribidi"): - feature.harfbuzz = "fribidi" + feature.fribidi = "fribidi" + feature.raqm = True else: # want to build FriBiDi shim feature.raqm = True diff --git a/src/_imagingft.c b/src/_imagingft.c index fd5530642..b2cf76ce7 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -221,6 +221,8 @@ font_getchar(PyObject *string, int index, FT_ULong *char_out) { return 0; } +#ifdef HAVE_RAQM + static size_t text_layout_raqm( PyObject *string, @@ -386,6 +388,8 @@ failed: return count; } +#endif + static size_t text_layout_fallback( PyObject *string, @@ -481,11 +485,13 @@ text_layout( int mask, int color) { size_t count; - +#ifdef HAVE_RAQM if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); - } else { + } else +#endif + { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); } @@ -1366,7 +1372,7 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); #ifdef HAVE_RAQM -#ifdef HAVE_FRIBIDI_SYSTEM +#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM) have_raqm = 1; #else load_fribidi(); diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 77a55b502..c83159e29 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -25,7 +25,7 @@ int load_fribidi(void) { p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { - p_fribidi = LoadLibrary("libfribidi-1"); + p_fribidi = LoadLibrary("libfribidi-0"); } #endif From 834c2e5e5dea378caf5603f58f0dcd476112cf6f Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:29:43 +0000 Subject: [PATCH 010/133] lint --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7275dc6ed..cb4ec2da8 100755 --- a/setup.py +++ b/setup.py @@ -373,11 +373,11 @@ class pil_build_ext(build_ext): self.feature.required.add("freetype") for x in ("raqm", "fribidi"): if getattr(self, f"system_{x}"): - if getattr(self, f"disable_raqm"): + if getattr(self, "disable_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, f"system_raqm"): + if x == "fribidi" and getattr(self, "system_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --system-raqm" ) From c3fce854f2e227e37036b96980ae00934db483a2 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:32:37 +0000 Subject: [PATCH 011/133] avoid NULL in fribidi shim --- src/thirdparty/fribidi-shim/fribidi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index c83159e29..f23741ecd 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -13,7 +13,7 @@ int load_fribidi(void) { int error = 0; - p_fribidi = NULL; + p_fribidi = 0; /* Microsoft needs a totally different system */ #ifndef _WIN32 @@ -36,11 +36,11 @@ int load_fribidi(void) { #ifndef _WIN32 #define LOAD_FUNCTION(func) \ func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #else #define LOAD_FUNCTION(func) \ func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #endif LOAD_FUNCTION(fribidi_get_bidi_types); @@ -52,16 +52,16 @@ int load_fribidi(void) { #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); - if (dlerror() || error || (fribidi_version_info == NULL)) { + if (dlerror() || error || (fribidi_version_info == 0)) { dlclose(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #else fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); - if (error || (fribidi_version_info == NULL)) { + if (error || (fribidi_version_info == 0)) { FreeLibrary(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #endif From f2b2d53ca82ea2ca882329d382d2812bf7818485 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:01:49 +0000 Subject: [PATCH 012/133] raqm: avoid declaring variables in for statement for C89 compatibility --- src/thirdparty/raqm/raqm.c | 127 +++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 5a0b2078e..96523ffb9 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,6 +220,7 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; + size_t i; if (rq->text_info) return true; @@ -229,7 +230,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -242,10 +243,12 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { + size_t i; + if (!rq->text_info) return; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -551,6 +554,7 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; + size_t i; size_t end = start + len; if (!rq) @@ -572,7 +576,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -646,6 +650,8 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { + size_t i; + if (!rq) return false; @@ -658,7 +664,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -832,6 +838,8 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { + size_t i; + if (!rq) return false; @@ -841,7 +849,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -879,6 +887,9 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { + size_t i; + raqm_run_t *run; + size_t count = 0; if (!rq || !rq->runs || !length) @@ -888,7 +899,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -906,7 +917,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -916,7 +927,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -939,18 +950,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -983,9 +994,11 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { + size_t i; + assert (run); - for (size_t i = 0; i < len / 2; i++) + for (i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1002,6 +1015,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { + size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1021,8 +1035,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (int i = len - 1; - i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1030,13 +1043,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (int i = len - 1; i >= 0; i--) + for (i = len; i-- > 0; ) { if (levels[i] > max_level) max_level = levels[i]; } - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1064,14 +1077,16 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (int i = count - 1; i >= 0; i--) + for (i = count; i-- > 0; ) { if (runs[i].level >= level) { int end = i; - for (i--; (i >= 0 && runs[i].level >= level); i--) + for (; (i > 0 && runs[i - 1].level >= level); i--) ; - _raqm_reverse_run (runs + i + 1, end - i); + _raqm_reverse_run (runs + i, end - i + 1); + if (i-- == 0) + break; } } } @@ -1083,6 +1098,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { + size_t i, j; + raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1185,7 +1202,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1194,7 +1211,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1216,7 +1233,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (int j = runs[i].len - 1; j >= 0; j--) + for (j = runs[i].len; j-- > 0; ) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1247,7 +1264,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (size_t j = 0; j < runs[i].len; j++) + for (j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1277,13 +1294,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1448,18 +1465,19 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - int last_script_index = -1; - int last_set_index = -1; + size_t i, j; + size_t next_script_index = 0; + size_t next_set_index = 0; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1471,9 +1489,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (int i = 0; i < (int) rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1482,7 +1500,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1499,34 +1517,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - last_set_index = i; + next_set_index = i + 1; } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - last_script_index != -1) + next_script_index != 0) { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } else { - for (int j = last_set_index + 1; j < i; ++j) + for (j = next_set_index; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - last_script_index = i; - last_set_index = i; + next_script_index = i + 1; + next_set_index = i + 1; } } @@ -1534,7 +1552,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (int i = rq->text_len - 2; i >= 0; --i) + for (i = rq->text_len - 1; i-- > 0; ) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1543,7 +1561,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1559,6 +1577,7 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { + raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1567,7 +1586,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1653,6 +1672,8 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { + size_t i, j; + raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1676,7 +1697,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1685,7 +1706,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1693,13 +1714,12 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; } @@ -1745,6 +1765,8 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { + size_t i, j; + raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1762,7 +1784,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1771,7 +1793,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1789,11 +1811,10 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From b4a57d6fc5c96749430dd244fa4ce4f7104ab311 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:05:50 +0000 Subject: [PATCH 013/133] support FriBiDi<1.0 --- src/thirdparty/fribidi-shim/fribidi.c | 46 ++++++++++++++++++++------- src/thirdparty/fribidi-shim/fribidi.h | 6 ++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index f23741ecd..20364ea24 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -10,6 +10,20 @@ #include "fribidi.h" + +/* ..._ex adds bracket_types param, ignore and call legacy function */ +FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( + const FriBidiCharType *bidi_types, + const FriBidiBracketType *bracket_types, + const FriBidiStrIndex len, + FriBidiParType *pbase_dir, + FriBidiLevel *embedding_levels) +{ + return fribidi_get_par_embedding_levels( + bidi_types, len, pbase_dir, embedding_levels); +} + + int load_fribidi(void) { int error = 0; @@ -17,11 +31,22 @@ int load_fribidi(void) { /* Microsoft needs a totally different system */ #ifndef _WIN32 - p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == 0); + + p_fribidi = dlopen("libfribidi.so", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.so.0", RTLD_LAZY); + } if (!p_fribidi) { p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); } #else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == 0); + p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { @@ -33,20 +58,17 @@ int load_fribidi(void) { return 1; } -#ifndef _WIN32 -#define LOAD_FUNCTION(func) \ - func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == 0); -#else -#define LOAD_FUNCTION(func) \ - func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == 0); -#endif + /* load ..._ex first to preserve error variable */ + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + if (error) { + /* using FriBiDi 0.x, emulate ..._ex function */ + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + error = 0; + } LOAD_FUNCTION(fribidi_get_bidi_types); LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); -// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index aa446fbef..0f0cdac21 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -83,9 +83,9 @@ FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); -//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, -// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, -// FriBidiLevel *); +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); From 9c178435fba30f820a1dcd7845313609466c925a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:10:37 +0000 Subject: [PATCH 014/133] raqm: fix FriBiDi<1 support --- src/_imagingft.c | 4 ++-- src/thirdparty/fribidi-shim/fribidi.c | 23 +++++++++++++------ src/thirdparty/fribidi-shim/fribidi.h | 32 ++++++++++++++------------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index b2cf76ce7..0995abab3 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1397,10 +1397,10 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, '1'); + a = strchr(fribidi_version_info, ')'); b = strchr(fribidi_version_info, '\n'); if (a && b) { - v = PyUnicode_FromStringAndSize(a, b - a); + v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); } else { v = Py_None; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 20364ea24..55e2a6ab3 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -11,7 +11,7 @@ #include "fribidi.h" -/* ..._ex adds bracket_types param, ignore and call legacy function */ +/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( const FriBidiCharType *bidi_types, const FriBidiBracketType *bracket_types, @@ -23,6 +23,14 @@ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( bidi_types, len, pbase_dir, embedding_levels); } +/* FriBiDi>=1.0.0 gets bracket types here, ignore */ +void fribidi_get_bracket_types_compat( + const FriBidiChar *str, + const FriBidiStrIndex len, + const FriBidiCharType *types, + FriBidiBracketType *btypes) +{ /* no-op*/ } + int load_fribidi(void) { int error = 0; @@ -58,19 +66,20 @@ int load_fribidi(void) { return 1; } - /* load ..._ex first to preserve error variable */ + /* load FriBiDi>=1.0.0 functions first, use error to detect version */ LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + LOAD_FUNCTION(fribidi_get_bracket_types); if (error) { - /* using FriBiDi 0.x, emulate ..._ex function */ - fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + /* using FriBiDi<1.0.0, ignore new parameters */ error = 0; + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + fribidi_get_bracket_types = &fribidi_get_bracket_types_compat; } - LOAD_FUNCTION(fribidi_get_bidi_types); - LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index 0f0cdac21..7712a5b22 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -72,27 +72,29 @@ typedef uint32_t FriBidiParType; typedef ret (*t_##name) (__VA_ARGS__); \ FRIBIDI_ENTRY t_##name name; -FRIBIDI_FUNC(void, fribidi_get_bidi_types, - const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); - -FRIBIDI_FUNC(void, fribidi_get_bracket_types, - const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, - FriBidiBracketType *); - -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, - const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, - FriBidiParType *, FriBidiLevel *); - -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, - const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, - FriBidiLevel *); - FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + #undef FRIBIDI_FUNC /* constant, not a function */ From db0dad909e53ac2f25e4badab4d2aad464c49a68 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:33:33 +0000 Subject: [PATCH 015/133] test --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index cb4ec2da8..031cf5e4e 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,8 @@ def get_version(): NAME = "Pillow" PILLOW_VERSION = get_version() FREETYPE_ROOT = None +HARFBUZZ_ROOT = None +FRIBIDI_ROOT = None IMAGEQUANT_ROOT = None JPEG2K_ROOT = None JPEG_ROOT = None @@ -417,6 +419,8 @@ class pil_build_ext(build_ext): TIFF_ROOT=("libtiff-5", "libtiff-4"), ZLIB_ROOT="zlib", FREETYPE_ROOT="freetype2", + HARFBUZZ_ROOT="harfbuzz", + FRIBIDI_ROOT="fribidi", LCMS_ROOT="lcms2", IMAGEQUANT_ROOT="libimagequant", ).items(): From 8c02e3803b995fe0e0d8db2ea4a59c394130d611 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:37:21 +0100 Subject: [PATCH 016/133] Revert "raqm: avoid declaring variables in for statement for C89 compatibility" This reverts commit b3cfe73854e74bc25a88f53b177713bfb63812e4. --- src/thirdparty/raqm/raqm.c | 127 ++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 96523ffb9..5a0b2078e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,7 +220,6 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; - size_t i; if (rq->text_info) return true; @@ -230,7 +229,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -243,12 +242,10 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { - size_t i; - if (!rq->text_info) return; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -554,7 +551,6 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; - size_t i; size_t end = start + len; if (!rq) @@ -576,7 +572,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -650,8 +646,6 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { - size_t i; - if (!rq) return false; @@ -664,7 +658,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -838,8 +832,6 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { - size_t i; - if (!rq) return false; @@ -849,7 +841,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -887,9 +879,6 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { - size_t i; - raqm_run_t *run; - size_t count = 0; if (!rq || !rq->runs || !length) @@ -899,7 +888,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -917,7 +906,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -927,7 +916,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -950,18 +939,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -994,11 +983,9 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { - size_t i; - assert (run); - for (i = 0; i < len / 2; i++) + for (size_t i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1015,7 +1002,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { - size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1035,7 +1021,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1043,13 +1030,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (i = len; i-- > 0; ) + for (int i = len - 1; i >= 0; i--) { if (levels[i] > max_level) max_level = levels[i]; } - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1077,16 +1064,14 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (i = count; i-- > 0; ) + for (int i = count - 1; i >= 0; i--) { if (runs[i].level >= level) { int end = i; - for (; (i > 0 && runs[i - 1].level >= level); i--) + for (i--; (i >= 0 && runs[i].level >= level); i--) ; - _raqm_reverse_run (runs + i, end - i + 1); - if (i-- == 0) - break; + _raqm_reverse_run (runs + i + 1, end - i); } } } @@ -1098,8 +1083,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { - size_t i, j; - raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1202,7 +1185,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1211,7 +1194,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1233,7 +1216,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = runs[i].len; j-- > 0; ) + for (int j = runs[i].len - 1; j >= 0; j--) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1264,7 +1247,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = 0; j < runs[i].len; j++) + for (size_t j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1294,13 +1277,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1465,19 +1448,18 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - size_t i, j; - size_t next_script_index = 0; - size_t next_set_index = 0; + int last_script_index = -1; + int last_set_index = -1; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1489,9 +1471,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (i = 0; i < rq->text_len; i++) + for (int i = 0; i < (int) rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1500,7 +1482,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1517,34 +1499,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - next_set_index = i + 1; + last_set_index = i; } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - next_script_index != 0) + last_script_index != -1) { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } else { - for (j = next_set_index; j < i; ++j) + for (int j = last_set_index + 1; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - next_script_index = i + 1; - next_set_index = i + 1; + last_script_index = i; + last_set_index = i; } } @@ -1552,7 +1534,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (i = rq->text_len - 1; i-- > 0; ) + for (int i = rq->text_len - 2; i >= 0; --i) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1561,7 +1543,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1577,7 +1559,6 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { - raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1586,7 +1567,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1672,8 +1653,6 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { - size_t i, j; - raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1697,7 +1676,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1706,7 +1685,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1714,12 +1693,13 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; } @@ -1765,8 +1745,6 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { - size_t i, j; - raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1784,7 +1762,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1793,7 +1771,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1811,10 +1789,11 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From 43bde01623d6db01435b8a821160ac48d9b722b0 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:47:08 +0100 Subject: [PATCH 017/133] disable Raqm/FriBiDi vendoring by default, except in Windows tests --- setup.py | 22 +++++++++++----------- winbuild/build_prepare.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 031cf5e4e..65fc7c47f 100755 --- a/setup.py +++ b/setup.py @@ -125,7 +125,7 @@ _LIB_IMAGING = ( "codec_fd", ) -DEBUG = True +DEBUG = False class DependencyException(Exception): @@ -292,7 +292,7 @@ class pil_build_ext(build_ext): ] required = {"jpeg", "zlib"} - system = set() + vendor = set() def __init__(self): for f in self.features: @@ -305,7 +305,7 @@ class pil_build_ext(build_ext): return getattr(self, feat) is None def want_system(self, feat): - return feat in self.system + return feat not in self.vendor def __iter__(self): yield from self.features @@ -317,7 +317,7 @@ class pil_build_ext(build_ext): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"system-{x}", None, f"Use system version of {x}") + (f"_vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ class pil_build_ext(build_ext): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"system_{x}", None) + setattr(self, f"_vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,17 +374,17 @@ class pil_build_ext(build_ext): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"system_{x}"): + if getattr(self, f"_vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --disable-raqm" + f"Conflicting options: --_vendor-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, "system_raqm"): + if x == "fribidi" and not getattr(self, "_vendor_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --system-raqm" + f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" ) - _dbg("Using system version of %s", x) - self.feature.system.add(x) + _dbg("Using vendored version of %s", x) + self.feature.vendor.add(x) def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fd63f4f1e..100f07e90 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From 0488a2761ac3ccc6cc65f910a8254b0e0677b0c9 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:51:45 +0100 Subject: [PATCH 018/133] can't use underscore prefix for distutils options --- setup.py | 12 ++++++------ winbuild/build_prepare.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 65fc7c47f..fbf869ffb 100755 --- a/setup.py +++ b/setup.py @@ -317,7 +317,7 @@ class pil_build_ext(build_ext): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"_vendor-{x}", None, f"Use vendored version of {x}") + (f"vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ class pil_build_ext(build_ext): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"_vendor_{x}", None) + setattr(self, f"vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,14 +374,14 @@ class pil_build_ext(build_ext): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"_vendor_{x}"): + if getattr(self, f"vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and --disable-raqm" + f"Conflicting options: --vendor-{x} and --disable-raqm" ) - if x == "fribidi" and not getattr(self, "_vendor_raqm"): + if x == "fribidi" and not getattr(self, "vendor_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" + f"Conflicting options: --vendor-{x} and not --vendor-raqm" ) _dbg("Using vendored version of %s", x) self.feature.vendor.add(x) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 100f07e90..dc372f36b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From aae94110d76acedf7d4fb12bb45cc77df06027e2 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 13:08:38 +0100 Subject: [PATCH 019/133] lint --- 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 dc372f36b..3b1a15eac 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] write_script("build_pillow.cmd", lines) From ac31061f221d4dab40ae36d61f1b09e76c20d484 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Jan 2021 19:29:11 +1100 Subject: [PATCH 020/133] Handle PCX images with an odd stride --- Tests/images/odd_stride.pcx | Bin 0 -> 14313 bytes Tests/test_file_pcx.py | 8 ++++++++ src/PIL/PcxImagePlugin.py | 13 +++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 Tests/images/odd_stride.pcx diff --git a/Tests/images/odd_stride.pcx b/Tests/images/odd_stride.pcx new file mode 100644 index 0000000000000000000000000000000000000000..ee0c2eecaebe5a629ccfab523971c347a878ae79 GIT binary patch literal 14313 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 670c03b95..61e33a57b 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -44,6 +44,14 @@ def test_odd(tmp_path): _roundtrip(tmp_path, hopper(mode).resize((511, 511))) +def test_odd_read(): + # Reading an image with an odd stride, making it malformed + with Image.open("Tests/images/odd_stride.pcx") as im: + im.load() + + assert im.size == (371, 150) + + def test_pil184(): # Check reading of files where xmin/xmax is not zero. diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 3874e5436..d2e166bdd 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -66,13 +66,13 @@ class PcxImageFile(ImageFile.ImageFile): version = s[1] bits = s[3] planes = s[65] - ignored_stride = i16(s, 66) + provided_stride = i16(s, 66) logger.debug( "PCX version %s, bits %s, planes %s, stride %s", version, bits, planes, - ignored_stride, + provided_stride, ) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -110,10 +110,15 @@ class PcxImageFile(ImageFile.ImageFile): self.mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] - # don't trust the passed in stride. Calculate for ourselves. + # Don't trust the passed in stride. + # Calculate the approximate position for ourselves. # CVE-2020-35653 stride = (self._size[0] * bits + 7) // 8 - stride += stride % 2 + + # While the specification states that this must be even, + # not all images follow this + if provided_stride != stride: + stride += stride % 2 bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) From b39977e1c2fca2aef8c28d31522136a11a9be59e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Jan 2021 21:33:35 +1100 Subject: [PATCH 021/133] Document license for several fonts --- .../DejaVuSans-24-1-stripped.ttf | Bin .../DejaVuSans-24-2-stripped.ttf | Bin .../DejaVuSans-24-4-stripped.ttf | Bin .../DejaVuSans-24-8-stripped.ttf | Bin Tests/fonts/{ => DejaVuSans}/DejaVuSans.ttf | Bin Tests/fonts/DejaVuSans/LICENSE.txt | 40 ++++++++++++++++++ Tests/fonts/LICENSE.txt | 3 +- Tests/test_imagefont.py | 12 +++--- Tests/test_imagefontctl.py | 2 +- 9 files changed, 50 insertions(+), 7 deletions(-) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-1-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-2-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-4-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-8-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans.ttf (100%) create mode 100644 Tests/fonts/DejaVuSans/LICENSE.txt diff --git a/Tests/fonts/DejaVuSans-24-1-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-1-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-2-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-2-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-4-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-4-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-8-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-8-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf diff --git a/Tests/fonts/DejaVuSans.ttf b/Tests/fonts/DejaVuSans/DejaVuSans.ttf similarity index 100% rename from Tests/fonts/DejaVuSans.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans.ttf diff --git a/Tests/fonts/DejaVuSans/LICENSE.txt b/Tests/fonts/DejaVuSans/LICENSE.txt new file mode 100644 index 000000000..30516578f --- /dev/null +++ b/Tests/fonts/DejaVuSans/LICENSE.txt @@ -0,0 +1,40 @@ +DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. + +DejaVu Fonts — License +Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below) + +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. + +Arev Fonts Copyright +Original text + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. \ No newline at end of file diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 06eaa9a4e..88a28de59 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -15,8 +15,9 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception. OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. +KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later. +FreeMono.ttf is licensed under GPLv3. 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0c219fed1..2a2349e3b 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -52,7 +52,7 @@ class TestImageFont: ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) assert ttf_copy.size == FONT_SIZE + 1 - second_font_path = "Tests/fonts/DejaVuSans.ttf" + second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" ttf_copy = ttf.font_variant(font=second_font_path) assert ttf_copy.path == second_font_path @@ -156,8 +156,8 @@ class TestImageFont: ("text", "L", "FreeMono.ttf", 15, 36, 36), ("text", "1", "FreeMono.ttf", 15, 36, 36), # issue 4177 - ("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875), - ("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875), + ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875), + ("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875), # test 'l' not including extra margin # using exact value 2047 / 64 for raqm, checked with debugger ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), @@ -855,7 +855,7 @@ class TestImageFont: layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" font = ImageFont.truetype( - f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf", + f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf", 24, layout_engine=self.LAYOUT_ENGINE, ) @@ -963,7 +963,9 @@ def test_render_mono_size(): im = Image.new("P", (100, 30), "white") draw = ImageDraw.Draw(im) ttf = ImageFont.truetype( - "Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC + "Tests/fonts/DejaVuSans/DejaVuSans.ttf", + 18, + layout_engine=ImageFont.LAYOUT_BASIC, ) draw.text((10, 10), "r" * 10, "black", ttf) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 82e2b4ebc..a80aca2fb 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -10,7 +10,7 @@ from .helper import ( ) FONT_SIZE = 20 -FONT_PATH = "Tests/fonts/DejaVuSans.ttf" +FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" pytestmark = skip_unless_feature("raqm") From f2f92d22d180ddcecab5bf9c0a4c0151c04d0980 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Jan 2021 21:10:49 +1100 Subject: [PATCH 022/133] Do not use "use built-in mapper WIN32 only" --- src/PIL/ImageFile.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index f2a55cb54..f58de95bd 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -192,24 +192,14 @@ class ImageFile(Image.Image): and args[0] in Image._MAPMODES ): try: - if hasattr(Image.core, "map"): - # use built-in mapper WIN32 only - self.map = Image.core.map(self.filename) - self.map.seek(offset) - self.im = self.map.readimage( - self.mode, self.size, args[1], args[2] - ) - else: - # use mmap, if possible - import mmap + # use mmap, if possible + import mmap - with open(self.filename) as fp: - self.map = mmap.mmap( - fp.fileno(), 0, access=mmap.ACCESS_READ - ) - self.im = Image.core.map_buffer( - self.map, self.size, decoder_name, offset, args - ) + with open(self.filename) as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + self.im = Image.core.map_buffer( + self.map, self.size, decoder_name, offset, args + ) readonly = 1 # After trashing self.im, # we might need to reload the palette data. From 685e95118250e764ff50e6ed29d5ed96fc873b4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Jan 2021 21:13:07 +1100 Subject: [PATCH 023/133] Removed unused C code --- src/_imaging.c | 5 - src/map.c | 260 ------------------------------------------------- 2 files changed, 265 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 01dd22486..a5b12d325 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3973,8 +3973,6 @@ PyPath_Create(ImagingObject *self, PyObject *args); extern PyObject * PyOutline_Create(ImagingObject *self, PyObject *args); -extern PyObject * -PyImaging_Mapper(PyObject *self, PyObject *args); extern PyObject * PyImaging_MapBuffer(PyObject *self, PyObject *args); @@ -4030,9 +4028,6 @@ static PyMethodDef functions[] = { /* Memory mapping */ #ifdef WITH_MAPPING -#ifdef _WIN32 - {"map", (PyCFunction)PyImaging_Mapper, 1}, -#endif {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, #endif diff --git a/src/map.c b/src/map.c index 2636a684b..c298bd148 100644 --- a/src/map.c +++ b/src/map.c @@ -28,269 +28,9 @@ PyImaging_CheckBuffer(PyObject *buffer); extern int PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view); -/* -------------------------------------------------------------------- */ -/* Standard mapper */ - -typedef struct { - PyObject_HEAD char *base; - int size; - int offset; -#ifdef _WIN32 - HANDLE hFile; - HANDLE hMap; -#endif -} ImagingMapperObject; - -static PyTypeObject ImagingMapperType; - -ImagingMapperObject * -PyImaging_MapperNew(const char *filename, int readonly) { - ImagingMapperObject *mapper; - - if (PyType_Ready(&ImagingMapperType) < 0) { - return NULL; - } - - mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType); - if (mapper == NULL) { - return NULL; - } - - mapper->base = NULL; - mapper->size = mapper->offset = 0; - -#ifdef _WIN32 - mapper->hFile = (HANDLE)-1; - mapper->hMap = (HANDLE)-1; - - /* FIXME: currently supports readonly mappings only */ - mapper->hFile = CreateFile( - filename, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (mapper->hFile == (HANDLE)-1) { - PyErr_SetString(PyExc_OSError, "cannot open file"); - Py_DECREF(mapper); - return NULL; - } - - mapper->hMap = CreateFileMapping(mapper->hFile, NULL, PAGE_READONLY, 0, 0, NULL); - if (mapper->hMap == (HANDLE)-1) { - CloseHandle(mapper->hFile); - PyErr_SetString(PyExc_OSError, "cannot map file"); - Py_DECREF(mapper); - return NULL; - } - - mapper->base = (char *)MapViewOfFile(mapper->hMap, FILE_MAP_READ, 0, 0, 0); - - mapper->size = GetFileSize(mapper->hFile, 0); -#endif - - return mapper; -} - -static void -mapping_dealloc(ImagingMapperObject *mapper) { -#ifdef _WIN32 - if (mapper->base != 0) { - UnmapViewOfFile(mapper->base); - } - if (mapper->hMap != (HANDLE)-1) { - CloseHandle(mapper->hMap); - } - if (mapper->hFile != (HANDLE)-1) { - CloseHandle(mapper->hFile); - } - mapper->base = 0; - mapper->hMap = mapper->hFile = (HANDLE)-1; -#endif - PyObject_Del(mapper); -} - -/* -------------------------------------------------------------------- */ -/* standard file operations */ - -static PyObject * -mapping_read(ImagingMapperObject *mapper, PyObject *args) { - PyObject *buf; - - int size = -1; - if (!PyArg_ParseTuple(args, "|i", &size)) { - return NULL; - } - - /* check size */ - if (size < 0 || mapper->offset + size > mapper->size) { - size = mapper->size - mapper->offset; - } - if (size < 0) { - size = 0; - } - - buf = PyBytes_FromStringAndSize(NULL, size); - if (!buf) { - return NULL; - } - - if (size > 0) { - memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size); - mapper->offset += size; - } - - return buf; -} - -static PyObject * -mapping_seek(ImagingMapperObject *mapper, PyObject *args) { - int offset; - int whence = 0; - if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) { - return NULL; - } - - switch (whence) { - case 0: /* SEEK_SET */ - mapper->offset = offset; - break; - case 1: /* SEEK_CUR */ - mapper->offset += offset; - break; - case 2: /* SEEK_END */ - mapper->offset = mapper->size + offset; - break; - default: - /* FIXME: raise ValueError? */ - break; - } - - Py_INCREF(Py_None); - return Py_None; -} - -/* -------------------------------------------------------------------- */ -/* map entire image */ - extern PyObject * PyImagingNew(Imaging im); -static void -ImagingDestroyMap(Imaging im) { - return; /* nothing to do! */ -} - -static PyObject * -mapping_readimage(ImagingMapperObject *mapper, PyObject *args) { - int y, size; - Imaging im; - - char *mode; - int xsize; - int ysize; - int stride; - int orientation; - if (!PyArg_ParseTuple( - args, "s(ii)ii", &mode, &xsize, &ysize, &stride, &orientation)) { - return NULL; - } - - if (stride <= 0) { - /* FIXME: maybe we should call ImagingNewPrologue instead */ - if (!strcmp(mode, "L") || !strcmp(mode, "P")) { - stride = xsize; - } else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) { - stride = xsize * 2; - } else { - stride = xsize * 4; - } - } - - size = ysize * stride; - - if (mapper->offset + size > mapper->size) { - PyErr_SetString(PyExc_OSError, "image file truncated"); - return NULL; - } - - im = ImagingNewPrologue(mode, xsize, ysize); - if (!im) { - return NULL; - } - - /* setup file pointers */ - if (orientation > 0) { - for (y = 0; y < ysize; y++) { - im->image[y] = mapper->base + mapper->offset + y * stride; - } - } else { - for (y = 0; y < ysize; y++) { - im->image[ysize - y - 1] = mapper->base + mapper->offset + y * stride; - } - } - - im->destroy = ImagingDestroyMap; - - mapper->offset += size; - - return PyImagingNew(im); -} - -static struct PyMethodDef methods[] = { - /* standard file interface */ - {"read", (PyCFunction)mapping_read, 1}, - {"seek", (PyCFunction)mapping_seek, 1}, - /* extensions */ - {"readimage", (PyCFunction)mapping_readimage, 1}, - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject ImagingMapperType = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingMapper", /*tp_name*/ - sizeof(ImagingMapperObject), /*tp_size*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)mapping_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ -}; - -PyObject * -PyImaging_Mapper(PyObject *self, PyObject *args) { - char *filename; - if (!PyArg_ParseTuple(args, "s", &filename)) { - return NULL; - } - - return (PyObject *)PyImaging_MapperNew(filename, 1); -} - /* -------------------------------------------------------------------- */ /* Buffer mapper */ From e4b9f88de4378ae622b54998b186e93e68fab4c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jan 2021 12:59:45 +1100 Subject: [PATCH 024/133] Updated test now that Win32 uses map_buffer --- Tests/test_map.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/test_map.py b/Tests/test_map.py index 2b65fb3f9..9131e6b7d 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -4,10 +4,6 @@ import pytest from PIL import Image -from .helper import is_win32 - -pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer") - def test_overflow(): # There is the potential to overflow comparisons in map.c From 11cb3fba9c93275d78f4339973560938fc07bd9e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jan 2021 13:01:42 +1100 Subject: [PATCH 025/133] Added test --- Tests/test_map.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/test_map.py b/Tests/test_map.py index 9131e6b7d..752c5f268 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -23,6 +23,13 @@ def test_overflow(): Image.MAX_IMAGE_PIXELS = max_pixels +def test_tobytes(): + # Previously raised an access violation on Windows + with Image.open("Tests/images/l2rgb_read.bmp") as im: + with pytest.raises((ValueError, MemoryError, OSError)): + im.tobytes() + + @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") def test_ysize(): numpy = pytest.importorskip("numpy", reason="NumPy not installed") From faf8fad76d5f4a2ec9a41bc92fad106b35a613f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Oct 2020 20:35:45 +1100 Subject: [PATCH 026/133] Stopped flattening EXIF IFD into getexif() --- Tests/test_image.py | 24 +++--- src/PIL/Image.py | 168 ++++++++++++++++++++----------------- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- 6 files changed, 106 insertions(+), 94 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..3d0804950 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -663,43 +663,43 @@ class TestImage: exif = im.getexif() assert 258 not in exif assert 274 in exif - assert 40960 in exif - assert exif[40963] == 450 + assert 282 in exif + assert exif[296] == 2 assert exif[11] == "gThumb 3.0.1" out = str(tmp_path / "temp.jpg") exif[258] = 8 del exif[274] - del exif[40960] - exif[40963] = 455 + del exif[282] + exif[296] = 455 exif[11] = "Pillow test" im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() assert reloaded_exif[258] == 8 assert 274 not in reloaded_exif - assert 40960 not in reloaded_exif - assert reloaded_exif[40963] == 455 + assert 282 not in reloaded_exif + assert reloaded_exif[296] == 455 assert reloaded_exif[11] == "Pillow test" with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian exif = im.getexif() assert 258 not in exif - assert 40962 in exif - assert exif[40963] == 200 + assert 306 in exif + assert exif[274] == 1 assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" out = str(tmp_path / "temp.jpg") exif[258] = 8 - del exif[34665] - exif[40963] = 455 + del exif[306] + exif[274] = 455 exif[305] = "Pillow test" im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() assert reloaded_exif[258] == 8 - assert 34665 not in reloaded_exif - assert reloaded_exif[40963] == 455 + assert 306 not in reloaded_exif + assert reloaded_exif[274] == 455 assert reloaded_exif[305] == "Pillow test" @skip_unless_feature("webp") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 01fe7ed1b..354dbbed7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3309,11 +3309,11 @@ class Exif(MutableMapping): # returns a dict with any single item tuples/lists as individual values return {k: self._fixup(v) for k, v in src_dict.items()} - def _get_ifd_dict(self, tag): + def _get_ifd_dict(self, offset): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. - self.fp.seek(self[tag]) + self.fp.seek(offset) except (KeyError, TypeError): pass else: @@ -3351,11 +3351,16 @@ class Exif(MutableMapping): self.fp.seek(self._info.next) self._info.load(self.fp) + def _get_merged_dict(self): + merged_dict = dict(self) + # get EXIF extension - ifd = self._get_ifd_dict(0x8769) - if ifd: - self._data.update(ifd) - self._ifds[0x8769] = ifd + if 0x8769 in self: + ifd = self._get_ifd_dict(self[0x8769]) + if ifd: + merged_dict.update(ifd) + + return merged_dict def tobytes(self, offset=8): from . import TiffImagePlugin @@ -3370,87 +3375,94 @@ class Exif(MutableMapping): return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): - if tag not in self._ifds and tag in self: - if tag in [0x8825, 0xA005]: - # gpsinfo, interop - self._ifds[tag] = self._get_ifd_dict(tag) - elif tag == 0x927C: # makernote - from .TiffImagePlugin import ImageFileDirectory_v2 + if tag not in self._ifds: + if tag in [0x8769, 0x8825]: + # exif, gpsinfo + if tag in self: + self._ifds[tag] = self._get_ifd_dict(self[tag]) + elif tag in [0xA005, 0x927C]: + # interop, makernote + if 0x8769 not in self._ifds: + self.get_ifd(0x8769) + tag_data = self._ifds[0x8769][tag] + if tag == 0x927C: + from .TiffImagePlugin import ImageFileDirectory_v2 - if self[0x927C][:8] == b"FUJIFILM": - exif_data = self[0x927C] - ifd_offset = i32le(exif_data, 8) - ifd_data = exif_data[ifd_offset:] + if self._ifds[0x8769][tag][:8] == b"FUJIFILM": + ifd_offset = i32le(tag_data, 8) + ifd_data = tag_data[ifd_offset:] - makernote = {} - for i in range(0, struct.unpack(" 4: - (offset,) = struct.unpack(" 4: + (offset,) = struct.unpack("H", ifd_data[:2])[0]): - ifd_tag, typ, count, data = struct.unpack( - ">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2] - ) - if ifd_tag == 0x1101: - # CameraInfo - (offset,) = struct.unpack(">L", data) - self.fp.seek(offset) - - camerainfo = {"ModelID": self.fp.read(4)} - - self.fp.read(4) - # Seconds since 2000 - camerainfo["TimeStamp"] = i32le(self.fp.read(12)) - - self.fp.read(4) - camerainfo["InternalSerialNumber"] = self.fp.read(4) - - self.fp.read(12) - parallax = self.fp.read(4) - handler = ImageFileDirectory_v2._load_dispatch[ - TiffTags.FLOAT - ][1] - camerainfo["Parallax"] = handler( - ImageFileDirectory_v2(), parallax, False + makernote[ifd_tag] = handler( + ImageFileDirectory_v2(), data, False ) + self._ifds[tag] = dict(self._fixup_dict(makernote)) + elif self.get(0x010F) == "Nintendo": + makernote = {} + for i in range(0, struct.unpack(">H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) - self.fp.read(4) - camerainfo["Category"] = self.fp.read(2) + camerainfo = {"ModelID": self.fp.read(4)} - makernote = {0x1101: dict(self._fixup_dict(camerainfo))} - self._ifds[0x927C] = makernote + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) + + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + ) + + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) + + makernote = {0x1101: dict(self._fixup_dict(camerainfo))} + self._ifds[tag] = makernote + else: + # gpsinfo, interop + self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) def __str__(self): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 054495e6f..ad260acbd 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -478,7 +478,7 @@ class JpegImageFile(ImageFile.ImageFile): def _getexif(self): if "exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def _getmp(self): diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 575cc9c8e..8b49d10e5 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -82,7 +82,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) - exif = self.getexif() + exif = self.getexif().get_ifd(0x8769) if 40962 in exif and 40963 in exif: self._size = (exif[40962], exif[40963]) elif "exif" in self.info: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 2d4ac7606..30eb13aa3 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -968,7 +968,7 @@ class PngImageFile(ImageFile.ImageFile): self.load() if "exif" not in self.info and "Raw profile type exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def getexif(self): if "exif" not in self.info: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 2e9746fa3..bc12ce4be 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile): def _getexif(self): if "exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def seek(self, frame): if not self._seek_check(frame): From 4b14f0102d8fa585c900ff8a174defb2452c042b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Oct 2020 20:16:48 +1100 Subject: [PATCH 027/133] Save base IFDs when converting Exif to bytes --- Tests/test_image.py | 8 ++++++++ src/PIL/Image.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index 3d0804950..b1db41235 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -752,6 +752,14 @@ class TestImage: 4098: 1704, } + def test_exif_ifd(self): + im = Image.open("Tests/images/flower.jpg") + exif = im.getexif() + + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + @pytest.mark.parametrize( "test_module", [PIL, Image], diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 354dbbed7..73eef3d81 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3371,6 +3371,8 @@ class Exif(MutableMapping): head = b"MM\x00\x2A\x00\x00\x00\x08" ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): + if tag in [0x8769, 0x8225] and not isinstance(value, dict): + value = self.get_ifd(tag) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) From b25bc400093283104e7eeb232074795f2e2cdb8e Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:35:16 +1100 Subject: [PATCH 028/133] Simplified code Co-authored-by: Konstantin Kopachev --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 73eef3d81..d318bc236 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3390,7 +3390,7 @@ class Exif(MutableMapping): if tag == 0x927C: from .TiffImagePlugin import ImageFileDirectory_v2 - if self._ifds[0x8769][tag][:8] == b"FUJIFILM": + if tag_data[:8] == b"FUJIFILM": ifd_offset = i32le(tag_data, 8) ifd_data = tag_data[ifd_offset:] From e763f8f2be026cd598f0e1bd3c8b50c5b2d9be64 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 07:47:59 +1100 Subject: [PATCH 029/133] Save interop IFD when converting Exif to bytes --- Tests/test_image.py | 9 +++++++-- src/PIL/Image.py | 7 +++++++ src/PIL/TiffTags.py | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index b1db41235..e1c14d0d8 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -752,9 +752,14 @@ class TestImage: 4098: 1704, } + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) + def test_exif_ifd(self): - im = Image.open("Tests/images/flower.jpg") - exif = im.getexif() + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + del exif.get_ifd(0x8769)[0xA005] reloaded_exif = Image.Exif() reloaded_exif.load(exif.tobytes()) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d318bc236..df3ebfd18 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3373,6 +3373,13 @@ class Exif(MutableMapping): for tag, value in self.items(): if tag in [0x8769, 0x8225] and not isinstance(value, dict): value = self.get_ifd(tag) + if ( + tag == 0x8769 + and 0xA005 in value + and not isinstance(value[0xA005], dict) + ): + value = value.copy() + value[0xA005] = self.get_ifd(0xA005) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 796ff3479..9e9e117a4 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -184,6 +184,7 @@ TAGS_V2 = { 34665: ("ExifIFD", LONG, 1), 34675: ("ICCProfile", UNDEFINED, 1), 34853: ("GPSInfoIFD", LONG, 1), + 40965: ("InteroperabilityIFD", LONG, 1), # MPInfo 45056: ("MPFVersion", UNDEFINED, 1), 45057: ("NumberOfImages", LONG, 1), From c0ee869c2c09bca7825e0fd9ef76515de880d6da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 07:48:58 +1100 Subject: [PATCH 030/133] Only draw each rectangle outline pixel once --- .../imagedraw_rectangle_translucent_outline.png | Bin 0 -> 235 bytes Tests/test_imagedraw.py | 14 ++++++++++++++ src/libImaging/Draw.c | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Tests/images/imagedraw_rectangle_translucent_outline.png diff --git a/Tests/images/imagedraw_rectangle_translucent_outline.png b/Tests/images/imagedraw_rectangle_translucent_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..845648762ccab1e1d2047f653a9bb6bda4e22e65 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^DImhline(im, x0, y0 + i, x1, ink); draw->hline(im, x0, y1 - i, x1, ink); - draw->line(im, x1 - i, y0, x1 - i, y1, ink); - draw->line(im, x0 + i, y1, x0 + i, y0, ink); + draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink); + draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); } } From 61ee8ec03cc9f12810e239d1618047cd7330d800 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 30 Dec 2020 03:27:28 +0100 Subject: [PATCH 031/133] document and add tests for SBIX color font support --- .github/workflows/test-windows.yml | 2 +- Tests/fonts/LICENSE.txt | 2 ++ Tests/fonts/chromacheck-sbix.woff | Bin 0 -> 740 bytes Tests/images/chromacheck-sbix.png | Bin 0 -> 1410 bytes Tests/images/chromacheck-sbix_mask.png | Bin 0 -> 1415 bytes Tests/test_imagefont.py | 40 +++++++++++++++++++++++++ docs/reference/ImageDraw.rst | 10 +++---- docs/releasenotes/8.0.0.rst | 3 +- 8 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 Tests/fonts/chromacheck-sbix.woff create mode 100644 Tests/images/chromacheck-sbix.png create mode 100644 Tests/images/chromacheck-sbix_mask.png diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f3bb85f32..330db7c65 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -110,7 +110,7 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" - # for FreeType CBDT font support + # for FreeType CBDT/SBIX font support - name: Build dependencies / libpng if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libpng.cmd" diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 06eaa9a4e..884f6a5bf 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -15,6 +15,8 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception. OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC + DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. diff --git a/Tests/fonts/chromacheck-sbix.woff b/Tests/fonts/chromacheck-sbix.woff new file mode 100644 index 0000000000000000000000000000000000000000..518d4b7ea6c45b7c3c660ef94aa6b71affa9fa70 GIT binary patch literal 740 zcmXT-cXMN4WB>xDCk)&mnmGYTU4j5$C_DxI*pFoa zFMC0LaS70GHb6cPNF4(+14D6ACeRKh49lLy?gieV(neV{1Cf6`;7E?e*CY!ubQsB?9d{$1L`WtPRA`}Ggf|lf4pT1 zJ3|}i2C!Sw4gf8wWnclhi^Yk};9268#19OYm;(MZGjec!x+H4F{O3;P&Luk`i&?MD$f~p69W~x5zbY02Qr}@)!CD**2oiA59uI$vD+NvI$ z+t77`sm3X0xnYAtT+4Zv2_NU_^(l&OzbBKzvU7pw!~ef*_}LXEi#>3b6Xth5l*#7S Xof)d6z~~=tKfTnC)0e?ILG362-q6Sn literal 0 HcmV?d00001 diff --git a/Tests/images/chromacheck-sbix.png b/Tests/images/chromacheck-sbix.png new file mode 100644 index 0000000000000000000000000000000000000000..b906ef133a473b8f5bb3f777b0342acdf7a841cf GIT binary patch literal 1410 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL985qF{<{ljGcd4vdb&7?h^t!3eJOz^LVrEgM07i zyyg6GeV_4d;k=3nhqeSp$tKnm4zUS}qnyz&7)=L5H80GWdGj8_hmQ8a?f#fwPCU}W T?Aix_Wh;ZHtDnm{r-UW|CpLkcdw+BWkigGfy{KSPE0h6bL4Ohzo+Cj@j9oCg;tIo|fc z%}SZo>IdR~$6kNM$!|00oH)orY}7fUDP$04WNgWD+sw$lj1NTl2V=p5aPJPr_uF`+ VU$07d4J=?8JYD@<);T3K0RVGNmXH7d literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 5d611a27f..757395bcf 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -897,6 +897,46 @@ class TestImageFont: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or unsupported") + @skip_unless_feature_version("freetype2", "2.5.1") + def test_sbix(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", embedded_color=True, font=font) + + with Image.open("Tests/images/chromacheck-sbix.png") as expected: + assert_image_similar(im, expected, 1) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + + @skip_unless_feature_version("freetype2", "2.5.1") + def test_sbix_mask(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", (100, 0, 0), font=font) + + with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: + assert_image_similar(im, expected, 1) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + @skip_unless_feature_version("freetype2", "2.10.0") def test_colr(self): font = ImageFont.truetype( diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 57d1c2dda..e2ef548d4 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -352,7 +352,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. versionadded:: 8.0.0 @@ -413,7 +413,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. versionadded:: 8.0.0 @@ -577,7 +577,7 @@ Methods correct substitutions as appropriate, if available. It should be a `BCP 47 language code`_. Requires libraqm. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -626,7 +626,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param stroke_width: The width of the text stroke. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -669,7 +669,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param stroke_width: The width of the text stroke. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: getdraw(im=None, hints=None) diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst index 1bef62e00..28dc8324d 100644 --- a/docs/releasenotes/8.0.0.rst +++ b/docs/releasenotes/8.0.0.rst @@ -115,8 +115,9 @@ now support fonts with embedded color data. To render text with embedded color data, use the parameter ``embedded_color=True``. Support for CBDT fonts requires FreeType 2.5 compiled with libpng. +Support for SBIX fonts requires FreeType 2.5.1 compiled with libpng. Support for COLR fonts requires FreeType 2.10. -SBIX and SVG fonts are not yet supported. +SVG fonts are not yet supported. ImageDraw.textlength ^^^^^^^^^^^^^^^^^^^^ From c709aa3d28abbdc006288ac88de082ba00439be0 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 30 Dec 2020 04:48:01 +0100 Subject: [PATCH 032/133] minor test formatting cleanup --- Tests/test_imagefont.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 757395bcf..259a0f872 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -869,12 +869,12 @@ class TestImageFont: im = Image.new("RGB", (150, 150), "white") d = ImageDraw.Draw(im) - d.text((10, 10), "\U0001f469", embedded_color=True, font=font) + d.text((10, 10), "\U0001f469", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or CBDT support") @skip_unless_feature_version("freetype2", "2.5.0") def test_cbdt_mask(self): @@ -893,9 +893,9 @@ class TestImageFont: assert_image_similar_tofile( im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 ) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or CBDT support") @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix(self): @@ -909,13 +909,13 @@ class TestImageFont: im = Image.new("RGB", (400, 400), "white") d = ImageDraw.Draw(im) - d.text((50, 50), "\uE901", embedded_color=True, font=font) + d.text((50, 50), "\uE901", font=font, embedded_color=True) with Image.open("Tests/images/chromacheck-sbix.png") as expected: assert_image_similar(im, expected, 1) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or SBIX support") @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix_mask(self): @@ -933,9 +933,9 @@ class TestImageFont: with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: assert_image_similar(im, expected, 1) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or SBIX support") @skip_unless_feature_version("freetype2", "2.10.0") def test_colr(self): @@ -948,7 +948,7 @@ class TestImageFont: im = Image.new("RGB", (300, 75), "white") d = ImageDraw.Draw(im) - d.text((15, 5), "Bungee", embedded_color=True, font=font) + d.text((15, 5), "Bungee", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) From 8fb5fd7f633a64948c472e0716b0909a99e8029e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 12:14:49 +1100 Subject: [PATCH 033/133] Updated tests for changed helper imports --- Tests/test_imagefont.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 259a0f872..80ae55d32 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -911,8 +911,7 @@ class TestImageFont: d.text((50, 50), "\uE901", font=font, embedded_color=True) - with Image.open("Tests/images/chromacheck-sbix.png") as expected: - assert_image_similar(im, expected, 1) + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") @@ -931,8 +930,7 @@ class TestImageFont: d.text((50, 50), "\uE901", (100, 0, 0), font=font) - with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: - assert_image_similar(im, expected, 1) + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") From 297789284b8680a1d15549dc2d192f3abc552160 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 19:32:52 +1100 Subject: [PATCH 034/133] Fixed linear_gradient and radial_gradient 32-bit modes --- Tests/test_image.py | 4 ++-- src/libImaging/Fill.c | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..141a116f9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -519,7 +519,7 @@ class TestImage: # Arrange target_file = "Tests/images/linear_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.linear_gradient(mode) @@ -545,7 +545,7 @@ class TestImage: # Arrange target_file = "Tests/images/radial_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.radial_gradient(mode) diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index e4e4b4344..f72060228 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -76,8 +76,21 @@ ImagingFillLinearGradient(const char *mode) { return NULL; } - for (y = 0; y < 256; y++) { - memset(im->image8[y], (unsigned char)y, 256); + if (im->image8) { + for (y = 0; y < 256; y++) { + memset(im->image8[y], (unsigned char)y, 256); + } + } else { + int x; + for (y = 0; y < 256; y++) { + for (x = 0; x < 256; x++) { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = y; + } else { + IMAGING_PIXEL_INT32(im, x, y) = y; + } + } + } } return im; @@ -103,9 +116,16 @@ ImagingFillRadialGradient(const char *mode) { d = (int)sqrt( (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); if (d >= 255) { - im->image8[y][x] = 255; - } else { + d = 255; + } + if (im->image8) { im->image8[y][x] = d; + } else { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = d; + } else { + IMAGING_PIXEL_INT32(im, x, y) = d; + } } } } From 70fb148fc45fe72302445450190907ee1d161539 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 15:14:00 +0100 Subject: [PATCH 035/133] fix merge --- winbuild/fribidi.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index acb614bfa..27b8d17a8 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -99,4 +99,4 @@ add_library(fribidi SHARED ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") + PUBLIC "-DFRIBIDI_BUILD") From e4cc42265dc4e00bccdf960f13fbbd6dfcb469dc Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 16:52:46 +0100 Subject: [PATCH 036/133] add Raqm build configuration info to build summary --- setup.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index cc902c152..6c4840c75 100755 --- a/setup.py +++ b/setup.py @@ -304,8 +304,8 @@ class pil_build_ext(build_ext): def want(self, feat): return getattr(self, feat) is None - def want_system(self, feat): - return feat not in self.vendor + def want_vendor(self, feat): + return feat in self.vendor def __iter__(self): yield from self.features @@ -710,14 +710,14 @@ class pil_build_ext(build_ext): _add_directory(self.compiler.include_dirs, subdir, 0) if feature.freetype and feature.want("raqm"): - if feature.want_system("raqm"): # want system Raqm + if not feature.want_vendor("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): feature.raqm = "libraqm" - else: # want to build Raqm + else: # want to build Raqm from src/thirdparty _dbg("Looking for HarfBuzz") feature.harfbuzz = None hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") @@ -727,7 +727,7 @@ class pil_build_ext(build_ext): if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: - if feature.want_system("fribidi"): # want system FriBiDi + if not feature.want_vendor("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") feature.fribidi = None fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") @@ -739,7 +739,7 @@ class pil_build_ext(build_ext): if _find_library_file(self, "fribidi"): feature.fribidi = "fribidi" feature.raqm = True - else: # want to build FriBiDi shim + else: # want to build FriBiDi shim from src/thirdparty feature.raqm = True if feature.want("lcms"): @@ -841,18 +841,18 @@ class pil_build_ext(build_ext): libs = ["freetype"] defs = [] if feature.raqm: - if feature.want_system("raqm"): # using system Raqm + if not feature.want_vendor("raqm"): # using system Raqm defs.append(("HAVE_RAQM", None)) defs.append(("HAVE_RAQM_SYSTEM", None)) libs.append(feature.raqm) - else: # building Raqm + else: # building Raqm from src/thirdparty defs.append(("HAVE_RAQM", None)) srcs.append("src/thirdparty/raqm/raqm.c") libs.append(feature.harfbuzz) - if feature.want_system("fribidi"): # using system FriBiDi + if not feature.want_vendor("fribidi"): # using system FriBiDi defs.append(("HAVE_FRIBIDI_SYSTEM", None)) libs.append(feature.fribidi) - else: # building our FriBiDi shim + else: # building FriBiDi shim from src/thirdparty srcs.append("src/thirdparty/fribidi-shim/fribidi.c") self._update_extension("PIL._imagingft", libs, defs, srcs) @@ -902,6 +902,12 @@ class pil_build_ext(build_ext): print(f" [{v.strip()}") print("-" * 68) + raqm_extra_info = "" + if feature.want_vendor("raqm"): + raqm_extra_info += "bundled" + if feature.want_vendor("fribidi"): + raqm_extra_info += ", FriBiDi shim" + options = [ (feature.jpeg, "JPEG"), (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), @@ -909,7 +915,7 @@ class pil_build_ext(build_ext): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), - (feature.raqm, "RAQM (Text shaping)"), # TODO!!! + (feature.raqm, "RAQM (Text shaping)", raqm_extra_info), (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -919,10 +925,10 @@ class pil_build_ext(build_ext): all = 1 for option in options: if option[0]: - version = "" + extra_info = "" if len(option) >= 3 and option[2]: - version = f" ({option[2]})" - print(f"--- {option[1]} support available{version}") + extra_info = f" ({option[2]})" + print(f"--- {option[1]} support available{extra_info}") else: print(f"*** {option[1]} support not available") all = 0 From 852fd170f8f3bb45cb0a7709d62bbc52b568d8bc Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Wed, 3 Mar 2021 13:30:28 +0000 Subject: [PATCH 037/133] Fix -Wformat error in TiffDecode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/libImaging/TiffDecode.c: In function ‘_tiffReadProc’: src/libImaging/TiffDecode.c:59:58: error: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘toff_t’ {aka ‘long unsigned int’} [-Werror=format=] src/libImaging/TiffDecode.c:59:67: error: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘toff_t’ {aka ‘long unsigned int’} [-Werror=format=] --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 14a685df8..a67091921 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -56,7 +56,7 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); if (state->loc > state->eof) { - TIFFError("_tiffReadProc", "Invalid Read at loc %d, eof: %d", state->loc, state->eof); + TIFFError("_tiffReadProc", "Invalid Read at loc %lu, eof: %lu", state->loc, state->eof); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); From 346bfc95375fe9441f904af21af1e6ddb3d2898d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:55:24 +1100 Subject: [PATCH 038/133] Added IPythonViewer --- Tests/test_imageshow.py | 18 +++++++++++++++++- src/PIL/ImageShow.py | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 78e80f521..5981e22c0 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -62,4 +62,20 @@ def test_viewer(): def test_viewers(): for viewer in ImageShow._viewers: - viewer.get_command("test.jpg") + try: + viewer.get_command("test.jpg") + except NotImplementedError: + pass + + +def test_ipythonviewer(): + pytest.importorskip("IPython", reason="IPython not installed") + for viewer in ImageShow._viewers: + if isinstance(viewer, ImageShow.IPythonViewer): + test_viewer = viewer + break + else: + assert False + + im = hopper() + assert test_viewer.show(im) == 1 diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index fceb65378..9e7614144 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -225,6 +225,21 @@ if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("xv"): register(XVViewer) + +class IPythonViewer(Viewer): + def show_image(self, image, **options): + display(image) + return 1 + + +try: + from IPython.display import display +except ImportError: + pass +else: + register(IPythonViewer) + + if __name__ == "__main__": if len(sys.argv) < 2: From f067fe4c05bfed6e20257c3c54f77ade8547de86 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:56:03 +1100 Subject: [PATCH 039/133] Added import alias for clarity --- src/PIL/ImageShow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 9e7614144..bb04c4e29 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -228,12 +228,12 @@ if sys.platform not in ("win32", "darwin"): # unixoids class IPythonViewer(Viewer): def show_image(self, image, **options): - display(image) + ipython_display(image) return 1 try: - from IPython.display import display + from IPython.display import display as ipython_display except ImportError: pass else: From 5e0a4acb85bd3ea1dd2d41e25bc4616fde15b138 Mon Sep 17 00:00:00 2001 From: Kipkurui Mutai Date: Sun, 28 Feb 2021 19:50:27 +0300 Subject: [PATCH 040/133] Update ImageShow.rst --- docs/reference/ImageShow.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index a30a6caed..f1fbd90ce 100644 --- a/docs/reference/ImageShow.rst +++ b/docs/reference/ImageShow.rst @@ -9,6 +9,7 @@ All default viewers convert the image to be shown to PNG format. .. autofunction:: PIL.ImageShow.show +.. autoclass:: IPythonViewer .. autoclass:: WindowsViewer .. autoclass:: MacViewer From 7b094638094986a7506efb310b6dcbe72675276b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:56:49 +1100 Subject: [PATCH 041/133] Added IPythonViewer docstring --- src/PIL/ImageShow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index bb04c4e29..3368865a4 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -227,6 +227,8 @@ if sys.platform not in ("win32", "darwin"): # unixoids class IPythonViewer(Viewer): + """The viewer for IPython frontends.""" + def show_image(self, image, **options): ipython_display(image) return 1 From a1463ff211c37a026135894487d13f7c908ed8ce Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:59:47 +1100 Subject: [PATCH 042/133] Added release notes --- docs/releasenotes/8.2.0.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 28d39ca46..f27f295a7 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,10 +21,17 @@ TODO API Additions ============= -TODO -^^^^ +ImageShow.IPythonViewer +^^^^^^^^^^^^^^^^^^^^^^^ -TODO +If IPython is present, this new ``ImageShow.Viewer`` subclass will be +registered. It displays images on all IPython frontends. This will be helpful +to users of Google Colab, allowing ``im.show()`` to display images. + +It is lower in priority than the other default Viewer instances, so it will +only be used by ``im.show()`` or ``ImageShow.show()`` if none of the other +viewers are available. This means that the behaviour of ``ImageShow`` will stay +the same for most Pillow users. Security ======== From 690cf9ebe2d7002eb134980cbb07ed7b113ae5bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Mar 2021 20:54:21 +1100 Subject: [PATCH 043/133] Allow alpha_composite destination to be negative --- Tests/test_image.py | 8 ++++++-- src/PIL/Image.py | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..de1999d3d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -344,6 +344,12 @@ class TestImage: assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63))) assert offset.size == (128, 128) + # with negative offset + offset = src.copy() + offset.alpha_composite(over, (-64, -64)) + assert_image_equal(offset.crop((0, 0, 63, 63)), target.crop((64, 64, 127, 127))) + assert offset.size == (128, 128) + # offset and crop box = src.copy() box.alpha_composite(over, (64, 64), (0, 0, 32, 32)) @@ -367,8 +373,6 @@ class TestImage: source.alpha_composite(over, 0) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), 0) - with pytest.raises(ValueError): - source.alpha_composite(over, (0, -1)) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), (0, -1)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 01fe7ed1b..d2a7c3490 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1544,8 +1544,6 @@ class Image: raise ValueError("Destination must be a 2-tuple") if min(source) < 0: raise ValueError("Source must be non-negative") - if min(dest) < 0: - raise ValueError("Destination must be non-negative") if len(source) == 2: source = source + im.size From 441a1cf9cf7a6fa018e7cecdfbd70b5680b53f26 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:00:57 +1100 Subject: [PATCH 044/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 50401e2c3..aa25e7991 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,21 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Added IPythonViewer #5289 + [radarhere, Kipkurui-mutai] + +- Only draw each rectangle outline pixel once #5183 + [radarhere] + +- Use mmap instead of built-in Win32 mapper #5224 + [radarhere, cgohlke] + +- Handle PCX images with an odd stride #5214 + [radarhere] + +- Only read different sizes for "Large Thumbnail" MPO frames #5168 + [radarhere] + - Added PyQt6 support #5258 [radarhere] From 45c43fc9112e08b050fe5322399500c60fef2d90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:39:28 +1100 Subject: [PATCH 045/133] Added release notes [ci skip] --- docs/releasenotes/8.2.0.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 28d39ca46..92909cfb5 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -13,10 +13,13 @@ when Tk/Tcl 8.5 will be the minimum supported. API Changes =========== -TODO -^^^^ +Image.alpha_composite: dest +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +When calling :py:meth:`~PIL.Image.Image.alpha_composite`, the ``dest`` argument now +accepts negative co-ordinates, like the upper left corner of the ``box`` argument of +:py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of +cropping the overlaid image. API Additions ============= From f5d49f4f6166625fec381dc28886258a8be19f06 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:53:59 +1100 Subject: [PATCH 046/133] Added rounded_rectangle method --- Tests/images/imagedraw_rounded_rectangle.png | Bin 0 -> 934 bytes Tests/test_imagedraw.py | 28 ++++++++++++ docs/reference/ImageDraw.rst | 14 ++++++ src/PIL/ImageDraw.py | 45 +++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 Tests/images/imagedraw_rounded_rectangle.png diff --git a/Tests/images/imagedraw_rounded_rectangle.png b/Tests/images/imagedraw_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2e815f4ada247e8302c8cc5e053a162b4fe3ed01 GIT binary patch literal 934 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)OEIV9xb)aSW-L^Y+HYqE!YWZGkra z`|j^~zUcByEkULy+m*8KcJ6r0b6;iKWfq_w7JO{=uCm%MWtMrNFZA{Hn$kOmx$mrcylh_ev~WwS zgty)=UgoY9+4kq%s}Iie>-O!L=l3Kt@%*~)XYTyJsLt*EYRRoiQ~ffF50Ce5U9#%j z>c4MhiCe$nUA=YQs&jb>(YyN&-SnFD%b+*8urBrDH`(lM4Xf<@AJ06uUgws?wVYk6 z?5-Z%*7onYY+VlLYB~KI>+Eh%^j&Se=B7(%?7?^2%H&#C?dq#Ne=A={>DQrEyTiAw zUR_!9=<2SQ^JKd_`Zj3&Q?-~MRayVJ@2Sk5Woysh4fJT)&=JAC`dyqQ`}&;A_uI;J zlv-SYVgh1pg*rZu#DecshQo!%8SzkmVTE(;2wjg~sgADZUY zzLQPNopVC<%BkOdPLLSzJG}blm+ueEt;Gbb4{yqhJ-#pft}HMnx2;?7amw@XsBLc| zHh13om9u;GtUt$;Qj#OSRvcXRb!A^p_~m)B*=DiP^PeY#UN*T~w(d^Uv+T#W_ALBn zc5%;+jQ9C#u6b=+w^=&$YRQY^)!DYWbFUv*wd}9^!&`^?jtW=qlC{}=&$#%%`|oMt zH*QB+#!b5)D_i&Ca`e8IL-$;+pPvdzun)FNS@!G?Og_f*_k4A)%rW^DiIdyjMeGmF zGI)IT$Ha{r_Jx+&xQAvHSgg4Z&K>`Lal^6 Date: Mon, 22 Feb 2021 07:38:04 +1100 Subject: [PATCH 047/133] Only draw each pixel once --- .../imagedraw_rounded_rectangle_both.png | Bin 0 -> 530 bytes .../images/imagedraw_rounded_rectangle_x.png | Bin 0 -> 567 bytes .../images/imagedraw_rounded_rectangle_y.png | Bin 0 -> 528 bytes Tests/test_imagedraw.py | 24 ++++ src/PIL/ImageDraw.py | 103 +++++++++++++----- 5 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 Tests/images/imagedraw_rounded_rectangle_both.png create mode 100644 Tests/images/imagedraw_rounded_rectangle_x.png create mode 100644 Tests/images/imagedraw_rounded_rectangle_y.png diff --git a/Tests/images/imagedraw_rounded_rectangle_both.png b/Tests/images/imagedraw_rounded_rectangle_both.png new file mode 100644 index 0000000000000000000000000000000000000000..24f600e3913ebc74a9c301e815f06f4ac0cfdd94 GIT binary patch literal 530 zcmeAS@N?(olHy`uVBq!ia0vp^DIm7ZFWD}C_M}wCs{)<;eRDP)o0Zj@@g?*-%YXh2Cub>!tu2gw`02!~<0084 z-RnK1`%I^)u!s%^Wrko*Ci z>}`Huu3gs7ZhQOk`^z=c%tQ=M%qsIWS=Y?1;;o>R-BzHQ_2I-V^_n&7FNirrZ+iA| z>TR8qb5tKpF?HUtsJZ~?m)F{Rqb-XLEvk_Jv-s5<*OYTsYmCGKebp|D?4uWRojEl-z|T`s&ch> literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_rounded_rectangle_x.png b/Tests/images/imagedraw_rounded_rectangle_x.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf5211a3340d62724b64a5517693c1fa2403e7d GIT binary patch literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^DImsnv^Py)@I#H6G!aHYxGIp1VMO3=9uSE2Lu&$j^^gx8G4e{Yv2p zkI3asQ9VNDZr3i)l3N)brgi#JhWE^^-xt*;EnB3$euc-isrH`>r}fR}?p~rSHoY|G z)s>6WlCuNjBO`T#{Vi@?+BEI6=#;J5rxv}O7@MjXzmj?1v5@Su7beP23{88L@U%t0 zUOI2}narq-xt{scwG|aEzKp7;#;jQ_5L91BvpZlrp`NrOe<1yh3&3|zc8+e-t_F_ zROyHVGgTi<0ZJ_@HaK%q>b175p^DMd6%}G<*JfP{d~)X2S|jm|n^pz@L+woX(?2?D zn>h_W`!{4xVk&ixUF^)86}S4T?ZM~&{BLr5q-k>-Y>jTH`~Z%~^+)udCACbFI5_JE Oi0|p@=d#Wzp$Pyx_3|kI literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_rounded_rectangle_y.png b/Tests/images/imagedraw_rounded_rectangle_y.png new file mode 100644 index 0000000000000000000000000000000000000000..9b391b95e28ea723f75e90210945f1481d93d402 GIT binary patch literal 528 zcmeAS@N?(olHy`uVBq!ia0vp^DIm37 zx6T&5yXNj1ekQKN^_ScyZq&GA?4T4MP&VO)CM!@M7`Rka-?Xl+W8L@TeEEa!|J}W! zQZKw>cC3i#Pr23Q@l59RM>qGrtM~54Wu6K;S{iCM`Re8c{U?e)bai zaw}?Hd1Vm3RHWIrXHFfVaZ&s7&=$$1nAL zofUm1{en% z&%y@vNY;7$Q~ax)mtGGqnm7N!^FOmL6o&p;-`r5oBlscS1QtCv}Ao?m#zm1 NdAj= x1 - x0 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) + if d == 0: + # If the corners have no curve, that is a rectangle return self.rectangle(xy, fill, outline, width) ink, fill = self._getink(outline, fill) + + def draw_corners(pieslice): + if full_x: + # Draw top and bottom halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 180, 360), + ((x0, y1 - d, x0 + d, y1), 0, 180), + ) + elif full_y: + # Draw left and right halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 90, 270), + ((x1 - d, y0, x1, y0 + d), 270, 90), + ) + else: + # Draw four separate corners + parts = ( + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ((x0, y0, x0 + d, y0 + d), 180, 270), + ) + for part in parts: + if pieslice: + self.draw.draw_pieslice(*(part + (fill, 1))) + else: + self.draw.draw_arc(*(part + (ink, width))) + if fill is not None: - self.draw.draw_pieslice((x1 - d, y0, x1, y0 + d), 270, 360, fill, 1) - self.draw.draw_pieslice((x1 - d, y1 - d, x1, y1), 0, 90, fill, 1) - self.draw.draw_pieslice((x0, y1 - d, x0 + d, y1), 90, 180, fill, 1) - self.draw.draw_pieslice((x0, y0, x0 + d, y0 + d), 180, 270, fill, 1) + draw_corners(True) - self.draw.draw_rectangle((x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1) - self.draw.draw_rectangle( - (x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1 - ) - self.draw.draw_rectangle( - (x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 - ) + if full_x: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 + ) + else: + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1 + ) + if not full_x and not full_y: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1 + ) + self.draw.draw_rectangle( + (x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 + ) if ink is not None and ink != fill and width != 0: - self.draw.draw_arc((x1 - d, y0, x1, y0 + d), 270, 360, ink, width) - self.draw.draw_arc((x1 - d, y1 - d, x1, y1), 0, 90, ink, width) - self.draw.draw_arc((x0, y1 - d, x0 + d, y1), 90, 180, ink, width) - self.draw.draw_arc((x0, y0, x0 + d, y0 + d), 180, 270, ink, width) + draw_corners(False) - self.draw.draw_rectangle( - (x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1 - ) - self.draw.draw_rectangle( - (x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1 - ) + if not full_x: + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1 + ) + if not full_y: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1 + ) def _multiline_check(self, text): """Draw text.""" From 62bf920634164509f2465b2ae1d7924bc7065699 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 20:10:17 +1100 Subject: [PATCH 048/133] Added release notes [ci skip] --- docs/releasenotes/8.2.0.rst | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index f27f295a7..04d80e80e 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -13,10 +13,20 @@ when Tk/Tcl 8.5 will be the minimum supported. API Changes =========== -TODO -^^^^ +ImageDraw.rounded_rectangle +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as +:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius`` +argument. ``radius`` is limited to half of the width or the height, so that users can +create a circle, but not any other ellipse. + +.. code-block:: python + + from PIL import Image, ImageDraw + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") API Additions ============= From ac1a9b28c9b7b405d6f3e52d49feebd3508706d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 20:33:13 +1100 Subject: [PATCH 049/133] Added link to class and function [ci skip] --- docs/releasenotes/8.2.0.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index f27f295a7..d339479a5 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -24,14 +24,14 @@ API Additions ImageShow.IPythonViewer ^^^^^^^^^^^^^^^^^^^^^^^ -If IPython is present, this new ``ImageShow.Viewer`` subclass will be +If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be registered. It displays images on all IPython frontends. This will be helpful to users of Google Colab, allowing ``im.show()`` to display images. -It is lower in priority than the other default Viewer instances, so it will -only be used by ``im.show()`` or ``ImageShow.show()`` if none of the other -viewers are available. This means that the behaviour of ``ImageShow`` will stay -the same for most Pillow users. +It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer` +instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()` +if none of the other viewers are available. This means that the behaviour of +:py:class:`PIL.ImageShow` will stay the same for most Pillow users. Security ======== From e7f5bb18312e6c6db1ec3cd4eec7272be094baaf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 18:19:05 +1100 Subject: [PATCH 050/133] Ensure file is closed if it is opened by ImageQt.ImageQt --- Tests/test_imageqt.py | 13 +++++++++++-- src/PIL/ImageQt.py | 19 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 404849cb9..53b1fef7c 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -4,11 +4,14 @@ from PIL import ImageQt from .helper import hopper +pytestmark = pytest.mark.skipif( + not ImageQt.qt_is_installed, reason="Qt bindings are not installed" +) + if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba -@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_rgb(): # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb @@ -38,7 +41,13 @@ def test_rgb(): checkrgb(0, 0, 255) -@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_image(): for mode in ("1", "RGB", "RGBA", "L", "P"): ImageQt.ImageQt(hopper(mode)) + + +def test_closed_file(): + with pytest.warns(None) as record: + ImageQt.ImageQt("Tests/images/hopper.gif") + + assert not record diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 74ca3166c..56650e102 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -128,6 +128,7 @@ def align8to32(bytes, width, mode): def _toqclass_helper(im): data = None colortable = None + exclusive_fp = False # handle filename, if given instead of image name if hasattr(im, "toUtf8"): @@ -135,6 +136,7 @@ def _toqclass_helper(im): im = str(im.toUtf8(), "utf-8") if isPath(im): im = Image.open(im) + exclusive_fp = True qt_format = QImage.Format if qt_version == "6" else QImage if im.mode == "1": @@ -157,10 +159,15 @@ def _toqclass_helper(im): data = im.tobytes("raw", "BGRA") format = qt_format.Format_ARGB32 else: + if exclusive_fp: + im.close() raise ValueError(f"unsupported image mode {repr(im.mode)}") - __data = data or align8to32(im.tobytes(), im.size[0], im.mode) - return {"data": __data, "im": im, "format": format, "colortable": colortable} + size = im.size + __data = data or align8to32(im.tobytes(), size[0], im.mode) + if exclusive_fp: + im.close() + return {"data": __data, "size": size, "format": format, "colortable": colortable} if qt_is_installed: @@ -182,8 +189,8 @@ if qt_is_installed: self.__data = im_data["data"] super().__init__( self.__data, - im_data["im"].size[0], - im_data["im"].size[1], + im_data["size"][0], + im_data["size"][1], im_data["format"], ) if im_data["colortable"]: @@ -197,8 +204,8 @@ def toqimage(im): def toqpixmap(im): # # This doesn't work. For now using a dumb approach. # im_data = _toqclass_helper(im) - # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) - # result.loadFromData(im_data['data']) + # result = QPixmap(im_data["size"][0], im_data["size"][1]) + # result.loadFromData(im_data["data"]) # Fix some strange bug that causes if im.mode == "RGB": im = im.convert("RGBA") From 666d3c5d3f2c042fef3a2f2976458a67bf72e245 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 22:21:22 +1100 Subject: [PATCH 051/133] Use bash shell for mkdir command --- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 12c288374..32518c9d1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -174,7 +174,7 @@ jobs: if: failure() run: | mkdir -p Tests/errors - shell: pwsh + shell: bash - name: Upload errors uses: actions/upload-artifact@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4064a0589..e52fefc69 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,6 @@ jobs: if: failure() run: | mkdir -p Tests/errors - shell: pwsh - name: Upload errors uses: actions/upload-artifact@v2 From 7c7a68867df60e5ca668a6cc703776355c79fff6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 23:15:59 +1100 Subject: [PATCH 052/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aa25e7991..3695ed964 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Ensure file is closed if it is opened by ImageQt.ImageQt #5260 + [radarhere] + +- Added ImageDraw rounded_rectangle method #5208 + [radarhere] + - Added IPythonViewer #5289 [radarhere, Kipkurui-mutai] From 21da5b1ed8e905b7f3e4198207486317304b8698 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Mar 2021 07:09:50 +1100 Subject: [PATCH 053/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3695ed964..57e7f75cc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Allow alpha_composite destination to be negative #5313 + [radarhere] + - Ensure file is closed if it is opened by ImageQt.ImageQt #5260 [radarhere] From e54880c6528efcc41c4a942b938398c1ddcd5644 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Mar 2021 13:17:19 +1100 Subject: [PATCH 054/133] Moved RGB fix inside ImageQt class --- Tests/test_qt_image_qapplication.py | 27 +++++++++++++++++++++++++-- src/PIL/ImageQt.py | 9 ++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index a3d5620d3..dec790c50 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -2,18 +2,26 @@ import pytest from PIL import ImageQt -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper if ImageQt.qt_is_installed: from PIL.ImageQt import QPixmap if ImageQt.qt_version == "6": + from PyQt6.QtCore import QPoint + from PyQt6.QtGui import QImage, QPainter, QRegion from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "side6": + from PySide6.QtCore import QPoint + from PySide6.QtGui import QImage, QPainter, QRegion from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "5": + from PyQt5.QtCore import QPoint + from PyQt5.QtGui import QImage, QPainter, QRegion from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "side2": + from PySide2.QtCore import QPoint + from PySide2.QtGui import QImage, QPainter, QRegion from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget class Example(QWidget): @@ -49,7 +57,8 @@ def test_sanity(tmp_path): for mode in ("1", "RGB", "RGBA", "L", "P"): # to QPixmap - data = ImageQt.toqpixmap(hopper(mode)) + im = hopper(mode) + data = ImageQt.toqpixmap(im) assert isinstance(data, QPixmap) assert not data.isNull() @@ -58,6 +67,20 @@ def test_sanity(tmp_path): tempfile = str(tmp_path / f"temp_{mode}.png") data.save(tempfile) + # Render the image + qimage = ImageQt.ImageQt(im) + data = QPixmap.fromImage(qimage) + qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage + qimage = QImage(128, 128, qt_format.Format_ARGB32) + painter = QPainter(qimage) + image_label = QLabel() + image_label.setPixmap(data) + image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) + painter.end() + rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png") + qimage.save(rendered_tempfile) + assert_image_equal_tofile(im.convert("RGBA"), rendered_tempfile) + # from QPixmap roundtrip(hopper(mode)) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 56650e102..32630f2ca 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -153,7 +153,10 @@ def _toqclass_helper(im): for i in range(0, len(palette), 3): colortable.append(rgb(*palette[i : i + 3])) elif im.mode == "RGB": - data = im.tobytes("raw", "BGRX") + # Populate the 4th channel with 255 + im = im.convert("RGBA") + + data = im.tobytes("raw", "BGRA") format = qt_format.Format_RGB32 elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") @@ -206,9 +209,5 @@ def toqpixmap(im): # im_data = _toqclass_helper(im) # result = QPixmap(im_data["size"][0], im_data["size"][1]) # result.loadFromData(im_data["data"]) - # Fix some strange bug that causes - if im.mode == "RGB": - im = im.convert("RGBA") - qimage = toqimage(im) return QPixmap.fromImage(qimage) From f42d6cf1ac493ed7007af4c1c86810fcf1557e4b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Mar 2021 20:16:49 +1100 Subject: [PATCH 055/133] Save ICC profile from TIFF encoderinfo --- Tests/test_file_png.py | 2 ++ Tests/test_file_tiff.py | 22 ++++++++++++++++++++++ src/PIL/TiffImagePlugin.py | 5 +++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 57bc7f015..52ea3b6d2 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -517,6 +517,8 @@ class TestFilePng: def test_discard_icc_profile(self): 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 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f09117ca7..ba7f9a084 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -568,6 +568,28 @@ class TestFileTiff: with Image.open(tmpfile) as reloaded: assert b"Dummy value" == reloaded.info["icc_profile"] + def test_save_icc_profile(self, tmp_path): + im = hopper() + assert "icc_profile" not in im.info + + outfile = str(tmp_path / "temp.tif") + icc_profile = b"Dummy value" + im.save(outfile, icc_profile=icc_profile) + + with Image.open(outfile) as reloaded: + assert reloaded.info["icc_profile"] == icc_profile + + def test_discard_icc_profile(self, tmp_path): + outfile = str(tmp_path / "temp.tif") + + with Image.open("Tests/images/icc_profile.png") as im: + assert "icc_profile" in im.info + + im.save(outfile, icc_profile=None) + + with Image.open(outfile) as reloaded: + assert "icc_profile" not in reloaded.info + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os tmpfile = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 0b70ce382..98c70d7c4 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1481,8 +1481,9 @@ def _save(im, fp, filename): # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech - if "icc_profile" in im.info: - ifd[ICCPROFILE] = im.info["icc_profile"] + icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + ifd[ICCPROFILE] = icc for key, name in [ (IMAGEDESCRIPTION, "description"), From 68b655f3f014c6beb13f4c9a6fa53f1ebff527c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 10 Mar 2021 20:43:16 +1100 Subject: [PATCH 056/133] Updated format specifiers --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index a67091921..746994da3 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -56,7 +56,7 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); if (state->loc > state->eof) { - TIFFError("_tiffReadProc", "Invalid Read at loc %lu, eof: %lu", state->loc, state->eof); + TIFFError("_tiffReadProc", "Invalid Read at loc %llu, eof: %llu", state->loc, state->eof); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); From 188d4f6b6abffe7ecb71f3e03d11079138661b60 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Mar 2021 12:03:45 +1100 Subject: [PATCH 057/133] Only import numpy when necessary --- src/PIL/ImageFilter.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 9ca17d9ad..6800bc3a0 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,11 +16,6 @@ # import functools -try: - import numpy -except ImportError: # pragma: no cover - numpy = None - class Filter: pass @@ -369,6 +364,13 @@ class Color3DLUT(MultibandFilter): items = size[0] * size[1] * size[2] wrong_size = False + numpy = None + if hasattr(table, "shape"): + try: + import numpy + except ImportError: # pragma: no cover + pass + if numpy and isinstance(table, numpy.ndarray): if copy_table: table = table.copy() From 2844fd2d18e2711db7facd9bd61f6aaa0ee7cef0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Mar 2021 22:45:07 +1100 Subject: [PATCH 058/133] Fixed unclosed file warning --- Tests/test_file_icns.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 7ce8cb286..30ec3dc72 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -143,8 +143,8 @@ def test_not_an_icns_file(): def test_icns_decompression_bomb(): - with pytest.raises(Image.DecompressionBombError): - im = Image.open( - "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" - ) - im.load() + with Image.open( + "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" + ) as im: + with pytest.raises(Image.DecompressionBombError): + im.load() From 38692f222f0f5e8bfa5edd34e719f8ea9416721d Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 13 Mar 2021 11:09:37 +0100 Subject: [PATCH 059/133] Delegate building of oss-fuzz versions to pillow --- Tests/oss-fuzz/build.sh | 46 ++++++++++++++++++++++++++++ Tests/oss-fuzz/build_dictionaries.sh | 33 ++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100755 Tests/oss-fuzz/build.sh create mode 100755 Tests/oss-fuzz/build_dictionaries.sh diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh new file mode 100755 index 000000000..1d05bea79 --- /dev/null +++ b/Tests/oss-fuzz/build.sh @@ -0,0 +1,46 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +python3 setup.py build --build-base=/tmp/build install + +# Build fuzzers in $OUT. +for fuzzer in $(find $SRC -name 'fuzz_*.py'); do + fuzzer_basename=$(basename -s .py $fuzzer) + fuzzer_package=${fuzzer_basename}.pkg + pyinstaller \ + --add-binary /usr/local/lib/libjpeg.so.9:. \ + --add-binary /usr/local/lib/libfreetype.so.6:. \ + --add-binary /usr/local/lib/liblcms2.so.2:. \ + --add-binary /usr/local/lib/libopenjp2.so.7:. \ + --add-binary /usr/local/lib/libpng16.so.16:. \ + --add-binary /usr/local/lib/libtiff.so.5:. \ + --add-binary /usr/local/lib/libwebp.so.7:. \ + --add-binary /usr/local/lib/libwebpdemux.so.2:. \ + --add-binary /usr/local/lib/libwebpmux.so.3:. \ + --distpath $OUT --onefile --name $fuzzer_package $fuzzer + + # Create execution wrapper. + echo "#!/bin/sh +# LLVMFuzzerTestOneInput for fuzzer detection. +this_dir=\$(dirname \"\$0\") +LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \ +ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \ +\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename + chmod u+x $OUT/$fuzzer_basename +done + +find Tests/images Tests/icc Tests/fonts -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ diff --git a/Tests/oss-fuzz/build_dictionaries.sh b/Tests/oss-fuzz/build_dictionaries.sh new file mode 100755 index 000000000..9aae56ca8 --- /dev/null +++ b/Tests/oss-fuzz/build_dictionaries.sh @@ -0,0 +1,33 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +# Generate image dictionaries here for each of the fuzzers and put them in the +# $OUT directory, named for the fuzzer + +git clone --depth 1 https://github.com/google/fuzzing +cat fuzzing/dictionaries/bmp.dict \ + fuzzing/dictionaries/dds.dict \ + fuzzing/dictionaries/gif.dict \ + fuzzing/dictionaries/icns.dict \ + fuzzing/dictionaries/jpeg.dict \ + fuzzing/dictionaries/jpeg2000.dict \ + fuzzing/dictionaries/pbm.dict \ + fuzzing/dictionaries/png.dict \ + fuzzing/dictionaries/psd.dict \ + fuzzing/dictionaries/tiff.dict \ + fuzzing/dictionaries/webp.dict \ + > $OUT/fuzz_pillow.dict From e2577d1736617968110ef658b94150f114b17e41 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 13 Mar 2021 11:35:50 +0100 Subject: [PATCH 060/133] font fuzzer --- Tests/oss-fuzz/build.sh | 3 ++- Tests/oss-fuzz/fuzz_font.py | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 Tests/oss-fuzz/fuzz_font.py diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh index 1d05bea79..fc54ee3ee 100755 --- a/Tests/oss-fuzz/build.sh +++ b/Tests/oss-fuzz/build.sh @@ -43,4 +43,5 @@ ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm chmod u+x $OUT/$fuzzer_basename done -find Tests/images Tests/icc Tests/fonts -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ +find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ +find Tests/fonts -print | zip -q $OUT/fuzz_font_seed_corpus.zip -@ diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py new file mode 100755 index 000000000..43c23fe60 --- /dev/null +++ b/Tests/oss-fuzz/fuzz_font.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import sys +import warnings + +import atheris_no_libfuzzer as atheris + +from PIL import Image, ImageDraw, ImageFont + + +def TestOneInput(data): + try: + with ImageFont.load(io.BytesIO(data)) as font: + font.getsize_multiline("ABC\nAaaa") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.text((10,10), "Test Text", font) + except Exception: + # We're catching all exceptions because Pillow's exceptions are + # directly inheriting from Exception. + return + return + + +def main(): + atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Fuzz() + + +if __name__ == "__main__": + main() From 16dbffc3a8deee69daf0b5f4f787b2b6dc07f763 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 13:31:16 +1100 Subject: [PATCH 061/133] _crop already makes a copy of the image --- src/PIL/GifImagePlugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 7c083bd8b..e66665965 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -267,14 +267,15 @@ class GifImageFile(ImageFile.ImageFile): # replace with background colour Image._decompression_bomb_check(self.size) self.dispose = Image.core.fill("P", self.size, self.info["background"]) + + # only dispose the extent in this frame + if self.dispose: + self.dispose = self._crop(self.dispose, self.dispose_extent) else: # replace with previous contents if self.im: - self.dispose = self.im.copy() - - # only dispose the extent in this frame - if self.dispose: - self.dispose = self._crop(self.dispose, self.dispose_extent) + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) except (AttributeError, KeyError): pass From 2f84f633e38e2f708bae2a0d65115e05abc8f3dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 13:40:55 +1100 Subject: [PATCH 062/133] Create disposal image at the destination size, instead of cropping --- src/PIL/GifImagePlugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index e66665965..ba08bd074 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -265,12 +265,15 @@ class GifImageFile(ImageFile.ImageFile): self.dispose = None elif self.disposal_method == 2: # replace with background colour - Image._decompression_bomb_check(self.size) - self.dispose = Image.core.fill("P", self.size, self.info["background"]) # only dispose the extent in this frame - if self.dispose: - self.dispose = self._crop(self.dispose, self.dispose_extent) + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + self.dispose = Image.core.fill( + "P", dispose_size, self.info["background"] + ) else: # replace with previous contents if self.im: From cfcedcc5203acb26f79d1b59a38521eee605b4f3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 21:21:40 +1100 Subject: [PATCH 063/133] icc_profile is now a keyword argument when saving TIFF files [ci skip] --- docs/handbook/image-file-formats.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 35c4177aa..0ccd3b1a4 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -901,6 +901,9 @@ using the general tags available through tiffinfo. **copyright** Strings +**icc_profile** + The ICC Profile to include in the saved file. + **resolution_unit** An integer. 1 for no unit, 2 for inches and 3 for centimeters. From d466620cfa31e5caf5b6fdd1aade93b5df144ee1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 21:25:16 +1100 Subject: [PATCH 064/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 57e7f75cc..dbe06304d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Save ICC profile from TIFF encoderinfo #5321 + [radarhere] + +- Moved RGB fix inside ImageQt class #5268 + [radarhere] + - Allow alpha_composite destination to be negative #5313 [radarhere] From becd633d3f6c7fab7e6027d86ae3fbb6a09cfbd4 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 12:21:02 +0100 Subject: [PATCH 065/133] Refactor fuzzers, add fuzzer tests --- Tests/oss-fuzz/fuzz_font.py | 10 ++-------- Tests/oss-fuzz/fuzz_pillow.py | 15 ++++----------- Tests/oss-fuzz/fuzzers.py | 33 +++++++++++++++++++++++++++++++++ Tests/oss-fuzz/test_fuzzers.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 Tests/oss-fuzz/fuzzers.py create mode 100644 Tests/oss-fuzz/test_fuzzers.py diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 43c23fe60..ec3a4b2de 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -20,17 +20,11 @@ import warnings import atheris_no_libfuzzer as atheris -from PIL import Image, ImageDraw, ImageFont - +import fuzzers def TestOneInput(data): try: - with ImageFont.load(io.BytesIO(data)) as font: - font.getsize_multiline("ABC\nAaaa") - font.getmask("test text") - with Image.new(mode="RGBA", size=(200, 200)) as im: - draw = ImageDraw.Draw(im) - draw.text((10,10), "Test Text", font) + fuzzers.fuzz_font(data) except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 894068f63..695e9e5eb 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -18,28 +18,21 @@ import io import sys import warnings +import fuzzers + import atheris_no_libfuzzer as atheris -from PIL import Image, ImageFile, ImageFilter - - def TestOneInput(data): try: - with Image.open(io.BytesIO(data)) as im: - im.rotate(45) - im.filter(ImageFilter.DETAIL) - im.save(io.BytesIO(), "BMP") + fuzzers.fuzz_image(data) except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. return return - def main(): - ImageFile.LOAD_TRUNCATED_IMAGES = True - warnings.filterwarnings("ignore") - warnings.simplefilter("error", Image.DecompressionBombWarning) + fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) atheris.Fuzz() diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py new file mode 100644 index 000000000..f7c395e76 --- /dev/null +++ b/Tests/oss-fuzz/fuzzers.py @@ -0,0 +1,33 @@ +import warnings +import io + +from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageFile, PcfFontFile + +def enable_decompressionbomb_error(): + ImageFile.LOAD_TRUNCATED_IMAGES = True + warnings.filterwarnings("ignore") + warnings.simplefilter("error", Image.DecompressionBombWarning) + +def fuzz_image(data): + # This will fail on some images in the corpus, as we have many + # invalid images in the test suite. + with Image.open(io.BytesIO(data)) as im: + im.rotate(45) + im.filter(ImageFilter.DETAIL) + im.save(io.BytesIO(), "BMP") + +def fuzz_font(data): + # This should not fail on a valid font load for any of the fonts in the corpus + wrapper = io.BytesIO(data) + try: + font = ImageFont.truetype(wrapper) + except OSError: + # pcf/pilfonts/random garbage here here. They're different. + return + + font.getsize_multiline("ABC\nAaaa") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) + draw.text((10,10), "Test Text", font=font, fill="#000") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py new file mode 100644 index 000000000..56b1ee9a0 --- /dev/null +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -0,0 +1,31 @@ +import pytest + +import fuzzers +import glob +import subprocess + +from PIL import Image + +@pytest.mark.parametrize("path", subprocess.check_output('find Tests/images -type f', shell=True).split(b'\n')) +def test_fuzz_images(path): + fuzzers.enable_decompressionbomb_error() + try: + with open(path, 'rb') as f: + fuzzers.fuzz_image(f.read()) + assert True + except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): + # Known exceptions that are through from Pillow + assert True + except (Image.DecompressionBombError, Image.DecompressionBombWarning, + Image.UnidentifiedImageError): + # Known Image.* exceptions + assert True + + +@pytest.mark.parametrize("path", subprocess.check_output('find Tests/fonts -type f', shell=True).split(b'\n')) +def test_fuzz_fonts(path): + if not path or b'LICENSE.txt' in path or b'.pil' in path: + return + with open(path, 'rb') as f: + fuzzers.fuzz_font(f.read()) + assert True From 6d6ef4a539e2eb2a5103073495841de63cf4e852 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 12:21:25 +0100 Subject: [PATCH 066/133] Ignore the pyinstaller spec files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 15add232b..5500ec037 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,6 @@ Tests/images/jpeg2000 Tests/images/msp Tests/images/picins Tests/images/sunraster + +# pyinstaller +*.spec From c17ce801cfba4a850381ca6811e5b27b09bffe10 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:02:48 +0100 Subject: [PATCH 067/133] I see a python file and I want to paint it black --- Tests/oss-fuzz/fuzz_font.py | 2 +- Tests/oss-fuzz/fuzz_pillow.py | 3 ++- Tests/oss-fuzz/fuzzers.py | 9 ++++++--- Tests/oss-fuzz/test_fuzzers.py | 29 +++++++++++++++++++---------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index ec3a4b2de..3cea56fd7 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -19,9 +19,9 @@ import sys import warnings import atheris_no_libfuzzer as atheris - import fuzzers + def TestOneInput(data): try: fuzzers.fuzz_font(data) diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 695e9e5eb..8112dd9a0 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -18,9 +18,9 @@ import io import sys import warnings +import atheris_no_libfuzzer as atheris import fuzzers -import atheris_no_libfuzzer as atheris def TestOneInput(data): try: @@ -31,6 +31,7 @@ def TestOneInput(data): return return + def main(): fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index f7c395e76..c3fa1f0ce 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -1,13 +1,15 @@ -import warnings import io +import warnings + +from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont, PcfFontFile -from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageFile, PcfFontFile def enable_decompressionbomb_error(): ImageFile.LOAD_TRUNCATED_IMAGES = True warnings.filterwarnings("ignore") warnings.simplefilter("error", Image.DecompressionBombWarning) + def fuzz_image(data): # This will fail on some images in the corpus, as we have many # invalid images in the test suite. @@ -16,6 +18,7 @@ def fuzz_image(data): im.filter(ImageFilter.DETAIL) im.save(io.BytesIO(), "BMP") + def fuzz_font(data): # This should not fail on a valid font load for any of the fonts in the corpus wrapper = io.BytesIO(data) @@ -30,4 +33,4 @@ def fuzz_font(data): with Image.new(mode="RGBA", size=(200, 200)) as im: draw = ImageDraw.Draw(im) draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) - draw.text((10,10), "Test Text", font=font, fill="#000") + draw.text((10, 10), "Test Text", font=font, fill="#000") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 56b1ee9a0..aa13ff1b2 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,31 +1,40 @@ -import pytest - -import fuzzers import glob import subprocess +import fuzzers +import pytest + from PIL import Image -@pytest.mark.parametrize("path", subprocess.check_output('find Tests/images -type f', shell=True).split(b'\n')) + +@pytest.mark.parametrize( + "path", + subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), +) def test_fuzz_images(path): fuzzers.enable_decompressionbomb_error() try: - with open(path, 'rb') as f: + with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): # Known exceptions that are through from Pillow assert True - except (Image.DecompressionBombError, Image.DecompressionBombWarning, - Image.UnidentifiedImageError): + except ( + Image.DecompressionBombError, + Image.DecompressionBombWarning, + Image.UnidentifiedImageError, + ): # Known Image.* exceptions assert True -@pytest.mark.parametrize("path", subprocess.check_output('find Tests/fonts -type f', shell=True).split(b'\n')) +@pytest.mark.parametrize( + "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") +) def test_fuzz_fonts(path): - if not path or b'LICENSE.txt' in path or b'.pil' in path: + if not path or b"LICENSE.txt" in path or b".pil" in path: return - with open(path, 'rb') as f: + with open(path, "rb") as f: fuzzers.fuzz_font(f.read()) assert True From 8b06fec6ab62c2c588c7e8723fec4fc2ff218515 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:14:39 +0100 Subject: [PATCH 068/133] linty bits --- Tests/oss-fuzz/fuzz_font.py | 2 -- Tests/oss-fuzz/fuzz_pillow.py | 2 -- Tests/oss-fuzz/fuzzers.py | 2 +- Tests/oss-fuzz/test_fuzzers.py | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 3cea56fd7..9f21a1fa5 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -14,9 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import sys -import warnings import atheris_no_libfuzzer as atheris import fuzzers diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 8112dd9a0..d816d535f 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -14,9 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import sys -import warnings import atheris_no_libfuzzer as atheris import fuzzers diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index c3fa1f0ce..156653f1d 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -1,7 +1,7 @@ import io import warnings -from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont, PcfFontFile +from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont def enable_decompressionbomb_error(): diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index aa13ff1b2..fc5b21840 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,4 +1,3 @@ -import glob import subprocess import fuzzers From 6189bca3bc238c11ec31de30091a60012d9acfa6 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:27:47 +0100 Subject: [PATCH 069/133] Skip fuzzer tests on windows --- Tests/oss-fuzz/test_fuzzers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index fc5b21840..f7be87373 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,11 +1,15 @@ import subprocess +import sys import fuzzers import pytest from PIL import Image +def is_win32(): + sys.platform.startswith("win32") +@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), @@ -27,7 +31,7 @@ def test_fuzz_images(path): # Known Image.* exceptions assert True - +@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) From 0ea13132a24cec8dbbf729630e480d26742879f0 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:32:44 +0100 Subject: [PATCH 070/133] Overflow error shows up in x86 --- Tests/oss-fuzz/test_fuzzers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index f7be87373..d7c16f144 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -20,7 +20,12 @@ def test_fuzz_images(path): with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True - except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): + except (OSError, + SyntaxError, + MemoryError, + ValueError, + NotImplementedError, + OverflowError): # Known exceptions that are through from Pillow assert True except ( From bb6b991d8d74ae4f09063dac9ed78d7df3ed3596 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:49:36 +0100 Subject: [PATCH 071/133] no colors anymore, they want them to turn black --- Tests/oss-fuzz/test_fuzzers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index d7c16f144..410fff505 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -6,9 +6,11 @@ import pytest from PIL import Image + def is_win32(): sys.platform.startswith("win32") + @pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", @@ -20,12 +22,14 @@ def test_fuzz_images(path): with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True - except (OSError, - SyntaxError, - MemoryError, - ValueError, - NotImplementedError, - OverflowError): + except ( + OSError, + SyntaxError, + MemoryError, + ValueError, + NotImplementedError, + OverflowError, + ): # Known exceptions that are through from Pillow assert True except ( @@ -36,6 +40,7 @@ def test_fuzz_images(path): # Known Image.* exceptions assert True + @pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") From 487dc16ce6ee4f71815329acb023d28ac6529d74 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:57:24 +0100 Subject: [PATCH 072/133] Can't skip windows properly because the depenedncy is in the decorator --- Tests/oss-fuzz/test_fuzzers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 410fff505..4ccdeca4a 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -7,11 +7,10 @@ import pytest from PIL import Image -def is_win32(): - sys.platform.startswith("win32") +if sys.platform.startswith("win32"): + pytest.skip("Fuzzer is linux only", true) -@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), @@ -41,7 +40,6 @@ def test_fuzz_images(path): assert True -@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) From 961b2c0242df0b749c2524ce51e4272f2bd160d2 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:03:41 +0100 Subject: [PATCH 073/133] True --- Tests/oss-fuzz/test_fuzzers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 4ccdeca4a..97fe4a60f 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -8,7 +8,7 @@ from PIL import Image if sys.platform.startswith("win32"): - pytest.skip("Fuzzer is linux only", true) + pytest.skip("Fuzzer is linux only", True) @pytest.mark.parametrize( From 862e3b9d8e19fb7c222ecd590ac94c97f459ae6f Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:11:48 +0100 Subject: [PATCH 074/133] Apparently, it's a keyword-only parameter --- Tests/oss-fuzz/test_fuzzers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 97fe4a60f..b5e6a3d86 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -8,7 +8,7 @@ from PIL import Image if sys.platform.startswith("win32"): - pytest.skip("Fuzzer is linux only", True) + pytest.skip("Fuzzer is linux only", allow_module_level=True) @pytest.mark.parametrize( From 76e0422eb7810963a455443910a6ceeb9e39d631 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:13:37 +0100 Subject: [PATCH 075/133] Isort linted that there's an extra line, which black didn't worry about --- Tests/oss-fuzz/test_fuzzers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index b5e6a3d86..c61cb7e55 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -6,7 +6,6 @@ import pytest from PIL import Image - if sys.platform.startswith("win32"): pytest.skip("Fuzzer is linux only", allow_module_level=True) From d0d42cd7c25bbdd27b5a64b69d5340b006852bb0 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 11 Mar 2021 22:32:30 +0100 Subject: [PATCH 076/133] Install pytest-timeout on the ci. (dry?) --- .ci/install.sh | 1 + .github/workflows/macos-install.sh | 1 + .github/workflows/test-windows.yml | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 9372d0c51..4917b3a7c 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -27,6 +27,7 @@ python3 -m pip install coverage python3 -m pip install olefile python3 -m pip install -U pytest python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout python3 -m pip install pyroma python3 -m pip install test-image-results # TODO Remove condition when numpy supports 3.10 diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index afcb9a5a7..f45824445 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -9,6 +9,7 @@ python3 -m pip install coverage python3 -m pip install olefile python3 -m pip install -U pytest python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout python3 -m pip install pyroma python3 -m pip install test-image-results diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f3bb85f32..8cab06efb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -57,8 +57,8 @@ jobs: - name: Print build system information run: python .github/workflows/system-info.py - - name: python -m pip install wheel pytest pytest-cov - run: python -m pip install wheel pytest pytest-cov + - name: python -m pip install wheel pytest pytest-cov pytest-timeout + run: python -m pip install wheel pytest pytest-cov pytest-timeout # TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+: - name: Upgrade setuptools From 12715c5ea9097af314d3ffec688800c91b9c9f23 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:22:27 +0100 Subject: [PATCH 077/133] Install Pytest-timeout in dev-requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 1ed1356f9..4b534ae53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ packaging pyroma pytest pytest-cov +pytest-timeout sphinx>=2.4 sphinx-issues sphinx-removed-in From b57aee53a2ecbbc56d3da212aa08ab9184e8a7f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 08:30:27 +1100 Subject: [PATCH 078/133] Added release notes for #5321 [ci skip] --- docs/releasenotes/8.2.0.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index bbac2449f..95b17ab31 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -51,6 +51,14 @@ instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show if none of the other viewers are available. This means that the behaviour of :py:class:`PIL.ImageShow` will stay the same for most Pillow users. +Saving TIFF with ICC profile +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As is already possible for JPEG, PNG and WebP, the ICC profile for TIFF files can now +be specified through a keyword argument:: + + im.save("out.tif", icc_profile=...) + Security ======== From d45247eb6683d660cd151a4b3db555eb3f97d03b Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:14:43 +0100 Subject: [PATCH 079/133] Add decompression bomb error to font fuzzer --- Tests/oss-fuzz/fuzz_font.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 9f21a1fa5..bdfda7a13 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -31,6 +31,7 @@ def TestOneInput(data): def main(): + fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) atheris.Fuzz() From 83dabda6b2df344ecc6352d2b4d1e20fee4120dc Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:18:07 +0100 Subject: [PATCH 080/133] Clean up comments and filters --- Tests/oss-fuzz/fuzzers.py | 4 ++-- Tests/oss-fuzz/test_fuzzers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index 156653f1d..1e7a4e27d 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -20,12 +20,12 @@ def fuzz_image(data): def fuzz_font(data): - # This should not fail on a valid font load for any of the fonts in the corpus wrapper = io.BytesIO(data) try: font = ImageFont.truetype(wrapper) except OSError: - # pcf/pilfonts/random garbage here here. They're different. + # Catch pcf/pilfonts/random garbage here. They return + # different font objects. return font.getsize_multiline("ABC\nAaaa") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index c61cb7e55..04d4fd16b 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -43,7 +43,7 @@ def test_fuzz_images(path): "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) def test_fuzz_fonts(path): - if not path or b"LICENSE.txt" in path or b".pil" in path: + if not path: return with open(path, "rb") as f: fuzzers.fuzz_font(f.read()) From ad37e86c40e42a10300c763acba7bdf53dd7c28a Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:21:18 +0100 Subject: [PATCH 081/133] DecompressionBombError is now an option --- Tests/oss-fuzz/test_fuzzers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 04d4fd16b..8de71eb4b 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -46,5 +46,9 @@ def test_fuzz_fonts(path): if not path: return with open(path, "rb") as f: - fuzzers.fuzz_font(f.read()) + try: + fuzzers.fuzz_font(f.read()) + except (Image.DecompressionBombError, + Image.DecompressionBombWarning): + pass assert True From c52b45df62a34b14c66159db777e9d3fc942fb55 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 12:32:42 +1100 Subject: [PATCH 082/133] Removed automatic retrieval of GPS IFD --- Tests/test_file_jpeg.py | 4 ++-- src/PIL/Image.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 740f9fa4d..3ee33d65f 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -264,11 +264,11 @@ class TestFileJpeg: assert exif[0x0112] == Image.TRANSVERSE # Assert that the GPS IFD is present and empty - assert exif[0x8825] == {} + assert exif.get_ifd(0x8825) == {} transposed = ImageOps.exif_transpose(im) exif = transposed.getexif() - assert exif[0x8825] == {} + assert exif.get_ifd(0x8825) == {} # Assert that it was transposed assert 0x0112 not in exif diff --git a/src/PIL/Image.py b/src/PIL/Image.py index df3ebfd18..31eab54a4 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3360,6 +3360,10 @@ class Exif(MutableMapping): if ifd: merged_dict.update(ifd) + # GPS + if 0x8825 in self: + merged_dict[0x8825] = self._get_ifd_dict(self[0x8825]) + return merged_dict def tobytes(self, offset=8): @@ -3371,7 +3375,7 @@ class Exif(MutableMapping): head = b"MM\x00\x2A\x00\x00\x00\x08" ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): - if tag in [0x8769, 0x8225] and not isinstance(value, dict): + if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict): value = self.get_ifd(tag) if ( tag == 0x8769 @@ -3491,8 +3495,6 @@ class Exif(MutableMapping): def __getitem__(self, tag): if self._info is not None and tag not in self._data and tag in self._info: self._data[tag] = self._fixup(self._info[tag]) - if tag == 0x8825: - self._data[tag] = self.get_ifd(tag) del self._info[tag] return self._data[tag] From 36a4b055bba2c2ffff6945f0bb071ac1554c26ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 12:50:30 +1100 Subject: [PATCH 083/133] Updated comments --- src/PIL/Image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 545fdc019..2e7abfb68 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3397,6 +3397,7 @@ class Exif(MutableMapping): self.get_ifd(0x8769) tag_data = self._ifds[0x8769][tag] if tag == 0x927C: + # makernote from .TiffImagePlugin import ImageFileDirectory_v2 if tag_data[:8] == b"FUJIFILM": @@ -3472,7 +3473,7 @@ class Exif(MutableMapping): makernote = {0x1101: dict(self._fixup_dict(camerainfo))} self._ifds[tag] = makernote else: - # gpsinfo, interop + # interop self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) From c801db7a32312a2e75cf9766f38972d237b56ab8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 21:27:07 +1100 Subject: [PATCH 084/133] Added test for saving PNG with bits keyword --- Tests/test_file_png.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 52ea3b6d2..c8d441485 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -625,6 +625,15 @@ class TestFilePng: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} + def test_specify_bits(self, tmp_path): + im = hopper("P") + + out = str(tmp_path / "temp.png") + im.save(out, bits=4) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 48 + def test_exif(self): # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: From b7a76899be42fc1f5fc91663fc0b93724bc5e9f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 07:51:32 +1100 Subject: [PATCH 085/133] Updated harfbuzz to 2.8.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 7b561aa4e..a20fef02b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -275,9 +275,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.4.zip", - "filename": "harfbuzz-2.7.4.zip", - "dir": "harfbuzz-2.7.4", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.8.0.zip", + "filename": "harfbuzz-2.8.0.zip", + "dir": "harfbuzz-2.8.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From dd097fe1fd6bbfd22611f1dc5427e4220e7a437a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 11:14:12 +1100 Subject: [PATCH 086/133] Updated list of TIFF compression methods [ci skip] --- docs/handbook/image-file-formats.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 0ccd3b1a4..ef95fc21d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -876,10 +876,10 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, - ``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``, - ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, - ``"tiff_sgilog24"``, ``"tiff_raw_16"`` + methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, ``"group4"``, + ``"tiff_lzw"``, ``"jpeg"``, ``"tiff_adobe_deflate"``, ``"tiff_raw_16"``, + ``"packbits"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, + ``"tiff_sgilog24"``, ``"lzma"``, ``"zstd"``, ``"webp"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 8f37f8dcb0c2eaffbb61b13b42cb3dd0582c75cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 17:54:37 +1100 Subject: [PATCH 087/133] Sorted TIFF compression methods alphabetically [ci skip] --- docs/handbook/image-file-formats.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ef95fc21d..fa4735cf8 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -876,10 +876,10 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, ``"group4"``, - ``"tiff_lzw"``, ``"jpeg"``, ``"tiff_adobe_deflate"``, ``"tiff_raw_16"``, - ``"packbits"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, - ``"tiff_sgilog24"``, ``"lzma"``, ``"zstd"``, ``"webp"`` + methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``, + ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_deflate"``, + ``"tiff_lzw"``, ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, + ``"tiff_thunderscan"``, ``"webp"`, ``"zstd"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 1d8c5a820cd335a6b4f8d534e3f2fa4aa9112e0a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 20:37:31 +1100 Subject: [PATCH 088/133] Use duration from info dictionary when saving --- Tests/test_file_webp.py | 13 +++++++++++++ src/PIL/WebPImagePlugin.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index c1eb86ae5..cde7020ed 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -176,3 +176,16 @@ class TestFileWebp: [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] ) assert difference < 5 + + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_duration(self, tmp_path): + with Image.open("Tests/images/dispose_bgnd.gif") as im: + assert im.info["duration"] == 1000 + + out_webp = str(tmp_path / "temp.webp") + im.save(out_webp, save_all=True) + + with Image.open(out_webp) as reloaded: + reloaded.load() + assert reloaded.info["duration"] == 1000 diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 2e9746fa3..c9b700314 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -192,7 +192,7 @@ def _save_all(im, fp, filename): r, g, b = palette[background * 3 : (background + 1) * 3] background = (r, g, b, 0) - duration = im.encoderinfo.get("duration", 0) + duration = im.encoderinfo.get("duration", im.info.get("duration")) loop = im.encoderinfo.get("loop", 0) minimize_size = im.encoderinfo.get("minimize_size", False) kmin = im.encoderinfo.get("kmin", None) From 94df4ec1c95932ec498e8be307498a1cd9241cbe Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:16:35 +1100 Subject: [PATCH 089/133] Lint fix --- Tests/oss-fuzz/test_fuzzers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 8de71eb4b..a243c0260 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -48,7 +48,6 @@ def test_fuzz_fonts(path): with open(path, "rb") as f: try: fuzzers.fuzz_font(f.read()) - except (Image.DecompressionBombError, - Image.DecompressionBombWarning): + except (Image.DecompressionBombError, Image.DecompressionBombWarning): pass assert True From 298600381f0ca6308688985c5ddfbc131b817981 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 12:00:29 +1100 Subject: [PATCH 090/133] Replaced tiff_deflate with tiff_adobe_deflate compression when saving --- Tests/test_file_libtiff.py | 8 ++++++++ src/PIL/TiffImagePlugin.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 7a5a5b462..d6f4900cd 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -471,6 +471,14 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert reloaded.info["compression"] == "jpeg" + def test_tiff_deflate_compression(self, tmp_path): + im = hopper("RGB") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_deflate") + + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "tiff_adobe_deflate" + def test_quality(self, tmp_path): im = hopper("RGB") out = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 98c70d7c4..19bcf4419 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1442,6 +1442,8 @@ def _save(im, fp, filename): elif compression == "tiff_jpeg": # OJPEG is obsolete, so use new-style JPEG compression instead compression = "jpeg" + elif compression == "tiff_deflate": + compression = "tiff_adobe_deflate" libtiff = WRITE_LIBTIFF or compression != "raw" From 242af47a686d0c4a6eaee07e59569795289701c5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 12:14:41 +1100 Subject: [PATCH 091/133] Removed obsolete "tiff_deflate" from compression methods [ci skip] --- docs/handbook/image-file-formats.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index fa4735cf8..c67f8fb8f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -877,9 +877,9 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``, - ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_deflate"``, - ``"tiff_lzw"``, ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, - ``"tiff_thunderscan"``, ``"webp"`, ``"zstd"`` + ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``, + ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``, + ``"webp"`, ``"zstd"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 03eecb51d561f24ca5d5714cf9af53f568e90a9a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 18:03:44 +1100 Subject: [PATCH 092/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dbe06304d..99aa94f9a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343 + [radarhere] + - Save ICC profile from TIFF encoderinfo #5321 [radarhere] From 7a32dfd5e30934000ca4dcc9624f6aec2fa20e0e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:30:09 +1100 Subject: [PATCH 093/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 99aa94f9a..6bd8ef8b5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Stop flattening EXIF IFD into getexif() #4947 + [radarhere, kkopachev] + - Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343 [radarhere] From 309d6f662cf43d65ed756e5bd3290ae291625463 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:32:39 +1100 Subject: [PATCH 094/133] Moved rounded_rectangle to API additions [ci skip] --- docs/releasenotes/8.2.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 95b17ab31..02df50bc8 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,6 +21,9 @@ accepts negative co-ordinates, like the upper left corner of the ``box`` argumen :py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of cropping the overlaid image. +API Additions +============= + ImageDraw.rounded_rectangle ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -36,9 +39,6 @@ create a circle, but not any other ellipse. draw = ImageDraw.Draw(im) draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") -API Additions -============= - ImageShow.IPythonViewer ^^^^^^^^^^^^^^^^^^^^^^^ From da9b1046935177f1e202c4e0f7934f600a68eeae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:43:32 +1100 Subject: [PATCH 095/133] Document #4947 [ci skip] --- docs/releasenotes/8.2.0.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 02df50bc8..d82bf45c2 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,6 +21,20 @@ accepts negative co-ordinates, like the upper left corner of the ``box`` argumen :py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of cropping the overlaid image. +Image.getexif: EXIF and GPS IFD +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, :py:meth:`~PIL.Image.Image.getexif` flattened the EXIF IFD into the rest of +the data, losing information. This information is now kept separate, moved under +``im.getexif().get_ifd(0x8769)``. + +Direct access to the GPS IFD dictionary was possible through ``im.getexif()[0x8825]``. +This is now consistent with other IFDs, and must be accessed through +``im.getexif().get_ifd(0x8825)``. + +These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pillow +6.0. The older ``_getexif()`` methods are unaffected. + API Additions ============= From 6591297239d4fd28ea41585fb6fa1f7c9096f6d2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 22:32:27 +1100 Subject: [PATCH 096/133] Increased use of assert_image_equal_tofile --- Tests/test_imagedraw.py | 115 ++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 55a4b03e2..06c5b2503 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -856,20 +856,19 @@ def create_base_image_draw( def test_square(): - with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected: - expected.load() - img, draw = create_base_image_draw((10, 10)) - draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - assert_image_equal(img, expected, "square as normal polygon failed") - img, draw = create_base_image_draw((10, 10)) - draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - assert_image_equal(img, expected, "square as inverted polygon failed") - img, draw = create_base_image_draw((10, 10)) - draw.rectangle((2, 2, 7, 7), BLACK) - assert_image_equal(img, expected, "square as normal rectangle failed") - img, draw = create_base_image_draw((10, 10)) - draw.rectangle((7, 7, 2, 2), BLACK) - assert_image_equal(img, expected, "square as inverted rectangle failed") + 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) + assert_image_equal_tofile(img, expected, "square as normal polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + assert_image_equal_tofile(img, expected, "square as inverted polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + assert_image_equal_tofile(img, expected, "square as normal rectangle failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + assert_image_equal_tofile(img, expected, "square as inverted rectangle failed") def test_triangle_right(): @@ -896,18 +895,18 @@ def test_line_horizontal(): os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"), "line straight horizontal inverted 2px wide failed", ) - with Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal normal 3px wide failed" - ) - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal inverted 3px wide failed" - ) + + expected = os.path.join(IMAGES_PATH, "line_horizontal_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight horizontal normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight horizontal inverted 3px wide failed" + ) img, draw = create_base_image_draw((200, 110)) draw.line((5, 55, 195, 55), BLACK, 101) @@ -945,18 +944,19 @@ def test_line_vertical(): os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"), "line straight vertical inverted 2px wide failed", ) - with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical normal 3px wide failed" - ) - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical inverted 3px wide failed" - ) + + expected = os.path.join(IMAGES_PATH, "line_vertical_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight vertical normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight vertical inverted 3px wide failed" + ) + img, draw = create_base_image_draw((110, 200)) draw.line((55, 5, 55, 195), BLACK, 101) assert_image_equal_tofile( @@ -975,26 +975,25 @@ def test_line_vertical(): def test_line_oblique_45(): - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 14), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 normal 3px wide A failed") - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 14, 5, 5), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 inverted 3px wide A failed") - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 14), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 normal 3px wide B failed") - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 14, 14, 5), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 inverted 3px wide B failed") + 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) + assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide A failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line oblique 45 inverted 3px wide A failed" + ) + + expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide B failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line oblique 45 inverted 3px wide B failed" + ) def test_wide_line_dot(): From 754752e78f61f5a45b6f64211ff965abdbe348f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Mar 2021 09:22:01 +1100 Subject: [PATCH 097/133] Allow fewer palette entries than the bit depth maximum --- Tests/test_file_png.py | 10 ++++++++++ src/PIL/PngImagePlugin.py | 26 ++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c8d441485..bbf5f5772 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -634,6 +634,16 @@ class TestFilePng: with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 48 + def test_plte_length(self, tmp_path): + im = Image.new("P", (1, 1)) + im.putpalette((1, 1, 1)) + + out = str(tmp_path / "temp.png") + im.save(str(tmp_path / "temp.png")) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 3 + def test_exif(self): # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 30eb13aa3..07bbc5228 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1186,23 +1186,21 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user - colors = 1 << im.encoderinfo["bits"] + colors = min(1 << im.encoderinfo["bits"], 256) else: # check palette contents if im.palette: - colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2) + colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1) else: colors = 256 - if colors <= 2: - bits = 1 - elif colors <= 4: - bits = 2 - elif colors <= 16: - bits = 4 - else: - bits = 8 - if bits != 8: + if colors <= 16: + if colors <= 2: + bits = 1 + elif colors <= 4: + bits = 2 + else: + bits = 4 mode = f"{mode};{bits}" # encoder options @@ -1270,7 +1268,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, cid, data) if im.mode == "P": - palette_byte_number = (2 ** bits) * 3 + palette_byte_number = colors * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: palette_bytes += b"\0" @@ -1281,7 +1279,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size - alpha_bytes = 2 ** bits + alpha_bytes = colors if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: @@ -1302,7 +1300,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") - alpha_bytes = 2 ** bits + alpha_bytes = colors chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") From 7ab8ec9b91681dea850269e08d3fd1adcd96afd5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Mar 2021 21:00:05 +1100 Subject: [PATCH 098/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6bd8ef8b5..3d022c77d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Use duration from info dictionary when saving WebP #5338 + [radarhere] + - Stop flattening EXIF IFD into getexif() #4947 [radarhere, kkopachev] From aa35f6b572c3491976e2336b28f481dad0a2a353 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 06:49:25 +1100 Subject: [PATCH 099/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3d022c77d..21eeb214c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Allow fewer PNG palette entries than the bit depth maximum when saving #5330 + [radarhere] + - Use duration from info dictionary when saving WebP #5338 [radarhere] From 2d8658bd84327b601ddd9147fd84b32d8e7e09da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 18:58:50 +1100 Subject: [PATCH 100/133] Deprecated categories [ci skip] --- docs/deprecations.rst | 12 ++++++++++++ docs/reference/Image.rst | 7 ------- docs/releasenotes/8.2.0.rst | 10 ++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index fd2f5620e..ef88afa23 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -33,6 +33,18 @@ Tk/Tcl 8.4 Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), when Tk/Tcl 8.5 will be the minimum supported. +Categories +~~~~~~~~~~ + +.. deprecated:: 8.2.0 + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + Image.show command parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index c4e8f37a3..0e68366ad 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -502,10 +502,3 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m Check support using :py:func:`PIL.features.check_feature` with ``feature="libimagequant"``. - -.. comment: These are not referenced anywhere? - Categories - ^^^^^^^^^^ - .. data:: NORMAL - .. data:: SEQUENCE - .. data:: CONTAINER diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index d82bf45c2..3ef05894d 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -10,6 +10,16 @@ Tk/Tcl 8.4 Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), when Tk/Tcl 8.5 will be the minimum supported. +Categories +^^^^^^^^^^ + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + API Changes =========== From ab56edb49f30557eb684256a1e3064936b737ecf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:18:36 +1100 Subject: [PATCH 101/133] Documented default quantization method --- docs/reference/Image.rst | 6 +++--- src/PIL/Image.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index c4e8f37a3..bf173ace9 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -486,15 +486,15 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m .. data:: MEDIANCUT - Median cut + Median cut. Default method, except for RGBA images. .. data:: MAXCOVERAGE - Maximum coverage + Maximum coverage. .. data:: FASTOCTREE - Fast octree + Fast octree. Default method for RGBA images. .. data:: LIBIMAGEQUANT diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..bec6f3f64 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1059,6 +1059,11 @@ class Image: :data:`LIBIMAGEQUANT` (libimagequant; check support using :py:func:`PIL.features.check_feature` with ``feature="libimagequant"``). + + By default, :data:`MEDIANCUT` will be used. + + The exception to this is RGBA images. RGBA images use + :data:`FASTOCTREE` by default instead. :param kmeans: Integer :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. From 0ff987917116d5b859959b1dc80092f7fdffac0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:21:31 +1100 Subject: [PATCH 102/133] Document supported quantization methods for RGBA images --- docs/reference/Image.rst | 5 +++-- src/PIL/Image.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index bf173ace9..2f4c9af99 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -486,11 +486,12 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m .. data:: MEDIANCUT - Median cut. Default method, except for RGBA images. + Median cut. Default method, except for RGBA images. This method does not support + RGBA images. .. data:: MAXCOVERAGE - Maximum coverage. + Maximum coverage. This method does not support RGBA images. .. data:: FASTOCTREE diff --git a/src/PIL/Image.py b/src/PIL/Image.py index bec6f3f64..0bfd1da80 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1062,8 +1062,9 @@ class Image: By default, :data:`MEDIANCUT` will be used. - The exception to this is RGBA images. RGBA images use - :data:`FASTOCTREE` by default instead. + The exception to this is RGBA images. :data:`MEDIANCUT` and + :data:`MAXCOVERAGE` do not support RGBA images, so + :data:`FASTOCTREE` is used by default instead. :param kmeans: Integer :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. From 4e0bc3bab61124e5c8d29743d77541de052c44d8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:44:56 +1100 Subject: [PATCH 103/133] Use quantization method attributes --- src/PIL/Image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..5e9d9a89f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1074,11 +1074,11 @@ class Image: if method is None: # defaults: - method = 0 + method = MEDIANCUT if self.mode == "RGBA": - method = 2 + method = FASTOCTREE - if self.mode == "RGBA" and method not in (2, 3): + if self.mode == "RGBA" and method not in (FASTOCTREE, LIBIMAGEQUANT): # Caller specified an invalid mode. raise ValueError( "Fast Octree (method == 2) and libimagequant (method == 3) " From d8acc3be0d9387b3c086f08693f6633d88ad481e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Mar 2021 12:55:16 +1100 Subject: [PATCH 104/133] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ec39c9fa8..79bb8079f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -465,9 +465,9 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.0 |arm | +| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.2 |arm | | +------------------------------+--------------------------------+-----------------------+ -| | 3.6, 3.7, 3.8, 3.9 | 8.1.0 |x86-64 | +| | 3.6, 3.7, 3.8, 3.9 | 8.1.2 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | | +------------------------------+--------------------------------+ + From 49fa3656b180713dfa9b680e4b9a5885aba501bd Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 3 Mar 2021 13:58:13 +0100 Subject: [PATCH 105/133] do not premultiply alpha when resizing with Image.NEAREST resampling --- Tests/test_image_transform.py | 36 +++++++++++++++++++++++++++++++++++ src/PIL/Image.py | 6 +++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 3ee51178d..c22b874c6 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -143,6 +143,42 @@ class TestImageTransform: self._test_alpha_premult(op) + def _test_nearest(self, op, mode): + # create white image with half transparent, + # with the black half transparent. + # do op, + # the image should be white with half transparent + transparent, opaque = { + "RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)), + "LA": ((255, 0), (255, 255)), + }[mode] + im = Image.new(mode, (10, 10), transparent) + im2 = Image.new(mode, (5, 10), opaque) + im.paste(im2, (0, 0)) + + im = op(im, (40, 10)) + + colors = im.getcolors() + assert colors == [ + (20 * 10, opaque), + (20 * 10, transparent), + ] + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_resize(self, mode): + def op(im, sz): + return im.resize(sz, Image.NEAREST) + + self._test_nearest(op, mode) + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_transform(self, mode): + def op(im, sz): + (w, h) = im.size + return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.NEAREST) + + self._test_nearest(op, mode) + def test_blank_fill(self): # attempting to hit # https://github.com/python-pillow/Pillow/issues/254 reported diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..6f7ed776f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1910,7 +1910,7 @@ class Image: if self.mode in ("1", "P"): resample = NEAREST - if self.mode in ["LA", "RGBA"]: + if self.mode in ["LA", "RGBA"] and resample != NEAREST: im = self.convert(self.mode[:-1] + "a") im = im.resize(size, resample, box) return im.convert(self.mode) @@ -2394,14 +2394,14 @@ class Image: :returns: An :py:class:`~PIL.Image.Image` object. """ - if self.mode == "LA": + if self.mode == "LA" and resample != NEAREST: return ( self.convert("La") .transform(size, method, data, resample, fill, fillcolor) .convert("LA") ) - if self.mode == "RGBA": + if self.mode == "RGBA" and resample != NEAREST: return ( self.convert("RGBa") .transform(size, method, data, resample, fill, fillcolor) From e85bf540cf1da0ada4ef424bfb67c8f69c1b2352 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 23 Mar 2021 20:58:01 +0200 Subject: [PATCH 106/133] Contributing: Include release notes as needed or appropriate [CI skip] --- .github/CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 563fcda6a..35bd47be8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -18,6 +18,7 @@ Please send a pull request to the master branch. Please include [documentation]( - Provide tests for any newly added code. - Follow PEP 8. - When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. +- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. ## Reporting Issues From 5e61c1842fcee02562cbb46dd44e8bdd39e3903f Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:04:41 +0100 Subject: [PATCH 107/133] fix support for old versions of Raqm --- src/_imagingft.c | 61 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 0995abab3..330294479 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -236,7 +236,6 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; -// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; rq = raqm_create(); @@ -278,12 +277,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; -// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { -// PyErr_SetString( -// PyExc_ValueError, -// "libraqm 0.7 or greater required for 'ttb' direction"); -// goto failed; -// } +#if !defined(RAQM_VERSION_ATLEAST) || !RAQM_VERSION_ATLEAST(0, 7, 0) + PyErr_SetString( + PyExc_ValueError, + "libraqm 0.7 or greater required for 'ttb' direction"); + goto failed; +#endif } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -340,21 +339,12 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// glyphs_01 = raqm_get_glyphs_01(rq, &count); -// if (glyphs_01 == NULL) { -// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); -// count = 0; -// goto failed; -// } -// } else { /* version == 2 */ - glyphs = raqm_get_glyphs(rq, &count); - if (glyphs == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } -// } + glyphs = raqm_get_glyphs(rq, &count); + if (glyphs == NULL) { + PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); + count = 0; + goto failed; + } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -363,25 +353,14 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// for (i = 0; i < count; i++) { -// (*glyph_info)[i].index = glyphs_01[i].index; -// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; -// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; -// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; -// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; -// (*glyph_info)[i].cluster = glyphs_01[i].cluster; -// } -// } else { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs[i].index; - (*glyph_info)[i].x_offset = glyphs[i].x_offset; - (*glyph_info)[i].x_advance = glyphs[i].x_advance; - (*glyph_info)[i].y_offset = glyphs[i].y_offset; - (*glyph_info)[i].y_advance = glyphs[i].y_advance; - (*glyph_info)[i].cluster = glyphs[i].cluster; - } -// } + for (i = 0; i < count; i++) { + (*glyph_info)[i].index = glyphs[i].index; + (*glyph_info)[i].x_offset = glyphs[i].x_offset; + (*glyph_info)[i].x_advance = glyphs[i].x_advance; + (*glyph_info)[i].y_offset = glyphs[i].y_offset; + (*glyph_info)[i].y_advance = glyphs[i].y_advance; + (*glyph_info)[i].cluster = glyphs[i].cluster; + } failed: raqm_destroy(rq); From c718cc6c94cacfbfcd7e84cff9f012df1b84c718 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:25:38 +0100 Subject: [PATCH 108/133] avoid unused variable warnings --- src/_imagingft.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 330294479..73f0f6362 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1367,7 +1367,6 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "HAVE_FRIBIDI", v); PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); if (have_raqm) { - const char *a, *b; #ifdef RAQM_VERSION_MAJOR v = PyUnicode_FromString(raqm_version_string()); #else @@ -1376,12 +1375,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, ')'); - b = strchr(fribidi_version_info, '\n'); - if (a && b) { - v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); - } else { - v = Py_None; + { + const char *a = strchr(fribidi_version_info, ')'); + const char *b = strchr(fribidi_version_info, '\n'); + if (a && b && a + 2 < b) { + v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2)); + } else { + v = Py_None; + } } #else v = Py_None; From 4b1dd40b5aa42af3f5fcf65856cb7bafae1cf256 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Mar 2021 11:57:48 +1100 Subject: [PATCH 109/133] Listed Debian packages [ci skip] --- docs/installation.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 79bb8079f..95d87feb7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -88,9 +88,10 @@ libraqm, fribidi, and harfbuzz to be installed separately:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow -Most major Linux distributions, including Fedora, Debian/Ubuntu and -ArchLinux also include Pillow in packages that previously contained -PIL e.g. ``python-imaging``. +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``. FreeBSD Installation ^^^^^^^^^^^^^^^^^^^^ From 4c36945206272a29958716d0cef5fbf6e3a0b56a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Mar 2021 22:43:46 +1100 Subject: [PATCH 110/133] Added prerequisites and Python development libraries for Alpine --- docs/installation.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 79bb8079f..bc34f1d01 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -366,6 +366,10 @@ 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 - 20.04 LTS** are installed with:: @@ -385,6 +389,12 @@ Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with 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. From 9872d57e3baa0659482bee143e7029f358cd6746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 27 Mar 2021 02:06:36 +0100 Subject: [PATCH 111/133] corrected comment --- Tests/test_image_transform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index c22b874c6..845900267 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -145,9 +145,8 @@ class TestImageTransform: def _test_nearest(self, op, mode): # create white image with half transparent, - # with the black half transparent. # do op, - # the image should be white with half transparent + # the image should remain white with half transparent transparent, opaque = { "RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)), "LA": ((255, 0), (255, 255)), From a4a38b805b524f0fdb0d1feca83ca73d3ccfff0b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Mar 2021 14:47:11 +1100 Subject: [PATCH 112/133] Removed return value of build_distance_tables --- src/libImaging/Quant.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index bee5e5599..f5a5d567c 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -789,7 +789,7 @@ resort_distance_tables( return 1; } -static int +static void build_distance_tables( uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { uint32_t i, j; @@ -811,7 +811,6 @@ build_distance_tables( sizeof(uint32_t *), _sort_ulong_ptr_keys); } - return 1; } static int @@ -1373,9 +1372,7 @@ quantize( goto error_6; } - if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) { - goto error_7; - } + build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries); if (!map_image_pixels_from_median_box( pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) { @@ -1580,9 +1577,7 @@ quantize2( goto error_3; } - if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) { - goto error_4; - } + build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels); if (!map_image_pixels( pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { From 71cd97a5199bc54d492150fdd7f6ad216f381e70 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Mar 2021 15:51:28 +1100 Subject: [PATCH 113/133] Added deprecation warnings --- Tests/test_image.py | 15 +++++++++++++++ src/PIL/Image.py | 35 ++++++++++++++++++++++++++++------- src/PIL/MicImagePlugin.py | 2 +- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index b326ca0f8..3fa071be1 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ import io import os import shutil +import sys import tempfile import pytest @@ -769,6 +770,20 @@ class TestImage: reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + @pytest.mark.skipif( + sys.version_info < (3, 7), reason="Python 3.7 or greater required" + ) + def test_categories_deprecation(self): + with pytest.warns(DeprecationWarning): + assert hopper().category == 0 + + with pytest.warns(DeprecationWarning): + assert Image.NORMAL == 0 + with pytest.warns(DeprecationWarning): + assert Image.SEQUENCE == 1 + with pytest.warns(DeprecationWarning): + assert Image.CONTAINER == 2 + @pytest.mark.parametrize( "test_module", [PIL, Image], diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..6f0fd1383 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -59,6 +59,16 @@ if sys.version_info >= (3, 7): if name == "PILLOW_VERSION": _raise_version_warning() return __version__ + else: + categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} + if name in categories: + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return categories[name] raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -69,6 +79,11 @@ else: # Silence warning assert PILLOW_VERSION + # categories + NORMAL = 0 + SEQUENCE = 1 + CONTAINER = 2 + logger = logging.getLogger(__name__) @@ -187,11 +202,6 @@ MAXCOVERAGE = 1 FASTOCTREE = 2 LIBIMAGEQUANT = 3 -# categories -NORMAL = 0 -SEQUENCE = 1 -CONTAINER = 2 - if hasattr(core, "DEFAULT_STRATEGY"): DEFAULT_STRATEGY = core.DEFAULT_STRATEGY FILTERED = core.FILTERED @@ -535,11 +545,22 @@ class Image: self._size = (0, 0) self.palette = None self.info = {} - self.category = NORMAL + self._category = 0 self.readonly = 0 self.pyaccess = None self._exif = None + def __getattr__(self, name): + if name == "category": + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._category + raise AttributeError(name) + @property def width(self): return self.size[0] @@ -648,7 +669,7 @@ class Image: and self.mode == other.mode and self.size == other.size and self.info == other.info - and self.category == other.category + and self._category == other._category and self.readonly == other.readonly and self.getpalette() == other.getpalette() and self.tobytes() == other.tobytes() diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 2aed26030..9248b1b65 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -68,7 +68,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self.is_animated = self._n_frames > 1 if len(self.images) > 1: - self.category = Image.CONTAINER + self._category = Image.CONTAINER self.seek(0) From fa6fed92cb8ca664a898e37f8df8188c363c4812 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Mar 2021 16:10:34 +1100 Subject: [PATCH 114/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 21eeb214c..dba9d263a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304 + [nulano] + +- Dynamically link FriBiDi instead of Raqm #5062 + [nulano] + - Allow fewer PNG palette entries than the bit depth maximum when saving #5330 [radarhere] From bf8cebc96d8210d10394eb29728b1953def34f75 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Mar 2021 13:49:37 +0200 Subject: [PATCH 115/133] Add libxcb to fuzzers --- Tests/oss-fuzz/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh index fc54ee3ee..513136fff 100755 --- a/Tests/oss-fuzz/build.sh +++ b/Tests/oss-fuzz/build.sh @@ -31,6 +31,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do --add-binary /usr/local/lib/libwebp.so.7:. \ --add-binary /usr/local/lib/libwebpdemux.so.2:. \ --add-binary /usr/local/lib/libwebpmux.so.3:. \ + --add-binary /usr/local/lib/libxcb.so.1:. \ --distpath $OUT --onefile --name $fuzzer_package $fuzzer # Create execution wrapper. From 0018685a8e475ce91ceb11a6df8e137f0b1f6a47 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Sat, 23 May 2020 09:24:41 -0700 Subject: [PATCH 116/133] Add Tests and support for Planar Tiff Images --- Tests/images/tiff_strip_planar_16bit_RGB.tiff | Bin 0 -> 31576 bytes .../images/tiff_strip_planar_16bit_RGBa.tiff | Bin 0 -> 37295 bytes Tests/images/tiff_strip_planar_lzw.tiff | Bin 0 -> 155014 bytes Tests/images/tiff_tiled_planar_16bit_RGB.tiff | Bin 0 -> 34501 bytes .../images/tiff_tiled_planar_16bit_RGBa.tiff | Bin 0 -> 41015 bytes Tests/images/tiff_tiled_planar_lzw.tiff | Bin 0 -> 159997 bytes Tests/test_file_libtiff.py | 46 +++++++ Tests/test_lib_pack.py | 54 ++++++++ src/libImaging/Unpack.c | 121 +++++++++++++++++- 9 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Tests/images/tiff_strip_planar_16bit_RGB.tiff create mode 100644 Tests/images/tiff_strip_planar_16bit_RGBa.tiff create mode 100644 Tests/images/tiff_strip_planar_lzw.tiff create mode 100644 Tests/images/tiff_tiled_planar_16bit_RGB.tiff create mode 100644 Tests/images/tiff_tiled_planar_16bit_RGBa.tiff create mode 100644 Tests/images/tiff_tiled_planar_lzw.tiff diff --git a/Tests/images/tiff_strip_planar_16bit_RGB.tiff b/Tests/images/tiff_strip_planar_16bit_RGB.tiff new file mode 100644 index 0000000000000000000000000000000000000000..360b4c165333468fbbdc015916edf3fc265df7e9 GIT binary patch literal 31576 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 TOKzPa?>J~e2{$#_l;{5+2s|yj literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_strip_planar_16bit_RGBa.tiff b/Tests/images/tiff_strip_planar_16bit_RGBa.tiff new file mode 100644 index 0000000000000000000000000000000000000000..b8c3dcf643853d875a274032c13377739be76638 GIT binary patch literal 37295 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_strip_planar_lzw.tiff b/Tests/images/tiff_strip_planar_lzw.tiff new file mode 100644 index 0000000000000000000000000000000000000000..8145703f4308af2329218d1ffbba2b7f8931d5e8 GIT binary patch literal 155014 zcmZsCXIN89)NVp21=1TuLI>#}T_tpo-a$%22azTspkhLALMS32Xab0U2mw(MQ4=}{ z2-px&6Pnl%Jcx=O&&~Pn`JU(gxI52e-o2*m*?ab$nYG?E4i4%7F#rG{2><{E0YE;& z@c)4S;Z!~r;A26)e4ZZ!|C0;zvB-Z|fREuoA%F@$PT}WA|0n;q4CX&9!^hbF@+|N% z0Vw=G*BazwssHdKA4~r~b*lb9^&I>U|Jy!KKK{3E*uNL%AOG-=`)9?(|2G!n-;Wqz z0m@GS0RY8t06+%`0F)vCfOQFe>-aZ1%#SPi@ep6GjRF9!^5Z3bz7>9%R(_d#{Im!B z^Jo|V;KG+>^Ya(-!y|qu<;%JMg)n})Jp=&A5eERu_-UH_^Dcfo%a{FIMw(xbDnFR; z0ANeBq|2Mq*7DFXqw^nn0lOCW&b1_YFZ00D#~AYd;G2w1EF z0uEjU0&=^70P%Z3K+OaYF!B-zNZ9}atiJ&PV}b&J6@&l)EH41C))N4{vJe0~aTVbA z%D+DublX*oN-9@u}UlFr%-`4lfIE-ibpD1GS=*y zJ(Lh7b_(u)8B}-^=nhJV7>9O>LCujC&zR=P2vdgZwdj};P@$cxr#f1FB=&yr?G8=M z5p>;jbgy3r`hb$-RiEoV`!9SL@vd&}@Vy?ldz8@Noa)hrD)KgEx?OWutx|^&&K_dA zKVdlveNED;aZ;)%bjk?QhD}AD@E=$;*deP|idiLIzUX}IY3xa$bL5?%E|75Q(St4H zns=7s1%BP<2I@ZKCZ+`*QURYHTTMfKJpS*SB`tN@l?3{#w4JuY`f*hD5%DgZX zCT%YzTuTxkjMF^`u4GM^K!@l{`jW%2DQ)<0&QJ3po!KTackLrD9X)Nq-7XKh9x**` zUvQr~DRbe7d0M~rqi!dU@!}^4Hp>Q554Nu2nH+Talxr2#6JCEytNfcg-pBj<^iEDGmxJ?nc0Z65q~h^X z-tHN;F)Ei=6Y)N08RsDHfN*$S!StW^7PMG&KYp!jad&>I_rldwC!bf}g-7sJBl4F= zo~+IXZJIw6`be6Uf>F|xLTHLHm~`6hGt z$o#j1Awf@5+TaMAPim9dhbp_R#BY7C2wuG3x#l0}hjHAF4yewpyK_I`($~cSwXcyE zE_wNno)}f}tyXnN`nub2?drg_5nai^0X49ib+@uzd5(hwi5^n6YylJe=ku>WIg;P@ z{yEGPA`0mgP~D&%_iSO#j>uSDu!E(=bIWlXQzLX}uOC=9QOjnb4{Ny*SWJuek+quC z&^EXY$_r2}3GGt_!$*xr9NU5y15Xc+Y$4`gKX`*;(Vdj$HjbWB4!soadVlX>;_rR- zy$*%MT}A6GO{Cy^S-52cPJXQ;#Ql9tTJD*EVoCyb1#kxoFV(ZPH`NNW=*Na`1(yJ` zbVYQ=@PdlrB?X^!M~z3ztWcpPqyn8$90w-|2m#NX61C|L!Pp!hFslCg`qh&u^dRGi z35E&CF*m8Sb9iJmgyUI{S0UtetP0$PtQ#p9Ny!}s##9EfNqMwkUEtpNaZ66pKes;;z%gp&@zl>FD ziUeiKL=?u;ZIL_P;Mrj~CHKh<{l?_3=iSF?L{k;sUwRI~UTe$LO|1>Uld;mKn z7Ft59(ib_tjjs-!vODrwM&Z6Oc6lqXII2oRQ?ngi15MzXo_Ej1QD9mAPb#j*WWQLs z7zFNcDm$tv=-Af~&} zYH@~ToYJ1trTb-Hg@V!Y(Hb67;_Z7fgKZ-?qfMj(l6b7>b9mM8PL~F6k3$&EOCBW6gW3#)m*|X{Imi7*cviA0^ z%!U>)rU*lw+LFX2GZA;8SC>VMva$j8p+Oqv0#q_Y`0Ps1@_=tH>pToeJt#&})z$MZ zdvH6_EOudgpAGj`0$RwQ3o0me@8{W$I1rlLrLorcb|imZ3I&Lp7Cd`^o^Dhy7Uh1x z330jmnikT^1lP^RsN1qbJIO`(c>pS^8DlD`IMruWJ$3kt#9w$vihP8N!)Kh-Pm|W#5cn z`aJ6f0RrzCC2~as0ID7YCyiVxSyBLIEDYU2?Xle83Rw<2yn~LhJNoUiOe!P`g z8Sq<`ZmLO<*xuMbR1hYiPc?M`>|%x~1xstLjrmY5b5X#oh>Q22TGf{0p%hx+4C2!t7l<|VrI^Zf?E^TM_1~x5t zuUaB3k?cIF2R6_`L{a6wMmUgw=iN;e5I6*&>3Nu!RGbVU&IuVn2=+ilr6c zfpr85ONuiuHCru3yah^Zl_K4i;>=2u7u0J#~MGhTFmv;l-;eIYYVz#Ci<00~-p1G?Su*1b^# z_6g0Pf`F>`d39VO9XI=1#_@#owiu3>iRM_t3;Hop#)xYJ_T3s0bBZ$ES`P3X6!|#} z@JXWE_%?YBx%$HwqK=@+!O2_%ZC^L9fSzNx-{ZpkQ9XI228vXsCymyUT#bcgQ3M^Ng84R_Weh4 z-2H~Q@@XNs7!INJkYbKt+PBIte_or|r%0D+65bzP;SeSUmHSigA<&*IA``yjt1bd3 z&*F&v_<~PZ5(CFe#Bu8JJO%;ClBL|nk(#C46R@OKg4TUWN*|8#6-1t|)d-LzGt_#* z(aF&$AQ5;|O~rXfL*sQ$%A7CWnI}aYNGPIGjygC5-6_$3`8eX zcdnN0Z+UK+S8fC}hme2Q_l&k%@Q~|~gDo|1?jIPX1K;`w$4n*A=vZ346s%c_(ka91 z#nI$Nx#1!f1_DG3{?U3Fuuh7o3gQk!-&aU5{(uDPwd3L?kc#iA01?C{1kMse>Lp~| zI2`)}O~QcyUFb(|4QM6T#Dx#&d(qp^!kL!p6*mlf`p>LsIW*VX5S5{T-I^zvSE#R% ztDA#Oig4~56r~ea@Y;mN!c)!=kS$V7hBUJi$Ci@-)Pty*Vr&E4$9ERY5(3Wy3b+kz zE0JLQ28{@yZoWZ@zL${WN(0#v0P-8;*0PARWAAl+is1*28hWbIq9}=k^r5$BVB^~0 zOvhUk7u3zT6H2qz=jqnmzxTZ_w2Ik| zN*#M6Np_TQ3%` z%;zouv_+ChNq9wNi!wglO znm|D3XLKuTb0kw;k|WYceGau0`2Xm2QUWf$gAsZ)N3K@1DaP(f|o)2lu=eD`1YX4+ue zc%#F|`$GS6cR-h)#zpY0U(llQ2)%11wo<~>hauX&*B>>0PVEou#C)_Vy6e^I(OMjm zWJDR+$1LW0kE^NLmp40jIaKBn$-%f5@yr;NbxLpiC6TtlL?pw0=Qtgxh6uw)swMiM z!Y2Y<#j;^bM$i>@zpG9J(E2@EnB?Jg!6UF%#X--(Rc$gt*RjRH#=+08`szeRNR2JA zfPw=TbB|6#J-q^|uQDV3F~SrNc9qb3!{T1+-fssVTX+0@SW=KPm2i1VrSw%zTq_=xRrFXUQ% znvUHHbyZ11fwgH~X2sn*R5tYY`LKndZ&io=Qls|{1z>Uu4$~F!64vG66lr?|U+=Vq zhr1)J;X((ePtb>TL6-C!yujhI?1isprA>@=lTzR5Z)e|Hbql$onl{GuW_N7x**q9^ zY1TD|SsGP4N>L6*UL^(w;YHdWBwhLOe)p8jyNRu|hBelV$7Nx$r^cmuzzz4<||M z`g6K2?NtwTUs~VptCAAA+Zp?8o!J_*DOI}GC#76o6nyYY(y5#;mp&e_b8T*;Tjl{1 zeKc`e<)I3$c<^6N*(zawF5(R1HXWP_zL; zh-vJF$>q_b33<#rCtrVW_>v)&#~V+TEf7$xQYi{;?bHNYj(4gTH8PsjK!>_+yx+@g zy{s&5XoW-BLlJI&r;ceNPZ7=etdb9|hlX;}(pl-B|EtMy; z@~ljbrIyCF zV}74yR{APYp>I%Q3*O*x!Lv}l=JzKF`_`xM3apnmzVMdjdUZ~R}|2Xrt;ky^DrqYkRJgc2w z!m06uq)kkm*Ils)D#MlyIqMmIZPbQ3eu54~# z-0l&5-%zs|h|4t{Yu$rTs#GB+Zn`3~Ws+IVA*Agysvm4`+kc!6FFPI;BwET(Q|)Ry#W`1VU>y8ngetFYM2WvfBAEJF!&M zaRuMGaMgAzX+Qnztvv*Mn9y4c|PA=gdznM=EOzzy#Y zH-u$}a%_v$HElMgDWu-`(z!j$Ke-zWsiweCzItWy!T8I*?BC~nO*f2&SPXg+v)8IO zuHXgMm!}kZ%QhKXTCQN1b3W>v<&t|Du~px8cKu=#_pHYqMf1@gjSyPOeqo(@Se9Eg zw#nc$*lj^c_y+o%<&U6+?vLT|BV|2z;!gP&-;a!Y-8kTrb29koboyB09K7+q_F{oe z>=#-M6kz#6T#(aWbVKtpjp z!3*ZvHnZmmg7cd}_a2_=*dz{Iyn+aJe^R(@t2QFo7ZH39ewqp09$L$jjES&QYWqb|KZcc>Gts4tcGf`O|V8wGM@>uJ#L%Sicmmh5k z6Aaa-{1_J_y=`@cqB}Ke24P|5OO{rKhVYLkqzB>2se%Bh3D2#O#-%m9Qr)L=*g>v4h_iP%Ch^y7*v`4QpyI9_&HMY7R}bX#4r%u0 zl~1l%r?0)%Qv8DZ_+&BLJu=Ak&NtQdCCB=Z_1ci@fR zXS8gLFA`49W=0<@8cWsrvylBGpqh6FTj?7T9qr-aSx@Xdm=i{`bDaHv)vSM>}8dJx(hvSXUHD zy_;kC$)}KFptpLd;=NN!CX}Kll|5U9&iWY5`GZriE%@4t&n{S|YnWQ4l}~X7e7_QB z^abqKSEUM!k}rEn*(9z(2d>>Mb@}}ISKsGn<_~MOrx&z`3ozMBj~YJ8jSHFV{hn<1^W`L#&kldZ%6X&H$KA;cT1F-jAfsa zj^*9)1;)Rg6jeF7d4D14*W0siZo2Bcmb#vLLR&=ETq^HxTdVo z{G{X8k0qWKJ%0d7)S>dgD6RH@$1V!6A*Bk6v(ah z`erAd_eT2j7<|)b&1D;rDDu7KjzqVvMBYPWk_nReK*o6z-TD)*@(p%mML_HwZ0j1T zz~uw4US?Z%DM|hqTzPX_{g}HpQuhF|J1s762nCcvx%-uEJKb_x-d24Nhv(($2FlF% zVmh}m+eu;~eaE`fk>zKllqaR;?n;%@aPIlI>%K5L6YqWz-O-6>zQS}T5a`)hcOxm= zF{$qNvb1}$+gno1Cn#MzbouZti%9_;c{r;HoiwvSNo)qb1h~HzgXSob<&*xB zXZA}~=qjkc!BI5ONxx9#e>yCl$hkj~o0rG|CZm7>Qp7o#ulwZTYqIm2`w$0|d-S4P z@}y&Mi1!V+(Es1^mmr{(lj1Esjn{sm?p0xUa39@1+`~jMSL1fPNub`{qgg@PHAd^6 zCQTW~jS`HaJX=v#B??jAOZ=7X=YpYkR+d`Fx#x(v$<)-Fu7)}}?YP93~58=VqxQ2Ae4BRT*v5q!e4YnEP+ldLP2W~ERzHZ0R7 zlxZsm8(Xy`ie;L~mf0yH;6fSZ`{?OIJ>t@(mI^6iN3;CpitOYOVylDxX8qR66%#pM z)16v8)XFzm3 z7`@wb?2^O#-s4g&yy1AMN!XhS1cWIYnU@nYm{2=PoJ}1q?>H_s2ItcnW96UD?i*bv z*4K{e*3%S5m&NiFOqX#vTPBVAvGN6>fE<|umt!gWL^% z1%rXj3WWl0RZ90gYpRtCmlGLkpsYUDe%C>Er$(^=w@(|~B(`+0JQpA6R~^w78-R%@ z4EC=cxtkG!Q`W7DBt(|C#}d{%^r3a@oSwL$_2pEigf5AQs-25rC*Yz?7VCX4d_Ehp zfPv;x7OG{EFZmvLT^#YE(lb-OQ0v!iqPT!gmb_EX(u}<5veX$l(HUyyikL(}p}JwH zSDRA7vhy<&@tc)TRf4n98r6!*Shvm=H-3g&7O$u-8kH%M47@QBoRlCes!GDYdTk;# zxF*u1A`-uz&WIUO9yO2iR-WrQen}UUX`E%}9OeJKIp#q;P9>C9yI$LQ{L(x8%*lpp zFN;oJiK+u-Dmct+T~Kof-9Jn$xOwow`jOJtqeRh0NXL7VO7F$L2DII3(+V#p)+-lg zd5)_;&f+ga4-1UnvML^2=+ru|&@E&Q59bB=@%;Q#gDwg)WBsZ}CgOvZTS(Or_(=cD zu~H(>yy7jl_VO-=TeWyzs1QkvwHF{eV0@jqLuIHIA;&LS6fM7bqxoQY z>wykZq*bQhf>j)sHdNb zkhiw)89QWTJNr;3Iuk_l(-h_oV+j&se4>G|N}l?kEoP&2j;|H|I5B71ct^=hNkW$O(D3Y?i?s#ftlsgt$d0fkY1w z(R%+>(+8$NYXDf^Nn4o9#tQt1kpwQ8i!xCvxrw(z!yq9LV1_m>a09bEcLWTN&KBMn z#D&R)m$U+Mo91tXH>D&XpE^#6ETjvBH9NSuE9!Il&Qr6tPU`N-SZ(CO@bJGF$4yK%1A@)34y5@s-4~BKyClSJMp{-m)l%HP=jwD`xx5h!E+=c}>5LNuSvd#+ zFV~`SvSt{JjcdY%g4eNjHrzf;C}$8%Nz@kp!NytP!X8K1kB>f0Q?*JtUdidpU)Vas zNI^Lin!*%pXJ25$nvcCasu}TVq_$^pE22ub%r4jO6Ec*290d2ZSM*xPulP&{ywXz? zc6%wsqlN^0JRT9((AQJ_=FQQe;rT2J=W4`s&oH^<4(8W{kt-rt`bDK?r?E{mPj%5y z;g#OyJ}(f>OTAU_JK9Qc8A7W!%4Y%;gjh^;<9I+fG2?fh;tqED&xzvl*!{U#!q}{x zkP`glHCyt0>D)GK!T;~WE05%1a!x13NorxXRAYE+V30(WZtm77e#nm;4DD4m$l0kJ z_Har%@7`h{+;p{>2WSPuhH=?|a42onve4+c z+K5F@sQgC2+jn1cROI?YEm!@Dx?MDI9+&Ee0A$w)ul$J#lPkkNee`UtiGUU;XV6oO zr`xV2YVqtzK!Qum>w_&L+(kk_iCZaWQ1hA@6_fSQ2khisEW9yBAe;+`;gJ=E#S%e# zFM?z4>$cqb@eE@o85$RKzI)epEcNS+lUgq_GM4enV()a;%`Hw}n29~&G;#2aic#WA zY|m+sKXt!waX&_V)(^}q(a)nlml`Tahs*o9gO~wX!mR_?Q~eSpM7n;SDyIQX_Xo{+ zX{55oz`bA*- zyuy?*cUIKW;MjTHIFr#UZQp{so@c!E(zczQ-3^{DDfx1I<}7;}n~jRf_;U7RwvHP} zAph;}5byVYyN(mi>bL!V+wfGDwi4H6v6Dlkr^hpIIymbtH^}8`3#bjds1zvPwy4Q? zh5vG|2zs}b!KX-rw4i_ZJtJvstS~RW(L*Q9WUGo2os^fUoxMO==#LS1xyvw8# zypW<*))^LUSn4B4iaDsn8I~FiE>ru#B4PxXPuEH62u|Y;rGr3u4~g3$ylC-${kHi?T}sHCbk2 z!N=@k#YG+*8=8QSTR>9}K#V@9+y@Ev9*iadr^q3Qih48*oTi0zUqzF=Fhnw%5{IRn zsb}Wlhy^&-5AOcuEzU&%tr}0O!#`G)3Jqsfim`-Dt#wtEW>r@wA@91(M5r6ST(6$xw>t<*O1Jg&q$BZcrZfY?@0URb3VVwvH%C%%@+qyzJb zIPRnvfGliaqo{w<$TQujs~%)K4T3RJH-a-PiqgqvjGM9yuchR!bo5(I)wI!r*^Nti zZy?lpD0^K}ZVyJmAczF`j3EN3ron&CJX;I(84M-*JTV0a%r>ZW`e$+nr*DnZRwNKx zlHKnsclWb+#tA*#Bw=yuXqCE9X z<|d(`)=%9;Xr%~hqA*e1K z{`n&JfOy%G-|4h&;&P6Z>W6S4Ez4o8a3v2uJ@|xlP>*Ed$~@qP)Iy&GdtQJm5$kYm z%Ha+;8PZcMtrNqs!0>WCD5_!>NuDQ76c22itCli4X@ytZJVkFc8<{a~sxeh+30zxr zaP5eDlohPF1+rSYXVI|ns+J7JB#37eOsc~J z{iI!?al3QJ4WAuHseeDPGfa9eply+n0rxnG}lBt|RQVa>?vu-3hI(0hX`v8{LeSAXMJHjjM+ixr-91S#Fcl>_(#p|=X zFHV<1GkwJqXX`+!6)vI~zZGJVK9(L)oKJ14Gg}`rF1clD12|3|hT6@-nX+(_v>r{_ zhoT$Fw0OgGK~u;S6_`FVr~X;TTgQWkU_Y|&oQi=)Caa-Xit1LHwyVs>UB6HQu-Wp& z?h_kFFN$jh#Vq4a_i5kvnV1)6B$II$q~b69mNC9iNSR0QGPe41i;V7PUVNW;=hJ&e zqj}DTc>XbUBV0gvugm^2lk*RgT%L+Qx(i`&Xf!a5qa;KzKmzqM_?=6%E9C^BX`zX{ zOcJjG4=m39@D)3`4LD&(S6c(p>IuaH)LaisX6GuinE-4IbBhs~P1z+~t01*_QM?3! zFC(iT5Xk3Fle|y&T|ExWBz#H6r5J|2l#L)ZoyJ2#xdjDR+xI@T*8-|*0rkP$#xsBpFnd9QBLe5C>(MljG;6y6u~@D%5g%Ouu-OpNS-5_6ueDI zm!xIkcy)O7T^z0CGPA*%9+!0Diy{GjI7f#-xlAw>XK?E975~VQs?J^eTL*TWkrqvG zazVc&wGk8vb(-x=4{3LF*DoN;OqfM5_zujqI;Lf1$nc_EZ#KTdI!){`Q|h5qc6FnB zIF(nipXB7I2nV4o2h}Pk8hm`%!vvLMt57bMQ=z6&ZLd`BkhAP>Q01bG0x4zj3Z5pL z6W#51lHCojxV1ekLJQ3Ht1K6aqzb?UIx$aOu9d)x-qxzt8 z^`q$C#q)%-YL`MHpS^UdK1wXN3%`jk`2|(0b!ByAOH4G<4n37?igmn*cKO#7lkjo( zeioZ^GBn7gKCDUl-MC4Q!^w|W;fs6^Q>RO7uBH=y_jdlTDQ3RBpghY#4`#OjYt-o> z;z}G8#|jm{I%o&FDOXMATHcKtKW-vZrfMDPtXx~ub^+vH|IE6@N8<(fWIr#1Lw2h< z@>~#3pK3K&yoEVNYMgFccmCbfxC*y+ zLy5HJ*gk;#GMuV`?1w~li@JA7)~)wi#@3CtwZ`_ub!ekUihmt?+^cCFf$wcqIHSNA zv?x&&w=c*c7C8Imy<%AE)TxLMMpvtdFEdyg#Z7Yarll)9Am-P<{Nh*u1sKa>Axo zvi)fwDOY-**kB^wBWX5e`hfG3I7K4~T@A|om<)tAGvN(s{^sHtksIWT629;6|Ey{N zc@*zn5)*VwY@tf&#j;z)f?SVS5%D%OFiFWF^bB3C$g-wI6Fj7|Y!0&~idmPhGBsSQ z*4!OGpiV&K+)-;Wp+Q(1P8#Fl2q!M!qHlUZ7`~;pGqz5-yd|omKZkZq zOT%jJ+@(iw_RDuRT1>i!*;hGqw-dW;gS4)%;w5a#S9PWj4a?{XS;2?9&6GoMKb7tM z_Wf-8;|39qk7!t}`D32c{_R;hvo|!YM#=wq;w4IL)rrsfw%NP3sd;spD!pGAp;R@kpUMYwzDV?N7GwlY9}Wc zjWv1roKReg+W7*!=le?Yno)RBI61I!>iWPB+C_8jr~xK*-fzaJ^a4R7{aO3E;5X|Uj6HtLrNqQL#}u~OIN?Gn>SWBt6NBWW7rA%{s3;yg3e z2>U`r&s_WG*N^%mvjKikpxpHl06lN(Jbl2oNF!8;RNyWlZ|zrr9L%zJpdD0F7*4Z> zo_H!ZEUT0YYeo-_`+}KOrujTRv7LAxkrnM&*ln#Q^y`!9U~X~|GgLE|)s4w|8VL66 z(-cm-fw5c&zD&x|El!);uiz93p%tvi9V@0ND2=vyOnFP*4U1I!X=y4Ba?dmF&u%Py zG6;0}z+6$lE5OL$?XzmHwu`+SFD#nH1a9Js`%#dEGzrnPX={KEsv;+1;%k@o)m0|n zkz&0MK0+kQ%|z;wMr;x2&Qy`!cfZfdMu&R%Li6 z4lT410M-rH5T@V3ln?uX#@4lLUY*Ly?bx*+EMVvDFUIA{ilD+_-qJ&e3gPoYDnE4? zB66IRXj4hCxy$;(7ONI~n$UgGbQpYqh?f{~Z})cj9Fe$+SpNOj+y^ra-Z)hPF?~rC zNYQFLY0{+B-teUEtm_8X4dgDTCXA;e=U(0rA66|DsxU@IT6hHj12u&iw{fKwsgj|> z@!~`c9L1d&3{J9ju9rzLnWHc+gHaLu+JwP-Bc~RU1099C)OZ zHd52;met`$Zu2=jLvGSh{*yHphF7jgEpczBMQm)aF_x{aw~rp=Gm#oM1uV{$iN_i! z)tmBtI(41YN1|EiP?lE_FHEK@PNiWi^$hsCj8LMCVDP3q?HaQr zy?#VcO@kz8g8E6UasiU9WCe>gAkObBo^tqe*|O6S*J4fi*#PJ?FNt9w0${@g|I~_r z3kx(m&uRu7Is8hrs#1XN0s`tbcZfH&6uhYm0Ncq3KPkdx>%@%<04&6aNZj!3MZ}<; zzc@))j&v7jVy8>zxF+E`_0kE83`V2Ma}r161Y>=Ut@KOS)-iZCXiA>eJ+2oDVBaa8zxq?W6t`46*V=Q?OinpoSm&S~7?tf2`jb)e9UvhB@U|^O0OqQF@ltx7D zXx}xQp0wK#PJT$xw&BU;;^&`V|CZ`J`DQ@h3h>X|3z%xcA1KFIC4@_e<|fiFBL>qv z?EfB6irLFWXSJBzCB|vxZp}&$@%^SQ3?uT=g$lpIEcMxD}OymLFG(&f(83E zN1i2PLbtBm`ID+WGT#3V{Du>WRoAf@tl4qsKDJBv+cOQDmG#ovN zt+=vy+L!aghN>zlMBE;x%6)8Tx>53$_}8EOxxEnA_o1bWNP!z}>HSfJ=FENW#+{~) z{MYC^ck_0RSRQOp&N@g}PT{vP9UiJ-CoU`o73M?O?_ z7QAnj-cWPw5`OcIne}ZQH?;nyb8F0AAI zpDAX`OAH(!1d9=2QG|6Igl3cR932ehy66^7Oea^2R3Z(_7t@j*9kfViRDk4K#Hm+7 z`{PFdjUYz*m|Z_aZWLniTte;-oQ8sRJ^h;o`kRS>ZOy`oQc#NGD#rks-JzoT2CKNB z($rtI{s%|P6)beyh?{voH2h86QB~wml1tr#!MxxP@*A#h>yN!&g5?^3#>hlVZ8tk1 zR>Hr`BA-N~4AV$w!i`x+L z6qNi4Mw0*6@v{pG`6BW+3ADn*tZ<~^?`DuB^Nu5I^z|9cyRTT`rS)08^*K@agamyJ zM*EVuzM1-gZylnX6VM#~pf3Nx&EOp{!z_I9Lq|Cx>npxn8#(uFU(!z`-=PpFD=i%D zLpwuQi*|_aFa{Oz5*vp<1^<$(Z)Y@vr~}}Y5-4?9k|N*1H!mdB!Dk0yOaP210wapS zSWhqU6z`D?Q8X`fXbNUzHNJUL6{eF!$?V+t1xwt)9V6i0{igGZffA?S32v(Pa7j|m z28oKj`UXo&ml8Kvnx02euFCT2QRTWKfKh>-0Z7s>4ZRWJT7jXSKPZO%s2SxH&UAO zHVeba{J=CvQVa+dRBh6g{iG|#Ckl;k7C~sj#^fyI&GRNV8%+LUn(ALQJ<(+BnI(NQ z55^m>A@Ut9Sy6cvJaQeV0^{J=yi8_*hVsA+4wgJ4!4ZJ-tmBE+NTNJ~C<~|H;7k=<2vXr$yift?6@`6YJA zgRcz5Vyzec0>zz8x^Z&O%*GKi_sI$;2QU zzjMOwX}bN*Hir|}Z1wXUIG16hnQ&kQN^apI1pu)agaNBj)CH8XlG)7%;j}8r?Evvk zDUb&S#BK%)=79jcV-^p2~yYmEXt zMe@!-0mE3%At=cr+;bp`;;!Z7_NydzL_$@Z+i6U02UC0aW=V(z8^TzGZA>Ds%_Dir zO8HZN|FsLhxdUT@U=&FxMY{h2?nuzI5gO<`c4nW zE5Pzjswn?08RPOT(Ky&8#(HfXv*WE!Gei=bd`JuwWomXeA~H1^LKB(g<{>%E$mdZ} z=Oy>EH6y>p&+@fe_C(b4@@Py#WOZ%K?S`1ol98CENFqa$@>BL!9@h~R&Z918?b5kXW*^a7J9 z_(UE8He(8}lt(ZX5fr{+Zi)(H4a6)v)6%dv1Fk3xZdB9_stX(OsC-OOkO*<1=?4T!sgmS*h{Y&WZr9nU2M3Iw!;a1o0TARf`8b!>9%Wcdr`eyTYapx0i+Z2~mf*19rz>fjMSe52QjBn&?pi%0R%!9YDX84?q zA~Im7DkFW)W&oMdMrG+~XVWj{rhmUyaQjMLTBHQ?p*pSx!#fc0ydPpQizFo!RxmKM zOGOJcm|KhO9^XKEYs6!D%Zv_3Kp*(oB1CRUk|vGRoWIAkKpKgxGF6~VML7TELH>fe zzST1ZDliYBQcwe;yJlTJWy~|!R8w!ot&Ijw1VBCm z!wg09@@*)Gkjw*l*hG}k$x|m2(hL&vt1ASar~B-tKx$4D_?(KyoGjol@W4GJ@1 zxJ9eN@_O-{UJNJVHU)&0cDQ4fEJ+)r@#H0_3kXgQoI494%Hl{GQ0fLkRhGXMgZ7Rf zL1jY#PjQ|5LF`U2xU?9trX9;4B!|Ej^H7UT$$0bC1@r@;cCxKrnDLv5(j)hQstA2} zNDMnrbnT&4pY!$k_#4#;OG0iUI9%jr(AN{*dEdjjK^st{HRY7P^0%t}gOmKd8gVV? zo5?bD3HM)_!UqoDF+cL;;DOsr2H*0G<+1UUP83aLy>gD!LE>}Hb zQrbfU>9+DsVmlem1`Y0Q&yFZHPDHZ&HGGccPOfDGR2(F)jnvZQW^SM8Kv`EbqJ|{Oh{Z5`3$7&*QFNtB(eTyf}Y1@J^35 z?#Q~J$-pMfb{tu_x*TI7@2tY#NuXEm*5amIDIPva(WnGN7N@ z<-7m+v6F+FjqENRzp3glnVbi~+80qai=UGlvBGni6g6AKU^n*aL>!}9QvJEl1tk*? zPgI1!hm-xy-KzJyuKyC49)?4#M`}m;mKQxcVwGEU__m>yb==OO$K@hFFZ^HECG6{Zlgn_RBXDtRm6={M5IMT+~|@JMC3LYB_JXwcHGbI&-Zzbzj;ZTjA6q&=b-bH{O#Hfnoi@OXX@Da(@%h73ejlC8$cL z-ELMFilua_<1F|W3}JOxoISEdJJV@_^pfCSc1MYPTttpYaxQ7PrfP!-#Da)$JQiw& zD7j`pF0H3hd>V)EoH|y~ly!xo1X9YRiCJ+s`&BGa&)ZiB$HbmcA>;b}S2tL}-XeFC zd^}1wOccq&z-q(_DK-Uw6`GUpIVKFM3XM|1vx4`}8F7%Z6<0ehp?O4eU0I}Z;V`gsa( z2v|Fo$ze&WsHle+OZYkwehrDw!rPbB1FVia58uasrOJIi$gUXvafw=){YL%d=d^=( z4(IeR>Vzn43PceLudwn%m(IDnmlft*ciFoZ6I5BBdJPv6+(5nUd=u1|xAsHExoqQy zwP(Va^VJ3QYvNV{brBNRHKRS@^03CuglhePa1`(M=;uF2oS%P@P&oeLkkQWHt`d2X z8>1og$ZS>@vi1@!p9UC0ji1uWIxw%aj1?A6uId!5rB9M(OA)IEq|6>=e1(Ae$<;#7 z9BT?|_@7F%cVDvGK^P5t6kN=bIMr*V=TS9L95=-whN*ta!(I*MDi25X5qk1zahk{) zvaJB%`o6#9@WlhQt-f~*6fSW=bf7cDl;I^ae97fJEbk!CqCj*FNX>Q0*Hnw{6v->} z7GiK%nuak_idc~T0AwgDL*mUf3WM``?t+!bI9|oO4tA-l0xZ?l)XYN8nban$Bv*cz zzph|l!AGdUK%L4zC}!t=r}di7py0YPHB*kOtu0nHt@SZYfj!q6dqsJed{;y;EkU1X zawaW8Gc%cFojZF$*2>Q|#|<1#`jPoU==lkViD4#@)$d z(_O`FQdn_A+O9O_lG+AwSJYi3{f@UrgP2AqdROYsK=#k%wA&p~p9K1|5oXp-tz~sg zzxW{+nWy#Nr|K65>U?}1!?x*+O?+&IDLd#9}R-n+=jq0nPn#Rc82Ov zaYO`Ku56*TxOqFRwhdkE3NyTp-&VD06WJoT2}Y=KlbfdPC!4eZ>&(uxqaeyF zX~z$j>sW{EmLjg6rOZqkxFk{sRJb6S!pug(DUO0vAC+seU8hw$*;$W@tx5akasv+5 z#}RNgNh3RY)yR~g5UMiV!(qJe0bsw}dduaDBD;4qvfH1LXI(Eir}0-;cYOBW>MTWz z?)Fs8*(qkS7b|ED(?l(r*1pZ8#6UuPUgYZ=pVMQeIV60D--nx@N6t^5@6ZpK`f}@Y z@$<-|uhiMC&%|4|A|R|+<|r&iK21_iU1P;{k<_dBBD5M+li=vdP|5?6v-}6}7W{)f z_?`C(k5;#6;I?vYE3Z+p)k81A$)9Non)1S{bo$BK+4I@(cC>?pYfjD7*lsW z#;1eC)Y2?rrdNFC@(Rw%%eYefzTOt(@q=$5_RPg|$D&j}p_3<`cW3VxNW9L66XlwRE>(d#a-_VNQFCGQ4>2-wE1FRvw7wI$F*k=l z8FZW&xU_+EjqS||+ZI8x9IK1xFNNMMM&H)=89eK%l%`zckH2ku0zu-sA@>y8Y zK2ZQwtWcMuw28|Q6{Py#vP`Ip!g<-g5Nc+p>U~3)YuIKbS;oKBUG*RSXjU4g_ohh`oSrv);z;A?nk2SBTW zHF@c)|IG5mlczrX8GZQ8|KppBZ%^-UJul|EICL&4>Omt z&8h?`S&}wx;!<5uEpJpUy%kY7Yd0cQ5Hr1$cFVMqyqbK$cbGmX&Z85Oo13f7n-o)3 zcl~e6^1h-`e7jSNv(7wJZ!UK)$a*Qe_ZH5-7T|G{5R2m{%pB z9mS{;BU=KaZGo--|9zR$S3n2Pz6I}W7?9Zq4lD;N*9=m|W>=;mls6FODipH|!+b~3 zJWtyfqtaG0wz;2Jk%bv1a!Ggu3yq)(E@XW|Ffezi1Yvggv6=l75HInh8u8kl(mlV) zouSd4{lMUTb35)J^A$C$KiZ)iO-37jIBTBj=TPH?%mpJ@`aDP7;cP*1Ol&2f6EzzG zYQrKm<&e=1DAuB+izMPeiMKHR2iF*9+yyf^i?C-9t_28v9m?E+QGUYq3z2z725T)y zwiYUN_iXaI2vC^~TzzD>f}n+NGSr0`6w`VQbl|>dQXwY|t;xzg3zSfgDE_#*D3au( zMsyF-Y(5RJGgp^at9iD_aDUq>WuI(Qk7x;DV}5q8^xICx4$>DOU?T|EGBj}?mdGKuK!_wo z#AGUpj6ZpYC6I5BsI-K-ThmJwm8N12Pg}{e$Y|qQZpWjA^Rzfuj0~e2#khej zeyaLu4qIfYMtORKVI)m`pc*@=riIQ+9J&EE0Dpi(cFmy7eHdpT1R!s0WgkWcA{m%u zqS%d9t>!WQA_pKcAMO;yG$NXaV53r-1ksFGaX^&>v0B_ASJeLSU#;tJv-_Oxw{8kd z2zU4ut5XUX?rQ6}x2-Z!Ia>3(R=u3>9u?1>G$UjAK~*48vUnUwSmB#5g~DB-02nIT zNo3`ckT!#tK6PZi%^2>0Vk}9P5*0GDeF%l@X9j?WfnaBP|1 zUfN`6L;i{`&moRE<>xYjz_w#s>P=qAsR?C9@CH_Ud3Ecu9vp-=V6prkONm(;4-}BtNim3 zIhNY7RU_Fin8Q-1822B_gSkfT&dP%=E$LncjS{z5WXiym#r8Zw+UJurnmkbqe#m@8 z6gz3T=SYQ#L4T2J^lt4k9}YHGH(Mpg?L~Joxv5hp0o*WlE@*B?l5HejQe?_B9i@QP>U|c&e{4$&hM+`Di=jsethGd38`Oswe=bwIj$jx?hS<5x$ zvGf>i7z#1H#87sY})CIMkAE_f1G4^LmrsY4WJN|#Fr;+u7X5ftqhXrLB% z#x$k!nCBOZl;`I{=DwmBucX8jlDqSce9MJ=kB1W8Kq-eXH2n`i3@PxvYHv2t)xn?t zs>R%_g&U>{*=*D`DJ{}Q#lkrUfIr+p&e;p7*i_Vj`y(P0Ua){5g5YAaaM3L|@a1W$ zpd$-{;Dh7Uz6MQi!=3Hz8==t*fta*?*!LsJ*s}DRAqZPg4B*2zunbvgS0(E3v62d3 zB1IU9@P`tr-m*mx6-oe!DDR_1-pY{GAt9`8vAP(X;am7prTB+X;SUEfacsHxiIQuf zVyN~bFAdYZ!ji8=R6M*~@gp3v@-l;*3Z-np$hp7QoA4qNLF50rKy+j$tBkBxnMzEf z@{wCfM=$2oB~QgWCP=~Mz7B=$#^hW_ZYh2t3S9+o!4f~gUar7eMqtdZu=SVT-?n9l z$lhcvBS#ZCCwNiN_l2p`+26%rD}P~(Tu{-D6xKAk%a!lBVU5d|-H8`)$Wa+lX9RMnWY1bb!}^~@hB*i$&3;G}O{o%kJgx?Yy> ztr0o`qyL7{eFbRQK>1Upj)^SxR&+D zVrl!f`-nf#?_NZ`13)~7>xVasd*Qw{8aB#Z*t~H3i1+enHQ_7FXRey-55wq5^E3Tw z0I!)^O%V~-VU6q^?1s;+JNABNNKA|Miixp@P>gff9?wuyy@6jySRiCGWoafjZ4VxZ;U1Rr94u-^6VY{czurfno?}zW+%Z)!V z;PW}@p9hSjbSdgt)IS@eeV0dnTtKqUO23;Ky_D3(&7COWm_B%ZZ1bGy5$7=-Wex#l zU5B&{90=37;n*lE@P#|irk1QwDk!#d&Go+5-LmjB^D>7zvI0r~7_JG;Ak~n^WPj}c zylO_rhIZPiLDS6{p*AFL;NK*|5$BM>AG==@nq|V@#jy?#JWY?x+K_N^Qbjg(#do~2 z4dL15uH;bEitW_ZV@I5GU9M}UZpNz5Fa5tkx)`rMPLM9+#G}_vCJjPIDh63lYgEzf z-eq$P*=ER930`1RkC?;PdCLkr-w_c!AAZe3#`7L}(z5BHuabvS=>&c!OcrYC{Y-KF z0+!F*bg^KJBve)nUnF z7E87h$8HNP1>00woga#;dY3E0`#+Dw06bStV4$#B>MRRBg%YwT3modD)qq8KQeigN z&yQHy*{{cp1U`2XjAUoOn@$Wqn*N-hlDTD=Ut2YG2%R_&wnT(-V4vdkBpIusK$63s zLg)8?kHK=50zFF8wv#C?|9K?lN>52!KNHTAvwc>2Pt7u~Hgi>9d$9LXTVbkJVT}Tm zD(_I7+NS4JQZMe{QW}J?@(^(qh(1vv_wtM%IxWn}S(Y@e%z2;P%(X2h1=ci8PRxEa zwwvb`K1NXAIy8ASqp@pDpAp;E%D1?^bqJIF0nWey@2F9WhG=<;-T||?*!wDT+2SG_ znkEj&7a#syT@i!wCaqMeLsHmcP9kY52;TnB(6990gu+1SX-kCCz^rWv|1}MV5>>3Z zQ>mq`gRiLN+y(!N5kNf0o8dyrLj}$ql`q!Q#9J$7t$gphd&u=E6&CPANU?En<#T<3 z9dqAkd#$`~`jt=Ojxh{6hs-E?@1pt(Q5RuHEF>YgjNhEQ{p(_RP_idGisO-ZxOb!F z!=sOq(_bEcys@`w_$qv}A{XgYG8Bwk zfP%?p`RA$t7eSVH!Z~Wgf_v|O$;@W?%pmW{SS($J zt^R-XRvu!N`?yjKxSij86KiLvkNuF@k%d*nU*j=G9-VT?2F9v*FqxvO>!pH2*^0K< z2dL)wA6Z2@@-jwy3PG7$4Zvo9`w5C^2+64IOt*|z(w0L=slmMtgNMk$oP;7j8wuJN zCVT#=GyD57i|E;7>qFN{T%(+9Uz#dx02!sO`L1@}jtV)f8hF;$jm)zJ!l=0eubd`* z0hX@Fv*-$xd7`Uq8%Kk<--jNU^1jQqQNn0f2jAUy%wik0=KFkruWJx=n4gZb4PRg1 zKUJTR)^iON08e?BYM8-qA&@GTqXrY}%KTCqH@-8&Eqh(CB1=u_-O!I)ZhKdpU+TD} z3bz{KbV%}_-r!0S^2}&w{ROdR@*=i}$UO(QY*ygL@Kzt1*6b-b6ee2KT7w$$a>_a| ziy>9{Iz#I$__g{jSPX9Y^4(5_7w$^)6rP3#io}?^O%Z1VK1Wz$k zS{BKiL(UBeCc?^0;zanCG}*&mU7jtA&VwtDh60u4lQo4UEXZ=esZbH_X1A#rf_+g> zy$J2L=L@u_x_Dx%$Uv)S_Hd|D8ekZ?mvuvch{@jl!mGqPbYh{7WIm}m#p$vXn zDka4=RXOya!E&H`@KHJaz|A-ebyseC;pygVRt2N!7B&grIKcI4=t+WA&4;4PTjwj)~ITEi{}fm;X0)= zEc4U7-o(AB)$0(}NSruxBmbjEIh49g;EUnHrrLi-CVK>Cc6Q3I=97WZ#@Se!nE3!c8 zmK=#*;EaDo>h>`I#q;5fLv-PR{IyBL3b8l{gQ=Ikd$mA8BkHk!ouN5lZ!VRHLeZRl zl(?>384^2F9W?zHoa2_5Lg!i}a2W+%Z_#IR*>u_8DOjH>mm`hzo6+3uvvQqJ58L+l z&~8cyGCaQoV>*v>@A^^?{8aXQjE4py?V10CVy797udF>!@uf^AI2$KEMqPuuJyytH zcyoR90V8_Mt#2+>^N!{AKispPV=}tFfpDMRe^=mz3V3GKV()SjpA)R5X(%dhy3#&m z2ST|BaimRUtut2u zT-^Vr?)Qtx>oVV#0A#(R9|tt2eXnBsOs1ag|7Tfu>e}LowM^LGZ?C)RZlZ2}y(_@b zx~uXTvp?h-R8*vAKP;gm*OTgX-X&1=&Fxln&6eNfkBm(9)YAc-w=?zx6pv(D*qi3* zwCJX%UvMMGK>pzML4WfljclT~sC~)tClqEsp3;Ps2YU>Zz7xvBT5sNrjMMb*2OFr} z`(RqJ1N-}OIi)fDXT%kUEGA$;cQo*q?7!PN|F#_aSA*-r$;K~#uZ|wBzfI|KSaNa= z$d`=5)W(9{MSi2yCwK*AHXc%FX4y?(q>;ID6 z`D%jJ4;;bF1a7J6a7LqfhlH=EgP%oS`DS(7l)C{AnivP5cWTRZngrntZpdCw7K2~> zD4j{X!UgNyeHy+OwNOGR{OyE3vNv7AwY7IgTR@}IcMl=(@ws4h^^yI1{QEV#1*`kn zO(-A@wUHKVj`5(TgvWotD`?BGz{S#9-@I33M(2#nMBLoMmogMv|l9l)qs$ z&75$Osa1Kj)%EbTXC`&iji=NhGglx_b&-U($ozCSbMV=^7TD^hDg!RxG}N)0uax+j zFZ2fAOifqe4Z)~Me)ZqK9D{eGy98op3>ua7K?5S@^_&=6H0`8y5CHTCd@a^-C< zyMPG?P_@a^XRq-K-z*HE5IhNX9+_V_nmX$X%1&l*VUzO}h|ouN+;GZo2mvFtpCFDo zDH7qivY)=&dLOs{SkSrWXKN=L5&CmxSay0wNTdDNhX{PTw$Qk)*iOs-fYwyob-lGf z;ZLC=YB%K>o-Y*|p;)g$hEEII=O1A9wbdodQ^4T{EqL-LSXNJjAnnnZ!K;xLzmRy} z@Kk6HUOO@YdOut{nhb@g`Zi}O(-KwfztVAIp&z`SQ0B2U#Y)&RoGK3*sUlF&UxL*5 zg9ww2-TjUH%&$H!>-v?A;>D}~-Yo%$%`%&cN{9KMc>?T2v?5do0d0pIep65N)dvQk=h`+yRPBvT0R^EFGg2>Y`xE~77HybOyNB z{w%rA?{dQU^FWDq-ACo-f4MCsxGA4xLuZJ-3jaw_{Fc|#lwpMw*ic_}ZPQUK zuc6yDLq;j@JiTeQy`*I7mZlFo$7^YTB+IfBPI3_bj2B7re>m|Rn2@g)NiC{s~iG2A|&&;Xun-p@$1~4?`D$< zH%c1UeAJ>>xxi_2L>?Shj^NIRFZf#S+_h5Dw+cJ3>Wu7iKJ>Jt$t_&!$^EFHb5_`~ z@)`;!ylRs(9AI0cik#fJyQvx=nB6G{fQQo>aB$#=cRg$}-9jEN*J20zOdm*ReF`WzH z!F4;x>ngZLU5{lG`a8!F`ib)GzxD@WDcKYgl|dEWfpGgHKz{Q#IWo_1J!dCH z_2lS`b&VU&zhh^zLLes071esTqHFyF@!a_CoXNPd5RlT^t@g!UXL(wU< z(lS^>L5<(2uGtq^oublj{5+vg@U(q*W;Afpq08&pMD*O@SldpN->InrtMU}tSwG6_ z;5EH-BCo()(qVdNaf(-#C^lAKF7}*K?CpfW!soQF&8RUSmU1DID06~Diy!ke8+f*} znt29Stc(CAZ*<;Zerg}@OQC$B)OVWlMUR!l1b4?drcON8JFUv~#?Ntf>;ceG;IRrc z3CMiazOp0qKs1b4%6s-k*tcdTXUbI+R@T+JX;bf}STerw%vOeDxG(0|RSa>3cKD*P z?*mrqh1NLuR-K&)|5d_~nRkQZD|=^+rGxphVwOH#GI6MX=XPhBYGQa3YR!{1wH{Y1 z4L#&36W%a4kjzJkZ%@6qZ<5Y0m959Dp4Ph&Dbm4M>W9YLA{5iTnrHuwcolWrxc)y) zq5tXQk2b#kx%x8!C{zN%n?H%;xVP1KGm7!E#mu%LntpsDYh*6&Nqgnm;W}S3?d;#vpw9Gq+ zpwhCltzPIP*7zfJr<3x4@}gUg)*K@ry^qbdjdn$4jRRyfxx*d;TMTbkns#D`(5 ziq^xx7R+_ApU3KJC+$q~*9Jh`oUtRZhk=YqCP}fV|B||fhdYC3Z}U0*diUuZ=}+bI_lHU^&-zG3TY{wk;Ux;HE8Ll%6}*T}WW6Y6Tq?*&H}<6S1_;^o#_uSswyZf47BBk-DFVqu*M!5qFW z4kE$Jb9eqF0&%V;Fh}#2C3y&~vtnjKIE2(2kX`SbcNlN&q^~v+Xf&Uarp$N{9}&B1 zy?1kU^;zkSOhX|dbDULCL#Vs6L{Gk4)@o%7&I>i#t(&)2xo2ANUq-1gz);4qWJ8SY z=^;{YYnN1tKhT)QF5bHr`E>C-Ke5m=3lQaFE6z=X)vmdkG(hYEV2a~qvPayV3SV0G zPnJ?X1Zg?1ld8LKXOoA+kmJ!FkG+-ej+>o=T>u|%r@8qIN;?4=~P|EAnO zPs$8f+pPVnqmaP^DQA)41N=Af(N9tZV2@8}E_NxUasU&Dj#k}fVrRe)ZgZb&o9C(v z15MkBhbj9%O}jMO{hJJ1QXNJ0MR*#U^KO1vz5iA=`lTdA1$J=_BJ0KBCM+mV-@5$c z)YJ;86j5J=tlpN0F9wIngitr{0}l4ZSR4PxT>RnK62JYa`^Nt9_tyRARIebSjjM2^ zi=Nz6`x`j+kif_M`95Vo5ytv6&yT)>O~qVJOfNOfX#L(}o71W*cg8`)gOG2L6A|&j z;eM&r!hUI7aKu)jE@+nHk0%F1h*i4jlxGq`0#x$;Mr(RKy3ggn8VU&dP{hXItrC<@ zI~U9AcWan%j*{6fPRf>E_e55QJW+GO9LfG;;bD$<5%=(q+#E>}I9r9NffI^b|8kVw z{PCdNWBg(}7$6u5TjeKYjH&ln^jwg8pIRXu+w2Opby0P^>||;_6y=Y43jUC`3mO@F z$rCqu9@`+<$9$>4Lk=s&cIuXkR`xs9XevQw<8NW{_!p8YW*FFFL zY>At|e$+gO=HQ*Dz6Ng>S-n?bSY&2F@{3H;>^#Ot|HU;<5=gA8E9Lv;2enJuFgSN6ZwUxNr915 zin+DC(Go_v>?#R_(t*9+PtA;}Zq#5qC!=I)z0HfQO_W>yw19V2-jQ%i4tSmNV9t-8Xj+ zowHMY3sRA&wP+gf@`X@$EwfrvnW&|^(gC}Q)$M4fApuG~71)*6UXJn3!3L{Z(oQzk zP;B{=1Tm??39zECJiTIXWatjS#5+4LGp<)vTP&jsEu*Wu4{c(+kb2gxJ}%C&%q11$ znNsqPH;d=La&*&SV8+e$^vqMoQXJGHd2KCPK$??5!0`GrZ{a9EgsNWO(HWF|tEwL4i<1B25Ch`T@!3Ani zKI`lPoy|keE#FhHxCg>JxC4dhEv=xVB?;WIaGV-327+K;WaRaY= z{!I0i#j`Bq>m4A)41xify(k|`xF+H8GY716#wtV4Wy0H8ImoZsFfG$XYQRk`=+fhg zQH|;;@dD+IsoHBD@*ZfEqGz_-8~N0pA_PvxM2+T-*{p5JW9*qcUHMr}add!lLk;Ld zUkvQ#qfa`L4KvQ5r5kgxP}K5;SN~k|wy=qb)X3}XJC_zO{b9DZus=EMit&(})`@J1%o=)`jxy?t$>> z7kjg(q343G)aX!Z8!wJjckGb+%31h@69Fv=y`t$Wjdw{)RVZ3y*!o!I(NFgzq)bld z$GkBe{lPT~TsxAN2EdLPT66xVUM!1!L&kGwNCAJ}s)#u_`E;04gQ7Xu@qhk~RkOS^ z({Vw_(Kv`ZWj{HDCSewmVhx}-C(Fypp1Zwq*tnqc<;3h~+t-a3jxT(6S9UyqrMoo# zC69w%_2-3E*-BC))z_Q7*6j4n#YoqnV@-50r~lmCuvPlMN&Eh=frOJ_*{=x-0&gTo z65fIo`py*{Bp)08E_`*pPhUT%Ce=}ZTc;x+z(glz)tIkGAWhBj?zRtL+?2I{SWKgx>F? zmx3fDpPb&i{Pyv;f4LM8|3=feV?4eev)=xdeDTYbw{sh2?^gf3P4t{+IBojxa0M?p zMPb_Ax2-a`c>W&4BJo&w0e&yE#!ELjeP@V53W&Yg&|H*BYEe>m%ieMsw>vR&odJt~ zSThvfm@qmpbjndDl51HIBO#qcK(WnGGfg#(T%yzmpw?5M30=_s8NTTAd_-5^e!RcN zMgHha)NB_2{sqLnYRG;OAH&lgheQRH!&$<<;Qbj;I$SU}-l^OtHT@W9&ur~$Jl9VH zo@Av45P4-!c?7_d28HZQM83OF>YRVd=2FC9!rc$%5c;gOb!9q?o?y|Gq3G(bgm$C9 z5hQyG!+lWfrjPPJMa@6e5<8G>YRCi4|2L!5riYp{71p#Aj?oZq@Hb^E6Buw#Rt4T@ z)yWnN$=pp$YcXqWLNIW$sUT{S#BB}vv|JnyDit0tpEwU z!(W{BDxC7_{GAPO_B7noG=K?(gWB%&i<{n968w zapaxCL<3}!JYs0UY|h6=$O*3Cn#bUGl-)%jj*ANTV_7n4RKeG>m#j{??(y9=XcGRW zW|S^elfE+2OBqY2OoL~CaJmIB33o{{<^#zj3Af^%<_k)Hs~MPiv*%5djji z0rEPWb8&FtO-tn0b;8%lp46=qatJzvYZ~0X!ok16*<9ysD3MCKlDXmcnA70Q$Wnz0 zi7i_CQ!9PSqRWI}AMqdIIR}ZYQkFJWOIuDW4u@hjYJJw!*4EXIGt}-d)&6X(HBZ!u zv(@R)5)R5o28(Idr6VjG1(R)v070|CPl&oj9FQMZi}6vEf|)0V=cez5cZr2ufLM*3 zCrqV3+v+6@fmIsS`2*8qkBe{XO1Qo?$}%((_*rUecyue0q1+px&?(Juz%ZO7`Aa?K zpPR|AO1ibmP9#A@vJE_o>8E`e zP?H*-{tzCLpCMFrk2wV1FG~BpaPH8-NLyaPE=AB3qp@+xTm?oi}X{8Mai0 zoz5M*&U>~odnmT5b`ADeaUU`!PM;`BAWAs~ayePxjsc$`R9s<)y(Pt9`T&^11S5^WltI#`4fSFRypB8g*&DE~IO+2Zb)+#KGfCQtI%T!+gI43# z*eT6&Kb_RhtCQibOOh(hu{pf&{>8;kr_n>#+XGpNVjCd`B{Es^W)|zRgLs7dQ_;Fb zv$+JbH6mJ#zlv91V&s+UjtD#kt{-?t{Btk3bdB)r0%#V|YRY5~N zs3fgEmOP56ZBJE~#bQOMGWb%2GJX-)yoqN3w{e~@Ipr3Y)HL`?+9W+==CX^s5m=-T zOyDGjhMk`?{IsjUhkwBYmO9~y`k6YR?g&e*4a3f(z8$yBw?fZ143S6rg}5Z7Kkocx zJn--O=+82xpc!`0|Agz+*i-E^Y-U%92Bz-}kRiQJRO#a$@$U2giWJf0WNl8U`Xtz{ zD5e>Nhx(M>FZ`e@B1?bbk~k#P?G8R04o15Ldj>gw0T_0Zh3Jp{xQ6}okwS+aq%P>g zgPshfWq-_4X40EYw!a9go7eM(|JPgL6-3+a#(3TDB6`jC3R)fqI8ypa^-9V)DE zF;d?i5_~7@_F|Ckbw~LQV&xP?ODxxNO(5~1Wvyt=@W5PPzrTiSt`$lkwC{v!c~08I zWkUZ+4YAx{S*7xZAe|c!KGgY!Fb#c)r$Nzite|t=`j$@i8()8~2MJ7w{>GjEm*Y_V z-{}|qmJ6vV&gli*&Y0j5{hpp~%yFm&a*7LDe4%&U@05mZa9=jM=`4@jCYAbV6j_>jK&t1{qoxP9KnbaCFhX!M{x+%T9LBj0&-ZjF4T1!16)Vn-*%X8r9U?tsF zBRcu#pbmUW=W2d3(O~Y@DzX1+J9uduQL(~f2pP^jG={Yygl(Q4*+7_+g@XVD7u})B zVB6x<7uN$B*)>O-vM=s6D1Y|pKdKzS#ni|R(3pMz;W1d!&f~FJHh{PjX4Tc} z@8NxJ%wAIMA1Lkp0k-UXFDj-P{TVq%3i)O1>;bkU47*zbY}|y05hhN;zyqyJyp3YK&>+DvOP1&dj%$tL4OLv@6fd z)UMY%*(EU5RF7(PtLNGoF}298s7_slK?evAB!9M#Pe$(HsiYf+UAhkQ?i+5-9+3{A zLl$lRqVCjYQkZlpuYieD$@+ngeQ{k9p2Kr7WNaUVvhg;>TDEW^Y;fhP@n%ZqN;jmq zH}anfG(-F0_BmnMoWpeR8z;!7=Bb218$+v~7m8l!XN^?Sa?S z5Et*cNjyvxl^c1UJa4~=uxEQ!d1v4H;%hgw@T=6f*PT~hXQ?ERt?m$gO;d)`BUi+! zXd8QYq!_UthPHrPd_Oed^dW5AkoTSl0YCzDh2Q6{c}V8YyLL%8FJA|(?vNnI-NbgstJyCYCBMsBe6O&R|(lgq*TjmzPYi~ z-vqwl+|6r`^g_r2myIaXojK%Sh5Bo!?v?&^$HW~!0cAy%alC4RL`&tQgtl89ulX$` zEqvy7wQiOU_@*d&2?B(rd0Jz|siFO%HQ!G{F+PWkJ82moli&hlskx5W8yU|WW|beR zejWW@d1)XDFR3Lp4$RCi!&w%YZ93pcE?C%s_Y@yDxjd^fQl3S>mR<&O6b>^YW0OtN zwMyS-OO2k0lWO7Z7!Y-C4^p)KeP@(=lo#E1BqAJSR&Lc_$;oW+QGQFMBnC@zC4Lh9 z+;f2;RkKL*fFU$}>x zQ9`9@l%DlsE2Mn7e0sqJk}-Io!jwX;`UZ1u=vBS76jH3K<$}G1GZxLR5FuZopRAk1W~=zDj|v)4N4l#0JKE z!B_(f&q^XP5Tg&mjKpTF;7vh<(Fu!6@f8p%=|(y4BuK}OvW$r)`KluFHLMns^`cJw zDua1+nq;WzXF!uQLNo+sZZbKRGslh_*)a_#)=Y;itS}w8yM8(Fa*nX6zl$Xv7nyrrQ=fF4DEuYw5khR&*E$3D&bV+EM8Z1(gyF=*$ z)kE!Hw3$W971hN4BQd+UdUa-dkM*oO-$k1GT_}QC5UmMfF2-@Qtavk8XC;h!&hXs#DR%a^ETFv~A=`}N^>?TA0aCK0 zazwuRx54aM!W(R#qu zcy#qCPza}LK6SRfCwXlr^dtHh@2ry3*$%n>U&$>(X+WViFh3=pA9IC|63p9N$!AXh zwJ9K&k)V}$G(|&({lAJi))GIjj)u}YH1fVM$yFyntHf6C%fX(;F7h59uFPQ~rbPw; z5ZpagSWY;E1y)0uTM*hyaO8@DQi2aIc>T_$p*!s5g@p7q?0GdJY5D0b0td~Y zyCgnMB?V`FK1w)n#fFzFlAkM9l&N(D(4|A|lxC)Pr79?&_D3ad(dun9_G_5){y{W# zj`1LXKor0yxCX1(yKcN$*uO8@0P|%&|8QQDYM8 zBoweBRDkZFT{q)pD$f9qarFo?tN9Zz^ARX~T=9HImqGLpenJ=@)1UX~I*6UlL)@2) z`z}d+#edgZS=;@63MQ(Kps^{dhp@3ov9iG##0W|P!f0?77#?v24&XxrxZ&`A=teGd zLJvByw5J(qH&Y(ZXWmAiP~@K?!|=Y`{XbL2@gE=INvlGUDVl++ZSG!z%%KUAFhqSm0XIIS|5 zzwG@-`fOecO$t1I5uV85g7;zZTeX2T(0J0-JMk5^mvSAOKhhF?g?+_pU!CRiSK-H6 zt!*}7THl%XBrDNP=r#P53!o3X^IWHGi+cGmXXh~qyooOIl;A(CYj)U2?(AOamHZ1t zf8eKEgeaJ93QTwO5i!v2$X3;hJsEulOqIoh1}o#Syk0b`?>xD-D?7C`GBmY8E_+km zY8Z06|FzAhV-YebnbW_V`&HH#TgktLxNqVek~XT&j$%uHmdH8yKp-hH@ZiNvtI%v#c5(|sb$3o#A z7p|~@Ygj=zSjBw)LN@(3{AeVllw@r`wO!1PS7#zVq zp`bwmAehIbFENNH^E`q}Z|WxPC}2TFWLo{Dj`s1x7ijb*?5kE!;;t8SC%CM559gy! z0?R+!AqVZ`lAbjdaHV9f?>mB98?HOSe|KmdcPWz)V&3MbT;ZqW@(ZnlF#aItf8^;t zz!itBZBE=bsy{K5mv}dV`s56|Uk1%|-QKJ|i+rC(C>~V4btuB6AI~M4z?orjbBgii z;)3Z!OyL+w83IZr49+Uz@rK~5M9vwvgT1tyU?M$j?N&7QCPq&ZJW}FP_bfz5;;B`f zZRMo)4RNrQ_rm@NMN=#{XiXrq@5E72q2bk+(LKi-=5ykU$KAVq6^m`~I9tUlI%2t2 z7MiWIKAPgWqdcGZcO$h$vPJ^rdtyGV^y$9=X2@|4yBzuC_Pf9T>jC+{8FqFwFxK0| zTnz!vVAO;fR5^+A5$T1Y?%d#?Nd!8K?;ds8=CrXi6f_^x%+|Ul{?w|LS~nY_b=@&I z#ie;9NK?#V^Mb&YD<|t*C+i2ED)yeNt1uF7GK}w9RzzjiUElg0ZX^!jlf@>?MiUhf z{B;0kr>6>>eIa z_QdeUc<+S6QfRsYWG-TO${jMbkKYgg z*#hTY`12U7N6Hf6=3=m8oWI6gHO#kYz#ELnCa&m1pvWMx16d#c>krR_PHW~?Rplsx z&%6#%1S4&_uwWYfwvty(z^XLgWb~;jek_wFp@y~blS;c>p8oARo&CzA%+i(|`Km(o zW8CX%71){Y^Ba3}gLoZTt3tf|)NGez{UHH%+5TCgoY#}Nowb~CP|Vc)f`EZBuQ#zXU0Vn5|Sj@8A*~v zsnx}{bA%+dDwW#Sdk(T{)vB%5wram;f1f{ox0{TUowqUwiW0JPz%MNd*<^c1S(yp`p z9R)6&C}0OzUWj*sm_s^7r`5xGXFY9xG3;Y(fAC#(kJZ77f!%g?I=j}i*Fl4SYH8!Y z{w0}TaMsdmyM1VBWx@Q)FiXkt=!A*7IZ=9ng=UooR?00E$xI9861=HbqBF&Ps~uVx zR??C@ml;P_v@Rgzs_C6;IkW6`-6}sU4!M8H4uqePY#TR35C&DhZsy=Bf{PcgxH7%X z2Dej9B`!YD@JV*Uyn_xSCj8XU-^9k zFz|IT3$4w)HrGm1W9#ieym~?wG@jWGbSxyGT+m8Wx^T9W&X-e{(Rt^^_1W8l+}f-j z=>zO+Y)I!A%Rat)Osv%3x?@v;^w|^gf}oY$COg{453Lmu(zr$K`Ad)W z3(57h_No>(G_i!>@nEZ^BATLY^|4lE@xmQ1GAwXDM8&VJxl_kIcBDF=+9Pr z9Z7Y1Z@0tv;wh{ck$j&WO+y=>pNibPv#k08^%YC;O?Rv?+!Rk~kD8b7TD}%)i^sQ* ziFH%)7XhY&wYguYGp8~;gO`;cR?@tPOgc0*^5az9{n=)^89SE~LeuZ_g z-*(r6t`jk5<^=8yC=m$@#?+GeKgREcAP<^3>A^^!u>a_eqrzDRj7F(JbkTbwlUM zs^(4qhA+=q@nS&jy_xBfuetpE)bmHx2Mhv?I1&pD1mg|G3M*VO8~EHsM*Qvb6}n@- z-XZ~uU%c?Q57efm%F~&Pb32ry?cVBIQr*qDxDD32 z-Y|9OaZ@N>olQ_-16ulAg%MEY5pPve3_60U24h|@H%&_?5adllG>aW@P_87D@HQ%OYu^Z_L836j~qu@|K76;_-m6VY}Nz+67fN zHT+mulznOSqRktwD!PIb44T~f5XB9OUlYY}jiP8Fs*?Vi^8H=!+t83`Z3@QYvcpR5 zS!?NZa|c;*lWyuIn)4`%PYffU0~svg{smYS6;ML#dOHgB{*#vR2b?oBN$$TeFvYk- zNYmJ$IccK_bZdVv!}Eov`wxB9L6(`uf%5zJlUAMY|4_0ao*~?C=wFzuOm8)O?B1}t z$v;F_>Uir$bbKuZ*Ich@BG2{3TbS^w%~m+|?Zk}hiVT7}fnd6L!SI{~SIiU+{L7o09EqH>97A zTW#(O^3!{%9zO5k|3aXVl7}A1JO?!8c>zjSU1&x+^l$LtQ-O=|G^t}b(t>m$BzQk441&fbd)bM>CBy$*!e-vzPl z1EO2<@JHeGsQ@>14Gs4m z6V^DeHwUD$HE+r4UMz^u#!WF5pXsXme6zb$UqMF-h>@lh?!E5r%yx_99N&bXkZfwG z$C|A=<@w<#+chqVJJ^}#!iNr2(~#E`cTpPmYUl)2)qlY%ol1WD2zP@r>n0^%LpGmx zFx8S3#wDtX42}M;d!HZ24-?J(3{xQ$H$>2s(*f?!pf^`*!3D%L!%Uxgkd83vxEZ!# zBsKX1&)_OeYYl+%i1X83Dc>wi4f69N2L-63${iND)KJFsYvOedo$2YCf%N+H7F~4u z=~&9G!?upP1cFC`lKd%hJzdl0plK#+8pz}@vf`A5x^DA?g6QfJJNGjM_twL_kEFgq zK2=IFjj)kmHt58G?7*D^bki`SK8_8G`M4W&$d}u0n|+2V#A$s;34Xbvg-sRd<&nii ztJQsPcWvIXxPOCE)w%j?ZiHoN@#gjZAEIB~8}asQag0ChdWq)qAtys!Pcod0G}V*3 z^&~~s8AV;9DTknlBf2S=iu(kGhGg1DFxS(x4qRg>2UKy?Ge=cp3@qOaN!fr08@A<| zD`{{9SLGsImP0e<5XR4~-CdQ8zq9tEF?PY}*u6%U_N4GGNq z!!5pG=#%imlEuV+zBWb1V|BkW`f*Ha9g*Y@~0 zVi)Doy$!qHlB0Aab1fOmy?0(CscJ~-65_|1IR-u9Ol*T8m0&2PAaXkPUGtfp(!f%` z#ZWseE!HmU%mJAk#FnM?;v!)y!7wi5Ni+2@AR*%&m!vAagS(yvblERN(te<2m*2_0 zqr^sX{xa+Is|gJ;bFnC{?pfNkxPs1Q`z1a@KlofRz0IvM$aiOQmdl9e30mUisgHO556wyb|Bsbr zhkM%HMLrE{xNGKMwO8rI+~zgT{AKnRUnNxg+Lqs$1-8CWyUrGb)Tft4RV;U8g;+g= zx%o<`qvX9Utl;{HYR;9hzuo3#VR(yPyZA-I0#9)XAusaXVdI@m9pR&*8DZqn1~Wv%Er&)GLNV zINbo*oj&%!DX7BQF+8GlEbXfg{Z>+~>!DG^J)G^=JN`|%U!#U?sWEx4%q0j>0Km}d>fq=N<8tZiElbh;jLA?uE$%`WuKs;v&WXRfsmty7i1jvMjP z&nnDie(-1c!_Hw9`Hm5*IC+Tc%JMG6IrvCEB=%rDHLBNFCOn8-UOw7ed#Sk2RJw#$ zzm~ZK%O$_}r6JRi?0jhF^5CSEiLVO7qz-_^WLyk zNB!S9Ejg42TR9!HxWkPo?}`|j$7Hll&cxXq7r2eQXdNXd)&_ z3D88gZC~f65(m=R^Zj;jZNGW8i?m%;>R2@E8QZa&i8(^!A`0DtT>}*t`4(BhdKTN> z3RlR{h53=%d>yT08acK};bhQxmZhnJXA!p~-*CVC*3>~JWLS7`XM`M?mRYLx5=$fs zAEd~nx7>iOxJq}e*VrEN?orWZ-}9@h>$4oBc2@a;@uC+2Zm%1^>uUR#6QA`jxzIc( zU(u5GK)9M<=-}y0$uwlZ+gf!)pi2uTBOOb>#ZAj|pH47=(J+ExX0XO&VBc}H3kB?K zPp^t%-IIKazXa*^ zaRjVnmgZ^(^vfoqdRm&NZsPi8tt8dIN&kl1#Cp2&F$r$>Cn3IjtzC_5T~sImv9h&N z>lskZe8Nh+jio7@X|c$gY>afs8!BenQkNIHZIA#HsVr^BVhmDm+hsOzJ3E4FQ5&E; z<{KAH>l=zNO`hAkmtjc-jSEvYj!0ZiG{kuOEV{g)p=NdTs`T?!2lmGld*PbhoKewx zGS4Dq9T8_1t+2{uKm)D?h9R-VK+e-np*I0lC@c+0v`(cT0XGDmK6)d(Qxi%8l#jnr zmGgWDw(Zo;1d|~byYAL1rmqEo_`AQ{aw?xWqVm5BDDCsAidc1F5%Y}lS$X=CEZ>A5 zh`4b{=g=OWZ+0*dbam_+sODM}uPQFfkpLPuYt?{^qQZD)L*F)8vYU*Ganc{ws3`pP z#p9~d{-feklD}$hbtm81>v#H9)SB}n{;hTTJL*-0J882lBVx(gs`xcF+XJ$9ybg>-n^jJ{*Qa2>)wpNyEqS35hRqn zFE=fd3b@+i6&QlWlt*93T!2@7)#g@B(0x@aNEhNHx#=Y|ZI)*a==0cWp!~(L^&D1b z+A;;`H9KFg<&3y`9sqE5d7ABXlM%J zj0+;@u$C1COh#Q7>REidcWK-C&} zFTVzQ&Pn~040oEM2!hDHKd)(JJXd?-araFpUDGK@?_#`Z(mpRD_2{Xg1+C_3u|<0l zYfKnKf~>$ilb)qZ3ZT|un1i|Dwq1$>M)%}mLfUhYrRo5?E-mOF^V6)n=^Bo2MC8Gh z4z_t&d#IEoFlL}$;x}Os)=dXlx8Aq6Qtf#Lrl<;8RPimZ^%P^hD?4b2Zhi78@|EK2NzFy0zI_bhOIA<1>!!}L`yCr>XRS`!bk zN0I_JK}tKz7=IF^H|~vS66FzF*>Kllg5tBp_wS{o>(V6$V4SU=8K#2$&NA0ZhV|2U z#;?VjGIJgr|0n&zo1gN!Eph7%h~CZmid11`w~qoPxf5ZoG`Gg#W9)PkNlWj^oyy*( zj;O^&TPuR!tHSSUvWcZ^J3}uMb@~6gcC6@T^E^dgJ+_}zPq357oRC01;?FXMV(oVf zvx$NmVWE7ArhuZV!#i9fs_xU8bC_6fvu2p4DJ83}k<9luc2gNfIaBeP3Ar;3c|>Ph z!JL-L6<36&;(z`y&E$E_q&2$!@w!Em@K#qy3?9R4{PFRHWOW5W5l?WoBB+Z=CJo`Y z!4i#*poneL>`M-x#M7Ov6&ncZQlk0AsVy70+6^4vi`bzXUGbdyI+~4!VpeUAHjHOf zaG_+D3fo#&(~$@!u9m3N;nhp64AFR1A5$B_R@@*dnrW&U;+qXr#b*w*lx==O!ZlH0 zD;Da^HVhNgnM8Cmsr4k8S${6#0=cckoyu-g$K$QC4pV5jO|1mgP4cOW1Va+W(IqCm zqtE+VV%W(jA~A4rCfB#$ny;|5p)Ad7+Pxko=)f!vWKpxO!jbHqR3>1{wR=G`y=ACg zvkZe&4AQ51Pc&!Z6$#cFA4^jl3HO|#=peRyrQrIh8icQy!H%BkhIX>5muec}7~L^? zCd=GKG&k_ym5)}Ig8a-YWPUswiZaJD`){6Cc70Le^Bn{dY){~v^K@GMqUZh zB;iXY@MV2uQ$M-0L}^MQVyDuoa^=Hjf+~T4&Hf7AezlH-Ay9WRZM8V8E0F_ua?nVw zF_}xDPGo{yCgRLdPf>A&K46#t*uXWqa#b_4?^>8urA*a}xO6tjzK8nC zmvw45U)@91j8k!a7-*3TIj{gRYhE=C-EMFAcfqBNo}75!Kev!IyB!CPP;Nh*8I4<@ z$s*&1NrgCqZAoXbD}gjanx92fv{MvMHsShd=PuqhO|k%Y9kP@Qh6;@dTqu%-8>4hstJQnzEvU(Hc?KZe{rgUtZ3G)(YN-kCr#tr|P zbyQ7@H};d<7J_fu+P9tr%m`s_rE31R4r17$4s4?>2X^YibP_0u2M2SI&<(IZ`$KgF zgw4m}D(DcA?$<=Vhx(o!jgdOoc?VmBq?7O;}Xc00(Y)e$vmF(w^N>&`G&AAWf+R`Hgh?%$2eSf{C@LO705UWqy6XAQ9c zY>e$E8-{srx)`nBFL-R8_wnV1Ek)aIvGIoO0r#l?nj45?8B_CK6LD1}b&56i9isqg z>PZYHCV4G1Ut<{Q4AV3wTyQ`!0}bT#yw}0PE2mU%VBa`uLn7X-fyf>rX?{A)x*!Xb;2R9qFY|MINKDT-Q8 z#5K04>u7#cYzPA%IIvL{Izs0dov~aA+b~8_V+<7GDcFk*Fj;q&GC_sLn9M52bj36Z z?kgAECjdN{*wC$bGWy_ZfGHEN$!bx@TWi)jnGy(^2CNp82ZpgBI^8tFI`xEsyAKL5 z<*kCEs3WPHcpCTXR1@QeN?*wChB8+;=pD7S^Jd7IefX@Kp@aID{;m_rL{-ZEJnV69 zC;z7tCgIgOqB-rky8J)fM&we;axYTTR~p8yeT4Z>*leQvFnxcn}u-Q;J zGk;g$>kLM7FhkK#eV0NRt>-|oLNN2S`3lw{M>gv&tSiivo{#47|)y z4D8be74!^th~WxdG4mg$8C7z)Z&?7zLn~&UOX6xiP!pm^O-zzW7rdqiZ&>3wfw`Glf@xXPQtxcVBOimap4hc%h_w-8BD{IjZwq`pa3rxJ1HUrhxMRh8cl zR1i!Cx}xV#h|I$MD}a{%fQZ?ONvh%@&35RLF_DL%V-atv0qd-7Wnd5EyhJXfVwuP2 znn{w%KzwMjHpdVZm8M&9*5-KYl#67=2p7r{0-1ayiVH;X;XsxVhp9|dkSzdG)8@ug zuosHfy#h)TNIay4bqvLa$^Je)sq$A3@2DP)Tj;ey*5R&A>}u#MQIUu@=izZl`%N`O zm7Z*;_i&unO?{&bBU%~(U@QeCbKIp|mG6hJE5Q70R z13zP{gz78RNaoJ>XKAMCiY~g;#6?}W+E_lEet>*>`HV-u;kVOqOGG|_tAi@GNJGdc zmvjXt5;O?}RT(3Eadtx=+3k?V!Q zxxaV)5;ao`Hh^A|5ep5qC%0VDo2HVL5pvmQ^-s3imf%O-kd)#Oj}u|tI@rH7V7~b0 z!Ug4LQ|e=!qF;}leaFt6f8r52!$L;g`;9Ft=}5?FYQ@TiBlr=fInh&E8f>X;97|qm zL+=^Yw$2o6TjunH{BqxUm&)a*@Bj9}t*bNIT^1fV`geQpR`(MRmHzL{Q{TCqd)#E7 zJn25R_~iZDnIP*9mJH#&a`wIv-q$s@>gSn;HFN(%x~{#^2z*yzCv!hE^J%TIW-xiz zzOaA3h%O1&ce^hJNTYQuKlHSzqz)-hm2ok2Rl_7M*>=! zWOJZXQve?DwfrnAu*Ky#l5|N;@`91b7c%PRgZxFjfUDul_%H87gs}X2KQAiD?d^0w zY^m&mO1oJ6<-B~axAuBuC{sp&CvV!4a&cGeNVXo-xh0l=6GNKqI%Ahq*l(3DVvG(< z`jlF&Yw|7~?Tq)NE!az4MfWYexTNf~Pi$P3waRs8$*IN(@2Ys|-Yw4wcKaP-E1QpQ zTSrqRI2?@k$^wa-@(#Q)yeWK)muj1O0^0bZ*zGdBx zkr78`4fXpn5=0iNBlc$#m$U8Wj0YyHfL(*iZfORVFf89$_?9Ic^oa@B{4OWslea zqDGRd&Sd+x#lr}3Hwn?WSWkBgZ70_CKS-X_U3vy9QZQAm`eDI%*RvC=l9HA``*GoKR%p$tlJxMk z_ZI!;|LWx2X1P^qtHdI0IYC{;&&9+y-Ng=)=>uOE(8I>~r7YcaY!NHjwI{8QZ(F*C z3hJa5Nx4ka#}+^ELZ~Z^A=7GX^3ob5d726~_}Uh)?g9f;r`Vk_D@YL8AwY4STbB^S zE?YCtKWT?QU=AQ@RgT@l^TN80NV2xlJ)lO*(W(83st;0Y=5>K3@P+&?bZ#Imx4+xG0)ok#?8vyDYR$}z^e>w>`L-{iOKxB zNzB@$^DIc~;?>1$D0=6_>ojblplOcQ>Pp^!wkCBi7SOon5P)muebOw)!mBoUhBj_p z_A0X0N!iVM!DZuC5RpKaZbdmOqj@>$fxizQPw$9hT`R()sqMRLk#J(>AQYBhCF zpzAP_+ftpahY;RKe{2zyhCiVE_69T=pO@Uz9{+37x&^f#?byvs9agQNRnN6zCNq!$ zUox7Y%)9iNyCq9ago8sVNc^09&4948V>MxF+SNKOho&`o=MU&qB?@mmOYe*~4`T_L z6Rdb{Fj3VkwZ>iG*=8?4_&QaY*Q%%2aaSBf_@MRn5T30jvQVq`C}4I=a;-Mdz+#s` z=CIhWbQc}G=!w_Y+UZQ;q@7&HfR4*N+m5wGMIEkzOo`O^k2?W9wD*Gd&p$4_oIKn= zl&m8zBHXr{WgSt@dSX^(!|`})L!XW1#Ex}!#Pfx4m|LLwBFAE18762+Z*^=TYRu9+ zyAHa}HGl|+{wa7smUSr#S?Il^5pbHle_A@DE-qrCO%D2->d%Z4*JJF-`FQ1k)Kb5d zg0_40sM^JrKAR6g7v~pbjY<6!2{b5WwpA_2k01VKv36-uvgx`=ld@~En}q_e3ck1K z+``NKzg`V9f8(gd+2%!Vsg< z_F9x@vzEEZ&hD+p-s-=6RWH8e_2=eUQJ~&cG(b4pTG3-~#muFn2_6L1X#$a5L=)<*kQ4o}Fm z*CcSC1T{&t=ZR302+R;_ON2lU4`wseAJ~6P^WbbjLNp&v6e+x z3gc7N4sgE>@>~u~%8ajN=ztvkYy*C>F%HULlMHMS8(SqvuM}(){`N@-ZQud10w98g zA?_d|8%-B#lXyrNcSoxfrBRGWsn$lSpQ5>RXubv)a?6pH)?ZTRfH$|ZXg*9F7A$XN zftW-x#lf#7jaqTF1tMcHA5IXV6+Ea;3>8Sw4l&pv0{r>fTDGZ%f@0I4HxHz9XV>wN z2qB>4Y0D*`8k0GAun%^!&H>%INIVb5w#(r>Fi!@J$6=fN6ELmKNM##6xiEc}29tNa*_dKwV%=)*;d zxeYa9pht@6MQE!~8_b2%`9@C`!sWsm4iv$MoVi9!B8(M7VeHUst~Q+Ql+D5DeOOvU z^OXfz{Rz5Dfnm9J$_99E1KgLjX3BSFNuh_b*3@TVHDb701Y#+O&fk!1z7_UqM+(6T ze%&X`ztRpyiCVAhS#f#sOM}%^FBvx;%Rs26()oWrbiX(N6_o7^)VP z@-ej(R5G+eK7xsrF|0frE^5Iu)`+!@GN_pktJqMA;6o@k_ZlW;E)I|6z*cNvRXF0! z!L%^Y8xuO&Aa?)|GK12YhOaCj%?9`)1V78+ixTaa+-Q~?dn7=+O#4Cxn#9^`Qm{h; zRy~C}rS0WHZIqzx8XwY#!m9)@rklT@x16UOzVc7|HQp$ltultKdgA`@g?q)%vX3|2 zO-Urva|UY5gS_jpnfyRM&FFjB@RQ_C95#zv5^s_GIOA4J`@-Y70RJ$8TwYH z?UW*e5@bluWJ=H;3ED13Oj5X0%Et~!D}`{c6#GK^Bu5&Bs{~GnMueryfD!>(%J_7A z>-|gXtiMmz-dXnKT=K0sacPhD0bfaPqM3?mredhfG)Pyxv{5wDjCi`yg@?xQfi*Cs z=k#Ro!4@gJ4?BHfV;E!D=_M2rxg(4Z1g!>q_;4f_o3!C{4oak3bJ+mh-t_rDnOLjZ z3w78S-voj8rBQ4d{6uQ}HNtp9%3P8MbV^_>&(kAzrAv|9MDRxviz|leg@Bq5Ys4L! z$VNE@IJ=kr1GbY3^1#|0&hCrum{d$~paojPB*lD_s(FHy8`3pnRP`_xP~K`R5Ta3R zFrGEnN2ooj_$`&Ide7EkN}_K9;LbAo@?h-5Xo}gG#KM+4ZS=pmX}W5Jri*9b#^^Y# zGMvf8L~hD}7c#g~8}a5r z2_odrX867YJsSe_OOg9h_?pZ(Bt=K0kU?gAZ-b26fS;x4$w2Lh4fxCpsS=@&_jrZA z#BNmV&4qQJ_s$5>4$-y6QkZn$n*TQM;Q8KR_sm^%$d-x9nVR=h(`$@~ToGu?MpJl@ z-E|;aw16r?j7N}iskToFRq`;a78;Qc5Lq~LuHh}se1m2{+q4M+xKIEjf7V^&ft!5i z*YN?p1b!(1rewy;8Q3yqd?vG;r^mZ2G;nCi$!hsox8rZ!HX-N#1@1aJx;susc<8jRoG zGFA(~}xUx^gn^@1Jjc`S}a(B*^7rLY`@u zqNt*oztYT8A7L*J;?2bPI8eJJ)_#xnz@Ecj7HM0AK+!E^DgB&+q^aHE;zYw7a7WlpV8C8BPRtgSC&?hqOh)g>q-JL9h9!0~$(l>jq7;nmu z3Awg~4kOMBGs{rSGQgqbwjp2XCYEX zT|Zt`d{{BS!j@$;hP!&{@%g19_)G;5!9ml9&`g1Gcd${;z+y5We~X6{}&cNPYa*=Rw1S>v44!npqDHMgbMo zkD!TmsarSj}b4=6aRkE7?Xgm7& zt~+C&vixT~=(Br&FZJp&<@$MBj$EDol5^wN`0b{Jdp;H7#)6+`^`%XP;0!*A@G`3Yt~c*aN%$Cl_KN zywJV>9`5}wg!i9Z*w;Q^bYpyvT|rpm!(PrS&Bc6A+b2c&w!2gAC%225qnEigz23fH z-JweFzf%u;J3ssVapyVw;n?P*cRz~PE3(`~eFwH)TM}63*T8GK^o#Z9KlWYUm~r6T z@s1a}umA5b|Ch}VzwBN7zYE8Tx`!rqU47AVZ2o!kW?`aZecFkhcU_q^1@BrCL=Jl! zwpEP-&OS#I%bK@bxE&%t$?8d}?(t`ldT-Bv;2d`U(n_Dz9Qqi*4`C+^4K88bI_n-Q zc=@WdSd5Q!Nj~1wW=IkT?rljCZH7EmJcmWGzSLYdoG-ino$vaJ(`D6B>{-vOLKw5k zyuvH3&Z;7~FAomyuMI+{_uFiFW=^j;U~-uAPk9k0s>Sl!mPe*K#61^W~F z%I(fF19)AH{oimbU%9TTNCbbP9B&Az+$wG;YfP*zINp>j2|OOPh423=@EiKA(wP-! z^7GQy9t&tISC=O^3Ky~R-i6rmyU>UYCkYQrvTJ{PHj{DUw%g6!HV2+fW=et_KV;W( zj$cWaZdv&xwdRZ#%j$Grt^T!hTwA=Uy7%2Vg184Rhq(c-s6?wLjG z09K0KH_W^*hZELxVQ-Xvj)stdbI1Ri8E*6CuZ)csMQ3)bWse1qtBzO!(x~$K2U+Ul zi7QLsm04HwHVW{fhP0aT2bbnn6ofq4b_`4922_lTG80b_NOjv!Bpy$8XWqY>y{h8$ z*$Rxycuz8`doKf>SrQcSn{-yn7OJ+>=WwSk-Lf&G1h|&liVIK->=^-OGSNW3pZMlvLWu^&{?Ny7$j_;h9e|uWu>Y>~GF+ z51-y~ocW3q@XrmmKg6@@THng8h_!6Uybi;{I`=U9g%651Q?Y!Lwc$tMCQxyx?S@BS z$6a3hbn>CH3O8%@3HC_GR@%-&*Ph;=guX-TiK^$$)+R)(tB;~uCTMP}{<87=u>Y`j zcu^4U7x5`&0zFua-ia1S$Ogs&`Oc7YG&0X zMHV8}$}O%x3;*cYSPX>!^E{$o;$9Q2FN^tvPr54}by!=(N_WX8p62M1d-gebV|}2yMc}swA?0+NIe?Dsai}& z3vS|d_HLvgh4WWVJrHloNG?a)7d9}fS-L!GiRuHUK;;NgThKq`%)+o!SNE(+u(LX_ zXF;c5cN4k8co(F#3F`DK zNe{%zu8AFFt-q&!XM$^iYoFwl$65-UJ@2_;STwpn=M3w)7v6kF?ZbiEIQy3z-oF{w&!OiZgp_QH?Mz;y{4>j*Y70G&TqWJ>ug`&<929n0q(XaaPVsL zgG`58rc(Z7oVv)Z#tW}#;MDDZP^=Af%)@PDpcMffS4!OjT3fH!-dIb88vj1)~iQ^Fa0D@O` zH(nN4bVN|l3YYGI9|fa|#NrW`?SUN+1$C}V3d;&z@i?qac{8})bTw27)Rej7mebuZ$iWyy1_gWH9(l-mxq)w$tgdWC+f^#>8IqjlCfzAlAQq8OH1YIX`i zQgqzoH5u;a;4Def?U%dke;~|6-7Sb)U0CMn6!0;FAAha2sPV}|ivE#!mQNDZO>G+> z&lA;+#ZrKno412U#1pt!zp5wZmw#~ymP>nRJ@rSrPgMa;#mafPtk!`vNr1Yef02(E^6p4}09)D{j^pPO+mKSo~r zVcUh{t)C@+nks&3)Oy;=(z)xI{crkHwjP60+^wgtOCO;T$N8r_B@q{*8u7`o#$0^0R^i z+XN4E^O?u(Q63f237`#_M~1I;I)`Y4V9kyJY*QY^J@i7TP0d3KF!e_UeOlS)D@Hb8 zlcB&EPKTl-#xx<+`yE=qGT*#z%#muFg;c2&u9clt`oJ$_zz}yw0jA{%NK7F*?F5a; zcZ5m6%2FWvGF*Wne#Kgy6!|nqJ0u5RNHN)Qtc+}I5o=9CG>{9#3&v--29HM5$N8|H zcyX=$nceoIiyYi^3^2YJ%HYA$U!WeQsxQ)jL}5yz=mFLz zIw%J|$RNGIUyn(>;y{NpaIXyQNi$A5!i^%RMgrFKgr$qnOm1d<2_{Jy@99h#VK9#bO|p-|+dbQ1>>pMuuH1%~zpu z=7V-Zsg30Ta)xF`oalb+^t{EJt;9ghAn?c)9kT&m$S+L_;9B{I90^p&*G7+l3G0wd zzBYz$Y!zbjHmnm7BC$jxOPNF3wbV_!#Qu^0`~|k|umuy%Zh=$zaIzS#5rJ*th?)b- zli*4T+RX(*h1m207wX@&=`^1z~@k7?ams|l*_z|dL170%t zv+P2!#Mp5JyvEnYNzZUob^$MhNT&F1gbbMepq(m*X5^6JCip}ORq;SBUHyWt z{))LB`DuM3k_B(8Mrh~mw>e_F$^p;04&^@re#GHj+za~B%*(*Fi;OvZh% z$vEjVzflNOh>?8~pie@II0jca0qy*WXb5>>15G+1pOzyxc~;nMP|U_k6)+>M;Xb=5 zkq;UUAvZ4F}NzaV#eVq`k5`tMg z)SZR<-2>4J(1u&kO>z7GtY8DL;R#2(ZUGo2v}B0TKDibfS=&X%DhWKflq5|9CS-L6 zQA&&mh!ggO$$%~?)GIS$hYe#AAV&ym_<)@V`7D1@XM;U)h=vJ7u#FqIhCwVmM^*RI zY1sOiCj~|%EBz&CiB>yl1w7#_yuktM$rwz~^Ht=UJ{FvHEa3aK+Ae`Ml#8s}4Mho{ zaFzzEp?%D{6DI~bJ|NGv;LiliTm~=ue{7xkH`I$C|BZc_-RzqgWXryVq#1j*Yzav- z_AS|x-0IHQvqg4E#+D=@NmAD^mLwsSTyH7~j&YPL@em$O> zHp3jnDvVn5Dhlw0z#SoQ zLy+{ZVCDo4^qs<8qa-pFI3aREg1qrdFtZD2fdvcoO*pv(#s-B}juYNcVvXQ=>*BPR z^4&G^oJQHQAwBfJ=ei_hxHt^RpKV*a2y#J{q0lrFWd;E-AAwgFvBF#icgCDGe-PA< z7oNk?m}rJK8gOSb0*JgRGHZgkrKkX!Af}~|HR%-IIE6Mv!KqPbtEr56(k%)uu?5Ge zA>n~UDZnq-FgjnZW(3OXpa`EJfxn1w9Y^m1v(y0Y15Ubt@N-+G>em!)(EXSF&2>H zP^rgJcjS3V2y$g$@x}QD~4Ej6MVa zBN)TSV`Pp1jHL!tQve_cYQhTbQLB7Zs&veFGSP?ZJYSfQSX-omq8P3m(vS2rBM!V5qU!-);NJ{i)I-^>77uHD-q<4qeWr3mjY<>cmUah zCzg4|3RG)y^KK*KW@9}a8eU&XJnZ&zF!dwgw;7!$N1`spt zlwq?WLQ9OWans)Dn&L$THJBLS9RvRNIK~K?J^>R!GV=n!D@FOYrma+o511rBO#!41 z-Uf+#(;0YljBz5X70=B;QRe_@N4Bq1cGnurl_UlBlYug`fG#2^4*g$<0gU2AVa;T> zK)x`LJ)04R@}gi~m?*Gq$#tzLhE->fwJfxubM%_~$?hv+suj%O4DRL00TTO4UumIV5kbl8QM zv;P)jnbJtb3!k)=RFXi~)0yMaK#IMlOSTV)vve@e?(&3!!L?s zQ_Rp@se_yt48y6HTT3L$6@Tef;Jh1N`pE%xVHf}nPfg7+q=2SLv>GDt4MwZN0WScY z?dVtM*qtIZ7eUKfRl@r`4xA29xePaaTPLPZJiZWxq4c0)JoxcUqR0=wYdOMw&5J) zn>?#>apXVN4&6iBMDqbIXxAf`t=T}=M zqYYlJ?fFq@_WQztx7tU=?h1A;{Y754Ge|Fg5@t+jj_1V`FjT8N`U(mlS{2l>ec{aN6G)Q5nF%ABu1={)u^(cxF^>tJ2)wZ@xz>|WNLeOg05 z&iB6xH@kM}`oiw_A7}3$bCyUxu2JN2)8l4_l0@!EgaHOT^!(%ej&Rc-X7?a=N#Vl7 ztLtT?ypHD)8i$AZvK8h_AvdN}^v3c%Q0%+9S; zLDa|LRBkjM!-S~M{&_oc^><%;%U)ElZcqUi*_Qi<8+Hg>uwtgK$S|2OwEJ-gH(dC> zIhrsOn;TZX*lrPeWto-5!j88WgkQ;IJy5tB`ugjIYu6{2!%OOU;--)V7Tg-9UB~3u}W*8$qp6#0*|v?v;13%Nq)r zLV+)WUx71`>t}cGyJYoVWN(&mqd+lhVK@J@f|ThKg4YG|@G z6fdcs&mvqu`!bj~{CX=MBetm#$+JVWMwB(|R{NBMY|aFftGaccs8Dgs3aMyvD-OYg z6qdg$34VObhkxX546j6Jk%wMMlqd_%FC2>y5kb`1HLT`x`3Va4?On&vHa-_(s)u+n z!S|ML)aG32+}t={{Z2UJhp1zJw}L4aBrC|wdv`hJrZ6w4Ll+rWEXbL9lcDCw_Lp%L zDiz!Dr0d~tI^6zI3m+o07+=+hcl9n?nyd*$HLx7qN+pV-SePN7 zN8z`Yb`!%%l7*Lp0C<`9r?pYbA?_|X@#fMU#6K0yB-^d*z_Nzdp%LVF69 z;&H+JfO=k{ft~Ie#cJ_XZu_*U_t$>sL!ns=$giF>`sfI+Eo;%ka$Ey!K*s zz%5Klg`WWHuWCweEE1J`DKLTcejBABuTLt9hS#ql-X_@jSiytx+Dm~s-5!kPL_ltP zn*LDC7{SWVpBtBLmO1fVA+RkZSC6SR$h?98MbX3>*P)yVp!RlJA(NAMAoDYUxDa%{ z8fY@g3|k-t7KC)WJ(Cz>kKwIQ{yCGyro%Oxl)ALQJX)ouZPDr2gi8J>xTxJVQf zG-4`A=aVIQ?1RYeO`=jS(BeFQP|AFqG|-X+7514*nLMjX)eOuj%rMQ&+NPv-p9FKy z>1Kv~O|@D)4HnkuNYntHx|l=R!cn_S+$?&&;FugJLR-TACQek~DJ`@pdd6PBr_v)X z&0J7x7PBlb!US7qXSZdD^x&1OkLNP-wIoI!6Rpw`VR{9+85?gYfwL*ble4L--rcwX zdQj%_Hc?E?QA&hQOcn8z6`km0S_w|)Ustx1eBn+J+fIIn7~A(WYY4YM4YY4oK5Hw| zg%^9g_XTfM*M{1Ln6L54=K87+>24BJt%Cr7vv+XgG0JkzUlx&JHE42|FyI*mn5WV+ zlKvI9YIFVDjn1_Ga{r#!`rBiFCmCu>@Vbve=lzc5#je{tzJFAE*^ifGY0JEe4dDCc zo=q|tPI`;0V_%{p=KF_}1o(){?YVW{wEiY%Sz4AsIz06lgRUlByK(+9Xw7`a`>TRj zaU8%G?d@XbNr6ET?Z}q~l5Ilt{6%v}%tXa}Rc?_6lsA?^^CO;qMnL`WOf%jd) zh=~g1XmJ;8HU`}Tx>81cnELYoK!Q0p6>-1=Ve-b%w;)-Ax*rk2{1ppNQ+Ek#6gu7 znKfJeIawEAj269&f=$X8JQm7mHq0=&yH6}EAy3$%E54C~>=*dUa&*;g3L53PKXo$O z%~h-m>WE6yXP`xuKsoSA@T;9uR_*0lDkIPFfkDxbx+v{T_&;O;`}D=h{n4>s>1XAM ziY=szx1H6dqf#}!E*6?=NuKQ`Srwmq&zm|h)by9)8lNerXBCmYk&8*$aY=8>f^k+H zWq3~N<87trjHKUWu{;l`aK+Ttq06T9ZI+gT|EOv5>iO4pHp823uSMg6CC|5&b>lOA7 z)X`kGguON^LzsC%FkmiKO;4(V^NI~G3e)b|W<4X+or}-ow&@L;+(Idd_+`@)^fN^k zNC#TtAybgIc53s4x-eKiS6fAVq=LXWd}Wc6)OP=$d-?&3VR^CF4Rk-h#7cC=o6-aa zhD~E;JG#BI+s_-?lBsZzxssS50hZF_2Ye<}(_QOTGRr?G)>V~(Z)hH{U)h~d!UgAm zqI5D`3z31k?y^%s(W+{r*!duP8H6R0x(#PBLqN?zto&GRD<0U>r5VGyl4u4K6EcG5 zwvw6%xPJaI?$|Z%BfN+?nV}_4)BSoX!X_{2o0ZEqT~|Za7=c$0yqN(H4yQw>M97Xa zhSoV*C|(SXb`A(a;uy(5U<%9fmg`HCW+zM0fzqB9S$csSvs;#-M&^!^Sj{9*AD&iE z;Ehlia|)c*Mpk8Mb}d@yg=R%zIk{G8rGvC83{Mdyq{;*Lc~EsBt)9&5C(v@SjPh)n zw>(4R5U7;EYP-w3faOJ?bHoXUlK$XU;e>24?i6791M++2PVHZhL zd|zG~a8%8H!kMEe5U~~VNh&gc8z={`gs_$=3;~5TPZXvB%0H0$!~txsp$7}t zqhvbZ%yJ;@_+Qg3kbu+akQ0nSX@Ek$!goPv$sJ|Q3MB?yIgKq7iT|jdy z0fdTYn~*`If4M3I$%3Ou6;gV_JA^GX)l?}^IYNKGVx6Husv-phc$&tDyfz|tQkFMQ zVa)jP`f(0HIMIKRX(#}6BB-tMv{D2eh+-wlQeXZz^dmA4_<$g+7y`>o0l4cT zL}7i!=qjG&j{eUm+Dc^E1704YHCr>i4J!;oF)*0179y{hNZWFx&Hv45RA5aH3Ij00 zQes__8mEsS>>xP>3^Bq8ptW(v1VLC{X@Eps>mj*r90g$`2bn5D}-jaEvFPh8Z2&JD1K3i8F&M5riUz>t0)lRU9J>PxN=5+rP3qi+ zkPZU@^a1$EiWpvuJg4^~XNJoV_SVkX;s|=^EA-G{cLJi4sKs}}ql&Y|- z83NZF3z{RIwpOZ_#Bu2u+JFts0>xlr;XR~7qQr)u05tWnrbf$e<`B z$4p)bN7Ve57tW%Ew!eUH6~U!OXjSTXpc{%u?Y+Zd5|@qa~Z=(dQEF1#%&O} zJWtkkV0aQxDFXbA1zemKVfhqPbC}oD*0e3CQstV&&o+hUvy46k1aq zuNq79C-9n#gx*-L3RoyB-{6nr>LOSK9BW&Fx0s4*&DoCqUB=>2e6XGiLsu= zyaMBymzz#21SF^jmMM7LH8UtvZd#1;ezNXIn_Y?ko&ysoPs0GwMMffl%SLlG3^yEZBd6(G2JZWbPEDy@_gT<*1vbLcZcmo}Q6(bcB_N z2W-V0KaA8Ag2Ouy)J8e{bpuE{(h2}#PU0oOnS2bd3vg&6gq}Dd22wK)WdLBPk*Hmb zuQ;)vR!dNPe$n$0fmi9vyHUmkB$Iv$U}JBn#EUldb6xQO2wszAGd9S)4k8yoz||Di zH~0dZfSo3m%_?vgiQUihSWuuP9#73ZD^e4-dJ)Ak^=K&2H~Fl1*CaxTN?;9<7>yY` zPaq;K527OHzfU2;u{RKn)@|NfTM~nAYzEWy8n9M&g}KGURD8Ukc&Lv zR_5x;b1hLUSF|t$Q^v=0Z`5#fVT=q%iBSSq0}j%~fZ707sKBbnf~ayl+4j_IJnd;S zw+X`#MF<__SswM}YB>FX9)P*u1>?D+3V?`3%O+$dkz{YXaHpsxt$1ENKG}=KE5?tm zk{kdB{Y!Lr3xT_#KT05l zfk>rUvRu1^jLEPRC4^dnyB;CmR&g?HIzkVZWl3`lUHC2`w&x&K`Q7u$jjze&lxLaG zw|l?-QK@kF^8478&j-!P`PZ!~rq|BwpS&b5_INcqxl&;QZR(ByXIRqU_R0j2U+VOl z7By=#GC0%v$V8YSQsm$LW!2&FrXTrh33U_Cq&zC7ekS%EAGZT!u(vU$^Db`SkbUG! zBUWz9pJ~i0OYm~MPuoZ!u;dk*^8J7cz;>o1PWv8!-aF1654pSh%YRxMGZ%g4SWEu5 z^M45#h!9|<#*k*;YwC9|^{=bXRuayxuU|iU>(yG~AIBfPJJY+KxO2{5HMH#MyF{~J z{(pJgeDmp>*@YAP?4&+^`w7JI;+QsSr^;=r#C_=Lg#q3SiF2rsAoW*Ztyj{7#qP7QkJfrMueeBeu7)EVHAg2)S;a1 z{21v}?s$3Xtj3|saTT<^g)55>+@^D53FI`DpO5v&o->Y1KW$gGg1HoO9gn?2+&z69 z_4)33vzIa3PDdsrVo!);ByyogCyty`MU}W+MY`MRy&=;^rt1rw9=it{sg1g~wAvv? zK03y~k#<8tktz@$QHE|_UHH*6x8(i2HpkAM|0f-FhU}ZJd6HmT)FpFbd}2|}3q8mQ z)+}prqD3t=VpmXs@?-Y9;Dd~=lI99sEi@#ka zaX-p;-auvV*}y&8XQ@Tg75k!()eq`BjN+}E?4Rt@Ftb@Eesr)GQ%!&CgF&D5@|f~t>CoZ3D0k3-g3qRurTe3X%b!Y4*>9r%N=Ci|@j@mn;`v4Vr+TN> zi^r~f`msG$?ch<*)o?5_e?_i)Px{yD`AlKd3!|qoY2=Jjn;UWdI2Br!?zN2poRv5; z7sS>X+<1g-eK99@CUI{%^G4`Ngz$#3VDs;~)5vfiP4g*3MZV)?n%$gLH%hjn)DMB` zzqocWwD`?bQ=vmd9AS@PV@VkhkgQFY{3`+1KA6>kNtr%owEP!hcBkheLeuZTnJDe_ zzuRzP(gC@gV*MfJQ+%Cn2)MmezfNQnucW9Cj?L9O@lygjJ~`KAEhY2ZOx7n`+Vf(P zxgy{7Vw!Hg1yP8XDKdU<`)8HA<>o>ADk#U>QD*0hfjB+0V!LI?KvhB(LvKZU_(whM zjwuzFj)8E;4k8hvkz0fqB)2Lv1retLV;`qi>DY z(nJwk_yRdcfEbNj*^&aid&#sl``#Azae#e33K8(<7T< zsoFSI0pm@(gM}{-O8o{YEc^h$c(aBQ_7|Y@%60>&CS`t70=&d{_5~=`vuR2iEy{LM z--J`e`jViS--08PY%QCa0lb*|5@hm$Z^qMSJxc!X-uQmBl1v(ApNY#gmh7HsAp|x>(_s)(S3E=XnUz2@^z|2u_C5XRex~z9hOXM6_Y0!;1Ke@5 zMWgoeSA@IMz|?o9g(S%%FJN8i&DZ;Oq4CIkIb|OidZ&stE$VUQt@3{)3X9{4Q{7U| zIcB=~ncA&xXW|*A%0I!ESyl60SXom_^U`A1!7+ zHr6p{xi(oT&NuY~^~ozLrjb~9K7S$!tMnua`nf+D#t@Ph`u@1&mP}2Z$@6&cXPdS0 zl`7G6r)$O!;8tv^an}qPL^qd^**roul!A*UxLyQ_ki-}+U{Mi|mEIG%38kM2#n2Rp z8CXBPrV5?H9khP+Ro!O%V6Wi?`UcNW)0!QEsK1sL)O!`Ugxq~~ME2+0O588yy0UIn zJNfOH-CU;q3R$Ts5k_0nvD!&QXszW*Zw#r#-1Nr=u$fZ1U(KcHGcuroEAeMPFY)OG z@}j!Bk|0CK-oW}59hVwU4A3U*r!u5Fgh@$qmUD~IvT^xWK$3i1b2nGr zvbzu_{I}!njKlt&2nf3P>0K#~|7l4Eq0B4JyPj$J9yqoPFS(2V?ReG^cg?KZcAAou}_ z!PID1^{lRc;aT2hM57qVSXvOO+3XuD>;ogZGE_F;akHEdv-er{!yLt3>BU_;Aijn| z0wD?R5VTKQjH)aK%oWjrg7j?&gF>V|BEi$8S$)6@Jef0&qE7=wH|-oS5ciY|0%K0S z?H5vUA``QpsK12;K!cqOG@7HM$!CZRa1(IA`(ED!tTlV^=`2CG1mY|Luam99DjY3D zo?C$9rr|)B#H9Wd%TOk_Tp%~i=Lbup`=;US->^29_6_%MLz?I?Wgz;4=gk7~M@g!t zD4i+Umn_{^B`bs@*#OAuA_{v4N2iF9i-Z}OdxqPux5jAPsi&OS4Sq|fFqIS z4FkbjnkNaXa?scZsu~m^l7eRdCY6xqsv#d#9+OofFnZvCMorf=MF>b*)orDraE-i@ zY?B>=VsGJr^i?)ef^IL#x`59Xe@op$GGF3^Z6x3~X?{+X1rxY@yiit-y`~IQL$koL z!yCVZaWJqk68!yHO&JTAhGoP=g}uFcD%qz&+LGmqrVhdTPibg)!6(-li!U2PR*2kNs+?+xngA(Q8UaX# zZKOaMa4@d`!}YckqM#t}8cqX|+d=}_%GJ!kQXau+XdRmpY{w5}AZ;unB8{Aai4@{$ z*opG;2Mkcy_g}F*7r4k8;g0h5bB&(=wziG&S$tbz!0GW03@w-6+B5X(A$gG8iJ1yb_UiL9j4?@s8k0)R9U zI%0&VcO0a#$}KW-jNihC4dF5`U^t0U+V1J#nwN+<8@>yi`pudBnQ@~Ej-t9Y$RRln zFj_8A?Gc_;EeWjVakB{%xq$BwMO{X6t5NL5ub_N1^*4@JXA7#t^Ez>Y-*WW#AmF^E zKEi-P?XG)6pyy;nm~}v2O@s@~ZR;6cIdb=yUYsqCTSLexauBxmOswU280W-qIvLBcYt9E39?LqDnkSzGU)Mwjs;oi7G~m#p5Xdonc)jZnt$GI zs>K3il9k`hYb4w(CdP^Mj1qFZG;>Ol@zPJ#7>Ste(Z)TMne~1?LQhwaEgXb`r?CIO zCmuBwy5#Al+3cwfe=2$NK;?MoWr&oghWJjRr-@h4m%V4>&NGc)IxN;fs;9#Z30jHG z-qq0-3b2(x#rpy6KDUm_-JSnh_2`60v*T#Pk)#JLUPmKeZ2d|b4!$#c((6HZ=S)QB ztd?ZFQRP^-AKt0ak(NKmy8Y^AL(hwlcJI9}M(0$}zAZo{Pg03{*6&O9l*4$FjQ>^g zu$n}A-P~_vznfk)Oh23M%&N{+D^X+-&YRwI2uo2)TfTbGc-+bJvrOqIs}0u@qL@`4 zWkGp15~%So*uKF-{Vue@_Lk=@?N`U|PHpw{2kKX1j0YfSzOjiza4*?~#G zo|W=uuS=FVzW)SRg4W}aZ^I}K#}CmGe9*x{_dSrnnXeY`Ky|Nyk_Jx5G0f8aSf$4j z{G2_dQ{j=SqsX&Se%L(=X~Fo$v$f%s8QHTyc*HbM?~#h;SO6{r!DeCyU&r{C+Hm}i z+*tzd#%B_y&Ig{Q>p330G%#uFOs{(gk#s{jqtam$=RQcImJr$}N^CeW%!-WWiAcio z>z){(e|jpG^p5i~j%*h@5=B&*Yz)FK-SOFg6J5Lr81*qMkGZF1GXiz$H&D z@c#T)x7PMU>zdXX#d|pTvjUXA1ZNTzKIb|9yuCI`OZ@Z>`t+)ldY}%Ye zlDJWxM34ZbB$e0idLG6P-6&_-hS0nYl-x1M2qS!9b)LMEv7T@$wBr54t6>D0``nd^ z$33Tr??C)G(sjM@1hSOrSX@;mZ78P~n9m6e;O69O~luRpHWqsKYqbcTUdiY3Uwd#%h1WYF0>vYj|n$zi` z<;_}Fv29^xKw0|za$8aMhRyRd2F@3o!Ds@;$ZAE{)U4yTPyYRlJd&XZ6zr6$$<|Kv z=z^`(M)km=8{JwtmLqMNIf}0u-hUVEYt_gO$f{P6Q8dX08om_<=e`R6dHVU6g7M~; zU&T!A#7`l8=T81kKU99abV#N&iZp!D&omP^j9X9!9P?p%b~ql|awRsG=IU?Fjm9m- zgkI!n==}rq(z-@N88Zi@gg|+&Yd+Fq##yFMCd$0YfF~{Y~d$rvi z91jId``UVsX-QOt=UI*#+s?cprne0c+AfBdXt*sk@E} zH1~&W3M?`w1}F>C3E7*iOT%s7<@@bzayc~yk}(2#MRR6exv_DE^hX7Q0N})H*9hhB zUPC^*;3yNRB_-V#vh^D=rU9wK;+=r+eg-WVGh4etVwWg4~+MFAA_(y^mX9ld$|3wd4XeN z{2PXvZqp;ihUrOPh^gK&`CPi{ph+ucKrIr=n$eWpc?1m8+Dr4`3VhuCd@I56OrD45 zP4>&yBU2AvJvny(Xp3n31_fqfmSWfGUnu<}K`(z0rPk6X_3u*MvS~KK5m9)e5{`B0 zWs+IOPJ?_vlnX)6MKgV5iK5gN3)y_M&-OnBo1P4**;2{S8c(&Uy${Rz(?1&0-`z!6 zWcook8){!D^CLkgZeEkpE=AC|b8^rpw@$Zo5()Ebp_i6x_S@0tqr})TM#9nk4c^-n zEB=}0SpESi>GMd@EmIz^%}8e*bbAN7z|6@vkCSAK_`nol;9PU5rX7qG*+sCkKdpl! z1D!c4mrc8s$eftQEU5i?i7{`!c|7g*jM{k^@B2aZn%!=(?UPcI1g(q`vDBo0NDB|3 zA4TCIZQVb_7M*^Y$60VDd`QWcs>JV&u+cM(jVm@aU>TIdNFMAOgccfX`=ZR0+@^8Mr==2xc1qB3xG zQ2}XWV%_$ILX;xgbt$S{GGk&ICt4f{+x&gd!1&tiY^v+~&Hg>9f=X<~5G?P!-+?cU z9V1G?v(YtUA8jbM&p_gl&|mLc?>+h?ZJB!}UugaDOwGE$55~E~_;PuuDq-dr+=5pY zreG!4^%@BM3f&lkUlXW=1>BKrw`vRHd$h%4eo?aU9VH3-K|)UlG&*9bN88r=jgQEQ zES3On`-mgGeGc(JY3erP)M*nwxh`*6!_0H*Hyt*+iR-Vo$rMu5ZC0M~CjKacwST;}C1o=5^WQ6<3(xoM zYfXYbMEo6tINK-L^ikSEb#VI`d20BY6mIJWbL~L(FOw5TR=@AJ-#lM#lz5Ip6r;0p z^lbO=ld=(_R2%6{4Yl+F9)7qK@y-8J!R{;5k$#l_CsvD&mGRET0g`35cKZA|*B4k$ zS;NjhP;H2K=$)$@#{#ooq?Gl5aME6)s;&F)G|jQr$5u)gOWB_Ndg@LoNKwO)A*g12jsN7eVzQ5(Z1>eqn4^5M1 z$ZpDOJ8ZihikpXUeS_Xh zh0^z;3y)(E0co68K*myFEPii~6+=kCV-I2{+ajQo#Mo|<@U4Q36wb9BmqUTZUP8AQ zAqz{>Sj{+E7iH}jc=9HA^9;QG4zYcc$a<;}y9-Da6z(Go_Zmh~^#3D8kStp}=My!j zDP6N;M|wsNC=}QSxtl7dpIi8&_qhB0pu5xW%bh|zRIOh|t;COqdfflJ^8CRXSHJOB zH{%@b>NRUoJ;BCT4`);<#(H;uY2-zj9skm^l=$#On|GAg{yn8nLpyyVU&sY_y*ZUa zT8#}_WM#ZQv;TP6gQfosvc`CutNd?}_0+5XqzKs?Hu5)~G<^yBaJuJ8p62oyK#B-U z@co|@F|lvY>xZ`Y=)QYPWE(C&KF0Lb!ZmtnJ2kTYjHhP#|pN%lOlGk{L#ou6CDi&rH`x;U0 z5VwW6f06a~wVmaIUK_d;1?`#~mR|yyxuR@97OutVAbYpsm^+H2ij7w^&yZCnYtK}> zIUPX{7DgS%4k2=b@k52I+LJ1JhHix6!oY~VIgqx-f4W4Qx-{kTUEB8NShn3%G1C|0 zs2I=pwF!-#e`ILnK*PE|%JpyX*X2Ir_+Mr zf0klfXw4uVM5fitIx~Xao4XV(zCj(Xo%S(zMePBrJEEj7tHZ5r3g;3CQn8Cyrc1P( zJzk)lj~sbX;!}6{D*C*g*UJ*68V!5JzQ(<>C5zY9A6PyJ`FK>jH)XHgmx;CoSOQ_EnIx5T(z*X zZhiGhz(l=pbUo?LUWfd>Vejs(H{DmyacOE6%HArdSH1W(+GS2|UeK$RGcM@YJ=V3J z`x%^SGG+v+W4<+oiH+HR`hYB3)W0wSvD+_1?C6Y-qkD=Iab-RNW53>&PnC>R$WuUQn%9kr^i3Q<*)EYL#m~81~-3R(|P*&XXEQM7G8zb z9s5h+($enlYj(WN3;*8!vhDEtNf4^P+)Y;Uap=wE=-Yk^H+{lS@73(AJl$U`D#No^ zmB6~9EX)5(1J6mc^~C#Xyp`h>MrDYM;R%s0ew?v`(ln@|xN8m~XUzVhp2i-3wL*j? zLzuNmQnv1pMBdzXuK5b*71X`5)Vd`t^L6D-Zc_*O_W6y!x?47?DQoxafVK?qg_=VTdNmtO^{^v=RBfev0okoI67@R6ZE?G$#-&4|}Mb(8z8 zv`@8Fts$k@>Q;-JlwH>m$r=lcRa+dGCookJ=aqXv1Dp^|`jYg=6$WZSIkXj>bnkhL zm0B>ku*gWI<~hkKKj7)>3?qKu3@NqSTkhgR(`E5TSM`4|K7BvxS}*`wTy#L6C6gR zFL19&=hoYc*j-dR1?P{HD>gejJPg)Iw~?-kcAy{53|X})_Pk#fm>Ue;96r1X3=tiH z)_^&;bZv`Y;uRN@W!ul0O0e%&4mi|+dByvrAl5gJfSp_bFO;Osu@=wY)h`{+dw;MA zGdW~BDIs!GB&FO%hHEZtQSpj%-*Sx4q`vD*j7D4jQCGvV0WAELSS!(&Vcd)%3iFA~ zD|k3$c*a6%5==`o*P{pMI=Vgo{mQ!X!a)b9n~bj$ce+R_Il`;LgAmjJ2j*s0HC>)_ zXH0Ia%>Q4T>G|j(`$b|I*lX}X9%h&+AOf#cvYht#p}tYVLh?Uvj-As?W6$70?7%mX z52J5A?IwS_u;uUC^sj&)>+^)J(UaD88zLMFK6GB`3cdMGzI>f5PrvV~7+1>cp{y@A zO`oEYj}yZMLqXH#|CyHg(is_|y1%m~NqbmrMaaF&hoyxWMe#%eGQia_fT5`}V}83) z>n^*F;DDrjkXwrSdio^co(JieFiu6n zl`V%i{%0ZbbV(-s!4scT+QD-V%eJ;2%I24f*quImUeND%I%M)i)gpUzs-su7Kt$aj z`{k)Ohk8s=>XC+>qe=4K5m3$=U799J<>S&2ipthh)r>vXBgL6oX?AJ|in4X7(uowN z6HSKYbS(Os{Pu_&?WyJ8R^8YkuZak;-9!^x`Z~$%nQ!FrlefM!nso=PdC0mQzCCEt zgeLs-kc|zzY->MF>{$qjj4h{%v6;{x`ywKp16#g!3$ejXoo~B8ibp9vtG<4m>&@)I z=DWKbTK#0&bGiy+Fuq~>K4JddJtod;Oc@-RpDnd1O|zzF9!%D;8PKp$5pN|81Q$Zwfy5mKYnLUy4yU)OC2N-RpQuHl_PUnjL6wTkgGL~HHrqU$9xf5V+ z%PH`*Paacq*R9u3H)*VK?=N=a!ap~c8=1ZsXgih5n%(zvxZ<<_sqwSb$DR!RqqltS z?^;5Wru$Xj;zN{Uze98VX;F&vh zSmcul9_a0<>xcfw!F5D0nQcAO69+x|rjwv~D#_L37H8A=Hp5-eG--W{G?r$QA@I&IY{f9ox7OaZAv8q{JjxY|1@U|Do+Bxj9YhxRu z4-Fd!N38XwPC@A79ZUffIs@sOfnHDO7ux`244}fUKlNv(P#MwK_guq8Ud#A7NB?4r zpOu74v^o>TaY9|fHQXkgWd0J7lo4^2fpF3xn=n|xJ18LJ0Nq)%8UpAViBScFKJl#L z`x~EOu^r0*kQJuMf~sMlY!p3bvtwAHZw|oB=*qn3Ob}|0&Y!&MlK) zVL1@#VkX0c**KpZWHy+Na;J%kfo6u_FCd&$B4QKD7=%8vB{=5iu->V604fEW;Py)G zW(^#ajdlYgsD08RM>KM{YLVA85kD0A`Ju|CvHTQAdaGPrIT-He$nF@wF`rMZJPoS} z-hzf%l{?mt9$T1#P=TlSj}r5p(%?%Nbqg2`f&(M3k-Bgpc>SV7|8ScBM<9i%Ew7d> z-#Gh+2GgBSaSQFRatz2?q$+YHREJwEc{fEkQ$*z8-m%O0xcu$NYJz^5`cx%_aT(8w z!^M0tcJGuGDJ6n@rKu_MMZw01c1<^;W=fB2NE}fQgN?&ZO+f?O6G@rb{+St>9ObMAjDB{EVpGmxEfWUK z2zOW{>J{v;vi#wfamg=+^uL}BeIA5o^)K_TsozW>?Xw^m=Tk5_?w-fOFW-p3a8Jb7 z_=4vgizZC1D#sC)`EsJ(9`&~a!`cQ*P1(JDIm*!V7fGHs9R^>WTwI5pL{4(S8GE+q zfx9vV)l-fZCu|mxY4C=G&6rQGV^5mUg+@4?m&xcZ`Lahqzl9j>wE>q-s7cco-U;m} zym03Ih1Sscqo(ToBVk{2DcGMVVI|?-M|P47``*V3N9V~dp{FlDHnT1@u}b+%&~qv!Vc&2pOYXe01!~`1S&}?^2J$B~0zXyxlG{hp zcKwX~mu=FJv%^!z0n>sI=dl&j}bqzw-i`#o%Cxts!`&&N59>>$^9bc$>99^ zWJAAvY_5tGzJB`DzAC7y5_e_ewAs~0yZFby#8WND;VVG_S_%j4>XGWomyhU{Iy9&s zvTkea`&qc&61o+;BQo6 znzfs@T#@c*N6u$ao^B?X6jKG~@MNwm|G*6E!>x@Vx8<5Y!HAmA40&2Gxo2#6;;v8Nde(LQn4jTiHrzGO-ezi`M#j;tYiKT{-%+Vt;*1I<2-o zT1zgauSF|IE7E03$!(`wGpBZ?PtQKpd)g=;X|ir{X<*9F9HvxMX%0*M*W3Z5H+#$U`7P=?_ci{V&?QUzkcTGOr>`9S z`0Gyr4)V)}5rton>W3Grotg@Xy~SbLg8sdDZHXgtA^UUzPU!RWeYScdZWc%`vrg#PHU~w^8Qtdmb~KW6MrPGPB7_l!{}^UQw!JX78ku&f$=~LP%wYkXb6#=e*vZ@B4fCet&)b z0gvb9e!t&t*Xs-eDXA?=gfY-Hv*o$KD#0(qx=|UDQ>$zk6PhW_-`gn$T7K)h%e|@< z&_6II&0lmX!btK`(EBUNsN0=2C97!Y2S;NPaxwt!Ib~1r%mR8(uL38snQ~S6h!f6i z#wQbXL;^ovg6x2t6wgDfP4?CrVdLhslMk2=0I<94E3gtE|6O)~%@jv!c0G5}%b5>m zA0#TVyH(GoqXG415|&}&jfB}~Kx}4kX!I+D!&?Eb{A@VhD-aSrs$O1B42gf;o4I); zQogp#!+7Q;k)ylnP*fy9@8)8`KZ(P{{3Tgev8f-PV`bQG!f?yXNTq58O>X0^Te&}| z;I|3sEs8Ytk=9^|-3%E+M2MCFb(5z8k(r4S_x&OauoSqGcS?3ZAK17fyx*oKXQddW zThG0cTf&F-xz!M5#7Yl-^4W@{TTTsTJD5|GOth}OXi3(_1iNwQD!K;e&hdv}~ZIecdB4n$&IhouH ziKiYuan-6q@|)(2`BE+WWG&^8UkL0>Q3Vk+X12^c!?~GUbs=u&oxi{30)>$JHD|Z6 z_3@1%3H(Dj6NM_(H`c>$W!yaComr9(ZNEOF)Z)GTGT8p7Au7md9=OP%tHYG|ii>MZ zH#3eTEp@%;`N`0dOI?>5k2N2tHL2Sa>oEk}!P)~biX1LApVfogL;6D-Y@SHcRwsfk7AIC%(?nOQ#%0IV zPEkkU6~VBJDV~rzBkvTkaV+<;*dY2sr$eb?8klLH%#WA%fe@N;nz1u%j4nSM-B;-y zLyqn~h)OnNhpP*R@^(B9$)S$AYC99A-?!6caSYZU*#3&a@`2}5`HjH9WkECm8PMku zW0_%gQ%%vr^qo^+Fo-Va>G5f{N7Rll7@V5Z$XxucpeybHiZ@U{hdp1mrs(AjyFfQe z1!22cj#(xqyBFu{Q~Jt-v*Wgxdv5H6*#Oc-;9;4b_Gr+#>z-w7ZDB z$M4`Dh}WIAMPw8Gco|4`0El(2?B??wm#o&vE2Hzj<$!~mOKI@iKKb#?7xOsNvovV^ z{$3z0fSJ0$d^`R&La*%Qjy(eX3fnb!~uB46q6R)k>e7b52C zCbpOBp-#%JYg(oGpn%k7@2suc%9gB?I2DbQBY0<5@Nd_k-9K+%&@N4%ZhgMJhffRw z3C17*?05IHpSB49LChE5h)#z@=W#n+)^aYV+ay>-KJZ zy@)^vGFyfD+M)n%z#jtMU8p;>+OzOeWJms7Pf1@}H>}Xu6*>$wE%OJmT4?+xy3hL}vz1znPQ(%wO~XuhXt6EzuOdFRl4z6U5|(bx_^CIT zG*Cb{20_dw>1BlFqQ?h{yy) z$Ew-L;6rwCCdY8bb@3q?c;j9Grc#;QUvef`G_+X=AdNjKo0##*j|*m9LCA0q`Cp5 zwUUx*W({eKrY~<3@FSX1IAP!Z=mB0S(BruD6a{r-d2QjUc~_M~JAq1w`?hG6HU(w( zZ20O=m3$qrtuvwhyneg2ff8CZ&zFm`&P4A^%Nq(JenFSZc-`rEv#r(T?fHn3NZk(5 zQ8hZYXPDe8;h}-{&;ZlXh+%WBSYo309b?kmYPH2*-Pt)sC>HUVrw(;sl++gHz`-e_ zv&?B$-b^KQXkN29Mm=N*@DG&rpr(HlXb@T0^DxUF;E+3uMuLLFTom-=1(j*V#2^DD z2{C1De1rwBvK;=%MDduweq>EJHnTpyXshIDPypr9Tlqm>&y6_bliK4$9C9P;e{f9w zh}{Wq@ESJqG$sx0p;TvU#W4m^IH^4-X#+x9m`l2g5Vu&hoSw67?>@N+^(UymO<<=m zI2}6BSauurppC)SFPWaQPhZ~h?n%os6)U^B5iuk-eS*25Q zQ5Ns=Y+b1k1tRnHX+=GEiYw2SE5#dp@tdp48T+z|YpFN;$!}LxJ~wwVmp#vwS*%B- zgvZ4-{YY6MdU@fv8qW?bfn@~ydNw<)$opN}lkx!m-O_7W8cJ>jk*f$Rn#RMNU|lkr z8g|vdZ3Qs|L3}ERrL7@0_NDkC1CHm`??h%QuacK4=yq_VL-xqBHZUD5g55IzhYr3D zgMY!$YB&MbsmhZ6h=f%(j~r!s`HN))Fx6?F&~e47xlE%(C_Q}3i|)J!*XW3}XB}~0?sTL8%sPh5>A9238#U!8G2f)08T1N(%{gQyCijQLn)@qDIcT=R zXenIv$(&@Vv>@+>5b{>=4|%;KlQ2FJ;3bJLj4<4tK9pPT?hU594or8Re+i?|s@0K# z4j^~F6}>rlcb}UJjE;Q?CNCPV<(wnUc4z9~e4QW}39Chskl_QriZXVlqWMf4F(l9|%~=mn zuQ4H%YMgrl6JxkfKJ4q+lHWU0R4`w!(H?;vtb{NkX{wRuw<%YHi_XPGeatvr&O}!6 z(YmT}wwe6r;+Ca__3E;!Kk?8^vwE4`%b7IrJr8|(7KK)ursO#M;-hIHk+n!bNWQOz znzhy?F=IndU!$dA1Y;7`*a<2Ym;|(gLNyZ0lUB(qtBpF?^f_|irkH$}zcPnxQRi__ z>=D${GO?Aacp`93F}i|+O^4hD8Mje@>m+&M$QAL^nT-4End&tUI+p~O?>|IV@ae_y zh_+?%t;a3P3%fUzl$P-=opR8p^U;>AH1Gfg#!jQ2st?ghr9uK-P#SHz38PoP-6$2^>1IM+oe|vWF&%<6FMs)PmB5+AQ`XZ zg4$Cepd8Fi6;=ui&sv?_IikO$+#iV2@VQ@HzGgvr%yRnvj=<9g3%8$s-=`rrh2mO; zlGh&&o|Nzl-_cVQHW}kCg~E_Ff~5p@(vEJNjcKWA$?{_%iLnw2x2W!4!F4;8Ekc5H z?eM5SIwFlNC}tH+Obq=eWrp%&aDxumLs9ln-NVGwNUUhAW9;eYdd@M&LKvaPzr2ZG z(*LP6AthvBqpFUtfy-&f^n@MvueYl3m#+rYLG1QEV0IIFaD1pJLXkHACQUph+W z`DD#n-rzj>v_f|Ke}fm!yGi9L6W?xhIs_ZEVU;_m%h%?>A5smsgEs#QUgT7)O>HhE zwr)keW|q-&pHu=U_d%2dMh&+d=hl^RB^E}G)WVQRP2-=mc4_BgkSaV%u{>w(@164& z6Diz)!<2hQ<<>LPLDw-khGn$2>xBZYON3bt<>?Gn%8HRaY`}E^G^YP5AnLD9mKUA; zby;dDrt3e9*GLa69YcM{e6TQ}$l_k>z>f{JH`fnb6B#RDv3|kT$3Y8T2>M80;=Y%W z@=}(qm##}zr5r|9cpJ~(r3fz9S5(gEEuw927_;}*XmNpK%`DF~pmt_2X)`A?K5AAO zf;OJse9@b7sPhW_6M)yeb^xu-q55YYa$-tR)g0G`SBz=_CNLTTiulr~yDeb)G?1^LH$_mFj;Xict_iW=u~SIRieIzsey)`~?9a^O|PBatP~ ztebuKDz+rAdL4iCbK(_q?0xj5c<=TT!7mnfA3w{8>LJbzkSLyhp0EM=1+<514hEm{ zUa}&A?j-QTK4L!)`kd2Wb$Hby@H3RLzU(~AAM*9)(spk3VJ*S-ZR~4H2~?{ads?-D zb^yFro5LaCd5X0x`FGl6{LHzG@qXW1S+m8B{?T4f@5?v}Zuq?nfzL;$xo{>Wt7Ttj zsws;+d34R?{mB>l^||Yly)NbV4%(d3*fxu(is@m9MUBBNuG}nWFZtKk!QQ#!y$`P- zt@-NZ@sGB!@$c`aWal||pVbfu+eVV7Ht9wbQ~yT8+t_Tz9yIV$R(dB2McheHiva$fa?fk=F=vFT>r)62=FPI#HL96h>!aeBnh$ahZGo&{=1cIqAVVUW6erJ^=*+Bg+(!z-}eeg5J z!-7t9nKy=@LGa7heG!z5&Jkza3N}qBdeA^Vy%t||gB??(E~H*6 zF%}YJLc;^m)<&^ZfxkLRuo@jBIuw_4f)rDX$>ZF#iw=NR?G~5sJU0~t#Z$FqMsgaG z(=K;bT!(n)@j?JxUFe!q?CXlEFu(4+;;~r=PQ}E;_@ClYOznf zz`i!OUm|VFYfb&5O=0<{?aWts{aKl$H4% z%LunWBo2D1V#HlBz><2L`-JrzVwlBaASTl~N{dA%r`6tK$kAEm>P}yAn|<(@W57AD zz(0@L?d_x!`wP>12J5Sr6Bmf=K@HDkvkS>(G!&<{~wmost>4NV3edTe~RuZMA0GVV!533(ui z6~UUkI{$MCfFSm#r_cJU;DYE2(@od+|D^DMkQPfsST%&cx2i)Vo*ex4(q+8oSAO+F z*q0fnCVT&nE_EkKEwfJl1utA{uj?MnE_}O`Nb2Ohe!$`g9GgYIEXuZ73V;l{NR7x}8G2=-A=uhVQU0LOtn;ws7GD_C73@nHK z-V7LV#Ua5f-A+p;xe9(FunT)xM8oCX)JisDUA`=r6!J;9_cWPf<+5d3$Z~WKixJ;S z*R^Cc?(%L%Sps-$#cujVhc83m1Q1-FWDrG!k3bl3!$8a zmd~sl#K#7R(zj=7U6YV*tSW-(i<_kCZ(|8jsbbTHZwe15N&{Mb z#hv_8xK>7%6ZDfjqQ(U)7KNYLbo%dHKGRFj9H$r@m4167w-NLC(IV6Sp)mUC+S%V9 zO&ua{94lDz>*Sl)mYZW7In zPVPo@$(Ee=0n7M+#qk|5-|2Vw?`2Zdk4F2>m8~StBBjC5q03jy-pY4q)A7w+>N((;W$Q)wI#%9V!dd4D}tI~ro(j9`7M?fkUOc+#g_^6|& zxj)9m*|BqV<;3S@7E#GBbpkKLb~!mHqMYuMoWM=jy$a2;NX@cDdd5?$vZP7zg-Il| zOq%clgi53P+u1M4uJ5pbsfX&$FpPV_^s*S;kA>Z16s|SELV@3id9Y=n-TlkLq4Y3o z&>q3#{m4s4f-iDw1zgWyj8A|pO^K$2Kp7O#DE%)Px|kjt({T+F%8H%tShtG7)p&w~ zUX)XRhf^Vt!>skY?}EmT0e$(|J5huz<4gAq6xAcrD+9(p#NRap_z)ih-j^2Z7vW#J z#uzsblh;Kv$n)-7K=g8$v+Zf1Z0;6{P-!a%<&Ut!J3w5FNZh>O@*AN^GFyNR!pr|r z53iA1KMneAMxNHtT~Q~0LSOp;H+>+Q5*Dzt{~TqG8GXdla2R7uCrPuHA*C&QsM2M` zZ5bGQ;v0U0l}Xf+dD`EU^2RNOn;8S2*i-d*YIFkV)&=QDA#wDA**M|S7FQq`GlGYa8QAKI;pAC&+EG5p^rD!i^>vVrpY-{B}!XL&~Szv-!nd#^d zvuSlCRCk0S%-enGpD5*6_**VquN|V7r>NHsqYr`Wg}}HlFphSJt&F1EG-o&lqGyig zQc_kjP|VX*QIb&($0{F5DchPThWn`GX)C|GsH`vnx4os-Ce26LR^&pfg=gT7a#VDC zo+bFdS$cCWkUCcJ2H{hWP9uw^Z$PCIjI%^4$LvO_qyVo*N^c5#1Lv(8FBy7XX<)5( zd55W}v}rPpB)xyx>YY z0`#Vw?ycM{gKWx3ecgWmdNFwHKlqU$Trm#dE+!Or4IhEv41Eqq%;0mn;K~|s<@0!j zHuyf?p!tF@Ob@pF2Vk3s%ReV#<#59^*Uv-Tm@xva*m>X?gZ8sAK{=d}7IH^B4|Y3o zfs2)mmn+Ge(dJ&JKD_M9;0q%zbb7!aWg+<pcY1!3W7^ZpP&OLKYZ|A%;DXT8F;#fv66w$g^PZ#?Y;)rIOe= z3N9z}olhS#3I7Hhv|&5q?k6zwlLM?~Jgx2gY)StJCww+4GS=6B+sq&VDk8kcX0~>E zcHij)curjR%{X`*0X#1DJXh^Q?2URvjQr4C96z~bZ-H}ob6zO2DM0o^fO`w!JC%32 z#n!$q$R;{4_=DJF7w=)BIB)%4&h)vl4<4#D5*ABf{FPy9#0cFGBt8-{#O44YLKfjt%h2M(Ph{s*gf)K_#n0V!Y1x6U2>bxEvnGMK}8 zWuXV|^3Z-F#<}V}-fT-4AqqpNUK(=&SVS*Q%>Iqt?0YdvGy9RwZhHh<#lQO5x`H`k(HxBFpPh?{h*d7Kc-3Z=m=`Ko6WuHtX4L=0D|M z9~U77cb#&w$mhRI9&@OiDf!DZ0?ASq^#~5cR1{}+-|QPRw*Cw0@suL!Sx9o5{qRyd zo~=9ujt+tHSJdvrE`rtWbeUH1zfU=*x4}=}xu@E*`@U$8-{*Ew;O_k~ktpZH)8O6M z);U11qhZMyY0SyQ28U0efo0#VJ@MT-&y!GtolTD(9*C;Qb&1_Y)NdaVF9*1N|4iB= zRnA2@D9_tfb>d{wR}|v-G@JphoA_7*p%*VLD0Zcs)O>3&Cr;6w_=05=}F^ZWtK zNO=@PgeZ5;(QjqV>}KyUiv32z+1h%Qk&iE=t zUCf4I@w9TBC!%4JR?5ak7aR2$<;UsoT?tKU`W(cgT|24~?0lX-jU`*`0^>w{*|!(3VATj>J)eaqhVJoXutehY;S4FMl` zN1p|KDBOL0ZGkw^In+vPp~$}~N8L=US-lWB+3DMAtV#83K0KdAkl@DW5UlwPa{z)4 z+UW8=ZVl7Jo)JwbK7K*PC@t-r1SD8T@M%W8Q)$Ma#OV-iK6e|Jz+a!3ozzMho>0%^ z4@!1mL!^#!W%X{Q2?%(r6J7cK+cRtmLK90jAIN5uZwXoyRBkeCcFMB#oOZ{jtJ#%3 zG3vEi+^8yKkg&``FEi+8uc`bohfx6*FT7i7MK!Y`tiC2?W|CH(O4V%NsaTb)zXg6} zO3hauJfRlt4jKn5Zlj?>ArGLS>&!|Z(!F*0d2hd>s{*1%+T+K<>;{v^kK``AI}x_L zICJs{{3i_T2nmxf^^~7l*ASdr!a*N>Fh71?LC;Lc@p9xa%cgQ(KL3+I_vwu&Tm#h? zHtz&pgW*%GVHJ>_P}xA3X3pV;xy_)LIEAbxlpD3XFnLKPjlA>92be@7e0gq}vM{Sl z55zlqrVJ0M3bnSr?RljLL(vtchj^jUfSLlE|0yDK_sV-}R{Z=>_>L@S`s)?+YH{b| zF!2x@^37->uck!u1G5C!E%w}OopQwYb6LgBiA|lwkBGckm8F(S%k8_v{4oNK0*jjF zMB`SvA$>faN5u#*{ebHU?(@Twd?JIbX{xLC7g`b7IxGZM-U~^a50r{~_DO#*KZA4X zF?3+c2FO8cNx0p!^#I>TO|nRc#>OU4sYNXPyf^Q0tP!ue?Y2~-?6GAQrefEz6{o!V zD7#jq%|KmJ_D48GfR>s%-c7o+sh&zGf84fLX6KY4<)vZlX@nKQ)ds=`ilU**$LEuK zwC0SD$6?4nEj4;D6o(50D<5&+db=uM`mkIZtX!I(y>!VF9}D4%%aV#H%Og2;dT|bg zV4iyn;3bA2$4f2hoKbm|c?v=!!2D^AEYF?9oV3J<4)lO%1dqgoiiB?~OYUZd^nOhA zSA=egf#|%o^N0b8XBntRXUG5zfS^-gM)3XcxJ_%(i%BB=v=YU5`g>3zGB44!N!zU*f;GU=a_`I@ z-XAIEx04d+yK415b=1Cl9N|`K`G(SvQ;|-m&nS4uWP83gNw?>Tv3ViemI4?y^w_|p z7KXN)NprG9W4FMx|2$k?Y-TS+mdM&;gG3CT$l`_Lsr-;yARjj~`jXiscNcz>;9ke)myjmbdxx+8sa@wjOT452B$wu7 zcQRJ*Z4Hh>0S8zntubWF-+d}GIeI~3Z@3WuxhT*f!IXj#86 zof2OOJZS$O`8Msvr!o-(T?=3-uQK84Etc$^qg~=)Ii~&t>U}1Xs_j~OI%%n`G*MWv zmL|2ut;%e#r1BZlDamPAeD%JE0MN8!_-4y_*@(yWgI_z9pH|!xX2c2fRQVL;ak)4O zCpR5ECXarA-95C>IZtK|Uwfs-Fc67d;SSHq+|47*(@Ig`U!AtN0( zTVq~MBeAR4P0FO_1MOU52&Tm_56*_<*9?Ze-7BJfQOdTLm@mN=J6)V^V{jhO7X#Y2 zE-$CZW9bH-ROZ@rLgyOmWyS7{hrwSmK2DxED0nCx4_t$@+XO)a)n>3sP9aZq)Dr)uY^evJapExRqD(7xjP zD%EbePtZ4s5*P(odr5BQoCn!Gu4WO(Q&4luhAs7{IBe+md#f#2>zdX#W(%dyn&0le zzYg#F2_g0{{S%$n5u^MyzmRh0wB$_ZygG~JOzX-1zq3=KRQTYrL56iT8k5G$@n<3s zWm-%+R`oN)^aiTN0qU6)VN?%0&*>WtJ?>R|N~s}8Z{_<8^&wJFO+rQb+I_~_Qx~Q& zebP%Xi(6xFmt{pqvL|V$3b&hJ?D0B&W{i5X9)n2 ze-Cy(ogp@<*CgTl1+3X6f92%UuI)TQQB!)8(8j;eZy#?C7OD(Y$CrYpf*;t>of%m1 z4@g!>r+iZO54rkf;x?-w9x?T^9y~OF%Jtu}zzC{hoU4=&vNo9R!5Itwd^ zj#+OmWW5#+c1jB!Ix^YQMk%sme0V0q!P))W!BtWE4wJpUqEZCf_5&>&i)$pJ%JOAu zwD3g!5(y!bFF;dP=}5_2Fcy#9dXoKwVWGj;!||A$329RUW|Jv5Q~9ex9KAyRne)}v zds;u=B5Ym=gLFjLuDqKzu;OiB7)Te*3A5O^t7Vl2(Q-(vQ|CK!!k4Wjp-PTd**2|^_v$?7LRRl%W32)cNGJ))n>eh&~u&9FE zyW*e}X!S1)Ix2x`HEB4#+2eqvT1}-sxU%)2)XV>>S)hY|uy)y;vHa!^OE+HsrPNB7 z=b^sI8VM;2Tb8cs^rNZP-pGiL+3$b1&v1}BbQ@wzFge^2X8PsYdcrAjB3E50w{=Ng z3GjDiKxGPn>7SeOvcj(DBn;-dC2|uIyx8S%)@)rjh$50Geo69x*r6L<3?pgkP{P>N zxYfP1K5}^nMhVmZ(X69+FDZisxNIUFN06MkI>x$$5T@r-YrBg(X4S2(HC^X7^m*)v z&D~-n+$Zf9*OV-drF0lOb@Upd-bSKy7h`~4n&lleJufxOo)WMgF%~e&)Q}gDcP4nn z^T;7?)WQCp%`(>~#75PrK_Hl}i2j()wUk#N)*Z(UF|KRA@B6FxAQIgVR2y)z^REcR zc)Q*6MR~PoLa3%?*#SNnb&RT5*5ke8H_HASNCPGT(9OKVl9gu`0w6dJ|^20`1I?cEtI7DbmvfN3~i zSzKAZ68V!<<-$x>2wu`k(s%4(O&=rig-X*ciwaWZ0H0xCb1t>q0L59dsv=10L6FJ* zh;FtV2C&^{5+q(pwoAVuiC<%C}B`0<=Xe#lmZ-pF(gYnLbK6Df>|G-_bsFqoBIpK{#+5O?f)`P(AD{}lMu|M=x7c;!?I*kFzj`#nDw0&^cA($ z%O3Ea6AhKM0y~xp|Ngs>%4ODh&QMvf#SOS4cG1o?Huh=-p&DBKyIZy{drhN>ZDO-~_1;0+ zm~7ulZ+kmikCjM7&f2Xm@5BnPMC3rObOxG+)-Ave<_12ZVV#V>>zJk>{^~$i%OnRd zCi1hYkm`sYRmGi`m_Lv~xbX)SknYg?r~b}fU%)kMpUPgK6}wi!;k{`!wY9aRPi))i zzZ48=%cEBN8gXP9Cddo9%pp$BcK=;%rwasr{%>7A$mP;Z=Twz@6)%6VXt4EU5|@OR)Zs^0 zddj8JipbUYwwv`^y|n?g@*3s9KC_QG<@IvXp}1!zHp;P`=(k=OXU4_HKL_KMiew@2 zOWx6ex7~|^g_BqKGqI^FMTLB6|JCJ_5mex>gi>^`YF4K7S$Bas@8$PC=RN(6aT%)d z#1gU82RR%Uw(;4eX-`f=? z7LgEX;<^##;4G9RI@-1ix0v>DH|qcRd=H*U!#X`|+#L>T@A5uT43JZi9PI@4)ow_p?KOodb4;K*X^C z&K@T?qkiaZ!)ev}OPn3&1}S>R2qhlUh}}C%lf=48M7xnQeNF_oRYOn}FQSVVG4ClI zWGqd&;mxaGeJRr2O~1F$h@#=_LZkw)&V z?#SZ|wlJB;AOJ8TZJIh?H(ezHjFwd`tuEA=<___2Rt~*~a#YW|VwuLUEQ(bqp|Lvo zG6h@>h9~)JLb_(GwZb#MvBHh)>zEe;Z?K7ZNi#*(2|s#x`Nmr>WRISl8ntsOM^U03 z5_f_&Xg+vk+Zbsv|55XcRT2>OH_Fm+SL2KMk0-Y6iU|T|FrUSJ7z(VL{L>i0IGF}r z?iS~Yofi0P!&0zZ+{d|cgTUzdJxzfyCuaNjB5D^wKwife=vTb5tAJFpO$uJ~B21x~SK=y+fQZqVKIYpSD)OJ+@m5?7e&|D|$k6`+qa{Bp5+F zv37=*x#tMF6u@K$E=#Lsc6#REPfCQTG}upcSgQM2;l<56 zXT@xm6TB&YCnauQE7_~n*L49{VEM;yk~7%fq?DrCw6maIFe(dGL|RpKZVRdK+dV2f zwox0bkuz4^t2r`%DC2=MA~&fJ2QC?ogQgs%B;N&+2-P$il-uM}+?v&Ntu5*L@@1LC z{eFR7e7!#F!K{G%cR1A{{rbH?lhpk&L{DcOw|frV+XSX*cIgy#L8({x>UYZEU`g>j zKaUF#A#2gB{n?W8qRt^nDex$k>yZJH2@&O_QCK3yYv&i!%+_JUYb;A4*NHd{TzqPA zCY>1H0+gxnYra=q zi&CZ$*+2h?u{=2Fmu!}Z{g6a3Wgi+u2hH-d#+#e3%k zP<7l7O$U84A;UYW?T$Zko8Mioj8no9jvK|^WloSZa_|-#>;c5SKDPk-R**q;U~!!i zLj~9Sl)1JGb-#Uu|1!*%H(Ld9=%|Eev_yOAh8;cCGq)rSxVLu zJ}aWkxM+TU?P-p{pI^Vq996p#x~ASg%Z9TrODW$&6hC;cTFN2KY$#sL$0KIb%{R>- z=^o%zs88LK4)1!iH#ol}rSnbnwMsk3h7ObOk(!ZP>Arq)QIP$#(p$dNgDavD@>#;G zbBJd^v;vW}Iety67Z~{1XH1?JE0{Z%d-(V|afs05oC>yoJ0|nslakSNb#yk=m?Jmo z3wOKFn1$tjQ-o-&5Je{fO-V0d&BV7;n!uuuXbyK;GaSA>CFMVM9pC{wL*sg8;`RC>_`EU5_PL~HQ zSFPl>ah1^&ylR^2v)}Y(P1*3WQn@&q0HAe?3n~GU6>f&u9>Rd2!O}}ZCXB{XJ6W3a zQ-8$zi?@yO(!AK68{7Wxm)ZkZ5~O``?=$W^s2}>3?F$;=7A`g9b|-M_)o+Lu3dB7F zHfI30CWPb*SVM|gn;)_6mxAOgLG(eOkOt6x3+q#AzuP{l0*KXJD5IMN?*zU=`-jEr zpwQw|`v`B3!sW^ywge3b;1Xlf1K;#9fBk{h&A(A*f8Q8mswxp#X1|#N8GdElk-k<9 zG6#S%ciP=*f77*tnj^VOO*ogEfTsD;rGfARZLR}7ZUx%FZ^GSjfjdW&``{u^s3SKc z47!i_?zVu|zk)Sdgw)?uj-5^;Eury}&@yqIT1Bx0bF1HKkS~WtJ{Vj8+)qE&2wSLX#&bP)t@H#`#wH8rw709g? zWEIk`&QC6@!Mt`qlKOEIrCr`otLHGb0mWPaC3{ zcJF`=V~6+7K-H&np{QRuW$$C_#4Kxnk*eC2L_o0>Ah&WMy0R>Vir==(|;L5R1WR2a!$4Z zGq(O%nXpM&)OhOMt=}JjXy72%21xxA9bwX_C$|S^D=bxja$`YCgJN_QtVa!sVGpkK>z)+atz!OJPeYG;B@176;dA{w8mrSpV{HGg1qmx5)k2&;|dGsQ{g2w{? zqfQ4mkPp!t7iCr~FbY?}3>FyAQ-_u)6twm3N}(jdP}4TQ+pubIU*8{#7L+o4vs9_Y zB?+>8syG!jVp$_5-3^K*fpkl?bSpr%AB~Rs6v$#X+F{KiUg+fuo3&HSV)vk5n4t*(Ga^ATUer8cz@c~h^VPzu)Sk~Ng%^4=*qJa7 zM( zEKGB*&FI1->P$~e&PTM;ara(SrkI^vyx7hev$|r27Ev;*hoTf6CT}XO>X#-9K>}LT z%5}ir-r)4cJ+53Cx)`Re|VA;rYo*N*}Zk<>EXjRvHtV?!I+? zo;JDMhVG$_VvuvVnqnKDQ@Dt0+$Ub1HD3Bore|hXcD8wETwEj27Yly6CR4SE8jW;& zNc5V6k&tfULwt|T(hyEOK24)GYG`F_ccb<}_8(!oCN0Hq&-@fN<=bLPgWA){#`&?V zvG=V{5Ud3vtk3vCD(A!^;zVbCjBOQ^5ETKGC0_d|-iu#ddA_+`{O23N;X0do(P)6( z`>%pN^QEU?$TuJND%0C2JE)atsG-05O6!mBl0Tk?7_rGXHam^j;@E7$MahpY=S^z0 zDQJ_PIDdCzB|p53AhE_4v3BgS77RMYl31S&JBRlf9Szcg*~T>a#PSmFeQsX&*H>oa z>XOC)BA(oJ zKEMC}5Zm1IVgVcMymWMO`wsie98IYs-r){i!IsDzithN+a?0jjl;&d#A6zMUg*F?H zdY6a3a^;^F%t*>p&mQrTFB-v49fWzg+~*xOs7bwfeMq`snCr0AuHvF(q#tHG`uq#4 zsv9+hoS|AprVY`lkG{EJ9$sj4@WXPxroAE(6I)E2R@y7hWTuwxS8`u_eksaLNbZx{ ztOAAZwh-|e*HXbpOrR&Lq)kfeevB5iN4`Y9uNl92nwZ&Jy*~|ItzMzYeY1CP=+TFP z>1asoG%aC^dwZ^m;n$T_HvI)J8Ma0T+AYeXvtAZdR^2F(W~M>o(lV4ikxnyx_)?U$ zjcw>hml9hSmenKvQ`771#o5DQHm^GMT2xdS_Dvs^&Y2uya|Ey)?h;_1NLR-6!k6f~ zd$_VG|DY4?>9S+Z`4@OK+0iRNZD^40raF}SOgRm;n!PfQGR~e;#f0vY^V3-avZdK_ zrHiCY^e38SKqpf%+rx?z6;J2otr)9^aSE2ml6s@^z7OQxd6)2$54U>X53@vF(FLg8 z^3NA-Flojw2x>xaA=YU1`sy_`LxmW()G}b~qAJ8wo_0cYsJtRq#WO_9tJgF)OM}kD zTO>>a^s{Sy?C)Gs89PGv)Gjat*2&LHCd(hzwFxa9h0kslBAF>6B|s5_Et)bW8R)9p z_$!^K=4*3sw}$n|^)E)>oBv%iLY5b;x1IWi?Ak(JxqZrVOG&&a0%1h-%cY}!dE6K6 z`Gn89RP&)iMrBvyIL|Vt?cDVYINJssZVatOYzj=HOjA4RpXLlZ&0wqgS60# ztaSVQ^h?Uz!n)mY`lq29Kiqb*rP^;)xz}mNLo3rcvx(mRpjX-Z!uawY+udrMJjU%X zJ|F`9Tv-b>tI}@_K zRW+|lz^7{!k)s9AB#zwz23|CxQ)rsr>^Z?yjvh>DC{k)xH*9dI3$*Md%j5&hgAQB* z=~-3gALia1JCYBRYZfhtEgJQB(C^*nR}6T+6Jt-c^Wm_G_2KbRpo_rAB~J z5v2$Z2#x2VQD6xJdh&)E#ql~`fK^AO%|ML637@d2UrOYGm86)VCdSY<=>vSpb_1KdPi3l<8UDC4p( zE4yB{!d~nKbHSfEo3Fr9Y2o)uqRk7>mAnkEmR!`ZC#R-U%v@C8!%0B+f7P`wE~CUOD?{XWey zB;>+Kj%81RKTtF@L<*5-w`GUhXJp636d(;rIqxC`MT`T3-OC1&UJVJvYcztF3$~vR zA{u8t5JAZ%2UwH8zd%ybQZ*jdN*f_x1nN{-^{~n!5#-NI7R7|oO6hN22*#iGOIcAQVZ~_Euxg4O1tb z&#V83s<#h^D*waB&pe-bG#HPGV+;m^AxTo584{8v zNs{dGkR(ZxB(-P8U_7KDS+#19hf0zp+p2BtnMoz7mDK9-bLdHJS(R;V+qS>6-|uz( zuHWyv=Kne8ea?BmU$6VVU-z9LGTTqGf`cEt9zNcuQ7*Ot)sUmMtU5~>0JESz3ymrg zgeM%Rc&3o@_Pd6nMGtH)4W?D0+j`a?eG$Fjnhb#$K5lxn?@ zWuV&?O{Hxwi%Pq6FmmT^=^>5J2^U~@P{1>sr z-~ZJ@vGQphWWB?*&t-?tdPaAPrSV(s>#i+sE2i7m-rE(P#$9;m=#QWOiT473wR|Sg zW|Iac{#QxcZ1Kv@QjJl%goXdFhDyY-4Uiq}uXZ6-=+81hF3*VUK=sNuev5&j+3@ez zLVt!zFMSip@6*lM^ibUPOrCzi&@$?NXK(oR$Uly@ye>H6*YLWjAWk_X)AVmk>2|A# zjHf^Pm6<&dHT)+~86O4JVfsO+9K2Zvb_5!by(}V^fglVxhJl9Ch#>l@Dmoyi@mqh- ziWk5#dom^BKg+Uvto;Mt%i2Fb75w8l$RAfuEPt>%HSZPQe?lC}yVm5)oN>AC#wL0K zmw(}04=BAhkRI}e1GRGav9jc&V=a#tNkGHGv;QJO z=tR^rBJcqm$$&%YRVw5DwolL>OvjVL#@5-$ZkG51D?4A_S@>x4fFv^ceO1jJ=+-qR z=^FRm#U-#iR2k@;>)KY=>(nKCu9X!{rAGnX>422h@sWnjwZqK=FSIxtNgo3uUKZreD@xdG^TzlaX{dkV=Hm z>p{n~!~t;$;rmHg%FEq;H8 z2BR!Ahe?FeaCd6E{{vLX0G3OHku=Pgf{Lg}&?jhyMwuKMd+4GYvPCquTduIsYS&_^ z?}pi?zkw^88Q}hBtG7J(a;w^HA3RNtKC@bnzU=Pakel+7`M;L~n0QpC(dlkXMM{Bz zw9NFj!R02qtX9;3RdcLNboaOFz}xxvk*prxNX1Yj9f>v)(GPT&Xplb@5K|#<2J){D z9PxWfz~KY#N`~7UF3k>pu=MoMk(0b9=+$JdSAc6vtILc_=uB)& zW%WyDwzg@35+#gn?!_0^2`tyLGuH9Ueux0+(Yx8p%R{Q3YF$KHEXqKmf{hZ zJ|wK!>Ke7fH@9GCbdlYamr~;rvv;!OnA+qTEY?XAD@MYd^xW?D`Q%3OHZCedqT3kg zrZs-wuQ$+#+6P&AXqc_JyA3%qBa2S8$FOc6^b|L5kfZkKoa!T2;alIIBrEJ+UE zki7L-y$jso>e_cQ`utjMT6)ow-?pSSUHixmnPhLTZ2v_+^GgW`RD!;)R`R(ikcL4% zYhq6PNE8*brTSD}Fq@>1_l{Xo2q_hv7XrDPTKtq?(U)N%zIfI)6C79Gz4z$ePP-I$ zLD{2uC!g__wj5m@BEkA3x9VS=XbUKNJsmC$Kl8=2v!_jYlz3@-{%Y#Lq+{eSyOnju zQN4WT_o~yix0joZvj{B+B+%}((hzqV5lAE7H^lFE!i$0l0Ni2&>Z-!rskn?{Fj+~t zu+6b&_8)eNRaXx|8sUK(zR`t;?|Mh?lv#~aQJc!E!9KP98+@!QkI!+`8B^ufNkcQa zlD7El- zEyjXGw_{B?@zS#K{F;b#NqpoM4);?t`6UxOW$K_SxlS-%a zCoeY6XdqTneU1$se$#ROhBx_H7ePaTLapzpjH5x)EtvqiiNibk!qB<-@XO5Sfl>O9 zKeu$vxp^Ur_f*L&;uSgh-uj-u{Dz4t{c`*eccpbt^%Bib9=oqJwYD4%Wv6KWSJsq{ zxs;33YscBiIeC)SVR228rz8M^3!9lJ*rff<jw=#d`7>%_sL^0zJ7 zBVi_V#ZoIDns>I`v6$;boRx270Zu6vhUv_UDf5uovkvCvLM=pJv|(MaH(6@7RROe5d|1H znX5Z~vZ}QPDs6ytcExI`%9Lh+B89X@VZ>(|OejrgK!1QhgVcwDc2kyABG+8@Zoq-| zh;7|*?5?$Q_iv92o@l^@r+yU6Lc;tk=7Mqeibuk%Zu`1MQxK2hD{rPW`0B)~Bksk? zv7+T?4ZBWI><(OsFdt>#VpBXFC9&3~(Te3DV}3kMp8uC(RYeYV(8m{D!~*KAXhc4* z3-$U2*1OW7ST~fgWrjdDcIszArRaMfsvUowJPK4uM>V>nBRq*CmnrNC?$;%9pC#-! zZSpOYUKOs1W)JGzQaF>Gono21-dr+KtF7VIK@f2@1}6<^^oWD)#yAB z8vR?X@?}#3lQ+OH`)4eGSz&EK#ezUewjaIx=@IJlk(|=jetV~ojHv8I^G^3X_K7?s zR3#P459h+at^|^}x2C%V27>hG><7 zO$XR79aquesyqu#E0_d`wLm7e-qC>3a3NHXFD-%iu9W4@uP1Rby8h|gHc2B22{pY z4+SvLgA580GOZya&*Z8C*+SM zR4eCGAFNzA(8_#o8?4RQ*3z^vtN%@tq#~$>x3HoTY4M&x=1?iOCbRhOh>M9EyOr=kTcA z*Bg3x6vUr{&TuGr0tc9y9Un1anTjTu|4cf>BMY}*@jz6`Kdh8&Bf6^Gv&OH#Dap&0EgX{=j=k3S;aeDD)TGFIs^L&rd zGmZ+4^GDnUE|k%ek8O%*o%@SL{SQYE3@|C6GXwe$$n(j>H*jh1GCL?(G-!8a+wHk` zmYje!QlGtb@*n(Pu3y;;%-b+@@nNg6>_+49NUzi1##|Lu<0lIk0?Rl5N3JYQad|FRPCiS1d*`Qlt$@S$zY$rV(-yzG;_~?K zGxiw=?JPC5?n@49EZu6pI(W-+MXQOs*{qnPTFr%vN9059^x+f%d;CaJr2N_Ju-yM; zDO&?cYJKGMwJr{{ZXuh}=|w$ec+o71G^1VwL2|of z;JUwxgmHXrX0i$8(E?Ud>B|H4cR;5VOFwx&nf3dZ*ZW>PUUvLqCaHAmcd8iO;+;KD zWmlj}Tm`j%^v)=Ly?57fjw?+!UzxqC!zHV!WVh^A;66>@@qg)qLeS)B8&=5Jkegan zkOTScsmi5MpV4{GJTWmH&S#==WY@|BqCN0XCYJS`T2u+Y5p>pndB(HNs+>0YGz?5! zZ2Tgq%>1LG=as(wwMX6mfQfnZ$lrJ^^JrTs&6*NguJh2nRpM2Yro?$s;-xgry=DzK z1xxvbS(?I0ZFyjF%Yw~%)|J$@)B0{KhxXcF1TUsFWN3Kf)1}_G*=C5k*{?({C`o1AB^wVnZmJ7n@?B+|X zYOoN?WdS)XB$07;oZe1d&6Nx0Mb)xT9UqPH&A?e`%Hm>kcH0NpX zVg-^9>SI9U>)AjC2IMkPH3LRLxRwT_u@7ujiky|`-t?He%hL)nM30wGk45=C+w`(& z25ntBuDWrYn$?WfG}q0)E@9I(Y*@28%^JBzb=#s@ove2&YX7dO$=$Er9V=kHZ)Kgc zXCd2GaibVS$k)nlnvbg)LiV%;EFdKHRU(zxI1AOWV1#k1(161X2%#g3Qi$-?kTng{ z3eCP(&dFm<%e;rL1e0g}w(q>UX12=Vdzba3pL<(GgY-1&`c%*62~R#(8GyoYW2J%9 z5;$_vKsz7g^mEWs)+%)S(rJ{5XEN9u7WQ@Qy(Xn22n$Z6Pd71Cl`P}e5oMpU@1!v* z3hA{aYr;;fK_VE$UZJm05Zz;KQ5M&DKW^G5cv)RQ*$7)d#dgxwZ2wNRx_b~Wb|G}q zaCm-;aiGNL%o(xBGj~^-w-b(!zqbZ~Sm`?#jupbxj^)1QWd%w!^9-I+VQFC@njY3wl%-O80rE6`#;Yf*TzczWBBzKM-myt3q z{fsId_p_?E1z-^pi%?dd85<~3ItW>p-%U;LahhFKa=CiqO;*4xekpBRXJ5RVNola5 zv$ZhJ%dBF_Q^)?1y!o?4pqA2uC&Ml^GN;=kg}Lq)+!88`_oObc1${(m@C?YhP9{%d7t@9!sDS!VAF&u<1(kY^hI?3!?=nzF;$lnDOG$fI8<78IP+UcgpeE?$wJec&Kod9^rHezw_z#aS|=_Q*V*aHrR6T* zSWk#0s3x);bWLH}S#xvhLxjbOqEtwi)R}0@N`mcaC=LmtXd8Wi#U{;TsuGz1%q!Q<4@a4QTX3U=SG*(7#yob}`ptMWZ%8bb?d77w$L-nF zx2Tv3;Ha2I29hcgilT;sOtlamZO>}#N9}tI@3Ur~pp2JF0(0Wh{wPuFQpZcnwl8um zr|PPRvZR^f9bj^LjyaPurE@G3t59cM(nK6jOG%I9Ymz$a?N}T+V#8EO=oB@qRtoH% zk-f8?Dc=|JO$u~9)G)Fqn9XMVZTD3fbxBwho3}%;-?-LgdGvD`Qdg{dO9jPcRA{dm zm6(A!8lvB69e_)%ITS42-$aAy+=UCE!I-x?L3AAPh%$aw6Ij)J+@?f*c(0vqY^F+D zMn&4(%bJF8N$JURbiVuuq!&I`P!s zs-xaKaZjs6r^bu=N|S}VE`dgQ#Y0f|q?>J+i3BU&Skf@0R-HbJj*LK5%>E1@x1kv^ zL6zD}Ob;lvQ8=^8Dc}O@2UR;tHiWzHh&Z-3|9^n+V8qFp&@}op=_+}fr8QQ0iW-4@ zt&llVRWJ*{ZAeB*01Qd!u!_40*P0yBMkkET*wT<(;gKttF!n(F?j`Hb{B#db_-aDQ z%IbHa5y@*6crZCrGn?jWE1=x>WMNZ|KX;Z34KoW^n=l6r7HdjPpF0;Bg}(c6z+OG0 zU_&?)g?P{4nbWk(7|9`7ic8{_DK1CiMi!j(Smf=Sk@>Z;S#;yd-)+T_y05z~grq!| z|9jA^74U_0rNR#ZAolo4H>)8UCz0v$8Me9vV^f@dVLX=|qe6kKu=@_60&pnG&K0EG z4q*~E-AqxfDYbtYI0owIs`A~2{s7jHwfVg4+&`-dvlAOd4)pm?_}~m#xuwr0YkH0% zOj7ML=cSbDsJT)l$HxT1_!^-?YbU0aKQ4l z^A0G_w`=Y24&Tz{Y$mv0Wk*F}`a#^52B&g^U|;$Z0HWh9<`g1=L*aZ+18PkvXaJ{q z{9@58QQeAr-9k-JCQCO{T?-_0 z)p4>AXlj|6X5D03yJ=fnHUZO-yJqrZC{fyR{Dl2y|Q0gsWEoMOUcP6C8@1Y@D|*qI2y`D0k991tTbsNGHig zSYWx!iREV3ScIgX(}a*^Os5Tfq}M`&1~H-0`m1-E?rSJZIvr&aT2r!+^7JW)y*cGT zX^lR*PyuHF? zYGQEHj-w9mPjDV^gG;vf+tn$gVl7g_?eGj=Lj-Usg$eKB9%m|{GN0dG7q`@$v$A!jw!gU+!pgnC3e|cLnXDUn3e5=%?{{E`8$G$D+ zd`GT~z2&_2OKA54y3I$Hku?V{qT+F8luii){v(t=`ONf`%Ac!H+0ltsQ%!DaJO1DT z!v;CDu&HeIcL15e66C1u!cCfs=Q2)ZT5Y6Nlr_r(qtul94ba{);L98;tYXWuV@;F= zldU)8wp3vl%YZIq(n&U59t|Gsx1wRHwHwb$o##ya!k$@BkmSiJ@yq+u7kXxy5M9dx zMlES*g(ktW1e}|7OtUoAoCd{$>HwEsXF2~BA*MbEa9~7pc7Bg1ub}3y1pVyn{Dlcz z3gpEgYERLI^ddwU#8gA}rgvhwW!A1bXwNvsrkrIs$Y@1-Y$vn*7yvH+am~o223cG# z7;>aG{k*Vj{S)VTb<>825BE4O{P1gJ&J`L~$fx9$uoSTSVh+LELcnZz$ZiZl-&5hp z9Y0JPX`C4*idqkixB4)i3vIMOyGUV(Vfoovz5gkevrHAtUN+)Fm)D!Be+xtr9Y=G# zn+3GmP^%@(S9l~}@i&W(^@%*lkIWGC`@1gCnmUwhqRzG$$HL-vB(^&z!~}JhrM@T6^KqrnTGs87R9ue z2B(`VFIa{HBbM=amQW*)zKGvVr{An)TLfxbVP=EqR77#iX5=q4Z zze8k9B$0+99Z){wSqww0jmIOY19_JF3tt;Bj!DVPe!cSd{fv$V8sbZ%Zv`T1sF=$} z%%661hK4HXSSa1(Xdn?m!~G7Cj0EII2lrAp6xPEocMvfh%jWR7tJ+a)|J!p^1BmR3@<& zlsut7Z;!{59|Qi68%NB}LS~)Ombcx^&U%`?^C6YJ=G*3*pZ)*Kdf!EbddhpSxbG%f z?#SzCD9l`b>+1ERhk!7;U|<2b_vVIlDgo7Zei?Btrvv#Ms3C$#i)Vgh7nOGa~_>2ORtlKx%W zcu!->e{;sF*dhHo#xT_viJDw~iSf%(x?V~{7ckKvvZQ95)Hi{xbSU(JyzU3g_a+gc z0|Kr>F*ku!2C8Bv3%^#1*&83RuMMqlLO`(#^Xm8j**X#~o=^*ixX8(V#2{(d+ZqZi zdgYT##jGiaFI6c02SVN<>1$0VXi(_U;|4C4<%LAPR*yV>=g9E+7cyfiRf&HAkxP+S z38rdAWp1hsU~tU%vdY$u{)dE&hQM_9d|GhM0yKgC6qiKIupb#e@o!-1R1Su?Je?G5sgN$uyvO zj%;f=eWGPtwU<19X`342;6-$|F9fsjq&9>4v_m(i;UiwM71Ds?nQTPICR*4}1K6;R z1s{E+EMz5POd<~i(&*b>vpPOfh}E;ohHt=m3@DJgN=b>%I^KDZqH1E8yrxghP{HFg zIJEL5{v3(82B)!d&Hgf|nv%o@ife!icXdqGkB*&%(y7q=M!0FSP1M+T!QCH~qR+m6 z0Kau(j8d1KO$~Hh6{;$>RB7I}WS90KCQ-% z7vnGs$XJgZCP}GlEbZ%e<{zb23r!7uhak9 z$Av^R6_NOp+7`e$1+u5~(aBuE!q_Rba^UcIC+lrK%V+YZsu4Px{)&i~JndmNDm5oP zatsYKYy7OX{1&h&dB?m(wI`BtH^!Z73-wX-jzEEjNZmxgt~E82e&R?9DWhoH$*#ON z?cI6kcAIDNQP$bEegbiV;z4c#1D%&hajC<@JbFQ3$2j0mVzj?i+ zK5vHNtEIp~3Le5N>ji%s`T$jdNY=Z~_bj6jG-cuv{nL%pq6v4?Bui7Zt<7oL_RtR7 z?6uGaukI_d;*;y3dCStlNQbG^9vVW6iOzq9(EmeQ?90pT+>-0m!QC`8lDXjvW6m?C zM!6`Z_YZX8md$lcK*z4lW1}boS{qXKU_tF+aLE%XE_ykX)93yz>+n`%}(Vq#%*C_$#X%E7*?P~0+ITWMO4KT*R6x(qUBugf&c z+jaSW*|e;R&2u0^ABR;$Z>XU~=orA_O_2S`trwSW-0t+=pNiX(6(^%AgEc{n(yP9L z{TWC^I1$^9RQ_wegSiRwaw=h4UZN2)%7@RCod9rub#i`j@-Gp+$gVoL=>_X!HIzOW z_J}T-+V3uSg~#n)axV{QFb{=HDQ2dQBAW{630m~^{cj;p>s31!ZJ^npSJxgdvb&82 zQTv_%Z%0~TITZ^ifU@ZPRw@xdZ!K*67)C=>^fZ8qN3wXAwgH*o6px7|(mY|BvZaky z$xO>VQ`GBPzA@#6o&uTJdC4hV`x^lpggndOu(fx6OQnpmW&iUcqTO?pOw~~PUtSCw z=3y+h6VRAnlbPB^HhWGSsK0*f_v#bYLp$cJ*thFs*NkXYV*qJcMZc_pX2K*kT$YhS z31~+=_(eto+OLNl%*y+ck%?SiD+|swdX75aaG0xEc&bl-PqK%wd}+waP9kHDjUco+ ztpAZFRAPDs>dcnei@;$8is!>FCQUR-qXL-N8{$}Vz4`2zFvHW9*Vp(P)RW+BUA}~B zD#z32Kowp}@g`Az*osoSiOu7Vb&+|bHMYcQ1y$>dyoQ2~&|BWmB9q+UCMmmiY?o`W!h* z{f)mZ4OYp#guW}8=C;FEt51mo0n_UN*HF`k!L!16?FHA)ncj%MMoWSt`C4E?%=y6) z_T$fkM*RgEV#wFD2p+R`auQDH)Lj@d}$IndXTA5oz(W0ZkXPDE*{cfMP<$qvDu7 z%N@?PWiuQ{^Q;-C^cfCdV-GB18DfG3m^3$zLD8#lXcf(Mg1^q@{ly!-kC~@UbbsewR zP5RcQztSypl{VtouOCkYF5Z)}^OMj|6VPa_G}XdA!jl>lo3mvADRA?oVOIuR{-&VI z_b|pt3w!$EyDy{ZW7eyKE-R2kz6RIZGNt6o@#*S-L<{XyxzZLCTbBscow6XLZU>v}oyHYN<)B{T&VY}LsCpH9EXak;$i8*|>BXIye?7Tm zRd_oo=NfJ6?WGzZ2#oDyQQ!@(>Y5D_bkn|t>4ipQ#d3v2T-G&Rc7wRFX6Q7c98De?E4GS5YWZq? z25-zQZ5!HAn(~S$8JL>2uVkw3-_8!&nMPriJqP6vKL!S|(+x`fW8m>$#M4|B@lx zCKz19f!~;GAf06r1T)7#XL^~v*{pzN-cAj{qtTxcY17Y5Bb!oge`~$3@o)zNKKX1` zw!6XrIz;qYa}Ef@7{F*S#~_2~o$VsZRQQ(05K@wx&ZPucyJ2A<`e!|C0Ow@3AI?$_ z#h6gtUV$y2FP=De)ihb>ThwVM)~agFFI&+0T>>mmlybtE!hME#s|BIG&$v47&Hr$&{|S0 zG?^e@v*a>J32{$^3wX1J0#k{4E)f-yEY={oR+ohd-(eH8;2C>?5>&F20-TCHBduuC zOumgMs04d5yC^%0YiN)&k$bkmKmyOP-Mgf-%f=N}R3|NYW08FTyo)WjfO2!eMv)U8 zxyioY^f~QH_1FGqNy;pn2aq@sCES6AcRSF8$=vuEIin(#qxtAA2#436p-5a9?q+6+ z_91sb!lJ}o+GB~E(!SL@(XlF1WBMAtVaTsof8I=u`!EcAzk|A|9maeWKWW6bRR7WX zGQP*eK+G#18np@rlKGC9UkNs1GdYvQqwKF^bnd#U)WHmec!_qZMPyW+xPM5$PRDK<-e6Y*SJzb;Int41CnK$TRyp?Digdc15ExF2r>V^kj8T%?fVaBQOk- z!0F8F%k?`2irP7)z_9IQIl@=8`xZ|%S!t$LalRd^(F$t{sI4n&9K&3vJ-@x}e_)Xa z?1Abt@7&oZ4p@5m-{YU@deEK(6?danML-zGAeFFyp>s3{HC^##itV_alb1LsGL9y2 z9dkX3vm5yDbRJY9#)M{;BmALt#qVmX^-N)xB^3?)`cA)-7O9LS`$V*mjZ6xWXC}~J z0P%v4zJG6^n!w|H^cJ7!z5@*MVI%kV9!~1#3#Y?wAO*}{CO%)+gNDHS`x*u_2jeJk zC>S?$S!PA(*(5k~rjpv6M}?roVt>X9f`(?XCOX&S*-V5i-80xo+aF|$igs}@Ee~qv zLYKH`>-6Y;q@oBaWCg`3kN6xAsv^@!i7JQ z`O-q@H5srZ#6Fv-0RePX01R>o<8}Nh7d_7b+WruCvrE$1a3>GyCV?h4+{>vFunb=J zE1nIiV%Pj$uv3rtV%1F5X3*n_-htFHnl7^7O|#$PtIDP@&tH)Y+P>pu^1c5d1S0o-q8$hz%7>@**lPr>RJ#POysbD~-O~^G=(D}d< z6Ld;|JUV?VQ2;&QlS%SZe%$Qir-`8fw1I=vv5})3efxeWjy37a9#e=Yrrwg6P)WRe z*xG8dg*X_RlQLWUbheXM!?K&bOrS8C97iCr8YlvU8zEpX6UqLF>}6~SVPo1hei|1a z;=;8#*=Z*sJqWb*Bgffu62^hT8b~vMSAnS`Ad;Sgrm{9&{VM$)7y2ZGJ_>;^Ccq^g zI%d7Te$WTsfKs6&wLjS9B4L6g|}3W2a5VA`R@&NoJdXiDjn^ zOmgwsJ|f)%I%Gv0{gKc-!Sk=8Iu2aBTsi~xJ96G6DFQjp0ALH1%G9sBDK%x;`@bX2 z|CFEmW09fv2PluBN$5ByNn{ym{q$tClAW!*iZ-!;cm`g>A&eaO@Cqc2Ri4c@c5Am( zEyuIi@Ugj9(%2--3$2@1eq#ADB=Gn!2oq+GQ~@w95Pcm3-J9Ot&;5nUgKv8JTi=Dc zuYI`4_qarkatGdxK1wuj@GId^7aPr>Q<7;nzl~FyEX(cr^8jV->Tl$~l&wwr!nkA4 z7=N*Vh+s&Fc~}g~v=GGVUhWHELvcSqSP@naKK;yqb_M;yyY#+l1kGY}_G-*C|M(IaQPxY-GeCVD9;_38cznIoSX89f^pyxnU>?nIS z?K9|VWI&nVmN$A@ABkrU5r_5MXz-Z3hZ~(;)ypPzIamUtY3^nup7HdW9rVx}dTWi~ zTrz9`Bl!5>kMIcJTglG~62PPU>KFakBLVz~M_l4!?e}hUaWlVJbH;e|gKc%=`fYV9 zH>|Pu`9|!!)8zr4myE?Tc~P$~S2AB9C&dvgEzJBu$%fOJD@^9DG~=|B*^+t?&kOO{ ztDa-O5I{i)`xmIIAI#VSB`|~gTzcHOYM}`lqUpPV9$r%T9<}L)x%>7^9$dWh#m)UknRucr5Elt& zB*8f@--|#b?O(i(gS2x`j-;VStZ|$JX#;!gm>c~WFv5zGtRz{3la-t;k!-YMzqkXe zw`Ji4)$q@|(bod(5%;5m4jSo)`vu4Y9(+3so-hI43((gl$UPoRke(qXnOZJV&pp>H z+mg*aoqL*S{tS79tSH%^_U$``xc>LYAKOBs#c269>A; zf%YaKe$>mkg;!tPIY%n1bWGwfM_SvEywq$-8W(*V(4EGAb0`q~*&dn@08jX) zg02e%t1j{Z!UTH3rJ^8y_dDWI`@I=1db$v6Tuhvpo1~-}hD|Tp_Ex+t`*qxRZRXES zd38t#6_Ia_>S<*C`_0uY+3-a!`~&+Lm2ClP3m#x+YZ!QVmofy73uN|BFo3V@q%#+8 zK`gjFhcF88_a?xf0X=)NR$=`6c0WS*K0u>_c^BGND9JEF0rFY^7=od1YNHZ6fFHMh zgLB^Ia6|3cC*Oz)2-C6I9V?ga`E#t}rFxN(iVB!d6}nn4djYGjdDzCT3Wg4Fju{7I zdcN{GxKuL!SH5nEq`ItcJV|yuTp|h!5g&?a4YA>bBv-52<9)3w%V^_kp%$gnQIn0q zW&hB%^S_OJ{$b^%$t*vceazrp#Ov+z?f5S~tn3`xKF{`Manm7T$y&czH?Q6?ExfqS zqwYot^?qD=faJhH@v*Qy!_{FU|FHJbi;z@>!~Uxp zbV#4>YFEQ@;_*?27gNhTT_#OvFZb9AuNP`=}NSy4;3Z_4e>Tx2KMm~ygjc!N*jLqszAdA=Z(4JaOX+ zm8-kdBVDuEl>TU|(#EDm5a+3?-@8IQ)#Jkx&-8j!P+QM?^^1W{u`Va&s7<#cp>tsy zJ=>jiV|c0^gEEmr;#m2pO{p>cjdK6xnfLPr%{>Eheljr-WJ1%AdRIQ}Ap-WDfv@`R zqrvBW_)c;CF6Gppo_JR%r#d|=>jc)Gm14Zx9Te+2e0a$=TUMAIVq?;g3$i7#8+dnD z*FN`h-jc;gBGB1A$)^D?&TqQDiyY%QmileopMs)GWPiBwMCUqVRsOa znr%4CZraIlGZ;IKz7-mMizhp|2lwGMzDqRmepcoZk(&Ki9yC3hxA?&G${szigVt;x zwF98^e*MRhV8?^g-^W$il~AxeTUOaRJoFNf?Da{%Iu5tt0nNKMO8)1{w*(033!Fx{xCH(H{CkD z#wOTjl6y<#N=J=Wn&uL4O!|Z&JWf*0U-`Fm%c`LcQ)T)h5uBe3j@e5>YMN4;JC)$l z(6^C8q}o1&RNFgoOJ{O-gwB7U)JF3R>hH*Q$5|_{w6Hb1#&wL57XQE#jds%(lTLdW z)NaE?qd@eHy>oV#h{#vDHU?N~I_ndaor^(*Oj;akW=)&eFEj!RnRkIqB^JY1+}v5x z^2z4%f}HZPJ+tl6Th^MPboN-GyjWgks*#7WlcdfJd4uIX6yX^%Wgh+RD=5T4S~Ipp z*B>-T|N5A15`B{~KXXIU%IY0gqb=)aHoM(+`Vllp?6h#Dh(@+(NqVnhq|N+)a4;ui z{a2>wfSF=+2}iy5Y!Falu32EqGMK(Ef+IN!om-*8#Zil5Pc^Uk+$Ld|4*qC2LWU?9 zvcNOQ)jgNRAGzvGZzhAdX7%YkZv`gR3Q-d!^(NaObC)gATOt%IxY2I57Rq(PiI+kn z6ryXaH(x7r4iVJE*|4Hs&n6{IqP;ftQ-G`Uj@dCptD{zxCD33hF9YOB4$v&q)W?Jl z%y<>w&$rNk^H^`5xy(YZIB9kOEMUap6eMRF;^wi{u(Vh>XhqGSkWLQn2Gyuj2$XM9 zG3~)QC3F&qoNCJf3wdboD)xcVD}|5_y~>s5Sfl`YLU3$c;&ih>M|i z^Nca8n#va)xA3lGDMT#D>vLfcXB;(CY3vh1yX8~0CRCxg4XB+JGLXB^h_2=+e0l5T z&&2e=>BO;Y=^)U4m*!E+GvI+td~|;uZI-{ta>G5OMrgGA!n%5VqIk&ou3oo}la%U4 z7nU??MPJn;X z8euWkVY%7R#Y9^rj9sWuV;#XB3OG8h6$zE*5p<;6t3=;suFQ{RLqoP&YOKh~dZ$nc zEG#94FFVgHKcq&;Q6R$ha%UcItj}*wcdpI4&L|Eg(n~v}H#JI?*MO;#`Ih^<6RlTs z$3kWoA-Z5AR7wlhFJyJjI56c)W6q~A#|}7!m3-0pjH>$6Pc3h(e1o$Uex8i`U$3pW zbLD8dpSK%uQDl_*fOp@>!ceUl83yKMi1VQ1U1sWRcV?s1!z}x#`RP$H1D7~WdkSv7s5KLU0HJxfMrtj)fi_=$Mq@WQ>^vItO+-XK;qq(q=jhj%9t zic(_K=5jv5F(}>?|A!S%ZTFyz1TgW(v+Kdld`h9Dcx~P@%1|&!kyICD7cFdQGH?v5 zay(bH<{6P-;lm&EORIIJt09GG*3;nlz|fRe=B>pkU-l35vJH_vKQlM_Ns5aLEmiP} zd`jM>>g>yVjB)FpD#9FMHzy?x%xtYerbMTqCcdI4f~^h`w*cqKYS@L<2wG905aHI& ze3Mh70sa@WdX$wnxa#geCh^F2PuK36HTCkaskT|lq2=Ji9nZ9~0Bl@SyT@!=RtzL> zH6mePlgYx5{|{U59?$gS$MJ5=?wetlxear_--R^y`(2`o=6=aNm88CNzu!heXmbl8 zBuT2xJxNHCRCB42TP5kH^ZA|IKj%Ci{$>x4&$jpb^?tpcFQ4b|UG;cNp&Ofoogh@d zk^ze8c7ta$vFO%x@inHcPa26SW47vzSkzGytl$WhK0PgUAFmPys&3;jph105iXnHK zpA~C`m-Y+Gn#Xc>ln0d_Pe@GD-eEwqn2BsOqaU3-t;||g<&4FG=m>H)ibO{Oh2hi# zG^K+BlKWNu(tD`?gnk~ND3HKJLA$P6TsHICuzVyymPXdB6>t;eAXR?B8^ zPGM3Ke<$dl0ONqL$pfo17MlDI&aDr3?uDC2#xocQX|F^^NUx)|Xf{gOC`OGk2aqIYTWQ1NSs=udcvQO=psbN8gBUQ8J88Vr z7l~ui5v~B>&mKRZF38&jWi_KIok&s{jCCH$h)2-+(Qa}WP)``G2F|L{Btc@ zntLC^n7IW?g!`Qb@D3OqgDBT0iuP79ne%u74q1u*2CSp)|#5ZFLi zCCzlXRvHJxn8Si5E-7!J`qz=Q;i%*aH0`BmTY&^B8I@n5x&g=pg$PbpBwMFA)Q~TU zAVMn>n*O4$^<6#or+V-S^>1JeZ61xY2#q@!jc_{zdg+OG?diGmjr{6 z)-fEKC19mu^xsxKiDGmCssx6y5d2KZ2f!@eDZFAnfgnA?QfDxZz`$vMawp}oO5mI_ zG(7WP%`b5&vIpE!%3lzl5hyh+d3b;fWyR;R8WEfu#)@;toOg`4gOHph^x3VuEly}u zBZlr+#2TIN4WkNBsGw6;_+$l5+qs|L8 zjXi7A8@7(3&hk<=RH>`Hq&+mF8A76ASi_mqS};)1g`2}etY#{wFrL~@AW_79wQYU1 zh?JX$-CvpTIop|&Ui?XdZCYFl$gZrCx#-q6TAy!C-VKISh51j9x)vcq7pQxkxsWkF|xJPW*vQ z4;gnrGBRU>b}G%uA}ou<@Sqq_k1}O) z=Z+&G%=1VEtVWx{zi*D>cNu?B*|tX1ojfa*}v4y}&I%mE7It{BG?hI7%nJuYx1 zZmJuM<;4B!{PvSSh*m}3=zSSRTQ8}Nc&{me>3)e!Is1vS8S3!6?JfSM-`1-~n+=M5 zl->+R7LnG8CY8V$iMxOulNwZ^lz_5;D-Ve&4-;N85)l|DD8~!Qn2zK!M4Qc^Nh>(u zAwLSiat7U9*OftlfVDxLnhukd@}f3dQA?pz51w5D4<{T&d(I8r6sL5;sSFtBxXw^6 zd@KGRr{0&+k0h0haA*inBT=|1;Z;Wz*`y?Ct|ZhRAJ>>}O-&b!I>x=z!CT|fIgg~Z zpXZ$9rpR#t;kGr2o#c;>U(jga-9#I2vxx}+)Idrs3XewDr=m5*`hXTh?j@|*sta{T zxrp#&D;J|?9GgLeQ#^q)287|j1B51b4X~8SR$3noG*Q?xKG76e#3+Pi1<6~SFP(V= zUrP{~{gklxISOJ@;?G;+?P!9*Z@oU|In4DBREGjhpc(maO2tJ=Di2_IqIg{bIHctj znAfRJvw#-Uhu}CtK#4g&meADCM03A{L$l~wHkf|OeqfqcI#XGC#nlzS0t_Ps#-FKQnT(h@>h4)BGiD&U|4T;S+G6|SPW zgXXp1@ZJ+z*KAUyy>%ET8m>JZqBAX;POFvXv?pIQNVk@`>dmDecGafN^{(B-xSTNB z^wa}9M5tO+toy$e01VP_3`kQ23Z&*{{{hp<@>;5i6W1JXj*B>2_Ak10pVlxP3+#V# z;-r_HkSfpSMTgP2LseLLnew_pA2t4w6~5PEbQ4v=(_xV;T3di_6-K`J^~}d}x?-4g zfvkb#b9#ilLL=9ZY%QVOCw0Z;0rVqySjvF3cfhnRMp_|SG<|XZ9tg8c?Ul}0QmDM0 zU6a|Ri2!=e$Ox{+r9kyuu!T#z!&$ToI#*%T#lk_EG-FwGMh+yrk@W~9x{+fxH8eFI z_g}8LeW6;eUtz3Fj#_(sH6;LqZa_D zU;&hl#nDLRyjseF9=5h|(y$;UepVmN?KN+RHTRm-!PF{rDk_?B^%&bOug;1dG1Z79 zBWm{Nb&)lMaM^6`#sz~$8G|}`q_RPaYPNJ4OBzsSlF5iA+K&J2G%*twVoi%%zpx() z9@-&z|Fz)Ad6bfq_9nN*bE})bZGF9SSseX*bA?%*Old;3YMa@Zv}#TmwNDwzwKJye zgyqc19reQn@ux1@go|fLvCNvdIjaU@x!kl_?YvsTyTb)CoE?3Pxk8sd*1vEnG!K3& zOkqZb@X;X0Je2D&rvJoBc5W@MSHpeju8WST+0IwZ`OU&MHIx~vSxSmM8B5MuMr1zn z4EWh};@BW5<;IZU_oSdX3b(}*D=LKg#5+N*ax~98eqS5I4X>6xQcD_D$!?wOR70-s z-$9|x3x65r_Pd0OK-ruGOfqA~f;Y_!=ZQ`6rGyi62RL)J)LO5VDl8y_Dd)=6z?w0B z>!Pwq>D38cYU@t`tWewDqY|3}uTW(6)4JqgOB!@3#8ODNVvb+XuyWSwWVL#(+1Ls> zOPZa|Cti=BI2XTudwFs2xWtX+&V;CfA$MBgl{t~nlZ=~;$deE z(MY>9=z`sk#DL2*ZqO1>nH5^2Oo8Q<6^kDz3_$wgm5L$Kcr%I_T&gTP&83APKDTk< zIVgV$~7LI~D0uPq%nP8@LYP4@LtN~1Ij0l|F?yGO4{ zqgRM4+Eebc;}M={z%W8Efl=qgQZ?qKQ|7!-&nu-N?YVsH9SW1RBc_yjh_AoLOE|XD zxeu12EPKYRG)mgrP1{80#`K5E~qPq#)a06~y zon#8tgoo8m;39cJlXr%t%%5hT^sUs+AiW3QoLWLBW$LE=I*$Jo?SV=vQif)G2$&U~ z$p^h}oUf_JoGOpr9hB_3Z5#9%;wq+KQ(aam zs!q3-6aP=OJ-5$<_nX7Pn>@@e&;04*CmcgJEgO++3(Y}NLj$IY(!w@SR{*^=;3fp1 znT0(sru~G#jWQ4rLpX(G1p~G7ae-8@>3neO(a%Q<(|444*5^guGESqI9`b3*J;K}s z8|23pg;Yu*@m7dGg0UfyQkaO@K6eRW8lnZE#PM?-Re<~IUP~Pr6W~&Lh$1VfrbSn5 z^fz`xWLlgMY(u#Zr=;Rz9~H~=+NU`xwxFkQ3aOlCz@!o2>zn!#I=L;*P4XWyJ-T2) zYr*n+Q6RK93>VH5fOtrA1Ct$?F(JWCjX+DCJCXr<4(e1s;oBteufJ=?~ex0 z{~(yF&MeS|l;=)#89UwtN@*8|Qg?~m3brs`8vs*e^fIFLp&Ctjv;9-K34w1+b- z)F3V{xrv^o!I z>-;O|0**_80?ir6b4Mo&avR-2u;DU;q%I=Y7k$(WLzU-5ykO`74^pJF4EYcuXE9nK zAY*S4QsSPb6RR@yU}cOFJ%}gvkuz--)S(Y9#$%%Wq*b~UAQ}zl5F}3os|@QwQ$vDX zWM>^$H8SEZM-I67Af;?%p~C#)=6o2Gln#*d>g7+y=QBY2a>wua@v2iPFesF zpX!2SMwj!nGjLo6W=p1!l200J{k7D6gzw3_X|4{i5tnQHOH>C8ss7=;M}NGGwmU>{ zOa;HL(Hl~SY}#ap;>LbP#`%g#m#B-78;^sEK=<=Qn zdcB;OdAp;zluPHK-ChCHLMtSgXJIh?B*t`U8_Hn_D}ow?_}!;a%tHA_DnM>6vw*WP zgFK{1grDsWfq{)W#Z!l8Aw8x1{U<}dAF%v?D;aK7m8e0lCq!I0!T7;!HEKH&bMfq8 zdG>Bc8)V1D<2OeL!f5AD#Q=%Qe_wNVAwV7K&B<4^T`f4O^T#fWrG4WO13M z!o^U5&?wwAo0mY=Q+C+JJYN}rq5~QYYY)vzcpFJSUb6vjIe<0M5#h#2S_W9-N~}Wj zIoddq17xk{P=K?B3Z!8Y;r$;Qo@iCky6LobHQ7BRr3VQr6PR@3XDaeYF_7}<$K>X0Nl z3RvbTrij3G2<0RM2t|=RdCZzmkG?Zw^`dHQ(6o10D&Q;0^}w7*kSe6p_E9!%c3dPB zDX3_p<~vuSIi;t@6wAx%Mo@AAM804Dn3Drs3yi}nspojJ`IL$T&DX&_)e=B2(Hh-^TH zNo2)1kXW%0P$_U5=VoXlL4MHK03HUghJ{E|v$1viXi$$_a_<>BXIiPmKm7hF(Kz4+^kBaiMh?H2{GP!+3(9 zv_XQMC7JS)Kz@!V(IN$@S)?rOCLqNxE{*f6i=k)%TM%pT0hbwuGvmYoEH--ye7GB+ zRCqG^=JoxbUf-wxaxBc2lK?p#xs@ahyr6&v~oQ(xMs#L%Pi5e(D;-ChkcVkV3 z=UDB)A$p$T0jG9XKHordIPbS>(9}lh!uo#&ByY6HKzz7Ns)Zk`k9?2$Dd2k`KqpwO#PF;HA)r^Kk`A6wAE7yur`=KDVanq` zJA^B4_ft5EGK~aH37)ThSPTo9a}N&Hqu#;CK|>1#mm}NpuGFQ^8k|L#k98iMg?@z5?esPC51ydjr_h8 z46Dhau1>mX;T(wp1(>tcSjEhfs9G264br+FYfOAa8PH;j=_6X&M#g z@*sH~sgQ{x0jp+W3gGzyP2zmHaHe>`s`F%B3`Plqerorl`9P=+Jm^SMMi39jlZTOy z^ULO?zL4mkBR_c}(v+alpd|NL4*QsB2VuoF6PLGD}V8B!}n){giraa|1F~L#q#k&Z+OPpgv>{E)A3? z!C@fmSSU{Qd6)-+a^)-N7cNN*Lj(L53^>gVN-0AF7D7@f56N1_sS(aK2?vshX=)R$ zQ0cIQ7avJ&H@(!PWx0e5%Mjy%FKx~Mzl0QTOPV+VmJ^TyVeSN|vKk87!t@xeL`Dmi zsld0|DDX;wUy8&Ju0#YT9!sZPo#r*MD)H96dHCC6(2Achr?0`R0tI5gXz_66m1$QW zz2TEdlu{nrEHC31pQ!|fMT0TYp&=%Ql+s9!98Ahdz!%3$=|cckO$KdO1&ahRZY;G` zH8vxkda8ct!BDb`S8q8DCI(K^k&$!Amtu3&8Z@WJxz%PYqb^@Q@7+^leZW&$3xgbKnVfp<4405;l%aM(bEu~Zpt@U^#aN&}j)58o`a zHTOa=szClNS>eCnpL<v59KDw{@N{#rgJUpo?XD?gwB&5AkMd*xd!b9q9PsN$AG_9*N+}|=6_;QlSqQJ9&YuyT_So zU3TQTaLA>@j9klmZ!#~Oc*sdLeawlr03XN;=Z&ny^qhQz{ZZAo7;C<`b@N#3tBvys z71&P~9==aD#4Ehmsr0y>Xd-d&IJk9ldp}X)@sXP^*LVM1`S0}Of#?5ktG)960V}vf zkcVxZD-L6iq{<@t3(Mc525?W=n1RA|{Mn-ARV}BgPl&DvQMo~nGZn)f&0!VkBf07Wgj(j{mL51m41ZStTe^3Aatwp*fcXu;VFHi zO3r~l;Hs>xwph6WA-0Kj`r6PJpm`zib=)ao?WQQB(xHy9;|))f0=y;>Yd*(Z(n61W zn$I{2Vk(z8PEJ*P#;*9GnDEnt;h4GzvAW^73li2}5yjFTbc?654qxNym7WTQrYPBW zF4QVGXhTLcM_*c8S2$XH@{Rt4cHO42(2I?~M{XiE;m2G)M+gVcqu0hXc&smz!{6|^ zb_iL{3UiSSQIGqoN2i~p9ebu#735o1jCoC-dHE#o=&?J# zm#D&lPhwK835@TT%b)!Vx-l6iJ?|+Pc&avM8SXkQED)nBZ+9Xlz*ofG^^v34LdovM zv*kxsWB&8}#`8Ae<<;<}V^7@{nCfM()(WIQ{qQK(ex}tyqaORPvS=tQFt~Tzc?Rqs zFxjQ2bX3r5;%+Mqrr;DmxHn8Wv*MZ?Dt7XsizDK=1lt=dy?*QZ`L>Kk3|1xmX_tykR}VvJ8w%+PcM=={)Ros=#qs^Myh;$~)eO=fbADEWwGR4my%mhxoDTPfo6 zzEAcPgfJA+o6KM0J_gk0j9A=Caq9{*k3R;Z6Rm`j18fjd27ra5nm?IhkL;jrylxyI za2YsY_*(8wwdh;9O9>CmW#Afer!!HZ;TUGOzSYiqF>YG))YK#W^i8Qc^8~t^otZv# zVd6xLi2Y!O$tpryB{xOPD?LU|?SrBP{zY{)ZtAIFL#3!yQFa8ngF@yB856B#oM*<& z8$wr~3URqOB7rNKtF829Lvg5N4fM!sK@ zteuR`HPw;l;q;%&3a4hWHLO4CBF$?1^X6wAb~1NkHDSFph{0exo+6e*4mWR zm^U3mQD-hc?!lK9=k-pR4wt>kxThrk%yVKjGGUFdtl$o1M=GXi)IIQO0eM}2hC)T3 zx!@X|sR!1=eseBZ^!+I4-f2tx@t%{KU3yT?Q(;`ypc%7HXXx+KERELKM+7UaG+d$( zSBdMVTb%}XHhtZ8)g5?aHjJ&43F2>}A|qxrjA4b}C1o5OvZdl~U)?_^)c7{q{PH1M zVOcwonB)2e+^z$IzLw`EMdl^h=wv{#qTi>&$LFW)_^gP8n)mLxNpY&_0pbJ?b>9t3 zlQ(Z@Bf?3IUfB-c?lf&pO8mB`AYw|cz;I{4B<<4JNq*7c)pha2@pJhT;YQZc-y{Z9 ztg~HXD-A*Ae0NTtj|Ly;Xdne`)EFTA!iD&#M6IHSbFtD`kZIJZ-LdC(24pmJ{no!GT+)>d(>=x zqd~}r`=4b#H1QU7ygJhg6FI3RiqY)W9=2f%e-n(xzMD1j_E#-6CmqffFMq4J%Gvt+ z$rl4wF@Tk=iLr_Y3W0@j$>w>o8nRgK(B`kL`P(o)VsgiRNgsn3!@FAA4z5bk+aWkoRYyJ2HBuEgPE)z}!Q{fr132;6x$xw*AC&Y{vNCK6?{r;(x-A!V>RDu87(EF}5YKfc_QPQiu6+0`{ z8J_gD1yL7Z-AiC!A+%+uH#k@=Rza*;aU&D9)B}9M4IagD(a3F{v9ClaCDFw>G1Et) z93c=3Bm!<3H+>AF@{8gniM6*9M5D(n$3(dxUg7LQg6T!9>T5!3pjASP

SKOPMGZ@E;T$W)e8px_uw*7=5siB5(I*RHs&%NCPdZ^?lB*ACnfN< zmpU&732|QIgLl%^3e6}S5Qt`G3i?-cO7IIcGu1;hsZ9c0i&m$KGP~=8tjet{PAoDU zeKSQGj}oTxv!bmw2lGXwLU745%U>8dQjdvY_@&ekylJVpRA2wp1JT6_(dS}cUCx7< zH3aq}5um)=G+Pwh*AMO!m#V-mk_6KmgiOP+?e+MlURV%~A42BmD#Y`{05ed=;yodD zNzX*PXlvw&)+@v?v4F62kwP62h2v=rZr3K%=0+)sMO9?FU9xW*Bx&QW;sQUjTa4XP z!|nr9As}$2=;HK8-`A3iQ6jsB;9DdE?iB%7ixvvig2zRH`g&gn`L89s3f_{^l)Fh( zI5gji!&G8$1~M<|#!!ii{X}M$Xa}}75{B*^f3Pw>%l#u9A1qWSxscMD7#&Baa@$$Ch9vV%FGsFKfsM?h$TJ3 zQ+EWG5`?5ect^eQzST1u2qmVJ$r@M?Datc$Bm{1+8Pmjl!HVV-<|6r-ne+zfqxiJL zs!y)O1tcckToq;ly^P0Ie60Y~!2frT&`m7}O7&El3h5GAmq+FGy^Xw=!XSy^IG-#* z{7#;qE$>U5>3p4c50xCg5~J*7I|S?zcPMwf$A`}xu6YrrOKc4b)V$@~=oYGKSnqk& z{!I6YSfOv1AH9p{CO*?E3DTIZ(^xLJVrdUk{bl%y#cjm_V?F$cm zwl~tvMZ$}`)$!}6gXATFJ>rUsUSZz)#Gk+;4lh$IQ~k?>vfBMKDKlVw#9QQ0G^W}dn+dT6%$1?N9?&cA`>^FqYhN8wZ z#E) z3>J}N#_95Z2=(&3I^fE0&9{<7wxy>wDcJ*k?iTj$FIDaAj^a0s?fF-p=r{_T%hGY+ zA7ske(k-%N9A66Fmpw^`Tu!y|DpgJK^@dy&4}4wfO>zdm38#NiXc&0Dd>$)iBo!N;Fv!Nj1M-HSW{rg|R2ep%CmB2&#d zk+g)#R24+OL29IEI=e3TdTNiS1l*61Dh2m*DFrO`nGH(WOFiu>IVZOMsG^LqKY@xo z>@T30eFre0`x8dB(Egxa)z$vtuPR@-wsq7{m$uQWsLQ2X@R13HH>DH=a4-Pam$_sR7Ir;ox-m+S zCuZsm(C7Ye`;R2=(3d|q)SrFZlXRW|owshu?Av>^_$cIh(8adM%1=q_xB{-MHhmaq z%{is;T!H3mFsyJ53WkX0Y4+$1GZsYqy?pY2uuSkMr9ku}tm$kZ?DZWzvFLn$GcQL( zhQ_v}bG6XnSFcd7_n$({Cm%kBg>Q~b_K6LTfeOH@&;boo(UiuEyuNCDgN&~Nq&;#$ z(gw=ymbM`-_Z!N^5edirv2AdKToB(+LFNqs| zmD@j4n1@t|2FC-?x-MZ1587~eG@roTZ(lGqq$KO`I#Our2{I=ArHAAk7p!dD$Cy zEEtPPDjDTrbrI6PR0FALHa|B${#i!P5$k8XHRgK_9`jZ1|5u;;P1`ZIBML7?Yww0< zqP<5oiwr(i;Wf^prw#_KGwZl$3NR2&3&!fv!1`>3pS<>YcPo z-+1ePX<#8V4}5TVr185%%4ou=H=Tz^UKU7pYh30{+BuTGlZQ`nNtg-lHQAi}*LMLD zp7~%yb4$FxPGLB6i=zsuv^$~~?tbBIam<0TZahvQ{tjfmOv-TiElMCb zzutISo-w(`8mPO>%RF-JY-G%Y`Da2w@Pyu%#(z3n@3}HNijq=&jM(PFlCloTha!tP zlIE5EIi|NnZovPDCbw8;Yi1vcGFl#+F}@O*`~13Ph0%+>c<+z73x=^15ibu7hKISf z$&XCjmNa~R{EWA5%$;rsYi{vPW?#__&9-E=46oU01nwtn^_%E2+B3(Lu9u;Vcd6Cz z&SH`k(?JUodML-!d_OC{mEL+*^PfjhOmL;P_#W}s2;~g;+x6&X8z4Wbjy)Q0pSB7kkMCZJ}|B1?bz#Th`HL)k2 zR5oM(GJ(<7#8PLXv3#S3u=pRf>XnyyBMyyxuipLeH{5%xBe?HgbdK1vm-D;GQ4^lR z*-i11M8v_!8+HAdEsd6#{Ft>z9o+_>v@cYD`>i@@UE!sBS!kty>RY1qd+pillX0=t zJ|@t+dqStKjs8@ZdSi6t>etH*Z{91ThyTKuyQe14ehYqj!{W5@^ZPQi3+p`^z5HHc zj|BR^JM%5wzY+VxXZzg+b<6Do7;CrcegCiR(*^0mPj;@%3dOL0Y+uQExPQcXoi}Fl z`@zWT{VT7Qk9H(|HE~(lyP^3tal7{C!R@sl|AO}}GylE4BKzcbSKMj;#gxCp!b^V& zuIC3v&HUS@{B7p}MZ|J@}Gkrgx`;>aw;p#hw>SiPKD;<3b zGM1m+mu0y4;SFVl@~qR_q|fZ%;?meZZ!GaVyWX?)Sc~VGqrD%_KRn&;6@E0qvh-PS zm#NThZ=gZ*R=kPC52O0oO`y;?>ez!1|7VX>gidjy{_b0yh};v=N0#>fqcNURF801F0SXZRv0UQ%H8N`3E%3$_q$-n#yC?q6>N@?I<1@TK|^E%_j>1nZu>ryp_c-utt(-Mx7|6l{D39l z3G^{VzO%01y!2l9+4sxuuWkPQk3+zGPw<(O<4JjMv8H89AB>T+qX!g`tnQ!Ffq%5t ztlV=lEE;eo_yLwHOI&~{+zhv3i?hD3MUJ(9>0(*ne@{y^ao#TKqLuqq9*gEZywjue z9?t^B5dMA6hs-wT)6kJyM`At$eIgd@kgRL>F2gU~oL~HOdqWgE)Uk10?b)48QKy$3 z*DLnk+}#qDDYKzp-2TtEOE^Dz5#g}VC^jU21{J|`RlS&&REPbE#<`tGT&H>E=KLgBXc*i!7f)$ z+tda3=jmjF?J8y`ZwOeYMM?UH(_9dbbXL0@j9$J|uSU%zv{r^=0l95h;g$GvJ;83x z7B%AjcQNVv>kn;?Von(S;_ElI%Ivn}vpA9aKN?OmIV9YIy<**lcNCpck{NUX0)1rj zOev(K1tE=3+SmdCCjF)#H;Y|^FJ;~7P7-1;1VYi=(u{tO|76_7XeKweg- zK`ez8|D^ns?1%zGYFG838eBakSOkjLdFj z!Lj3J{9_x|%1IE?ASsVa|3uT!3)Q+Ah1o>m2({hxCB${l(N-3b^tG@)fcRLjf>tGR4By#H27 zN5UZoTxgs@qY=Ae9qfBiwu%;4`J;xiZp9F#MZ9wP`0`vURu?)NQrhyv5=@Vms!HpgQWeUHTHSi*ttQEVj51Iu0{6Y4o|IgkVOC z#()-WHXNhrrFMf;Y-yR(sRMjUYsZI}d;;D{BUMZ1>Z_y-vraVE+;v5p!H>qtq}t?V zhJBav^azlJWR^t3^cuMCi+3c>c0?aN#gx36*Hy<|qVvO3!+UvSCZI9K6qEqt7Qb@BP7 z(kH{pA=&iPFLn9*y`K&>kUykWx$w6M&9})!@KlMs4fSKrWXCutR-COpyyhEq-M_o^ z)cEfsSyb;+@vi5i#s5A2K6&ngYmElaKKrS6lqwpWtQAgLW$M3DJnh><1~PECL#4kt z#!$_SD=`z^&kci5ZdOT4cjsnF`M^i7ho?4H;O);vsb~)zbvwgp@Qm0;AU(-7V&2_O zZm%8*NqiV=)#5|GeQH2>8R2TDq#wH3D1lFybegpXVt(PXLZ&PpIc98 z#V#)zkN7ob(*aSnH~f0ywvW_M zx780w3K_SgREzkQd-?5?A@|pNOnB?CG`~wG?skA>6!)6`qR-vXSBoO$^17wPg6;?{{xty$ z9i0C8kP^grU&p5)^V%lo?MuYQ!zAp7mchXWKjH-Si;y(rNIw)je@Z7Tak&%Ndi=$#-DnHnZ2%aXgN&nh$ zf7M@so#%E79Cq16TjvB{wj6tqJ}J|DUx{wTBwu3V`;*YPn>*hXawC~!+#=C4zZ$pI z$ro+$u66jB(pM@2{smc_K>}oIgF$JJx2Zk`8IIp+8{lDA6@QV zJ*lu|<`oyI1eb!zzb*nOsriG*lj71QVy1&)8vSBmmgr(%J9|>p`8&#)BLzQTl!KyH@$~62g zwDr&3_OQL3Gf-qujwv#$J%a1D=h?yJ8_U_24F)=^#JGU=qvn0qPyTo+U-9Hl&>#MD zgsHXDuOPg530X(_3%&`C-a1ehJ9w5W7PgnR_*W#RRFv5)7CrMXCjD;`OKhL>zOAO4m39LOnRyvIQu(UkIdJ`0 z=JHc4uk%0ot_HN{PNs#)s)?m?^ra-i+i|6L!C(2e!~V0d5x|f#)7@~3a9$=DB8A5_ z4jyyy!sRM_S5L?q9X}L`w@FRUZo=!4qmQlZ=nr}v@wGT=s>N@OJW}V8oboFPcA&HU zR5;c_VmeJ$D_NAiNMKKifDa_>*&@C@;)`t};KxMQ1|nNu{%i8zuNmOj9EI_%Ze@)C zx9$_QjbYrU@n7&|ThkkvXr=hB)ZYAaDKP zuH2z`zB{=Ui}?MTjE7DL{u1#viNl1llt&2xdpd@!} za|6}m#UouAI)9Qk(hjDXRDW3Wp%#WNX5-TbUNixB6PXo++%9o}FGt+J*+jjzd-0Eg z>GaHM>+F5XY&kN zSGcix5U7QKD0anW+}9rgyC}yV8lYRsgHqFu;BP`Jw;%bMyZLjwxeq$5uQ$gi;w{)3 zf^kW1-?fh%)f<-@5DE1@wa5|wZXvU`BTdG#p z&~QCj2LUUBMV4T|q5{Vv*-}o((+KSIO$993+oURRE)25 zg1sQV_%`4hQ%l#F zS-+@r&2d0_O{YeYm}ON{q)=ieC6-faH@O;jTvs~VMrPSoq}f&($B^|^Y?claD|eV; zh6eNoYC0FxGea%ko@68E-7ZGrZOd&}sStJKyF5-zz`=@AMMANvAW6RALwGuDu?idkB$S zzn*3LHR8z8CQE+!ik~hn+frsKq_yoUKpYsGG8#5Wy z>F)AtsSmXZOXr?1Ju#3-(<=SyE|q1^ll>&9>v$(&!T-wk#Xg_+_cP8uT=>@>u8sRe zDOBQgx9*tEKhf3*7kf(1UnJQmhs`TEHU>-q(VQs5x*JWr{V=Y$P@Ag}xJ<#{to)3I|tlolxvv0WP@^Ardzf7YF==j7S+r>i!Hi;gO zQ7&4kRC;L+nKE#yA%8J;!IPI#GY8sYwCv>4TgHDns|S}^Qwzt(G5KUuwHu6)R0nes{ScI?=lR(bY9X!Y&C^`Y~n ztqoCC-&`l7Zr@)1^KZgh>gAc^BjA5$Z+rw(LKm(@w1yn{k@WV+r2Z`hv%-~Fi0x^o zazjV@N|usS@e13}rFcln%(ba~O2cuaNs44XRHx+1UzAmQjvyNviklxA^yRNCS_z8< zma^2kHadEVu%+u(AN~DS*b?NX^(ddTP0`w4k5BwA6)LmFfhYT^p}bk&g6H+mSN^;H z>G0i1^$o;+zmwsUUFyQ|O9vsRgl!o%rz*a`x#)AOelwvcwDP1yQ9zTHvp~R}gplo1x6`d!*Zpiv8NTT8Z9 zEU<=z65bT1s;Q6$`89D33)c}`(cugIYO)P4B_*_=!E~Dv8#M-9V7JbOP8YCqU|!Y1 z*I8bzHIv?`b1#)jEPHBi@8f(lET27nD1)q-uIhSAH@)lYbq!Xyx|Mh?cnBr_$261I z!%W66Oy+vwb(t3YE5sK{dKsx29|^xnj8Ta3#%185zB_8UpO+paq~S&<%QJnkQG(pk z=f*;Nq`#XumcLOwG!AM;xJs0~{BlbNg@0X?o*Z-3&V+BgF1t|gd_lRL8os^kjtcw# z;OZ^BnttH_U&cm{1u<$2#zuEa%f^6_(j_9JOIid(B}R9Oh`?wNkWxWW>5?uH5lIOF zDHY?Rci-Rl-gED{f5CS4K4<6sdOe@7$77he*k9L?dNT!ST@c>tRx<5B;;V(9zC%@- zCF%Z&5Vcr`tVXORWKOE zjDika-kVw?{DrQ$8gvhjTuweqG|q_^zplR*%Or$o0Tm~BrPsqLiuS?N#a31f5~?H0 zHvAJMeMN+MD+D@9R`2-Lt$s_}vF*l7Lv>T!oAhz!j;y%iFV6XahM}74RP#klc|zXt zzZt_n?+PSE;5%XDlwLjQ>YfVrIB#>9m3pFJwP{^}!DTZG+&E}PNpa2S?vowCx)DbiR_YQVmfW#P-7dB&8- z+ov-5k+NoBsQF!Xpae_4VR(zvfj=+Dgpnb+o6Gg+Ndj)z{_)4?+z>|-rw@-r80-(5 zPIJ3te2gCDy^4t7+`qe0U4MOtm?qli(!kV;<4Ax}*iAT$Q>d~+&<8Cm#$7!|5{*DP zUEYI{c^yU)qoA8YNsMpwN|%Ct+NkC{5p=rJR@G>B{{v3YikxOv5FN?ZORJSNv)BNt zAx}K&a-%I=V`&YONmyFzd?$4D@xbXRU5v|+{pja0g9EYE*Sk0 zmE4`E772T6I0-RYBITFb?IGQ|c=Wzu-?zpZ1j%cQAXS@NH&$*_79YC2Wcq}3YmlV0 zhKhJ&cUKx&mRCXl#k*`oXXS)W;}>q0g~Tyl9(O1+-uNg5>#~W~>$5i|Xz^kQ`y?NS z89+{7!p(Xui8|e!8*I5&-tKnV68EnuyYr~ubNCan5gaCCKx5VUi;Hl>rU{bQPn>zW z!#~RR`1ud|^%a|u)Zr796)gE7o2$CqzI? z*X;YQv^}W9Ol;U25ql?GS;q|FXH6<`%96s$WY6QjdG4(r^Kmaj{Zw4Kwyx^>xK1>U zE@t;sP<1@606^ul@*}=Va>SYQjftPbs{EDjO3p&_vEdZ6WF&=IVm%QPfwKhJsS^2Q(}eCDWh-TCyDpp3)oVTsbA1EAu^r%eYW@d ztY1^8h9NU-UxYPlsy)*N22*snt8ULa{*F+yNN&IYJyo}zgfZdpSj}J13dB?$lJuQ_ z@4_5iUON_jBglh3$Qw6(l`eK)fmDS$y3NoWwv!zly-}sBe;|%lwFp$-BjMHKUuaaw z2{lh2rW7VWF{$>Zfw}yG(wtZK(o-R`UpCx@b>mbr@E!~b7{+PUY3)bA^yiYk#>~YA z3WD6(y!48VC6_mnWXM4W6yDD8!(s*31Adjk-yEqpzdbhx$O zPsabiVMFWXQF6|3QY<}u90_wlG9S_dG@EBuQ2=N5nFV@4EK>fBfc!TBi&uP4x>109 z0o?9&bn|AbM@v7(u z8wnx4=~*HwQ?vtywR*PE{0Zp=Q$fA=o|r(tUZnR&bbxFPg2oMQWQrO^8+dbq)L`IGMZYYD~xbnV&5UH`6caTc-8->L;-gPU}zrUqq}Y0{4O$-X+lbO#se?g1(Vn zcdniJglrpBw;Ysi`@lcDsqu9}CipLR?x7rMjT@lKTiL@)QRN1#%bl^vozb(4{c^zx z-SI%}d2rnETYfH0M#oCT^jQnyh<)n^4gR}-)LzTmwI*cZY1%sLpA!YuEtKfiXbu?0R8mX1RdN+PiXL8Ve;_vT3= zOHhZmy69LeGx)&EH0I~Am=rvoR+>YX+taISnVrAX3G!@M$-VkN)Dd#R)f z&XZwO*iE9`b@lTM1M3{au=|<@6KOOx^Nwb$N9B?c3E9sCvSw;*s6Dqzk_8id**csNHp==o8dm2w_|96TTHhi= zK1!ZFp=X;RD&2Hdcw~-R#bTBRMkZ!dnwNZbWZWL}7Wsnq&_U5}aY8ab{ z+Nt6unQ@RZ`FrpFN4m@3o5?{k0 z{?)JhLDOLSC20KX>+P}#tM#3-Y40#c(a;YD6k@NRkM*o0sK``wZZEkwQo7jyz15nc zbT2G1Z+d%fVwO!q^gbHqoiCNaqFH2C+e+4BP9auI)u-@o<7ZP3tKhr8nYDyB<`;8D z<{w?-NHGv<3g?LvhfZ{AG&elG>R#Oa#P5qNpeI`r685Ne4mm6!DxXd5@u7WyistMp zo@Jtq%G5ig1N8_2*tx1c8`ek6BzJ49+%R|RM8)*!K)bhb?m#3P9*!k(=wdCHEvqj-|+$=I#0$6%obu!*sK;;+$72;qO`OyIR-j zFK4^65jQKR@_Od$jNCq#Nl4TS5xWC5I!?q8cPO%&;pD#Ri7}4P`itMcsWvb7_^dGaPsF>+pQNtSNZVkAbv6J7+8EW1Ku;oEd)Zu%&$>hmyN#G1$WKOA_?E&sC;Qd1 zpUazD<^Qle3;%fG{PDKHrO~tJM@8aGw;pwhw|OH(5qpuOi^!I+-e_6Y{?=j2&jFl! zB)4IOnj}C$6WrRs`HNC4!4U}*D{&gS07kYJ| zNpg$aDkb{BQ}swPdY|pdq;Ea1{?Hr`qNQ9n#W5(}EPNPo_utne zjj}h-`97!2z2c=KJw93g5ZE1XS0HQq=ijm?pN-tB9tRkf=Q)Uokk`uDvuRS9@|KOP zLTuo)jJRao@}Q6986v{00&FBm*fmEyTo0g@m)B!Hm7=8Oq14FH%G<%*>mX=A^31;# z_F#F_TnhVBt=~H25pe{`|HR!g71zjC`sK!*jF}UH@~=xnVrzkc4H#8wLS5wOE?MF5 zINK6DO73}}LThf+HV`&9#;l{g!T5-u5NGBPWnjBwBh;?Angb#ziDU_W%eM=&nL=Nc z3@WdA8jKdmJ@t2DNG9MFqH`SMpNjf$BCG8~H1x!(Ib4%{$%Tu`718E-7CHgxfGl<) zr6OmxRMRI*$5_Ue0&)0!tWd06I;dM%%ftFqJ-^stAmoyrtJFvJ*~zAvO}xj}%>B+? zv!@xxcENAmn+z_@akJJjdW zaI^OxHFn4+iks2S=|0;UUKV9#imP{d*~<0E*@c8!+}7o@Km=j>0egkFDTf8>bS>uw zCL;lb3k{nhz~T&&XmoNzRI?pp*F?7wHYiuv+^Vhv{Lm>@>NK;ES#2|t&c;`G*AvUw zd#+wTUc-1Ab+Q9H%ylK3n+5--rlbGW+sM>dzyD_G_0g=9&byg2elat}xYPYyfn2Fq zH}ME~2{!&wD0eH>`2C)w*Um?goWa+f?8tk@NhL=d>)0z<7;`bE0QPLh0CUM1qGmYl zE4I_b{Z%!;9MMLd*wzmgASx*)Rw&Gdx7y=i(6TJtalBX|#-51N%W3`imP!U46LWw? zroY;qWDmbX*76eJh%8ICy(>7?%>@r@k|>Cx{hK=KT(!42(682N^S;tSTaSZcYJ#W$I^dum8wCXqT}~>0Cjgv1b-p?g`BiW4m0k09=MNume>L6czCU(x z{pyU|RK9}t75P637D|K%*Tl5kW04lYtRS+IyMe19{zHYaz8OxkU%e{Ozjz`fbtmhT z9yj6u6>>MZodt*JjS_?QW@s1n5xZGzt?tRNn=Xcr<&tv7cS|vqFK7Ys=iyXPHRZl{;omv{F04vp7#AmAx%>`Nlz|y;)IXFOd z?-+G+*g7WsYX;tzh*NOt^_^vQA@f*ETJ@(7rz|Xmd%*+tF+kl$vy>M+9R}S0KJ@=T z*|qq#)04uG)oX8mnypyz*%gI4TQ+U{G_2uSmxbNekg)`G8O)^L59;`Ays9SWRaNr2 zn%3Mvq!?@J6Qs{MTS_1Wo0znfS2wvWUV+RF_jTc?nh|}Ch7`Z{ZTde+%_?Kw#978} z^6iXAZ#p%GBh7U^(*LLHmkv2HFz7Bdv^oE9$!@jz>=9Xx5xP z@w*JY&V6q-qHT6n45lxBy#BW?YxTe}W&ipUj^dB`moh{7dA_UC!i9UL|Gi3y(!?-b zvQ{z(bCdxf#6Do_H4@8=#&nG|E$gd{)!O;j0cswjTwHVVLL$t~ExoYs``qkkDq%DE zM{FAkx4}oU0jyigQJx{b(xA`*oVW?vN)YGzKVXZx+?DK~=3GEs+D~{@2UEGVugp_~ z{ayDcta~x^bKFAwDqKi=fpJW=FGAW#U(%?K4JXeiR4P8&<_M_~mH{UmwGH|pk6q&i z$895Br=OUHsSBP0(eG#m%y|V9x6o&xAQ5VC{1u(ZiF*#pRuR|){{)<|9P_Z!{t+IF z@B=%2A@og*$Ph)yFuW2hE+!gQTCW0 zJk5(Zev1GCxa7nU@j$@z6zxnrm)sOMsGsv#0El4%#%}V$%VpO)1W4aF%|Ait{U>`x zr2RX5GZY>UhGVKqLEw)oJTxW{2g?O zFl2CrLs-&Yd&49S8=`f@{ZQ)suW(T;fCCcLq7om#OGDm5f9Er8;zHZd#s-4d1A%fA zV00jYv`I_-+Bqo*`2abXJVM@!Hb@o$<42rYaVuB|TV!zy{oxC7lnbu8Xj$MTb79Qa zWQTPk8G}4i+PdgVV=YFAV!Xd;{(J(ZdmX$MW(XTM5{caR)bNcq9dp!lUlMoQDh$Xs zdhF`ir`p@F+*_IGM7#9d%RhL0<=w7IB?$thQQ*hP2%#l~X65*B8z?{<;>ZwzE9F^# zE6)y-oYUA$KiVa`~{d?oopEJq`Ppo0-yY)xLy7qX#c~v+z)HNH| zkv*LoYdx_n{=JR7!i1okKCr7B)3@+pnXanvI8B3?BOqX2Wdp>s7Pp1|sbVFqIL9X+ zDlUI^qHHC8IaE^V>_l;WUA|qAXT6O(^mJ=ZULNjoIn(x{-F}PGA&@VuV&$Xa;a>}t z3dfXt$1NVFL5NJf$T%ac(74x*iKt(L6H#VddM!>&XaExS(a%M_LX$X}2zwlav243J zC1mFPk8LMEbTa}5EQXcuX<7c$YF`yt|1FmjE<0;X+ndBQYk-;Y;yif-XuEEta1AK$ z!HwHgoC!zHaq)y)7j3(RkelQViIAJt5_A0zkN$1gmIaZRhI#hg_Y05ohHLhPgdLO- z^rE%rT9MVRV{|UIp3bVY%g;vd`U|N(0r#jAC9ng1StR6DoLs&#=<0&Y;E-Kr@2{RsYjoS&!)rGE9@7Nyq_tK+4pJC zj|Qp^oA@(_41wQhE-W_Xb%m!E7cDg3S$Fh7}q0{T^epDz589tq~FDLmfGPH4zb7FBO( zy*_Ri04z4JK`z*uXyYS1dpUY3ys^10m4RIbpy=KvJ7Qt&p3L(?oG}4kUQ?&Ui)Kz~B#PK@5L96IR1^6P2^FT; zDS^)G-=7-?eEi>R`+uv}0cQ8V{963?Y_-J>^M313bjBXO=EeXxH5Y@1v86 zfsJe#DJqNVS5`-ErRpJ5^+!91-qAPM*@`G9yXv6t$ti?@Tf9Gg0YJsX`I@U+44e## z2`<(O&KE z*Z$ta^))=qSNe~!yn?xv5hp}YZ%L^1uCN2bw|uSja|vYL`5ba2l7JoXRVCzjmq3bo zq$*GeweF6tga)HndYx=6Tfk-L<_Uni)gsl8_Dy37M_KKWrtK$4K&wQ%hRDwsx3#9C z3xuiXZjVIA*wU-T$8sAV^1y)O_Lud1iajeRz%O^pC|i;OKuM4|w;ntwY>Al5DlVYb z7c}NHFWMMoOL?=U>6ERO;UxMsJ=nc?BESkPp{2OVI%(6VScEv@SbcD*}h+4%4( zP6(8P&B+&DNoo_;D1 zewh5}Z51z~+!VS8(#{4>|GLcjjdslNQzdw3%+#HJlNX z9%e$ihA+<@1Iqn{Hz_j6dk4m{!ZO(yRP(9rXz!63m;^SGi+6Cy#G=dG#Dl2wI4|{h z{rDcVX+poK0%5iE^FKkaX)T7K26b|St{-HGLC?O4bED5H;B6^yp0!g@{4bC@3rw#Z zDC3d7n?shA(qlXkc{_lgaNG(}$&E$E6G#%KWb1~!U__Z|supHnjQmvb=+63=e!*r3 z9t?xjOH)@6x^6@(sr($ZhBRlZ&i2+Lw*Sw$TdX^5laUPr*~-17fK|;j_%ZFqNDU2w zUvSmTs(7Jh9>E`zR1CtzjJAC}10K`pX;yXDSX&jsOgXfl3Vs*hoRzc6lhR>y`KLJX zQ%R(0NoxSJCqn43U~3Bpj)_mG?cj~vyhmSjjr5tijQ7C<0C;#S)mJ?8C}G58zBpG^ z2=i9LlT1-(tB=t$boiq~#&vQ$TMKe(FHAZi$I9B4s&9Sejadv&05 znxVb0@SWo!trh!(@Yctvho@};=_H%dnLoo<)WLCv8Xws37EEY>kWqHqgt90yX&M+J zR5VTYOpw7MZTK+RJcE$ljaG(+mNZw%P-yI5*Eb=$3k=aX6Jk#>9(lX;4OsS(6*nLokVBP!lqJ z8L`z;IRT-UY)w)eH}1E@g0+*T&wPs?o`>baWiHWaz{bTQSY7OIrj#&>&=uQS3#od$5PxEbv?(bau#`%6sjaR7T7 zo#xm>A?(OXg|BXvccWlIP4Q7|EpBw&aOqSTN*CJI$&yz#j(EQ{^TY%XZk5Q;cchi%zI-0j}|74*5 z-3c`Q%R0V}kvK3}Xw{*AE%4jG`-XJg1GfYPr^7yKNlLb6{%V!{poZj;R6JH@s0Mgm(STypPK5{VeH=w(Z~R_T?qX?Mx%tGqro{5A5hqGg{jfcBFfwE3>Pb z?MfBX9Sl^ifw(try{zrh!j34Fe8p(s5Z!2apZVNWp9!(>#zUXLiyGJr#5E=7XK1(u z0+xK+zBA9t&b!}6{wD9d*yrYA5buv>|JyOjJ;g_OzYMeJ_TiTBfX^>^Kb@N9jh?>h zA9s4_lkrz~;&fu*_@~|L2&IDOuA}Lg&kjJ|+z{^v74IFr?e$boK&2`6Irx@DF1}nN z&kX!Kck)W@_{;BuSAKoA@DCPfPANn&BDB`5owOM4nDF;Md%6&ufY9eA zT2~c*4k)1;IA>bF6F+Fp>gc8x8;GdrCB{@Xw1icFE@HtsRIRQwF~a5h;~J6Np@RjQ zKicFLJF%!_&l;<=g9o2KLnW5@NtZwrm0u8*yd(9m;K^Sbi!_iwV-tmMv$~i48-B~V z{LT(~-^fMZ5_0l&K6O_Ug9(!FFHzRDEOlQljW@yeE3tuu%mN8TKm`mQ>$oAKn^r7B zW)!T7KxZXeG_)jFxyuXP2iKlLC(LsjgS0cT^VO;rPsrXQ>C z^Aj4buYzCw?lD0Aj?2!#8Xv>bSKrEWA7|7&pA3*a{`-9VJhehhh@}(@M2V&rstpzj zi`c$6F5e?8y=2Ai^febT`U?TQ;Ub9=P)EIrI(@gywICU2+J(NyK}D7K8@fZ4 z(g)i}d1>39-o@H6M^G6_Xpj+aRz;eOlcCG*WHRjN0F}=ipq%nI7#Nf#tAHh}VdBi? zcbyXYoJ=c-%F@gdeNv9Q?&2HW`&%|F<$p!=rX>>8^}$h>MURW=4;Jfr?Shu^jWC}MDD$->W5dwNW;zL^Tuk*nr~x*S0Kh>)J6iYD8eK2pP9 zj$aE>^@gWV^Tphrd0NUMds`=an=EwFQtMPTRzVB1+bl%fWPxNR^qZ7bntwcsS=j=c z1|%>JEDG<_yhv1$z;mP1+?{ro?WR1%HjmI0nBXSIGyl7;U%bQ8Hi7;756btUvGkDH z20NW&h~T+g&u6G*K2-1k67x>d@}=ZVt<;=(bH6>IYALwxNI38c$CiYyc^&);L1&+x zacW7pqEUD^J8r;!4w$ItZih%6S){L8Xw94-PK3%#NBG%VH4yAVwJ*daAX8CLngfW= zX9&$PWbFt_v&+iX!9M$fHzpFpZl&cj4I6HA3|uxPpQ`jlCFnIPPHo%=2Hhsm>U`Gk zr_Fa7aHPInUm0t7w4<3LeMO!5soDhL!DFJn0%Wm)04+aCX>ZBxr;;kS?v|fJI%FU& zdrCBuw3H?Gc?-#&@CYRby^d>o=Olem-<>Zliv0?uUs%}VHDs`mOXh9nOtcakSWGOl z_Kp^g)#a7PxVVR)MNaf^HR?foQBEa`qW43^kOsSCGL%Cq!e!s}Y>?nW73Azfbf^H> z<*}?7@Vu$!Y$tENomT!CtF8Rnx&B1s0Rnff$B)JB*mQZeP8C6?0a9_%)_4Cdv#>@|2rMMwqdTS z$;$)Aqt|oA9A(%A@D(}PMUAPN)RAerE=AkZHjX-a5v_Ts*2)KhSLz4~0NUis0y|#I zY8QX*XqVLw=S@Iwm}&<+W_UsR5)`GyFP0Qlzx6#-pNqf9DxH&0=+My5Tn5T*~?dC_`80d^dWFStPav*QSD8$`-pmCVn@_YF@_=y)hE?BD1 z?4aL31}nABjKfw*AX8wcv%dHH_HykX!7$5~^TvDAf3>sZ&N4Mc7cVX}t~945*Io-w zKID8XUw5pT*+Fp8$doVI<9(62F{Jtu6ES>+!v>%$_+Ys3f9IJ5)aT$I{}aDSr)_di zDran}7OG`!@jxUDs3|2vKUYnh&$W_lq=Ro)nB%~WlhvJnRx dzofv!f>z~0AOIx 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_tiled_planar_16bit_RGB.tiff b/Tests/images/tiff_tiled_planar_16bit_RGB.tiff new file mode 100644 index 0000000000000000000000000000000000000000..0376e90a7be5f9da5b5882aa5e54af41de695128 GIT binary patch literal 34501 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

9Rv+tv1F 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 C$e*_W literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_tiled_planar_lzw.tiff b/Tests/images/tiff_tiled_planar_lzw.tiff new file mode 100644 index 0000000000000000000000000000000000000000..57cd6094a285b3ca74102c8041053b999711fea5 GIT binary patch literal 159997 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

T!*}83zCT$aQQ#mk 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index d6f4900cd..43e6daeaf 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -856,6 +856,52 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_rgb(self): + # 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") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_rgb(self): + # 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") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_16bit_RGB(self): + # 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") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_16bit_RGB(self): + # 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") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_16bit_RGBa(self): + # 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") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_16bit_RGBa(self): + # gdal_translate -co TILED=no \ + # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ + # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff + with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 8a1460346..af7eae935 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -320,6 +320,23 @@ class TestLibUnpack: self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) + self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + + self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + else: + self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + def test_RGBA(self): self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( @@ -450,6 +467,43 @@ class TestLibUnpack: self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)) + self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)) + self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)) + + self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)) + self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)) + self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack( + "RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6) + ) + else: + self.assert_unpack( + "RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5) + ) + def test_RGBa(self): self.assert_unpack( "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index b4ba283b2..5dac95c1d 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1363,6 +1363,94 @@ band3I(UINT8 *out, const UINT8 *in, int pixels) { } } +static void +band016B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, big endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[0]; + out += 4; in += 2; + } +} + +static void +band116B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, big endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[0]; + out += 4; in += 2; + } +} + +static void +band216B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, big endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[0]; + out += 4; in += 2; + } +} + +static void +band316B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, big endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[0]; + out += 4; in += 2; + } +} + +static void +band016L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, little endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[1]; + out += 4; in += 2; + } +} + +static void +band116L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, little endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[1]; + out += 4; in += 2; + } +} + +static void +band216L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, little endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[1]; + out += 4; in += 2; + } +} + +static void +band316L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, little endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[1]; + out += 4; in += 2; + } +} + static struct { const char *mode; const char *rawmode; @@ -1448,6 +1536,12 @@ static struct { {"RGB", "R", 8, band0}, {"RGB", "G", 8, band1}, {"RGB", "B", 8, band2}, + {"RGB", "R;16L", 16, band016L}, + {"RGB", "G;16L", 16, band116L}, + {"RGB", "B;16L", 16, band216L}, + {"RGB", "R;16B", 16, band016B}, + {"RGB", "G;16B", 16, band116B}, + {"RGB", "B;16B", 16, band216B}, /* true colour w. alpha */ {"RGBA", "LA", 16, unpackRGBALA}, @@ -1476,17 +1570,42 @@ static struct { {"RGBA", "G", 8, band1}, {"RGBA", "B", 8, band2}, {"RGBA", "A", 8, band3}, + {"RGBA", "R;16L", 16, band016L}, + {"RGBA", "G;16L", 16, band116L}, + {"RGBA", "B;16L", 16, band216L}, + {"RGBA", "A;16L", 16, band316L}, + {"RGBA", "R;16B", 16, band016B}, + {"RGBA", "G;16B", 16, band116B}, + {"RGBA", "B;16B", 16, band216B}, + {"RGBA", "A;16B", 16, band316B}, #ifdef WORDS_BIGENDIAN {"RGB", "RGB;16N", 48, unpackRGB16B}, {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGB", "R;16N", 16, band016B}, + {"RGB", "G;16N", 16, band116B}, + {"RGB", "B;16N", 16, band216B}, + + {"RGBA", "R;16N", 16, band016B}, + {"RGBA", "G;16N", 16, band116B}, + {"RGBA", "B;16N", 16, band216B}, + {"RGBA", "A;16N", 16, band316B}, #else {"RGB", "RGB;16N", 48, unpackRGB16L}, {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16L}, + {"RGB", "R;16N", 16, band016L}, + {"RGB", "G;16N", 16, band116L}, + {"RGB", "B;16N", 16, band216L}, + + + {"RGBA", "R;16N", 16, band016L}, + {"RGBA", "G;16N", 16, band116L}, + {"RGBA", "B;16N", 16, band216L}, + {"RGBA", "A;16N", 16, band316L}, #endif /* true colour w. alpha premultiplied */ From 64500434c21d2a038698dcc6c7b38cb04fce01e1 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 31 Dec 2020 13:01:35 +0100 Subject: [PATCH 117/133] Implementation for PlanarConfiguration=2 Tiffs, manually merged from f566c8a --- src/libImaging/TiffDecode.c | 222 ++++++++++++++++++++++-------------- 1 file changed, 139 insertions(+), 83 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 746994da3..f464ee23d 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -321,8 +321,8 @@ decodeycbcr_err: } int -_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { - INT32 strip_row; +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, UINT8 planes, ImagingShuffler *unpackers) { + INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip, row_byte_size; int ret; @@ -334,7 +334,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { TRACE(("RowsPerStrip: %u \n", rows_per_strip)); // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size - row_byte_size = (state->xsize * state->bits + 7) / 8; + row_byte_size = (state->xsize * state->bits / planes + 7) / 8; /* overflow check for realloc */ if (INT_MAX / row_byte_size < rows_per_strip) { @@ -367,35 +367,35 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { state->buffer = new_data; for (; state->y < state->ysize; state->y += rows_per_strip) { - if (TIFFReadEncodedStrip( - tiff, - TIFFComputeStrip(tiff, state->y, 0), - (tdata_t)state->buffer, - -1) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } + UINT8 plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { + TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - TRACE(("Decoded strip for row %d \n", state->y)); + TRACE(("Decoded strip for row %d \n", state->y)); - // iterate over each row in the strip and stuff data into image - for (strip_row = 0; - strip_row < min((INT32)rows_per_strip, state->ysize - state->y); - strip_row++) { - TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + // iterate over each row in the strip and stuff data into image + for (strip_row = 0; + strip_row < min((INT32) rows_per_strip, state->ysize - state->y); + strip_row++) { + TRACE(("Writing data into line %d ; \n", state->y + strip_row)); - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / - // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], - // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip); + // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - state->shuffle( - (UINT8 *)im->image[state->y + state->yoff + strip_row] + + shuffler( + (UINT8*) im->image[state->y + state->yoff + strip_row] + state->xoff * im->pixelsize, - state->buffer + strip_row * row_byte_size, - state->xsize); + state->buffer + strip_row * row_byte_size, + state->xsize); + } } } + return 0; } @@ -408,6 +408,9 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; + UINT8 planarconfig = 0; + UINT8 planes = 1; + ImagingShuffler unpackers[4]; /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ @@ -502,8 +505,38 @@ ImagingLibTiffDecode( } } + TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); isYCbCr = photometric == PHOTOMETRIC_YCBCR; + TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); + + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && + im->bands > 1 && + photometric != PHOTOMETRIC_YCBCR) { + + uint16 bits_per_sample = 8; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } + + planes = im->bands; + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + } else { + unpackers[0] = state->shuffle; + } if (TIFFIsTiled(tiff)) { INT32 x, y, tile_y; @@ -528,9 +561,8 @@ ImagingLibTiffDecode( goto decode_err; } } else { - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits + 7) / 8; + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size + row_byte_size = (tile_width * state->bits / planes + 7) / 8; } /* overflow check for realloc */ @@ -542,8 +574,7 @@ ImagingLibTiffDecode( state->bytes = row_byte_size * tile_length; if (TIFFTileSize(tiff) > state->bytes) { - // If the strip size as expected by LibTiff isn't what we're expecting, - // abort. + // If the tile size as expected by LibTiff isn't what we're expecting, abort. state->errcode = IMAGING_CODEC_MEMORY; goto decode_err; } @@ -561,75 +592,100 @@ ImagingLibTiffDecode( TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, 0)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (isYCbCr) { - /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ - if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + UINT8 plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - } else { - if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - } - - TRACE(("Read tile at %dx%d; \n\n", x, y)); - - current_tile_width = min((INT32)tile_width, state->xsize - x); - current_tile_length = min((INT32)tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE( - ("Writing tile data at %dx%d using tile_width: %d; \n", - tile_y + y, - x, - current_tile_width)); - - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], - // ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - /* - * For some reason the TIFFReadRGBATile() function - * chooses the lower left corner as the origin. - * Vertically mirror by shuffling the scanlines - * backwards - */ - if (isYCbCr) { - current_line = tile_length - tile_y - 1; + /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ + if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } } else { - current_line = tile_y; + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } } - state->shuffle( - (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width); + TRACE(("Read tile at %dx%d; \n\n", x, y)); + + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + /* + * For some reason the TIFFReadRGBATile() function + * chooses the lower left corner as the origin. + * Vertically mirror by shuffling the scanlines + * backwards + */ + + if (isYCbCr) { + current_line = tile_length - tile_y - 1; + } else { + current_line = tile_y; + } + + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } } } } } else { if (!isYCbCr) { - _decodeStrip(im, state, tiff); - } else { + _decodeStrip(im, state, tiff, planes, unpackers); + } + else { _decodeStripYCbCr(im, state, tiff); } } -decode_err: + if (!state->errcode) { + // Check if raw mode was RGBa and it was stored on separate planes + // so we have to convert it to RGBA + if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + uint16 extrasamples; + uint16* sampleinfo; + ImagingShuffler shuffle; + INT32 y; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + + if (extrasamples >= 1 && + (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + ) { + shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + + for (y = state->yoff; y < state->ysize; y++) { + UINT8* ptr = (UINT8*) im->image[y + state->yoff] + + state->xoff * im->pixelsize; + shuffle(ptr, ptr, state->xsize); + } + } + } + } + + decode_err: TIFFClose(tiff); TRACE(("Done Decoding, Returning \n")); // Returning -1 here to force ImageFile.load to break, rather than From 77a1a9aba3cc5a2d4c081cc81cd5c300ad3f9328 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 31 Dec 2020 14:35:26 +0100 Subject: [PATCH 118/133] initialize the unpackers --- src/libImaging/TiffDecode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index f464ee23d..02c1c9f75 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -412,6 +412,8 @@ ImagingLibTiffDecode( UINT8 planes = 1; ImagingShuffler unpackers[4]; + memset(unpackers, 0, sizeof(ImagingShuffler *) * 4); + /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ From a921c01102b046df07809e1f3e3a2fc24d078198 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:00:22 +0100 Subject: [PATCH 119/133] correct TIFFTAG_PLANARCONFIG size --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 02c1c9f75..b3a51f5b6 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -408,7 +408,7 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; - UINT8 planarconfig = 0; + uint16 planarconfig = 0; UINT8 planes = 1; ImagingShuffler unpackers[4]; From 671837840a49613637f24555326747ba3a8ed991 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:41:13 +0100 Subject: [PATCH 120/133] the previous commit also fixes these big-endian failures --- Tests/test_file_libtiff.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 43e6daeaf..c9f7d67c3 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -856,7 +856,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_rgb(self): # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff @@ -864,7 +863,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_rgb(self): # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff @@ -872,21 +870,18 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_16bit_RGB(self): # 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") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_16bit_RGB(self): # 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") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_16bit_RGBa(self): # gdal_translate -co TILED=yes \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ @@ -894,7 +889,6 @@ class TestFileLibTiff(LibTiffTestCase): 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") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_16bit_RGBa(self): # gdal_translate -co TILED=no \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ From daf7b6546e1bb9753062018cc7b0d987e2d66bf8 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:45:38 +0100 Subject: [PATCH 121/133] remove double pointer --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index b3a51f5b6..9b5916ac0 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -412,7 +412,7 @@ ImagingLibTiffDecode( UINT8 planes = 1; ImagingShuffler unpackers[4]; - memset(unpackers, 0, sizeof(ImagingShuffler *) * 4); + memset(unpackers, 0, sizeof(ImagingShuffler) * 4); /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ From fda638befecca25f9eb75ce39600dfc8e88c2608 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 10 Jan 2021 19:29:56 +0100 Subject: [PATCH 122/133] Planes should be int, not uint --- src/libImaging/TiffDecode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 9b5916ac0..07e9ab2c7 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -321,7 +321,7 @@ decodeycbcr_err: } int -_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, UINT8 planes, ImagingShuffler *unpackers) { +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip, row_byte_size; @@ -409,7 +409,7 @@ ImagingLibTiffDecode( uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; uint16 planarconfig = 0; - UINT8 planes = 1; + int planes = 1; ImagingShuffler unpackers[4]; memset(unpackers, 0, sizeof(ImagingShuffler) * 4); From c9ea87ecfd1f486e0ffdf278d7d000bbd78d7053 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 10 Jan 2021 19:31:56 +0100 Subject: [PATCH 123/133] Use flag instead of recalculating --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 07e9ab2c7..bae9b7a15 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -516,7 +516,7 @@ ImagingLibTiffDecode( // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && - photometric != PHOTOMETRIC_YCBCR) { + isYCbCr) { uint16 bits_per_sample = 8; From b1d3f0d5c21a935112794d06a5b70a2cb6c30e29 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 11 Jan 2021 20:57:08 +0100 Subject: [PATCH 124/133] not --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index bae9b7a15..7629aec95 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -516,7 +516,7 @@ ImagingLibTiffDecode( // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && - isYCbCr) { + !isYCbCr) { uint16 bits_per_sample = 8; From f2020eeab454814d57adfcbf4c6e932ded1f3a35 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 11 Jan 2021 22:28:23 +0100 Subject: [PATCH 125/133] UINT8 -> int for plane --- src/libImaging/TiffDecode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 7629aec95..232278985 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -367,7 +367,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin state->buffer = new_data; for (; state->y < state->ysize; state->y += rows_per_strip) { - UINT8 plane; + int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { @@ -594,7 +594,7 @@ ImagingLibTiffDecode( TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { - UINT8 plane; + int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; for (x = state->xoff; x < state->xsize; x += tile_width) { From 169bb4842f10303c6d2c37adc25dc9983cb5e506 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Sat, 9 Jan 2021 15:05:36 -0800 Subject: [PATCH 126/133] only use TIFFReadRGBA* in case of o_jpeg compression --- Tests/test_file_libtiff.py | 4 ---- src/PIL/TiffImagePlugin.py | 9 +++++++++ src/libImaging/TiffDecode.c | 11 ++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index c9f7d67c3..3f2e5dbc1 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -824,14 +824,12 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_2x2_sampling(self): 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", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: @@ -843,14 +841,12 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/flower2.jpg") @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 19bcf4419..24821d130 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1324,6 +1324,15 @@ class TiffImageFile(ImageFile.ImageFile): if ";16L" in rawmode: rawmode = rawmode.replace(";16L", ";16N") + # YCbCr images with new jpeg compression with pixels in one plane + # unpacked straight into RGB values + if ( + photo == 6 + and self._compression == "jpeg" + and self._planar_configuration == 1 + ): + rawmode = "RGB" + # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, False, self.tag_v2.offset) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 232278985..e20f57596 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -407,6 +407,7 @@ ImagingLibTiffDecode( char *mode = "r"; TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR + uint16 compression; int isYCbCr = 0; uint16 planarconfig = 0; int planes = 1; @@ -509,9 +510,17 @@ ImagingLibTiffDecode( TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); - isYCbCr = photometric == PHOTOMETRIC_YCBCR; + TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); + isYCbCr = photometric == PHOTOMETRIC_YCBCR; + + if (isYCbCr && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB convertion + TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + isYCbCr = 0; + } + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && From 4c2dfadf26b1d5a6a86e7dd66968f5222a1d24e3 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 11 Jan 2021 22:06:49 -0800 Subject: [PATCH 127/133] Swap pixel values on Big Endian --- Tests/test_file_libtiff.py | 2 -- src/libImaging/TiffDecode.c | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 3f2e5dbc1..22b641b5f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -17,7 +17,6 @@ from .helper import ( assert_image_similar, assert_image_similar_tofile, hopper, - is_big_endian, skip_unless_feature, ) @@ -892,7 +891,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" with Image.open(infile) as im: diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e20f57596..b1a30f449 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -292,6 +292,10 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { goto decodeycbcr_err; } +#if WORDS_BIGENDIAN + TIFFSwabArrayOfLong((UINT32 *)state->buffer, img.width * rows_to_read); +#endif + TRACE(("Decoded strip for row %d \n", state->y)); // iterate over each row in the strip and stuff data into image @@ -623,6 +627,10 @@ ImagingLibTiffDecode( state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } + +#if WORDS_BIGENDIAN + TIFFSwabArrayOfLong((UINT32 *)state->buffer, tile_width * tile_length); +#endif } else { if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { TRACE(("Decode Error, Tile at %dx%d\n", x, y)); From 4dd288c66c3e07cfffcc0f21e0bc7a9a2f4f2758 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 11 Jan 2021 23:28:58 -0800 Subject: [PATCH 128/133] unify reading of YCbCr Tiffs --- src/libImaging/TiffDecode.c | 291 +++++++++++++++++------------------- 1 file changed, 136 insertions(+), 155 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index b1a30f449..bbc190d27 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -213,24 +213,34 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { } int -_decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { +_decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { // To avoid dealing with YCbCr subsampling, let libtiff handle it // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle // all of the conversion. Metadata read from the TIFFRGBAImage could // be different from the metadata that the base tiff returns. - INT32 strip_row; + INT32 current_row; UINT8 *new_data; - UINT32 rows_per_strip, row_byte_size, rows_to_read; + UINT32 rows_per_block, row_byte_size, rows_to_read; int ret; TIFFRGBAImage img; char emsg[1024] = ""; - ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1) { - rows_per_strip = state->ysize; + // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call + // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip) + // gives us manageable block size in pixels + if (TIFFIsTiled(tiff)) { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block); } - TRACE(("RowsPerStrip: %u \n", rows_per_strip)); + else { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block); + } + + if (ret != 1) { + rows_per_block = state->ysize; + } + + TRACE(("RowsPerBlock: %u \n", rows_per_block)); if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) { TRACE(("Decode error, msg: %s", emsg)); @@ -263,14 +273,14 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { row_byte_size = img.width * 4; /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { + if (INT_MAX / row_byte_size < rows_per_block) { state->errcode = IMAGING_CODEC_MEMORY; goto decodeycbcr_err; } - state->bytes = rows_per_strip * row_byte_size; + state->bytes = rows_per_block * row_byte_size; - TRACE(("StripSize: %d \n", state->bytes)); + TRACE(("BlockSize: %d \n", state->bytes)); /* realloc to fit whole strip */ /* malloc check above */ @@ -282,9 +292,9 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { state->buffer = new_data; - for (; state->y < state->ysize; state->y += rows_per_strip) { + for (; state->y < state->ysize; state->y += rows_per_block) { img.row_offset = state->y; - rows_to_read = min(rows_per_strip, img.height - state->y); + rows_to_read = min(rows_per_block, img.height - state->y); if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { TRACE(("Decode Error, y: %d\n", state->y)); @@ -299,19 +309,19 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { TRACE(("Decoded strip for row %d \n", state->y)); // iterate over each row in the strip and stuff data into image - for (strip_row = 0; - strip_row < min((INT32)rows_per_strip, state->ysize - state->y); - strip_row++) { - TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + for (current_row = 0; + current_row < min((INT32)rows_per_block, state->ysize - state->y); + current_row++) { + TRACE(("Writing data into line %d ; \n", state->y + current_row)); - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / - // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // UINT8 * bbb = state->buffer + current_row * (state->bytes / + // rows_per_block); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); state->shuffle( - (UINT8 *)im->image[state->y + state->yoff + strip_row] + + (UINT8 *)im->image[state->y + state->yoff + current_row] + state->xoff * im->pixelsize, - state->buffer + strip_row * row_byte_size, + state->buffer + current_row * row_byte_size, state->xsize); } } @@ -525,180 +535,151 @@ ImagingLibTiffDecode( isYCbCr = 0; } - // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case - // if number of bands is 1, there is no difference with contig case - if (planarconfig == PLANARCONFIG_SEPARATE && - im->bands > 1 && - !isYCbCr) { - - uint16 bits_per_sample = 8; - - TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); - if (bits_per_sample != 8 && bits_per_sample != 16) { - TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - planes = im->bands; - - // We'll pick appropriate set of unpackers depending on planar_configuration - // It does not matter if data is RGB(A), CMYK or LUV really, - // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); - } else { - unpackers[0] = state->shuffle; + if (isYCbCr) { + _decodeYCbCr(im, state, tiff); } + else { + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && + im->bands > 1 && + !isYCbCr) { - if (TIFFIsTiled(tiff)) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; - UINT8 *new_data; + uint16 bits_per_sample = 8; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); - - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - if (isYCbCr) { - row_byte_size = tile_width * 4; - /* sanity check, we use this value in shuffle below */ - if (im->pixelsize != 4) { + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } + + planes = im->bands; + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); } else { - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size + unpackers[0] = state->shuffle; + } + + if (TIFFIsTiled(tiff)) { + INT32 x, y, tile_y; + UINT32 tile_width, tile_length, current_tile_length, current_line, + current_tile_width, row_byte_size; + UINT8 *new_data; + + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + /* overflow check for row_byte_size calculation */ + if ((UINT32)INT_MAX / state->bits < tile_width) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } + + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data + // size row_byte_size = (tile_width * state->bits / planes + 7) / 8; - } - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < tile_length) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - state->bytes = row_byte_size * tile_length; + state->bytes = row_byte_size * tile_length; - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, abort. - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + if (TIFFTileSize(tiff) > state->bytes) { + // If the tile size as expected by LibTiff isn't what we're expecting, + // abort. + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - state->buffer = new_data; + state->buffer = new_data; - TRACE(("TIFFTileSize: %d\n", state->bytes)); + TRACE(("TIFFTileSize: %d\n", state->bytes)); - for (y = state->yoff; y < state->ysize; y += tile_length) { - int plane; - for (plane = 0; plane < planes; plane++) { - ImagingShuffler shuffler = unpackers[plane]; - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, plane)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (isYCbCr) { - /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ - if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + for (y = state->yoff; y < state->ysize; y += tile_length) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - -#if WORDS_BIGENDIAN - TIFFSwabArrayOfLong((UINT32 *)state->buffer, tile_width * tile_length); -#endif - } else { if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { TRACE(("Decode Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - } - TRACE(("Read tile at %dx%d; \n\n", x, y)); + TRACE(("Read tile at %dx%d; \n\n", x, y)); - current_tile_width = min((INT32) tile_width, state->xsize - x); - current_tile_length = min((INT32) tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - /* - * For some reason the TIFFReadRGBATile() function - * chooses the lower left corner as the origin. - * Vertically mirror by shuffling the scanlines - * backwards - */ + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - if (isYCbCr) { - current_line = tile_length - tile_y - 1; - } else { current_line = tile_y; - } - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width - ); + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } } } } } - } else { - if (!isYCbCr) { + else { _decodeStrip(im, state, tiff, planes, unpackers); } - else { - _decodeStripYCbCr(im, state, tiff); - } - } - if (!state->errcode) { - // Check if raw mode was RGBa and it was stored on separate planes - // so we have to convert it to RGBA - if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { - uint16 extrasamples; - uint16* sampleinfo; - ImagingShuffler shuffle; - INT32 y; + if (!state->errcode) { + // Check if raw mode was RGBa and it was stored on separate planes + // so we have to convert it to RGBA + if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + uint16 extrasamples; + uint16* sampleinfo; + ImagingShuffler shuffle; + INT32 y; - TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); - if (extrasamples >= 1 && - (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) - ) { - shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + if (extrasamples >= 1 && + (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + ) { + shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); - for (y = state->yoff; y < state->ysize; y++) { - UINT8* ptr = (UINT8*) im->image[y + state->yoff] + - state->xoff * im->pixelsize; - shuffle(ptr, ptr, state->xsize); + for (y = state->yoff; y < state->ysize; y++) { + UINT8* ptr = (UINT8*) im->image[y + state->yoff] + + state->xoff * im->pixelsize; + shuffle(ptr, ptr, state->xsize); + } } } } From e43804620174e057774483230ffe0290d54d3d00 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Wed, 13 Jan 2021 18:33:49 -0800 Subject: [PATCH 129/133] Refactor into smaller functions --- src/libImaging/TiffDecode.c | 269 +++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 127 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index bbc190d27..913b0742c 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -213,8 +213,37 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { } int -_decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { - // To avoid dealing with YCbCr subsampling, let libtiff handle it +_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16 planarconfig, ImagingShuffler *unpackers) { + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) { + uint16 bits_per_sample = 8; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + + return im->bands; + } else { + unpackers[0] = state->shuffle; + + return 1; + } +} + +int +_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { + // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle // all of the conversion. Metadata read from the TIFFRGBAImage could // be different from the metadata that the base tiff returns. @@ -260,13 +289,13 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { state->ysize, img.height)); state->errcode = IMAGING_CODEC_BROKEN; - goto decodeycbcr_err; + goto decodergba_err; } /* overflow check for row byte size */ if (INT_MAX / 4 < img.width) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } // TiffRGBAImages are 32bits/pixel. @@ -275,7 +304,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { /* overflow check for realloc */ if (INT_MAX / row_byte_size < rows_per_block) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } state->bytes = rows_per_block * row_byte_size; @@ -287,7 +316,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { new_data = realloc(state->buffer, state->bytes); if (!new_data) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } state->buffer = new_data; @@ -299,7 +328,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { TRACE(("Decode Error, y: %d\n", state->y)); state->errcode = IMAGING_CODEC_BROKEN; - goto decodeycbcr_err; + goto decodergba_err; } #if WORDS_BIGENDIAN @@ -326,7 +355,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { } } -decodeycbcr_err: +decodergba_err: TIFFRGBAImageEnd(&img); if (state->errcode != 0) { return -1; @@ -334,6 +363,98 @@ decodeycbcr_err: return 0; } +int +_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { + INT32 x, y, tile_y; + UINT32 tile_width, tile_length, current_tile_length, current_line, + current_tile_width, row_byte_size; + UINT8 *new_data; + + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + /* overflow check for row_byte_size calculation */ + if ((UINT32)INT_MAX / state->bits < tile_width) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data + // size + row_byte_size = (tile_width * state->bits / planes + 7) / 8; + + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < tile_length) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + state->bytes = row_byte_size * tile_length; + + if (TIFFTileSize(tiff) > state->bytes) { + // If the tile size as expected by LibTiff isn't what we're expecting, + // abort. + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + state->buffer = new_data; + + TRACE(("TIFFTileSize: %d\n", state->bytes)); + + for (y = state->yoff; y < state->ysize; y += tile_length) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + TRACE(("Read tile at %dx%d; \n\n", x, y)); + + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + + current_line = tile_y; + + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } + } + } + } + + return 0; +} + int _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; @@ -422,7 +543,7 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR uint16 compression; - int isYCbCr = 0; + int readAsRGBA = 0; uint16 planarconfig = 0; int planes = 1; ImagingShuffler unpackers[4]; @@ -527,133 +648,27 @@ ImagingLibTiffDecode( TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); - isYCbCr = photometric == PHOTOMETRIC_YCBCR; + // Dealing with YCbCr images is complicated in case if subsampling + // Let LibTiff read them as RGBA + readAsRGBA = photometric == PHOTOMETRIC_YCBCR; - if (isYCbCr && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { - // If using new JPEG compression, let libjpeg do RGB convertion + if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB convertion for performance reasons TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); - isYCbCr = 0; + readAsRGBA = 0; } - if (isYCbCr) { - _decodeYCbCr(im, state, tiff); + if (readAsRGBA) { + _decodeAsRGBA(im, state, tiff); } else { - // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case - // if number of bands is 1, there is no difference with contig case - if (planarconfig == PLANARCONFIG_SEPARATE && - im->bands > 1 && - !isYCbCr) { - - uint16 bits_per_sample = 8; - - TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); - if (bits_per_sample != 8 && bits_per_sample != 16) { - TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - planes = im->bands; - - // We'll pick appropriate set of unpackers depending on planar_configuration - // It does not matter if data is RGB(A), CMYK or LUV really, - // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); - } else { - unpackers[0] = state->shuffle; + planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers); + if (planes <= 0) { + goto decode_err; } if (TIFFIsTiled(tiff)) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; - UINT8 *new_data; - - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); - - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits / planes + 7) / 8; - - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - state->bytes = row_byte_size * tile_length; - - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, - // abort. - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - state->buffer = new_data; - - TRACE(("TIFFTileSize: %d\n", state->bytes)); - - for (y = state->yoff; y < state->ysize; y += tile_length) { - int plane; - for (plane = 0; plane < planes; plane++) { - ImagingShuffler shuffler = unpackers[plane]; - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, plane)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - TRACE(("Read tile at %dx%d; \n\n", x, y)); - - current_tile_width = min((INT32) tile_width, state->xsize - x); - current_tile_length = min((INT32) tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); - - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - - current_line = tile_y; - - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width - ); - } - } - } - } + _decodeTile(im, state, tiff, planes, unpackers); } else { _decodeStrip(im, state, tiff, planes, unpackers); From 1c295bf43c3aab8c6044ee8c323bf39f3b4f5eee Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 25 Jan 2021 20:29:04 -0800 Subject: [PATCH 130/133] Check for dimensions and sizes to fit into int --- src/libImaging/TiffDecode.c | 82 +++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 913b0742c..cd44417aa 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -365,38 +365,34 @@ decodergba_err: int _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; + INT32 x, y, tile_y, current_tile_length, current_tile_width; + UINT32 tile_width, tile_length; + tsize_t tile_bytes_size, row_byte_size; UINT8 *new_data; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + tile_bytes_size = TIFFTileSize(tiff); - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; + if (tile_bytes_size == 0) { + TRACE(("Decode Error, Can not calculate TileSize\n")); + state->errcode = IMAGING_CODEC_BROKEN; return -1; } - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits / planes + 7) / 8; + row_byte_size = TIFFTileRowSize(tiff); + + if (row_byte_size == 0 || row_byte_size > tile_bytes_size) { + TRACE(("Decode Error, Can not calculate TileRowSize\n")); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { + if (tile_bytes_size > INT_MAX - 1) { state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->bytes = row_byte_size * tile_length; - - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, - // abort. - state->errcode = IMAGING_CODEC_MEMORY; - return -1; - } + state->bytes = tile_bytes_size; /* realloc to fit whole tile */ /* malloc check above */ @@ -405,9 +401,17 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->buffer = new_data; + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + if (tile_width > INT_MAX || tile_length > INT_MAX) { + // state->x and state->y are ints + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { @@ -441,10 +445,8 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging // UINT8 * bbb = state->buffer + tile_y * row_byte_size; // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - current_line = tile_y; - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, + state->buffer + tile_y * row_byte_size, current_tile_width ); } @@ -459,38 +461,40 @@ int _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; UINT8 *new_data; - UINT32 rows_per_strip, row_byte_size; + UINT32 rows_per_strip; int ret; + tsize_t strip_size, row_byte_size; ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1) { + if (ret != 1 || rows_per_strip==(UINT32)(-1)) { rows_per_strip = state->ysize; } - TRACE(("RowsPerStrip: %u \n", rows_per_strip)); - // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size - row_byte_size = (state->xsize * state->bits / planes + 7) / 8; - - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { + if (rows_per_strip > INT_MAX) { state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->bytes = rows_per_strip * row_byte_size; + TRACE(("RowsPerStrip: %u\n", rows_per_strip)); + + strip_size = TIFFStripSize(tiff); + if (strip_size > INT_MAX - 1) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + state->bytes = strip_size; TRACE(("StripSize: %d \n", state->bytes)); - if (TIFFStripSize(tiff) > state->bytes) { - // If the strip size as expected by LibTiff isn't what we're expecting, abort. - // man: TIFFStripSize returns the equivalent size for a strip of data as it - // would be returned in a - // call to TIFFReadEncodedStrip ... + row_byte_size = TIFFScanlineSize(tiff); - state->errcode = IMAGING_CODEC_MEMORY; + if (row_byte_size == 0 || row_byte_size > strip_size) { + state->errcode = IMAGING_CODEC_BROKEN; return -1; } + TRACE(("RowsByteSize: %u \n", row_byte_size)); + /* realloc to fit whole strip */ /* malloc check above */ new_data = realloc(state->buffer, state->bytes); From ab24c98491fe9ce345d1f2194bfc25ce2ffd267c Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 25 Jan 2021 21:45:57 -0800 Subject: [PATCH 131/133] Add sanity check for memory overruns --- src/libImaging/TiffDecode.c | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index cd44417aa..d6bc66907 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -392,17 +392,6 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging return -1; } - state->bytes = tile_bytes_size; - - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - return -1; - } - state->buffer = new_data; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); @@ -412,8 +401,27 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging return -1; } + if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { + // If the tile size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a + // call to TIFFReadTile ... + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + state->bytes = tile_bytes_size; + TRACE(("TIFFTileSize: %d\n", state->bytes)); + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + state->buffer = new_data; + for (y = state->yoff; y < state->ysize; y += tile_length) { int plane; for (plane = 0; plane < planes; plane++) { @@ -482,6 +490,15 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin state->errcode = IMAGING_CODEC_MEMORY; return -1; } + + if (strip_size > ((state->xsize * state->bits / planes + 7) / 8) * rows_per_strip) { + // If the strip size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a + // call to TIFFReadEncodedStrip ... + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + state->bytes = strip_size; TRACE(("StripSize: %d \n", state->bytes)); From 52ecf1b142a9d9307411f41823fae7c3f9f920c7 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 8 Mar 2021 20:20:29 -0800 Subject: [PATCH 132/133] Stop guessing strip size and pass expected size --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index d6bc66907..021c2898c 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -526,7 +526,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; - if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { + if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) { TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); state->errcode = IMAGING_CODEC_BROKEN; return -1; From 6eae8fd59213a6a403dd69f673ce596e5b9d7fa1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Mar 2021 08:11:41 +1100 Subject: [PATCH 133/133] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dba9d263a..a2d40305f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Fixed linear_gradient and radial_gradient I and F modes #5274 + [radarhere] + +- Add support for reading TIFFs with PlanarConfiguration=2 #5364 + [kkopachev, wiredfool, nulano] + +- Deprecated categories #5351 + [radarhere] + - Do not premultiply alpha when resizing with Image.NEAREST resampling #5304 [nulano]