From 08c7b17e236d3e7a431ff40f862ca4de2a5c67df Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 9 Jan 2023 19:04:55 +0200 Subject: [PATCH 1/8] Raise ValueError for BoxBlur filter with negative radius --- Tests/test_image_filter.py | 6 ++++++ src/PIL/ImageFilter.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index cfe46b658..ece98f73d 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -24,6 +24,7 @@ from .helper import assert_image_equal, hopper ImageFilter.ModeFilter, ImageFilter.GaussianBlur, ImageFilter.GaussianBlur(5), + ImageFilter.BoxBlur(0), ImageFilter.BoxBlur(5), ImageFilter.UnsharpMask, ImageFilter.UnsharpMask(10), @@ -173,3 +174,8 @@ def test_consistency_5x5(mode): Image.merge(mode, source[: len(mode)]).filter(kernel), Image.merge(mode, reference[: len(mode)]), ) + + +def test_invalid_box_blur_filter(): + with pytest.raises(ValueError): + ImageFilter.BoxBlur(-2) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 59e2c18b9..63d6dcf5c 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -183,6 +183,9 @@ class BoxBlur(MultibandFilter): name = "BoxBlur" def __init__(self, radius): + if radius < 0: + msg = "radius must be >= 0" + raise ValueError(msg) self.radius = radius def filter(self, image): From 173b65d0956e0e5f15c52b0bb46c6694446eace5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 11 Jan 2023 20:02:10 +1100 Subject: [PATCH 2/8] Raise ValueError during filter operation as well --- Tests/test_image_filter.py | 6 ++++++ src/libImaging/BoxBlur.c | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index ece98f73d..a2ef2280b 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -179,3 +179,9 @@ def test_consistency_5x5(mode): def test_invalid_box_blur_filter(): with pytest.raises(ValueError): ImageFilter.BoxBlur(-2) + + im = hopper() + box_blur_filter = ImageFilter.BoxBlur(2) + box_blur_filter.radius = -2 + with pytest.raises(ValueError): + im.filter(box_blur_filter) diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index 2e45a3358..5afe7cf50 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -237,6 +237,9 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) { if (n < 1) { return ImagingError_ValueError("number of passes must be greater than zero"); } + if (radius < 0) { + return ImagingError_ValueError("radius must be >= 0"); + } if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type || imIn->bands != imOut->bands || imIn->xsize != imOut->xsize || From 5a2369fc33818aa85131862ad881cc1135252cfd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 11 Jan 2023 17:18:02 +0200 Subject: [PATCH 3/8] Verify the Mastodon docs link --- docs/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 674b31bd7..a4663bac8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -85,6 +85,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more + Overview ======== From 335cde81b4f813c25bd830fa7cfe2663502bb616 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jan 2023 08:41:14 +1100 Subject: [PATCH 4/8] Updated xz to 5.4.1 --- 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 df39260e0..a34e8b342 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -152,9 +152,9 @@ deps = { "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.0.tar.gz/download", - "filename": "xz-5.4.0.tar.gz", - "dir": "xz-5.4.0", + "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.1.tar.gz/download", + "filename": "xz-5.4.1.tar.gz", + "dir": "xz-5.4.1", "license": "COPYING", "patch": { r"src\liblzma\api\lzma.h": { From 9e4aa4e1cb817ab4b63efdbfbe7426dee2741e67 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jan 2023 09:21:25 +1100 Subject: [PATCH 5/8] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9a267bc9c..bf3017ca9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.5.0 (unreleased) ------------------ +- Raise ValueError for BoxBlur filter with negative radius #6874 + [hugovk, radarhere] + - Support arbitrary number of loaded modules on Windows #6761 [javidcf, radarhere, nulano] From a75a1a95142ddfac63d1a07506362083fd8faa71 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jan 2023 11:49:08 +1100 Subject: [PATCH 6/8] Updated raqm to 0.10.0 --- depends/install_raqm.sh | 2 +- src/thirdparty/raqm/README.md | 4 +- src/thirdparty/raqm/raqm-version.h | 4 +- src/thirdparty/raqm/raqm.c | 554 ++++++++++++++++++++++++----- src/thirdparty/raqm/raqm.h | 15 + 5 files changed, 476 insertions(+), 103 deletions(-) diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 992503650..d1b31cfa5 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=libraqm-0.9.0 +archive=libraqm-0.10.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/src/thirdparty/raqm/README.md b/src/thirdparty/raqm/README.md index 3354a4d25..315e0c8d8 100644 --- a/src/thirdparty/raqm/README.md +++ b/src/thirdparty/raqm/README.md @@ -11,7 +11,7 @@ It currently provides bidirectional text support (using [FriBiDi][1] or 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/ +> https://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”. @@ -81,5 +81,5 @@ The following projects have patches to support complex text layout using Raqm: [1]: https://github.com/fribidi/fribidi [2]: https://github.com/Tehreer/SheenBidi [3]: https://github.com/harfbuzz/harfbuzz -[4]: https://freetype.org/ +[4]: https://www.freetype.org [5]: https://www.gtk.org/gtk-doc diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 78b70a561..bdb6fb662 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -32,10 +32,10 @@ #define _RAQM_VERSION_H_ #define RAQM_VERSION_MAJOR 0 -#define RAQM_VERSION_MINOR 9 +#define RAQM_VERSION_MINOR 10 #define RAQM_VERSION_MICRO 0 -#define RAQM_VERSION_STRING "0.9.0" +#define RAQM_VERSION_STRING "0.10.0" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 13f6e1f02..770ea3018 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -171,19 +171,23 @@ typedef FriBidiLevel _raqm_bidi_level_t; #endif -typedef struct { +typedef struct +{ FT_Face ftface; int ftloadflags; hb_language_t lang; hb_script_t script; + int spacing_after; } _raqm_text_info; typedef struct _raqm_run raqm_run_t; -struct _raqm { +struct _raqm +{ int ref_count; uint32_t *text; + uint16_t *text_utf16; char *text_utf8; size_t text_len; size_t text_capacity_bytes; @@ -205,7 +209,8 @@ struct _raqm { int invisible_glyph; }; -struct _raqm_run { +struct _raqm_run +{ uint32_t pos; uint32_t len; @@ -217,9 +222,13 @@ struct _raqm_run { raqm_run_t *next; }; -static uint32_t -_raqm_u8_to_u32_index (raqm_t *rq, - uint32_t index); +static size_t +_raqm_encoding_to_u32_index (raqm_t *rq, + size_t index); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); static void _raqm_init_text_info (raqm_t *rq) @@ -231,6 +240,7 @@ _raqm_init_text_info (raqm_t *rq) rq->text_info[i].ftloadflags = -1; rq->text_info[i].lang = default_lang; rq->text_info[i].script = HB_SCRIPT_INVALID; + rq->text_info[i].spacing_after = 0; } } @@ -263,6 +273,8 @@ _raqm_compare_text_info (_raqm_text_info a, if (a.script != b.script) return false; + /* Spacing shouldn't break runs, so we don't compare them here. */ + return true; } @@ -273,6 +285,7 @@ _raqm_free_text(raqm_t* rq) rq->text = NULL; rq->text_info = NULL; rq->text_utf8 = NULL; + rq->text_utf16 = NULL; rq->text_len = 0; rq->text_capacity_bytes = 0; } @@ -280,12 +293,15 @@ _raqm_free_text(raqm_t* rq) static bool _raqm_alloc_text(raqm_t *rq, size_t len, - bool need_utf8) + bool need_utf8, + bool need_utf16) { /* Allocate contiguous memory block for texts and text_info */ size_t mem_size = (sizeof (uint32_t) + sizeof (_raqm_text_info)) * len; if (need_utf8) mem_size += sizeof (char) * len; + else if (need_utf16) + mem_size += sizeof (uint16_t) * len; if (mem_size > rq->text_capacity_bytes) { @@ -302,6 +318,7 @@ _raqm_alloc_text(raqm_t *rq, rq->text_info = (_raqm_text_info*)(rq->text + len); rq->text_utf8 = need_utf8 ? (char*)(rq->text_info + len) : NULL; + rq->text_utf16 = need_utf16 ? (uint16_t*)(rq->text_info + len) : NULL; return true; } @@ -357,7 +374,7 @@ _raqm_free_runs (raqm_run_t *runs) * 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. + * #raqm_t. Returns `NULL` in case of error. * * Since: 0.1 */ @@ -381,6 +398,7 @@ raqm_create (void) rq->invisible_glyph = 0; rq->text = NULL; + rq->text_utf16 = NULL; rq->text_utf8 = NULL; rq->text_info = NULL; rq->text_capacity_bytes = 0; @@ -498,7 +516,7 @@ raqm_clear_contents (raqm_t *rq) * separately can give improper output. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.1 */ @@ -518,7 +536,7 @@ raqm_set_text (raqm_t *rq, if (!len) return true; - if (!_raqm_alloc_text(rq, len, false)) + if (!_raqm_alloc_text(rq, len, false, false)) return false; rq->text_len = len; @@ -575,6 +593,53 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) return (out_utf32 - unicode); } +static void * +_raqm_get_utf16_codepoint (const void *str, + uint32_t *out_codepoint) +{ + const uint16_t *s = (const uint16_t *)str; + + if (s[0] > 0xD800 && s[0] < 0xDBFF) + { + if (s[1] > 0xDC00 && s[1] < 0xDFFF) + { + uint32_t X = ((s[0] & ((1 << 6) -1)) << 10) | (s[1] & ((1 << 10) -1)); + uint32_t W = (s[0] >> 6) & ((1 << 5) - 1); + *out_codepoint = (W+1) << 16 | X; + s += 2; + } + else + { + /* A single high surrogate, this is an error. */ + *out_codepoint = s[0]; + s += 1; + } + } + else + { + *out_codepoint = s[0]; + s += 1; + } + return (void *)s; +} + +static size_t +_raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode) +{ + size_t in_len = 0; + uint32_t *out_utf32 = unicode; + const uint16_t *in_utf16 = text; + + while ((*in_utf16 != '\0') && (in_len < len)) + { + in_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32); + ++out_utf32; + ++in_len; + } + + return (out_utf32 - unicode); +} + /** * raqm_set_text_utf8: * @rq: a #raqm_t. @@ -584,7 +649,7 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.1 */ @@ -604,7 +669,7 @@ raqm_set_text_utf8 (raqm_t *rq, if (!len) return true; - if (!_raqm_alloc_text(rq, len, true)) + if (!_raqm_alloc_text(rq, len, true, false)) return false; rq->text_len = _raqm_u8_to_u32 (text, len, rq->text); @@ -614,6 +679,44 @@ raqm_set_text_utf8 (raqm_t *rq, return true; } +/** + * raqm_set_text_utf16: + * @rq: a #raqm_t. + * @text: a UTF-16 encoded text string. + * @len: the length of @text in UTF-16 shorts. + * + * Same as raqm_set_text(), but for text encoded in UTF-16 encoding. + * + * Return value: + * `true` if no errors happened, `false` otherwise. + * + * Since: 0.10 + */ +bool +raqm_set_text_utf16 (raqm_t *rq, + const uint16_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + /* Call raqm_clear_contents to reuse this raqm_t */ + if (rq->text_len) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + if (!_raqm_alloc_text(rq, len, false, true)) + return false; + + rq->text_len = _raqm_u16_to_u32 (text, len, rq->text); + memcpy (rq->text_utf16, text, sizeof (uint16_t) * len); + _raqm_init_text_info (rq); + + return true; +} /** * raqm_set_par_direction: * @rq: a #raqm_t. @@ -640,7 +743,7 @@ raqm_set_text_utf8 (raqm_t *rq, * text. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.1 */ @@ -673,7 +776,7 @@ raqm_set_par_direction (raqm_t *rq, * parts of the text. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Stability: * Unstable @@ -687,7 +790,7 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; - size_t end = start + len; + size_t end; if (!rq) return false; @@ -695,11 +798,8 @@ raqm_set_language (raqm_t *rq, if (!rq->text_len) return true; - if (rq->text_utf8) - { - start = _raqm_u8_to_u32_index (rq, start); - end = _raqm_u8_to_u32_index (rq, end); - } + end = _raqm_encoding_to_u32_index (rq, start + len); + start = _raqm_encoding_to_u32_index (rq, start); if (start >= rq->text_len || end > rq->text_len) return false; @@ -716,11 +816,37 @@ raqm_set_language (raqm_t *rq, return true; } +static bool +_raqm_add_font_feature (raqm_t *rq, + hb_feature_t fea) +{ + void* new_features; + + if (!rq) + return false; + + new_features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len + 1)); + if (!new_features) + return false; + + if (fea.start != HB_FEATURE_GLOBAL_START) + fea.start = _raqm_encoding_to_u32_index (rq, fea.start); + if (fea.end != HB_FEATURE_GLOBAL_END) + fea.end = _raqm_encoding_to_u32_index (rq, fea.end); + + rq->features = new_features; + rq->features[rq->features_len] = fea; + rq->features_len++; + + 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. + * @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 @@ -734,7 +860,7 @@ raqm_set_language (raqm_t *rq, * end of the features list and can potentially override previous features. * * Return value: - * %true if parsing @feature succeeded, %false otherwise. + * `true` if parsing @feature succeeded, `false` otherwise. * * Since: 0.1 */ @@ -751,16 +877,7 @@ raqm_add_font_feature (raqm_t *rq, ok = hb_feature_from_string (feature, len, &fea); if (ok) - { - void* new_features = realloc (rq->features, - sizeof (hb_feature_t) * (rq->features_len + 1)); - if (!new_features) - return false; - - rq->features = new_features; - rq->features[rq->features_len] = fea; - rq->features_len++; - } + _raqm_add_font_feature (rq, fea); return ok; } @@ -817,7 +934,7 @@ _raqm_set_freetype_face (raqm_t *rq, * See also raqm_set_freetype_face_range(). * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.1 */ @@ -832,21 +949,23 @@ raqm_set_freetype_face (raqm_t *rq, * 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. + * @start: index of first character that should use @face from the input string. + * @len: number of elements 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). + * The @start and @len are input string array indices, counting elements + * according to the underlying encoding. @start must always be aligned to the + * start of an encoded codepoint, and @len must always end at a codepoint's + * final element. * * 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. + * face ranges cover the whole text, and is properly aligned. * * See also raqm_set_freetype_face(). * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.1 */ @@ -856,7 +975,7 @@ raqm_set_freetype_face_range (raqm_t *rq, size_t start, size_t len) { - size_t end = start + len; + size_t end; if (!rq) return false; @@ -864,11 +983,8 @@ raqm_set_freetype_face_range (raqm_t *rq, if (!rq->text_len) return true; - if (rq->text_utf8) - { - start = _raqm_u8_to_u32_index (rq, start); - end = _raqm_u8_to_u32_index (rq, end); - } + end = _raqm_encoding_to_u32_index (rq, start + len); + start = _raqm_encoding_to_u32_index (rq, start); return _raqm_set_freetype_face (rq, face, start, end); } @@ -909,7 +1025,7 @@ _raqm_set_freetype_load_flags (raqm_t *rq, * older version the flags will be ignored. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.3 */ @@ -943,7 +1059,7 @@ raqm_set_freetype_load_flags (raqm_t *rq, * See also raqm_set_freetype_load_flags(). * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.9 */ @@ -953,7 +1069,7 @@ raqm_set_freetype_load_flags_range (raqm_t *rq, size_t start, size_t len) { - size_t end = start + len; + size_t end; if (!rq) return false; @@ -961,15 +1077,161 @@ raqm_set_freetype_load_flags_range (raqm_t *rq, if (!rq->text_len) return true; - if (rq->text_utf8) - { - start = _raqm_u8_to_u32_index (rq, start); - end = _raqm_u8_to_u32_index (rq, end); - } + end = _raqm_encoding_to_u32_index (rq, start + len); + start = _raqm_encoding_to_u32_index (rq, start); return _raqm_set_freetype_load_flags (rq, flags, start, end); } +static bool +_raqm_set_spacing (raqm_t *rq, + int spacing, + bool word_spacing, + 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++) + { + bool set_spacing = i == 0; + if (!set_spacing) + set_spacing = _raqm_allowed_grapheme_boundary (rq->text[i-1], rq->text[i]); + + if (set_spacing) + { + if (word_spacing) + { + if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1])) + { + /* CSS word seperators, word spacing is only applied on these.*/ + if (rq->text[i] == 0x0020 || /* Space */ + rq->text[i] == 0x00A0 || /* No Break Space */ + rq->text[i] == 0x1361 || /* Ethiopic Word Space */ + rq->text[i] == 0x10100 || /* Aegean Word Seperator Line */ + rq->text[i] == 0x10101 || /* Aegean Word Seperator Dot */ + rq->text[i] == 0x1039F || /* Ugaric Word Divider */ + rq->text[i] == 0x1091F) /* Phoenician Word Separator */ + { + rq->text_info[i].spacing_after = spacing; + } + } + } + else + { + rq->text_info[i].spacing_after = spacing; + } + } + } + + return true; +} + +/** + * raqm_set_letter_spacing_range: + * @rq: a #raqm_t. + * @spacing: amount of spacing in Freetype Font Units (26.6 format). + * @start: index of first character that should use @spacing. + * @len: number of characters using @spacing. + * + * Set the letter spacing or tracking for a given range, the value + * will be added onto the advance and offset for RTL, and the advance for + * other directions. Letter spacing will be applied between characters, so + * the last character will not have spacing applied after it. + * Note that not all scripts have a letter-spacing tradition, + * for example, Arabic does not, while Devanagari does. + * + * This will also add “disable `liga`, `clig`, `hlig`, `dlig`, and `calt`” font + * features to the internal features list, so call this function after setting + * the font features for best spacing results. + * + * Return value: + * `true` if no errors happened, `false` otherwise. + * + * Since: 0.10 + */ +bool +raqm_set_letter_spacing_range(raqm_t *rq, + int spacing, + size_t start, + size_t len) +{ + size_t end; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + end = start + len - 1; + + if (spacing != 0) + { +#define NUM_TAGS 5 + static char *tags[NUM_TAGS] = { "clig", "liga", "hlig", "dlig", "calt" }; + for (size_t i = 0; i < NUM_TAGS; i++) + { + hb_feature_t fea = { hb_tag_from_string(tags[i], 5), 0, start, end }; + _raqm_add_font_feature (rq, fea); + } +#undef NUM_TAGS + } + + start = _raqm_encoding_to_u32_index (rq, start); + end = _raqm_encoding_to_u32_index (rq, end); + + return _raqm_set_spacing (rq, spacing, false, start, end); +} + +/** + * raqm_set_word_spacing_range: + * @rq: a #raqm_t. + * @spacing: amount of spacing in Freetype Font Units (26.6 format). + * @start: index of first character that should use @spacing. + * @len: number of characters using @spacing. + * + * Set the word spacing for a given range. Word spacing will only be applied to + * 'word separator' characters, such as 'space', 'no break space' and + * Ethiopic word separator'. + * The value will be added onto the advance and offset for RTL, and the advance + * for other directions. + * + * Return value: + * `true` if no errors happened, `false` otherwise. + * + * Since: 0.10 + */ +bool +raqm_set_word_spacing_range(raqm_t *rq, + int spacing, + size_t start, + size_t len) +{ + size_t end; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + end = _raqm_encoding_to_u32_index (rq, start + len); + start = _raqm_encoding_to_u32_index (rq, start); + + return _raqm_set_spacing (rq, spacing, true, start, end); +} + /** * raqm_set_invisible_glyph: * @rq: a #raqm_t. @@ -984,7 +1246,7 @@ raqm_set_freetype_load_flags_range (raqm_t *rq, * If @gid is a positive number, it will be used for invisible glyphs. * * Return value: - * %true if no errors happened, %false otherwise. + * `true` if no errors happened, `false` otherwise. * * Since: 0.6 */ @@ -1014,7 +1276,7 @@ _raqm_shape (raqm_t *rq); * text shaping, and any other part of the layout process. * * Return value: - * %true if the layout process was successful, %false otherwise. + * `true` if the layout process was successful, `false` otherwise. * * Since: 0.1 */ @@ -1048,7 +1310,9 @@ raqm_layout (raqm_t *rq) static uint32_t _raqm_u32_to_u8_index (raqm_t *rq, uint32_t index); - +static uint32_t +_raqm_u32_to_u16_index (raqm_t *rq, + uint32_t index); /** * raqm_get_glyphs: * @rq: a #raqm_t. @@ -1059,7 +1323,7 @@ _raqm_u32_to_u8_index (raqm_t *rq, * information. * * Return value: (transfer none): - * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * 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 @@ -1147,6 +1411,12 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("\n"); #endif } + else if (rq->text_utf16) + { + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u16_index (rq, + rq->glyphs[i].cluster); + } return rq->glyphs; } @@ -1194,8 +1464,10 @@ raqm_get_direction_at_index (raqm_t *rq, for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { - if (run->pos <= index && index < run->pos + run->len) { - switch (run->direction) { + if (run->pos <= index && index < run->pos + run->len) + { + switch (run->direction) + { case HB_DIRECTION_LTR: return RAQM_DIRECTION_LTR; case HB_DIRECTION_RTL: @@ -1227,7 +1499,8 @@ _raqm_hb_dir (raqm_t *rq, _raqm_bidi_level_t level) return dir; } -typedef struct { +typedef struct +{ size_t pos; size_t len; _raqm_bidi_level_t level; @@ -1264,10 +1537,10 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count) line = SBParagraphCreateLine (par, 0, par_len); *run_count = SBLineGetRunCount (line); - if (SBParagraphGetBaseLevel (par) == 0) - rq->resolved_dir = RAQM_DIRECTION_LTR; - else + if (SBParagraphGetBaseLevel (par) == 1) rq->resolved_dir = RAQM_DIRECTION_RTL; + else + rq->resolved_dir = RAQM_DIRECTION_LTR; runs = malloc (sizeof (_raqm_bidi_run) * (*run_count)); if (runs) @@ -1418,10 +1691,10 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count) rq->text_len, &par_type, levels); - if (par_type == FRIBIDI_PAR_LTR) - rq->resolved_dir = RAQM_DIRECTION_LTR; - else + if (par_type == FRIBIDI_PAR_RTL) rq->resolved_dir = RAQM_DIRECTION_RTL; + else + rq->resolved_dir = RAQM_DIRECTION_LTR; if (max_level == 0) goto done; @@ -1447,22 +1720,15 @@ _raqm_itemize (raqm_t *rq) 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; - } + static char *dir_names[] = { + "DEFAULT", + "RTL", + "LTR", + "TTB" + }; + + assert (rq->base_dir < sizeof (dir_names)); + RAQM_TEST ("Direction is: %s\n\n", dir_names[rq->base_dir]); #endif if (!_raqm_resolve_scripts (rq)) @@ -1483,9 +1749,9 @@ _raqm_itemize (raqm_t *rq) runs->len = rq->text_len; runs->level = 0; } - } else { - runs = _raqm_bidi_itemize (rq, &run_count); } + else + runs = _raqm_bidi_itemize (rq, &run_count); if (!runs) { @@ -1494,6 +1760,9 @@ _raqm_itemize (raqm_t *rq) } #ifdef RAQM_TESTING + assert (rq->resolved_dir < sizeof (dir_names)); + if (rq->base_dir == RAQM_DIRECTION_DEFAULT) + RAQM_TEST ("Resolved direction is: %s\n\n", dir_names[rq->resolved_dir]); RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("BiDi Runs:\n"); @@ -1617,7 +1886,8 @@ done: } /* Stack to handle script detection */ -typedef struct { +typedef struct +{ size_t capacity; size_t size; int *pair_index; @@ -1910,15 +2180,47 @@ _raqm_shape (raqm_t *rq) { FT_Matrix matrix; + hb_glyph_info_t *info; hb_glyph_position_t *pos; unsigned int len; FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL); pos = hb_buffer_get_glyph_positions (run->buffer, &len); + info = hb_buffer_get_glyph_infos (run->buffer, &len); + for (unsigned int i = 0; i < len; i++) { _raqm_ft_transform (&pos[i].x_advance, &pos[i].y_advance, matrix); _raqm_ft_transform (&pos[i].x_offset, &pos[i].y_offset, matrix); + + bool set_spacing = false; + if (run->direction == HB_DIRECTION_RTL) + { + set_spacing = i == 0; + if (!set_spacing) + set_spacing = info[i].cluster != info[i-1].cluster; + } + else + { + set_spacing = i == len - 1; + if (!set_spacing) + set_spacing = info[i].cluster != info[i+1].cluster; + } + + _raqm_text_info rq_info = rq->text_info[info[i].cluster]; + + if (rq_info.spacing_after != 0 && set_spacing) + { + if (run->direction == HB_DIRECTION_TTB) + pos[i].y_advance -= rq_info.spacing_after; + else if (run->direction == HB_DIRECTION_RTL) + { + pos[i].x_advance += rq_info.spacing_after; + pos[i].x_offset += rq_info.spacing_after; + } + else + pos[i].x_advance += rq_info.spacing_after; + } } } } @@ -1954,9 +2256,9 @@ _raqm_u32_to_u8_index (raqm_t *rq, } /* Convert index from UTF-8 to UTF-32 */ -static uint32_t +static size_t _raqm_u8_to_u32_index (raqm_t *rq, - uint32_t index) + size_t index) { const unsigned char *s = (const unsigned char *) rq->text_utf8; const unsigned char *t = s; @@ -1982,9 +2284,64 @@ _raqm_u8_to_u32_index (raqm_t *rq, return length; } -static bool -_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, - hb_codepoint_t r_char); +/* Count equivalent UTF-16 short in codepoint */ +static size_t +_raqm_count_codepoint_utf16_short (uint32_t chr) +{ + if (chr > 0x010000) + return 2; + else + return 1; +} + +/* Convert index from UTF-32 to UTF-16 */ +static uint32_t +_raqm_u32_to_u16_index (raqm_t *rq, + uint32_t index) +{ + size_t length = 0; + + for (uint32_t i = 0; i < index; ++i) + length += _raqm_count_codepoint_utf16_short (rq->text[i]); + + return length; +} + +/* Convert index from UTF-16 to UTF-32 */ +static size_t +_raqm_u16_to_u32_index (raqm_t *rq, + size_t index) +{ + const uint16_t *s = (const uint16_t *) rq->text_utf16; + const uint16_t *t = s; + size_t length = 0; + + while (((size_t) (s - t) < index) && ('\0' != *s)) + { + if (*s < 0xD800 || *s > 0xDBFF) + s += 1; + else + s += 2; + + length++; + } + + if ((size_t) (s-t) > index) + length--; + + return length; +} + +static inline size_t +_raqm_encoding_to_u32_index (raqm_t *rq, + size_t index) +{ + if (rq->text_utf8) + return _raqm_u8_to_u32_index (rq, index); + else if (rq->text_utf16) + return _raqm_u16_to_u32_index (rq, index); + return index; +} static bool _raqm_in_hangul_syllable (hb_codepoint_t ch); @@ -2001,7 +2358,7 @@ _raqm_in_hangul_syllable (hb_codepoint_t ch); * 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. + * `true` if the process was successful, `false` otherwise. * * Since: 0.2 */ @@ -2018,8 +2375,7 @@ raqm_index_to_position (raqm_t *rq, if (rq == NULL) return false; - if (rq->text_utf8) - *index = _raqm_u8_to_u32_index (rq, *index); + *index = _raqm_encoding_to_u32_index (rq, *index); if (*index >= rq->text_len) return false; @@ -2077,6 +2433,8 @@ raqm_index_to_position (raqm_t *rq, found: if (rq->text_utf8) *index = _raqm_u32_to_u8_index (rq, *index); + else if (rq->text_utf16) + *index = _raqm_u32_to_u16_index (rq, *index); RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); return true; } @@ -2093,7 +2451,7 @@ found: * @index. * * Return value: - * %true if the process was successful, %false in case of error. + * `true` if the process was successful, `false` in case of error. * * Since: 0.2 */ @@ -2371,8 +2729,8 @@ raqm_version_string (void) * 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. + * `true` if library version is less than or equal the specified version, + * `false` otherwise. * * Since: 0.7 **/ @@ -2393,8 +2751,8 @@ raqm_version_atleast (unsigned int major, * 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. + * `true` if library version is less than or equal the specified version, + * `false` otherwise. * * Since: 0.7 **/ diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h index bdb5a50d8..2fd836c86 100644 --- a/src/thirdparty/raqm/raqm.h +++ b/src/thirdparty/raqm/raqm.h @@ -118,6 +118,10 @@ RAQM_API bool raqm_set_text_utf8 (raqm_t *rq, const char *text, size_t len); +RAQM_API bool +raqm_set_text_utf16 (raqm_t *rq, + const uint16_t *text, + size_t len); RAQM_API bool raqm_set_par_direction (raqm_t *rq, @@ -154,6 +158,17 @@ raqm_set_freetype_load_flags_range (raqm_t *rq, size_t start, size_t len); +RAQM_API bool +raqm_set_letter_spacing_range(raqm_t *rq, + int spacing, + size_t start, + size_t len); +RAQM_API bool +raqm_set_word_spacing_range(raqm_t *rq, + int spacing, + size_t start, + size_t len); + RAQM_API bool raqm_set_invisible_glyph (raqm_t *rq, int gid); From ad46630cdfa3cd9d0c2fe1de6e599f6aa49355c5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jan 2023 18:04:41 +1100 Subject: [PATCH 7/8] 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 2a83ed151..cc7d0258b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -478,13 +478,13 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | +| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |arm | +----------------------------------+---------------------------+------------------+--------------+ | macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | +----------------------------------+---------------------------+------------------+--------------+ | macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | +---------------------------+------------------+--------------+ -| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |x86-64 | +| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 | | +---------------------------+------------------+ | | | 3.6 | 8.4.0 | | +----------------------------------+---------------------------+------------------+--------------+ From a2edefb45532d20cda0a2915d3f7363dd6ad8754 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Jan 2023 07:18:56 +1100 Subject: [PATCH 8/8] Only install python-pyqt6 package on 64-bit --- .github/workflows/test-mingw.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index ccf6e193a..6a60bc7f0 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read -concurrency: +concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -49,7 +49,6 @@ jobs: ${{ matrix.package }}-python3-numpy \ ${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-pip \ - ${{ matrix.package }}-python-pyqt6 \ ${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-freetype \ ${{ matrix.package }}-gcc \ @@ -63,6 +62,11 @@ jobs: ${{ matrix.package }}-openjpeg2 \ subversion + if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then + pacman -S --noconfirm \ + ${{ matrix.package }}-python-pyqt6 + fi + python3 -m pip install pyroma pytest pytest-cov pytest-timeout pushd depends && ./install_extra_test_images.sh && popd