From d970e77f3be2ca1317462670c5e48532908f3db0 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 08:49:47 +0300 Subject: [PATCH 01/17] Document _imagingft.GlyphInfo --- src/_imagingft.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 4b49eb04766..0825aa7cf59 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -66,9 +66,16 @@ static int have_raqm = 0; #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 +// These mirror raqm_glyph_t, which in turn mirrors harfbuzz's hb_glyph_info_t, +// with the addition of y_advance and y_offset for vertical text layout. +// Coordinate units are 26.6 fixed-point precision. typedef struct { - int index, x_offset, x_advance, y_offset, y_advance; - unsigned int cluster; + unsigned int index; // the index of the glyph in the font file + int x_offset; // horizontal movement of the glyph from current point + int x_advance; // glyph advance width in horizontal text + int y_offset; // vertical movement of the glyph from current point + int y_advance; // glyph advance height in vertical text + unsigned int cluster; // character index in original text } GlyphInfo; struct { From 55a2b209b96f575fb054aaa799a094aaf4bff826 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 17:03:17 +0300 Subject: [PATCH 02/17] Add docs for font_getsize_impl --- src/_imagingft.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 0825aa7cf59..778a54d454f 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -761,6 +761,19 @@ bounding_box_and_anchors( return 1; } +/** Calculate size and bearing for a given string. + * + * Python parameters: + * - string: the text to measure + * - mode_name: imaging mode string + * - dir: text direction string + * - features: font features sequence + * - lang: language string + * - anchor: anchor string + * + * Python return value: + * - ((width, height), (x_offset, y_offset)): size and bearing of the text, in pixels + */ static PyObject * font_getsize_impl(FontObject *self, PyObject *args) { int64_t width, height; @@ -779,8 +792,6 @@ font_getsize_impl(FontObject *self, PyObject *args) { PyObject *features = Py_None; PyObject *string; - /* calculate size and bearing for a given string */ - if (!PyArg_ParseTuple( args, "O|zzOzz:getsize", @@ -845,6 +856,25 @@ font_getsize(FontObject *self, PyObject *args) { return result; } +/** + * Rasterize a string into an image buffer. + * + * Python parameters: + * - string: the text to render + * - fill: ??? + * - mode_name: imaging mode string + * - dir: text direction string + * - features: font features sequence + * - lang: language string + * - stroke_width: stroke width in pixels + * - stroke_filled: ??? + * - anchor: anchor string + * - foreground_ink_long: foreground color as a long integer + * - x_start: starting x position of the pen in pixels + * - y_start: starting y position of the pen in pixels + * + * Returns: a new image object containing the rendered text. + */ static PyObject * font_render_impl(FontObject *self, PyObject *args) { int x, y; /* pen position, in 26.6 precision */ From d6d90e826319c9aacafc161935386b2a058cc8af Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 18:45:53 +0300 Subject: [PATCH 03/17] Add docs for font_getlength_impl --- src/PIL/_imagingft.pyi | 2 +- src/_imagingft.c | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 2136810ba6a..481ce3477a4 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -53,7 +53,7 @@ class Font: features: list[str] | None, lang: str | None, /, - ) -> float: ... + ) -> int: ... def getvarnames(self) -> list[bytes]: ... def getvaraxes(self) -> list[ImageFont.Axis]: ... def setvarname(self, instance_index: int, /) -> None: ... diff --git a/src/_imagingft.c b/src/_imagingft.c index 778a54d454f..d8538ce30cf 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -524,6 +524,24 @@ text_layout( return count; } +/** Calculate how far the pen advances over a given string. + * + * This is the sum of the glyph advances, which is not the width of the inked + * area that font_getsize_impl() reports: it includes side bearings and any + * trailing whitespace. + * + * Python parameters: + * - string: the text to measure + * - mode_name: imaging mode string + * - dir: text direction string + * - features: font features sequence + * - lang: language string + * + * Python return value: + * - length: advance along the primary axis, 26.6 precision integer. + * Unlike the other methods here this is not in pixels; + * ImageFont.FreeTypeFont.getlength() divides by 64. + */ static PyObject * font_getlength_impl(FontObject *self, PyObject *args) { int length; /* length along primary axis, in 26.6 precision */ @@ -538,8 +556,6 @@ font_getlength_impl(FontObject *self, PyObject *args) { PyObject *features = Py_None; PyObject *string; - /* calculate size and bearing for a given string */ - if (!PyArg_ParseTuple( args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang )) { From d7cb42f6458efbe00ccb05ebdbb8ae0369f160ab Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 16:11:12 +0300 Subject: [PATCH 04/17] Add macros to manipulate FT 26.6 format --- src/_imagingft.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index d8538ce30cf..c4c34dabfed 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -103,6 +103,14 @@ static PyTypeObject Font_Type; /* round a 26.6 pixel coordinate to the nearest integer */ #define PIXEL(x) ((((x) + 32) & -64) >> 6) +/* round a 26.6 pixel coordinate down to integer */ +#define PIXEL_FLOOR(x) ((x) >> 6) +/* round a 26.6 pixel coordinate up to integer */ +#define PIXEL_CEIL(x) (((x) + 63) >> 6) +/* the sub-pixel part of a 26.6 pixel coordinate */ +#define PIXEL_FRAC(x) ((x) & 63) +/* convert from pixels to 26.6 fixed-point */ +#define PIXEL_TO_FIXED(x) ((x) * 64) static PyObject * geterror(int code) { @@ -222,7 +230,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { } if (!error) { - width = size * 64; + width = PIXEL_TO_FIXED(size); req.type = FT_SIZE_REQUEST_TYPE_NOMINAL; req.width = width; req.height = width; From f36650b65d336cafd702d8cd1d99263af97526af Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 16:48:55 +0300 Subject: [PATCH 05/17] Use FT_F26Dot6 type where obvious --- src/_imagingft.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index c4c34dabfed..ffcf2301544 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -71,10 +71,10 @@ static int have_raqm = 0; // Coordinate units are 26.6 fixed-point precision. typedef struct { unsigned int index; // the index of the glyph in the font file - int x_offset; // horizontal movement of the glyph from current point - int x_advance; // glyph advance width in horizontal text - int y_offset; // vertical movement of the glyph from current point - int y_advance; // glyph advance height in vertical text + FT_F26Dot6 x_offset; // horizontal movement of the glyph from current point + FT_F26Dot6 x_advance; // glyph advance width in horizontal text + FT_F26Dot6 y_offset; // vertical movement of the glyph from current point + FT_F26Dot6 y_advance; // glyph advance height in vertical text unsigned int cluster; // character index in original text } GlyphInfo; @@ -552,7 +552,7 @@ text_layout( */ static PyObject * font_getlength_impl(FontObject *self, PyObject *args) { - int length; /* length along primary axis, in 26.6 precision */ + FT_F26Dot6 length; /* length along primary axis */ GlyphInfo *glyph_info = NULL; /* computed text layout */ size_t i, count; /* glyph_info index and length */ int horizontal_dir; /* is primary axis horizontal? */ @@ -620,9 +620,9 @@ bounding_box_and_anchors( int *x_offset, int *y_offset ) { - long position; /* pen position along primary axis, in 26.6 precision */ - long advanced; /* pen position along primary axis, in pixels */ - int px, py; /* position of current glyph, in pixels */ + FT_F26Dot6 position; /* pen position along primary axis */ + long advanced; /* pen position along primary axis, in pixels */ + int px, py; /* position of current glyph, in pixels */ int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */ int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */ int error; @@ -901,7 +901,7 @@ font_getsize(FontObject *self, PyObject *args) { */ static PyObject * font_render_impl(FontObject *self, PyObject *args) { - int x, y; /* pen position, in 26.6 precision */ + FT_F26Dot6 x, y; /* pen position */ int px, py; /* position of current glyph, in pixels */ int x_min, y_max; /* text offset in 26.6 precision */ int load_flags; /* FreeType load_flags parameter */ @@ -1051,10 +1051,10 @@ font_render_impl(FontObject *self, PyObject *args) { FT_Stroker_Set( stroker, - (FT_Fixed)round(stroke_width * 64), + (FT_F26Dot6)roundf(PIXEL_TO_FIXED(stroke_width)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, - 0 + 0 // 16.16 units ); } @@ -1089,8 +1089,8 @@ font_render_impl(FontObject *self, PyObject *args) { } /* set pen position to text origin */ - x = round((-x_min + stroke_width + x_start) * 64); - y = round((-y_max + (-stroke_width) - y_start) * 64); + x = roundf(PIXEL_TO_FIXED(-x_min + stroke_width + x_start)); + y = roundf(PIXEL_TO_FIXED(-y_max + (-stroke_width) - y_start)); if (stroker == NULL) { load_flags |= FT_LOAD_RENDER; From cd8911b4e58155449604b7561018e0bdcda95818 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 09:23:34 +0300 Subject: [PATCH 06/17] Fix misleading comment for x_min, y_max --- src/_imagingft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index ffcf2301544..837c393ce40 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -903,7 +903,7 @@ static PyObject * font_render_impl(FontObject *self, PyObject *args) { FT_F26Dot6 x, y; /* pen position */ int px, py; /* position of current glyph, in pixels */ - int x_min, y_max; /* text offset in 26.6 precision */ + int x_min, y_max; /* text offset, in pixels */ int load_flags; /* FreeType load_flags parameter */ int error; FT_Glyph glyph; From 5bf7161c52f2d324727f31b7c46682c62a7113f5 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 20:55:29 +0300 Subject: [PATCH 07/17] Make all arguments for _imagingft.getfont() actually required Avoids a segfault when `encoding` and `font_bytes` weren't passed at all (they were left uninitialized) --- src/PIL/ImageFont.py | 4 +--- src/_imagingft.c | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1f5fe66197a..c850ee2d7c6 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -297,9 +297,7 @@ def load_from_bytes(f: IO[bytes]) -> None: with open(font, "rb") as f: load_from_bytes(f) return - self.font = core.getfont( - font, size, index, encoding, layout_engine=layout_engine - ) + self.font = core.getfont(font, size, index, encoding, b"", layout_engine) else: load_from_bytes(cast("IO[bytes]", font)) diff --git a/src/_imagingft.c b/src/_imagingft.c index 837c393ce40..8fb8fe5916b 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -140,8 +140,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { FT_Long width; Py_ssize_t index = 0; Py_ssize_t layout_engine = 0; - unsigned char *encoding; - unsigned char *font_bytes; + unsigned char *encoding = NULL; + unsigned char *font_bytes = NULL; Py_ssize_t font_bytes_size = 0; static char *kwlist[] = { "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL @@ -158,7 +158,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { if (!PyArg_ParseTupleAndKeywords( args, kw, - "etf|nsy#n", + "etfnsy#n", kwlist, config.filesystem_encoding, &filename, @@ -177,7 +177,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { if (!PyArg_ParseTupleAndKeywords( args, kw, - "etf|nsy#n", + "etfnsy#n", kwlist, Py_FileSystemDefaultEncoding, &filename, From aabb26af93b43858b40fc55a02e7df95f46ecb69 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 20:58:32 +0300 Subject: [PATCH 08/17] Adjust _imagingft.pyi to match reality (and add tests to match) --- Tests/test_font_pcf_charsets.py | 4 ++-- Tests/test_imagefont.py | 18 ++++++++++++++++ src/PIL/ImageFont.py | 14 ++++++------ src/PIL/_imagingft.pyi | 38 ++++++++++++++++----------------- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 58dec008174..51c6f992cb4 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -97,12 +97,12 @@ def test_textsize( tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) for i in range(255): - ox, oy, dx, dy = font.getbbox(bytearray([i])) + ox, oy, dx, dy = font.getbbox(bytes([i])) assert ox == 0 assert oy == 0 assert dy == 20 assert dx in (0, 10) - assert font.getlength(bytearray([i])) == dx + assert font.getlength(bytes([i])) == dx message = charsets[encoding]["message"].encode(encoding) for i in range(len(message)): msg = message[: i + 1] diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 536a985435b..f647ee43379 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -571,6 +571,24 @@ def test_getbbox_empty(font: ImageFont.FreeTypeFont) -> None: assert (0, 0, 0, 0) == font.getbbox("") +def test_bytearray_not_supported(font: ImageFont.FreeTypeFont) -> None: + # The C text layout only accepts str and bytes, so bytearray is rejected + # even though these methods used to be annotated as taking one. + for op in (font.getlength, font.getbbox, font.getmask): + assert op(b"A") is not None + with pytest.raises(TypeError, match="expected string or bytes"): + op(bytearray(b"A")) # type: ignore[arg-type] + + +def test_core_font_arguments_are_optional(font: ImageFont.FreeTypeFont) -> None: + # Every argument after the leading string is optional at the C level, + # and the mode argument additionally accepts None. + assert font.font.getlength("A") == font.font.getlength("A", "", None, None, None) + assert font.font.getlength("A", None) == font.font.getlength("A", "") + assert font.font.getsize("A") == font.font.getsize("A", "", None, None, None, None) + assert font.font.getsize("A", None) == font.font.getsize("A", "") + + def test_render_empty(font: ImageFont.FreeTypeFont) -> None: # issue 2666 im = Image.new(mode="RGB", size=(300, 100)) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c850ee2d7c6..49c242e8807 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -72,7 +72,7 @@ class Layout(IntEnum): core = DeferredError.new(ex) -def _string_length_check(text: str | bytes | bytearray) -> None: +def _string_length_check(text: str | bytes) -> None: if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: msg = "too many characters in string" raise ValueError(msg) @@ -99,7 +99,7 @@ class BaseImageFont(abc.ABC): @abc.abstractmethod def getbbox( - self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + self, text: str | bytes, *args: Any, **kwargs: Any ) -> tuple[float, float, float, float]: pass @@ -204,7 +204,7 @@ def getmask( return self.font.getmask(text, mode) def getbbox( - self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + self, text: str | bytes, *args: Any, **kwargs: Any ) -> tuple[int, int, int, int]: """ Returns bounding box (in pixels) of given text. @@ -219,9 +219,7 @@ def getbbox( width, height = self.font.getsize(text) return 0, 0, width, height - def getlength( - self, text: str | bytes | bytearray, *args: Any, **kwargs: Any - ) -> int: + def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> int: """ Returns length (in pixels) of given text. This is the amount by which following text should be offset. @@ -404,7 +402,7 @@ def getlength( def getbbox( self, - text: str | bytes | bytearray, + text: str | bytes, mode: str = "", direction: str | None = None, features: list[str] | None = None, @@ -772,7 +770,7 @@ def getmask( return im def getbbox( - self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + self, text: str | bytes, *args: Any, **kwargs: Any ) -> tuple[int, int, float, float]: # TransposedFont doesn't support getmask2, move top-left point to (0, 0) # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 481ce3477a4..cda075348e1 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -24,34 +24,34 @@ class Font: self, string: str | bytes, fill: Callable[[int, int], _imaging.ImagingCore], - mode: str, - dir: str | None, - features: list[str] | None, - lang: str | None, - stroke_width: float, - stroke_filled: bool, - anchor: str | None, - foreground_ink_long: int, - start: tuple[float, float], + mode: str | None = ..., + dir: str | None = ..., + features: list[str] | None = ..., + lang: str | None = ..., + stroke_width: float = ..., + stroke_filled: bool = ..., + anchor: str | None = ..., + foreground_ink_long: int = ..., + start: tuple[float, float] = ..., /, ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ... def getsize( self, - string: str | bytes | bytearray, - mode: str, - dir: str | None, - features: list[str] | None, - lang: str | None, - anchor: str | None, + string: str | bytes, + mode: str | None = ..., + dir: str | None = ..., + features: list[str] | None = ..., + lang: str | None = ..., + anchor: str | None = ..., /, ) -> tuple[tuple[int, int], tuple[int, int]]: ... def getlength( self, string: str | bytes, - mode: str, - dir: str | None, - features: list[str] | None, - lang: str | None, + mode: str | None = ..., + dir: str | None = ..., + features: list[str] | None = ..., + lang: str | None = ..., /, ) -> int: ... def getvarnames(self) -> list[bytes]: ... From 48405649dbbe4c36daa20454ab4d2266694126d8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 27 Aug 2026 09:04:04 +0300 Subject: [PATCH 09/17] Add PILLOW_TEST_SNAPSHOTS environment variable (and make `assert_image_equal` raise an AssertionError, not a pytest.fail.Exception) --- Tests/helper.py | 42 ++++++++++++++++++++++++++++++++--- Tests/test_image_resize.py | 6 ++--- Tests/test_image_thumbnail.py | 2 +- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 924b9733403..b62eef0b89a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,6 +10,7 @@ import subprocess import sys import tempfile +from enum import IntEnum from functools import lru_cache from io import BytesIO @@ -33,6 +34,21 @@ uploader = "github_actions" +class SnapshotsMode(IntEnum): + NONE = 0 # no creation + CREATE = 1 # create if doesn't exist + UPDATE = 2 # create or update ("this is the new expected result") + OVERWRITE = 3 # always overwrite ("this is the new expected result") + + +try: + snapshots_mode = SnapshotsMode[ + os.environ.get("PILLOW_TEST_SNAPSHOTS", "NONE").upper() + ] +except ValueError as ve: + pytest.fail(f"Invalid value for PILLOW_TEST_SNAPSHOTS: {ve}") + + def upload(a: Image.Image, b: Image.Image) -> str | None: if uploader == "show": # local img.show for errors. @@ -95,7 +111,7 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) - except Exception: pass - pytest.fail(msg or "got different content") + raise AssertionError(msg or "got different content") def assert_image_equal_tofile( @@ -104,9 +120,19 @@ def assert_image_equal_tofile( msg: str | None = None, mode: str | None = None, ) -> None: + if snapshots_mode == SnapshotsMode.OVERWRITE or ( + snapshots_mode and not os.path.isfile(filename) + ): + a.save(filename) with Image.open(filename) as im: converted_im = im.convert(mode) if mode else im - assert_image_equal(a, converted_im, msg) + try: + assert_image_equal(a, converted_im, msg) + except AssertionError: + if snapshots_mode == SnapshotsMode.UPDATE: + a.save(filename) + else: + raise def assert_image_similar( @@ -146,8 +172,18 @@ def assert_image_similar_tofile( epsilon: float, msg: str | None = None, ) -> None: + if snapshots_mode == SnapshotsMode.OVERWRITE or ( + snapshots_mode and not os.path.isfile(filename) + ): + a.save(filename) with Image.open(filename) as img: - assert_image_similar(a, img, epsilon, msg) + try: + assert_image_similar(a, img, epsilon, msg) + except AssertionError: + if snapshots_mode == SnapshotsMode.UPDATE: + a.save(filename) + else: + raise def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None: diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 98380ffd446..9766b7e6e1a 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -222,7 +222,7 @@ def test_reducing_gap_1( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 ) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -242,7 +242,7 @@ def test_reducing_gap_2( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 ) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -262,7 +262,7 @@ def test_reducing_gap_3( (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 ) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 2ae230f3df1..f353d8596ae 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -149,7 +149,7 @@ def test_reducing_gap_values() -> None: ref = hopper() ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, 3.5) From 64f77f5f3016a1f451d3314911044202d1a3212d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 22:52:45 +0300 Subject: [PATCH 10/17] Add test case based on #9898 --- Tests/images/text_size_ladder_basic.png | Bin 0 -> 5993 bytes Tests/images/text_size_ladder_raqm.png | Bin 0 -> 5986 bytes Tests/test_imagefont.py | 20 +++++++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Tests/images/text_size_ladder_basic.png create mode 100644 Tests/images/text_size_ladder_raqm.png diff --git a/Tests/images/text_size_ladder_basic.png b/Tests/images/text_size_ladder_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..fcec73f4ffc359e4df1afdb06132c5082634ac68 GIT binary patch literal 5993 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 Q=Kufz07*qoM6N<$f?@D<)&Kwi literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index f647ee43379..6c25f36fe48 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -12,7 +12,7 @@ import pytest -from PIL import Image, ImageDraw, ImageFont, features +from PIL import Image, ImageDraw, ImageFont, ImageText, features from .helper import ( assert_image_equal, @@ -213,6 +213,24 @@ def test_getlength( assert length == length_raqm +def test_size_ladder(layout_engine: ImageFont.Layout) -> None: + # See https://github.com/python-pillow/Pillow/issues/9898 + image = Image.new("RGBA", (220, 140), "#fff") + draw = ImageDraw.Draw(image) + + y = 5 + for size in (12, 13, 14, 15, 16, 20, 24): + font = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", size, layout_engine=layout_engine + ) + text = ImageText.Text(f"Power bolt ({size}px)", font) + draw.text((5, y), text, anchor="la", fill="#000") + y += size + + name = "basic" if layout_engine == ImageFont.Layout.BASIC else "raqm" + assert_image_similar_tofile(image, f"Tests/images/text_size_ladder_{name}.png", 4) + + def test_float_size(layout_engine: ImageFont.Layout) -> None: lengths = [] for size in (48, 48.5, 49): From 69e9168d16a87d861b136e2ce85fe4d624d14a57 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 25 Aug 2026 21:54:56 +0300 Subject: [PATCH 11/17] Basic text layout: Do not sign-extend high bytes of a byte string Sign-extending e.g. 0x80 (char) to 0xffffff80 (FT_ULong) is not what we want when looking up a glyph. --- Tests/test_imagefont.py | 11 +++++++++++ src/_imagingft.c | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6c25f36fe48..362f8df6d3e 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -830,6 +830,17 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: _check_text(font, "Tests/images/variation_tiny_name.png", 40) +def test_truetype_bytes() -> None: + """Ensure bytestrings with the high bit set do not cause sign extension.""" + font = ImageFont.truetype( + "Tests/fonts/FreeMono.ttf", + 20, + # Have to use BASIC as RAQM would parse `b"\xe9"` as invalid Unicode + layout_engine=ImageFont.Layout.BASIC, + ) + assert font.getbbox(b"\xe9") == font.getbbox("\u00e9") + + def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: with pytest.raises(OSError): font.set_variation_by_axes([500, 50]) diff --git a/src/_imagingft.c b/src/_imagingft.c index 8fb8fe5916b..36160a91fe9 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -471,7 +471,7 @@ text_layout_fallback( } for (i = 0; i < count; i++) { if (buffer) { - ch = buffer[i]; + ch = (unsigned char)buffer[i]; } else { ch = PyUnicode_READ_CHAR(string, i); } From f3970f19a1281ad79dd3b5021a8611b16cf246c9 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 18:18:19 +0300 Subject: [PATCH 12/17] Round, don't truncate variable font axes --- Tests/test_imagefont.py | 11 +++++++++++ docs/releasenotes/13.0.0.rst | 9 +++++++++ src/_imagingft.c | 6 +++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 362f8df6d3e..dc20aafbbe9 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -854,6 +854,17 @@ def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: _check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) +def test_variation_set_by_fractional_axes() -> None: + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 200) + + def mask(weight: float) -> bytes: + font.set_variation_by_axes([weight, 0]) + return bytes(font.getmask("Hg", mode="L")) + + # a fractional value must not be truncated or rounded + assert mask(600) != mask(600.5) != mask(601) + + @pytest.mark.parametrize( "anchor, left, top", ( diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 45667d9ec07..f04bb73e2b3 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -90,6 +90,15 @@ TODO TODO +Bugfixes +======== + +Fractional variation axis values +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:meth:`~PIL.ImageFont.FreeTypeFont.set_variation_by_axes` truncated each value to a +whole number before applying it. Fractional values are now used as given. + Other changes ============= diff --git a/src/_imagingft.c b/src/_imagingft.c index 36160a91fe9..1ec3fae868b 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1504,7 +1504,7 @@ font_setvaraxes_impl(FontObject *self, PyObject *args) { PyObject *axes, *item; Py_ssize_t i, num_coords; FT_Fixed *coords; - FT_Fixed coord; + double coord; if (!PyArg_ParseTuple(args, "O", &axes)) { return NULL; } @@ -1529,7 +1529,7 @@ font_setvaraxes_impl(FontObject *self, PyObject *args) { if (PyFloat_Check(item)) { coord = PyFloat_AS_DOUBLE(item); } else if (PyLong_Check(item)) { - coord = (float)PyLong_AS_LONG(item); + coord = (double)PyLong_AS_LONG(item); } else if (PyNumber_Check(item)) { coord = PyFloat_AsDouble(item); } else { @@ -1539,7 +1539,7 @@ font_setvaraxes_impl(FontObject *self, PyObject *args) { return NULL; } Py_DECREF(item); - coords[i] = coord * 65536; + coords[i] = (FT_Fixed)round(coord * 65536); /* 16.16 fixed point */ } error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); From 962204e966010f1b54298e08d2ff978c13190d95 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 08:52:57 +0300 Subject: [PATCH 13/17] Basic font layout: Fix x_advance and y_advance being off by 64x --- Tests/images/text_size_ladder_basic.png | Bin 5993 -> 10097 bytes Tests/images/text_size_ladder_raqm.png | Bin 5986 -> 10111 bytes docs/releasenotes/13.0.0.rst | 7 +++++++ src/_imagingft.c | 4 ++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Tests/images/text_size_ladder_basic.png b/Tests/images/text_size_ladder_basic.png index fcec73f4ffc359e4df1afdb06132c5082634ac68..8b2fcf095075bc8891aecf63ae8dc36e50fb9159 100644 GIT binary patch literal 10097 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 Hv*7;+c-Kix literal 5993 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 Q=Kufz07*qoM6N<$f?@D<)&Kwi diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index f04bb73e2b3..50ffce87c39 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -99,6 +99,13 @@ Fractional variation axis values :py:meth:`~PIL.ImageFont.FreeTypeFont.set_variation_by_axes` truncated each value to a whole number before applying it. Fractional values are now used as given. +Kerning in the basic layout engine +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When libraqm is not available, kerning was applied at 1/64th of its intended size, +because a value in pixels was added to an advance in 26.6 fixed point precision. +The full kerning value is now applied even when libraqm is not available. + Other changes ============= diff --git a/src/_imagingft.c b/src/_imagingft.c index 1ec3fae868b..3b5579636d8 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -493,8 +493,8 @@ text_layout_fallback( ft_kerning_default, &delta ) == 0) { - (*glyph_info)[i - 1].x_advance += PIXEL(delta.x); - (*glyph_info)[i - 1].y_advance += PIXEL(delta.y); + (*glyph_info)[i - 1].x_advance += delta.x; + (*glyph_info)[i - 1].y_advance += delta.y; } } From 85681483dbacdec0e10abcc1d7e498e24354598f Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 27 Aug 2026 09:35:56 +0300 Subject: [PATCH 14/17] test_float_coord: don't overwrite same snapshot --- Tests/images/text_float_coord.png | Bin 2875 -> 0 bytes Tests/images/text_float_coord_1_BASIC.png | Bin 0 -> 598 bytes Tests/images/text_float_coord_1_RAQM.png | Bin 0 -> 598 bytes Tests/images/text_float_coord_1_alt.png | Bin 809 -> 0 bytes Tests/images/text_float_coord_L_BASIC.png | Bin 0 -> 2165 bytes Tests/images/text_float_coord_L_RAQM.png | Bin 0 -> 2165 bytes Tests/images/text_float_coord_RGBA_BASIC.png | Bin 0 -> 2165 bytes Tests/images/text_float_coord_RGBA_RAQM.png | Bin 0 -> 2165 bytes Tests/test_imagefont.py | 19 ++++++++----------- 9 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 Tests/images/text_float_coord.png create mode 100644 Tests/images/text_float_coord_1_BASIC.png create mode 100644 Tests/images/text_float_coord_1_RAQM.png delete mode 100644 Tests/images/text_float_coord_1_alt.png create mode 100644 Tests/images/text_float_coord_L_BASIC.png create mode 100644 Tests/images/text_float_coord_L_RAQM.png create mode 100644 Tests/images/text_float_coord_RGBA_BASIC.png create mode 100644 Tests/images/text_float_coord_RGBA_RAQM.png diff --git a/Tests/images/text_float_coord.png b/Tests/images/text_float_coord.png deleted file mode 100644 index d2270826a5bd84efa4a8dbde8a987d5db0a2dc94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2875 zcmcJR`8yMi1IPD4nA}%xiMg*KM96JoQLb99%`KB7G*^U}Tq8+yA5%F~sD)fxb6=7B zHUI#?0kJZ>2>|?~{Fkq>GW<<7 zw}n;!;5-Aw%-A6cyFDLx54J8&+;8BMYY7WO4%Go=Y$ssWcl^$aC$!|<?oyLHJVhA#>T+a%DM$0yoUA6S>WktAqTe#LK*Ff0pr1Xlk;jS|c7CzbiRdOCVmnLQVm2cY#x@(GCtq#U` zvEM$`<0^ydbZ+wO^Wdm~^$p{w>`ZAuwN3t#Vr-rP0Ik}3+) z*NuNv9XWFnIM~<47XR=bR_Hs;_Z2F&3Wiu9G#K7sH7()3)Lh|it1ts-DN`?AQQSh6 zr)Hl898z9em;jfZb08))uhcEO$uSWQN6#?l&P@D7%U9*mCi_EP3qSGafu4DTKD&CX z%Tba=4=6>%*{lWhV(OU94qULly7cBiin&$D_=Ch9*%Vcc%PHj^gBS66-HO&zm~5c~ z<^qi|&=GL?8EQBV;L-Z_dm*>13|x!ljdpk3<+I$W$Hm77YYrV}Xy4sl*6k-7hJ8l> z0z2BN=``*=Ih03acNMOZFL=*%@EiCA$<<~?T-il~$mYvsS)m8!3(AB*+f99orcrMe z&6c=lN6M`eezqQ6$2CvmIcM8n!vLR% z&s}5;3YS3C95a+;qd?f$a%s{Oz_v3Im0?njqwdI}lpP z5JSz3oCH`M(6FOm(z22IB5}OV0Vxe8H7k))A4+{s&I_(2GyM(@-MYLJmrqHI(g{VX zO8%JCm%%k2f(7djXP@O;-jS$sufXSwoLQ_&Jl&YT4`UZa^uufT`ZjJ$ZRtOmFz&L( zYKDuI;rf)4TBI}>Ykhm`-KAY8JNkRoLFBe|{bI0ObJF`P`{Ro#;lB+yVLhrJU%J?J zok&KzVmY7OeAl(`NRWoDU zgKQv&ey=0hLpy9k*&l{?-;p$m(!F)vRAz^B<&UKP48j=BvXj4p)`n@hpM z{p`RrN{e07O#1-1@7)mHN(di!rCOJwfzJKA%4r+-NMT@v;Mat$UX?$u_-*ezFGFQ% z4VvS|&F0*(ojt{A(a{a7QIaX1)S?Fw6bgfG2KeJ|mK z!n~9O9zq^~FEnp@R?GPt2=yG^)i<7<=uQO5$d>6~Zb99ZpiiQo2XQmlWDx4zp)4f- zdvSz#21K2=5sHx<&##`KI^*qt>;Vh-Z0k{&pY>b}69%R`9e2Wg^2uo7&XZvbkAuM?MwHS+M1`@5IyHz-l z87pG8fvH^^$i%DFaId*MW4J6kukzF`zV8GU1XmLh_6%HR@ZdCb)&UE+m6P(2>;kg@)mUGen#G^8(a7RFvEP=;%M+gc zUT{{Q3v!=2kM450J}y)Fd}%pm%UP>(ona^0`f`d|U#$Z7E_bz8E)41`K9$hcffVCk zN(?HvPIMIL2p5~X*r!8mfi#nU=lk&Hl3<(fb>h$Ii5sYJ>n05j#F-`6IM?;Xmq!4I{jz-z+b;NP3LN4#U3~&tx()1GY7AW zeJvnz*0s336^lu64M^cusTX(M2MqfU$9<7gyJ-g@)ve`uYUP&t&ZY#VE)P(OdO}5r z{1%!ZvX+N(TXL~Qj>n8y(^Wxj7k_H;?I8s|I2t8Y9zl5rrygL<>b-EtluF9J) z8{mFUT=?7$KKICu|IldY9f!H+f##Yrn-eUFrbqv4DkqDTqiVMVsU6&V<3E0OxZgrx zqaN?b(xZp^=1)vD(%X@g%IhAhx0GvGkI?xOlV>vrdzmFq*vmfV*ggN(*TFHYLXJW0 zK+w^?4WlI2qy>)3d1Wsafw8Gv&vCUj1|2IUsvM=nYF={9Lz)Ye7T-SiY@VwB5jwi? z<-S}l0ze3VJ0SnM+^eXc46OM4Ko`9dFT@nC_v`t@0|tjvtWI}a@>n3~o7CO#B{fsM zuSST*$JZZF+&^nS)p)FsJ_tv)WpuwhjbY&M_hO=%8!uZ62A4N;mQo_vOdANVM1jfN qU}%~|K+R1{q?be&;BRLCCDYp>FzOUFp$oe zoxS(yeD@t`hwF6z@6$YVd~ey@%9gJ;j|4z5cxVx%c6JA3ay@anH2={0Jx) zEG4rqK}=xVS<$?y+Hb=TZ(h8aqgY^@;+vV-dp9fQ{3zVQc-7+2p1OMvAIrt%&d}D` z4RlS%hYO4#C+}c9^yji>OMKgh3IV4dfgHu3?{g}?asQFU*dp+3k9F0W93A}?=Syo_ zu9my6?>EqE5IA(Rwtd5v=+7LVGb$a`5B*S!JRBqLQt=?}?vI-n%ir;z@0%u=X2V;2 zR`HDU)%mh#&%d}P`0ur`x6#oG5x@Ap&+CF`tzMnHdHJj2uQiqD_i|o0l~&OC%~|K+R1{q?be&;BRLCCDYp>FzOUFp$oe zoxS(yeD@t`hwF6z@6$YVd~ey@%9gJ;j|4z5cxVx%c6JA3ay@anH2={0Jx) zEG4rqK}=xVS<$?y+Hb=TZ(h8aqgY^@;+vV-dp9fQ{3zVQc-7+2p1OMvAIrt%&d}D` z4RlS%hYO4#C+}c9^yji>OMKgh3IV4dfgHu3?{g}?asQFU*dp+3k9F0W93A}?=Syo_ zu9my6?>EqE5IA(Rwtd5v=+7LVGb$a`5B*S!JRBqLQt=?}?vI-n%ir;z@0%u=X2V;2 zR`HDU)%mh#&%d}P`0ur`x6#oG5x@Ap&+CF`tzMnHdHJj2uQiqD_i|o0l~&OC`(T-s{47XV_Qjs!q43M-+3F(?cT+FV6}2fgM=c> z5e`KKHx5QaCyow5Cx;#Zrlb}D0c=ulobNV@&FeqXJwJX^nZczEo5kc;Z29~A@DC1S z#S1ql-g}a;F*o|A&yVF(Q#RSY&wO`2@Y7YktaP@W=jNa9_&>K;s_a@Hb7{u>nF0dZ zGdb<|*R?Sw_w=}Thqhgc{xrYiv(KSU>29DT(C!<$d4UT;Zp0=$wQS^cs(8G+`1I6g z_kXR{IeX7_`kRf<`|j;$z9b;EyyolLxih_2-${8Ndu!+4#A?@_m#oSi#GQWsY%ZuT zJ}M)%wDte2C0j1#ZPC)*qnKx~(?hOIRl@tr^Tn?(Xx^I9$?Yd578n1nZ)a@OpG6zC z+_|3jIx0$!*?sdNmS=Hae!qV_J?)U%Yf1ma<8x1Xs`2taZ*$-A*lORF)6dssXWmlX zwR}r~X56+9n-{M>qW?@L{#i&#`u|VWQMIcS{|oKf>aED5-Ezm?pZj~T)g1nae06@o zsu%Aa-d}ohJLDYSmzT>kl9zos6`kSAB{Hkdva9XQ#{8YO8+HqvZSl#j(0sAyJm`_tFkuP;vvJ0BS}|Du=Rq1*lY{{21n^v?P%oNin%eO}1d zg;}mY=k69_pJX7%UidGjdiK^WQs*KUKC@*1EG5=zV7K^K@aDPmZ@xaHr(0LJa1o!m z-rEVgrLxx@esXx@{=IL1$KAQ6cyr;U`HdBB;)`1^9n`!YvbyXKuh&13Kig~k*T3>E zuDiYVR_4_Qm526xC|zo|=%4MvjY1J6CbnmS!z*TNPHes{v($9D_S$9Ze*8YaKm`PtE*n(zi%eMEWqID>gTe~DWM4f`X_ZS diff --git a/Tests/images/text_float_coord_L_BASIC.png b/Tests/images/text_float_coord_L_BASIC.png new file mode 100644 index 0000000000000000000000000000000000000000..14073bd6f40ea1baf27179bf5fc615feac74dc69 GIT binary patch literal 2165 zcmai$c{J2*8^K-4Fm$CuxKOOi0QAzoPPxU#Ge0p8;nwDu}^afTk)u+dP(ZB)LX*> z$nA&9`Bk1+>m1d=@C$Jr5tadz;xnUlKMd%J<%8&h!SL-E-&p=;=1TLcb!K$^q7nkQ zpIaXR0ha*)!PpoqD+9pc#_|Y6PA0!G1hTL4<1QSi`{urSU_u~>91^$biQY&<%Gd1L zo#h!_=og0MSxvWBdjd1-Keop(jW5`@QFLUKg{d~XDb);DOw;r4Uy|N!VGBl)g2Rpn~KVp)u2Uf@KYqrxkDP0(2tL- zpQ+v^mD*k$&bNbVnx$t9W*aE$r|0`(K(gkCRh6<5IFQDdgDzLb2nUHYQ>^y%1;|zn z5FdVDYD)S|p=6IUmt+n}R3_>-8mm2PdEjZN;Y`^S!mX=+9~9=br=b9oQl(xD@zm0uaKT186YBRp%Gi9X4hyGXJQM5EST4P7HgrZjFn7D17ANo=9P=eeU428|T7vdMuAL{7i$PMW3l3Qu zl^ib_#)10?VJc*YiD=4LZd4hR%&68??Q&5-$@p-_!_8)4(Op1ugLum`+6m0j%{;_z}xn1y3zA>31{Zujw=;xaZib@--~tu%G% z=z8|qanRu~i?q@YyGa1xd}{~4=}vMm@sbg6v1$&B9cdPn21VphADoPKkqLR zCjUc>tuY#}F`+d)j1j$zr@?LD&*ph^)I@rIj%xv(U2@KGHFHA(AKo^pm1x1KZE34U zK^EupYYW)@(DtMf$x4n~+EfZMmg4ykR89nA zBaacQ!Pe4y-=#$L^mLI_Xo1{N=so3Uin=#Xx+y0dn_>kHz_2YHj7pjm;?&S5hg1D= zUB5Uyn~I0qwsskT#fZUKD&U#&Hu3KS-faFex5}mc#~n=gWB@KlDG#fxpUa>-B(Y9q zkt)J^pFcZf?v>f+!;f-}{yN82McpVdD{@K4Yi;gw6rZdv*X$sLLcLwX7kzSPS}MBp z6X#7cFO7`3!(8ut5=wuBAw7+N#3lSttxT8AzNTTp| zXvL8JOtL}MubH&t@bWxcNBS8=dsBPTO;&e-HY`L zH=mxL9>30wqX`ptWTWgdvWq1f_acaJ-MDw~>LuHft<5|D)I7O|IvI_OJ%0|_qoA)_ z{}DFP`6|k!myiZd8a(y;lg+`+KjYQIeX<6dBh8EnR|QPk@EVck#*@4!0~^PXpWpOZ zvsJxb8d_MoD^KhS^18z0KA}tb#{cu^TP<^E>~|JJ+@v1r%ObkYvU;MghB4U%$zn`3 zEA5PQ*3it8Dghq%wuK66eO@rV;`;TyZ(}Mm<|9W7_}!jAj(HoMzkquRus?eG#0fR# zEPd00^u(Rkvn}oc&%SZDAveIUeO>oy0zQUh>A&dP=(aaUnV|QlzXZeAHYXUymwxE) z*KP^#PBQh!qnvFbL-Ph_q}6qQ95sybsE5GsHJZ5u#}$< z=Uvo0P&6-*fS0Dgnlj;xIEbq^pD|bRLPGz;OG-qVCM-kY27{BP@=)u&LRZO-?LYI zJf_Q71*!F{AG1+{cFvR|&W`8n<|CAh!*7iOy34`esD*ZtI?D?us#}%kA*d{gm-Hu? z(QB|4r{Y9w#@g!$w)uIYu>b9bMeT`}2?_l>{?s*58f0FTB>4YPoOuA~|DpE*p#Mv~ cpu2sV*?vap8qq6e|2qM~Vl2^h#_kFK0#mH`bpQYW literal 0 HcmV?d00001 diff --git a/Tests/images/text_float_coord_L_RAQM.png b/Tests/images/text_float_coord_L_RAQM.png new file mode 100644 index 0000000000000000000000000000000000000000..14073bd6f40ea1baf27179bf5fc615feac74dc69 GIT binary patch literal 2165 zcmai$c{J2*8^K-4Fm$CuxKOOi0QAzoPPxU#Ge0p8;nwDu}^afTk)u+dP(ZB)LX*> z$nA&9`Bk1+>m1d=@C$Jr5tadz;xnUlKMd%J<%8&h!SL-E-&p=;=1TLcb!K$^q7nkQ zpIaXR0ha*)!PpoqD+9pc#_|Y6PA0!G1hTL4<1QSi`{urSU_u~>91^$biQY&<%Gd1L zo#h!_=og0MSxvWBdjd1-Keop(jW5`@QFLUKg{d~XDb);DOw;r4Uy|N!VGBl)g2Rpn~KVp)u2Uf@KYqrxkDP0(2tL- zpQ+v^mD*k$&bNbVnx$t9W*aE$r|0`(K(gkCRh6<5IFQDdgDzLb2nUHYQ>^y%1;|zn z5FdVDYD)S|p=6IUmt+n}R3_>-8mm2PdEjZN;Y`^S!mX=+9~9=br=b9oQl(xD@zm0uaKT186YBRp%Gi9X4hyGXJQM5EST4P7HgrZjFn7D17ANo=9P=eeU428|T7vdMuAL{7i$PMW3l3Qu zl^ib_#)10?VJc*YiD=4LZd4hR%&68??Q&5-$@p-_!_8)4(Op1ugLum`+6m0j%{;_z}xn1y3zA>31{Zujw=;xaZib@--~tu%G% z=z8|qanRu~i?q@YyGa1xd}{~4=}vMm@sbg6v1$&B9cdPn21VphADoPKkqLR zCjUc>tuY#}F`+d)j1j$zr@?LD&*ph^)I@rIj%xv(U2@KGHFHA(AKo^pm1x1KZE34U zK^EupYYW)@(DtMf$x4n~+EfZMmg4ykR89nA zBaacQ!Pe4y-=#$L^mLI_Xo1{N=so3Uin=#Xx+y0dn_>kHz_2YHj7pjm;?&S5hg1D= zUB5Uyn~I0qwsskT#fZUKD&U#&Hu3KS-faFex5}mc#~n=gWB@KlDG#fxpUa>-B(Y9q zkt)J^pFcZf?v>f+!;f-}{yN82McpVdD{@K4Yi;gw6rZdv*X$sLLcLwX7kzSPS}MBp z6X#7cFO7`3!(8ut5=wuBAw7+N#3lSttxT8AzNTTp| zXvL8JOtL}MubH&t@bWxcNBS8=dsBPTO;&e-HY`L zH=mxL9>30wqX`ptWTWgdvWq1f_acaJ-MDw~>LuHft<5|D)I7O|IvI_OJ%0|_qoA)_ z{}DFP`6|k!myiZd8a(y;lg+`+KjYQIeX<6dBh8EnR|QPk@EVck#*@4!0~^PXpWpOZ zvsJxb8d_MoD^KhS^18z0KA}tb#{cu^TP<^E>~|JJ+@v1r%ObkYvU;MghB4U%$zn`3 zEA5PQ*3it8Dghq%wuK66eO@rV;`;TyZ(}Mm<|9W7_}!jAj(HoMzkquRus?eG#0fR# zEPd00^u(Rkvn}oc&%SZDAveIUeO>oy0zQUh>A&dP=(aaUnV|QlzXZeAHYXUymwxE) z*KP^#PBQh!qnvFbL-Ph_q}6qQ95sybsE5GsHJZ5u#}$< z=Uvo0P&6-*fS0Dgnlj;xIEbq^pD|bRLPGz;OG-qVCM-kY27{BP@=)u&LRZO-?LYI zJf_Q71*!F{AG1+{cFvR|&W`8n<|CAh!*7iOy34`esD*ZtI?D?us#}%kA*d{gm-Hu? z(QB|4r{Y9w#@g!$w)uIYu>b9bMeT`}2?_l>{?s*58f0FTB>4YPoOuA~|DpE*p#Mv~ cpu2sV*?vap8qq6e|2qM~Vl2^h#_kFK0#mH`bpQYW literal 0 HcmV?d00001 diff --git a/Tests/images/text_float_coord_RGBA_BASIC.png b/Tests/images/text_float_coord_RGBA_BASIC.png new file mode 100644 index 0000000000000000000000000000000000000000..14073bd6f40ea1baf27179bf5fc615feac74dc69 GIT binary patch literal 2165 zcmai$c{J2*8^K-4Fm$CuxKOOi0QAzoPPxU#Ge0p8;nwDu}^afTk)u+dP(ZB)LX*> z$nA&9`Bk1+>m1d=@C$Jr5tadz;xnUlKMd%J<%8&h!SL-E-&p=;=1TLcb!K$^q7nkQ zpIaXR0ha*)!PpoqD+9pc#_|Y6PA0!G1hTL4<1QSi`{urSU_u~>91^$biQY&<%Gd1L zo#h!_=og0MSxvWBdjd1-Keop(jW5`@QFLUKg{d~XDb);DOw;r4Uy|N!VGBl)g2Rpn~KVp)u2Uf@KYqrxkDP0(2tL- zpQ+v^mD*k$&bNbVnx$t9W*aE$r|0`(K(gkCRh6<5IFQDdgDzLb2nUHYQ>^y%1;|zn z5FdVDYD)S|p=6IUmt+n}R3_>-8mm2PdEjZN;Y`^S!mX=+9~9=br=b9oQl(xD@zm0uaKT186YBRp%Gi9X4hyGXJQM5EST4P7HgrZjFn7D17ANo=9P=eeU428|T7vdMuAL{7i$PMW3l3Qu zl^ib_#)10?VJc*YiD=4LZd4hR%&68??Q&5-$@p-_!_8)4(Op1ugLum`+6m0j%{;_z}xn1y3zA>31{Zujw=;xaZib@--~tu%G% z=z8|qanRu~i?q@YyGa1xd}{~4=}vMm@sbg6v1$&B9cdPn21VphADoPKkqLR zCjUc>tuY#}F`+d)j1j$zr@?LD&*ph^)I@rIj%xv(U2@KGHFHA(AKo^pm1x1KZE34U zK^EupYYW)@(DtMf$x4n~+EfZMmg4ykR89nA zBaacQ!Pe4y-=#$L^mLI_Xo1{N=so3Uin=#Xx+y0dn_>kHz_2YHj7pjm;?&S5hg1D= zUB5Uyn~I0qwsskT#fZUKD&U#&Hu3KS-faFex5}mc#~n=gWB@KlDG#fxpUa>-B(Y9q zkt)J^pFcZf?v>f+!;f-}{yN82McpVdD{@K4Yi;gw6rZdv*X$sLLcLwX7kzSPS}MBp z6X#7cFO7`3!(8ut5=wuBAw7+N#3lSttxT8AzNTTp| zXvL8JOtL}MubH&t@bWxcNBS8=dsBPTO;&e-HY`L zH=mxL9>30wqX`ptWTWgdvWq1f_acaJ-MDw~>LuHft<5|D)I7O|IvI_OJ%0|_qoA)_ z{}DFP`6|k!myiZd8a(y;lg+`+KjYQIeX<6dBh8EnR|QPk@EVck#*@4!0~^PXpWpOZ zvsJxb8d_MoD^KhS^18z0KA}tb#{cu^TP<^E>~|JJ+@v1r%ObkYvU;MghB4U%$zn`3 zEA5PQ*3it8Dghq%wuK66eO@rV;`;TyZ(}Mm<|9W7_}!jAj(HoMzkquRus?eG#0fR# zEPd00^u(Rkvn}oc&%SZDAveIUeO>oy0zQUh>A&dP=(aaUnV|QlzXZeAHYXUymwxE) z*KP^#PBQh!qnvFbL-Ph_q}6qQ95sybsE5GsHJZ5u#}$< z=Uvo0P&6-*fS0Dgnlj;xIEbq^pD|bRLPGz;OG-qVCM-kY27{BP@=)u&LRZO-?LYI zJf_Q71*!F{AG1+{cFvR|&W`8n<|CAh!*7iOy34`esD*ZtI?D?us#}%kA*d{gm-Hu? z(QB|4r{Y9w#@g!$w)uIYu>b9bMeT`}2?_l>{?s*58f0FTB>4YPoOuA~|DpE*p#Mv~ cpu2sV*?vap8qq6e|2qM~Vl2^h#_kFK0#mH`bpQYW literal 0 HcmV?d00001 diff --git a/Tests/images/text_float_coord_RGBA_RAQM.png b/Tests/images/text_float_coord_RGBA_RAQM.png new file mode 100644 index 0000000000000000000000000000000000000000..14073bd6f40ea1baf27179bf5fc615feac74dc69 GIT binary patch literal 2165 zcmai$c{J2*8^K-4Fm$CuxKOOi0QAzoPPxU#Ge0p8;nwDu}^afTk)u+dP(ZB)LX*> z$nA&9`Bk1+>m1d=@C$Jr5tadz;xnUlKMd%J<%8&h!SL-E-&p=;=1TLcb!K$^q7nkQ zpIaXR0ha*)!PpoqD+9pc#_|Y6PA0!G1hTL4<1QSi`{urSU_u~>91^$biQY&<%Gd1L zo#h!_=og0MSxvWBdjd1-Keop(jW5`@QFLUKg{d~XDb);DOw;r4Uy|N!VGBl)g2Rpn~KVp)u2Uf@KYqrxkDP0(2tL- zpQ+v^mD*k$&bNbVnx$t9W*aE$r|0`(K(gkCRh6<5IFQDdgDzLb2nUHYQ>^y%1;|zn z5FdVDYD)S|p=6IUmt+n}R3_>-8mm2PdEjZN;Y`^S!mX=+9~9=br=b9oQl(xD@zm0uaKT186YBRp%Gi9X4hyGXJQM5EST4P7HgrZjFn7D17ANo=9P=eeU428|T7vdMuAL{7i$PMW3l3Qu zl^ib_#)10?VJc*YiD=4LZd4hR%&68??Q&5-$@p-_!_8)4(Op1ugLum`+6m0j%{;_z}xn1y3zA>31{Zujw=;xaZib@--~tu%G% z=z8|qanRu~i?q@YyGa1xd}{~4=}vMm@sbg6v1$&B9cdPn21VphADoPKkqLR zCjUc>tuY#}F`+d)j1j$zr@?LD&*ph^)I@rIj%xv(U2@KGHFHA(AKo^pm1x1KZE34U zK^EupYYW)@(DtMf$x4n~+EfZMmg4ykR89nA zBaacQ!Pe4y-=#$L^mLI_Xo1{N=so3Uin=#Xx+y0dn_>kHz_2YHj7pjm;?&S5hg1D= zUB5Uyn~I0qwsskT#fZUKD&U#&Hu3KS-faFex5}mc#~n=gWB@KlDG#fxpUa>-B(Y9q zkt)J^pFcZf?v>f+!;f-}{yN82McpVdD{@K4Yi;gw6rZdv*X$sLLcLwX7kzSPS}MBp z6X#7cFO7`3!(8ut5=wuBAw7+N#3lSttxT8AzNTTp| zXvL8JOtL}MubH&t@bWxcNBS8=dsBPTO;&e-HY`L zH=mxL9>30wqX`ptWTWgdvWq1f_acaJ-MDw~>LuHft<5|D)I7O|IvI_OJ%0|_qoA)_ z{}DFP`6|k!myiZd8a(y;lg+`+KjYQIeX<6dBh8EnR|QPk@EVck#*@4!0~^PXpWpOZ zvsJxb8d_MoD^KhS^18z0KA}tb#{cu^TP<^E>~|JJ+@v1r%ObkYvU;MghB4U%$zn`3 zEA5PQ*3it8Dghq%wuK66eO@rV;`;TyZ(}Mm<|9W7_}!jAj(HoMzkquRus?eG#0fR# zEPd00^u(Rkvn}oc&%SZDAveIUeO>oy0zQUh>A&dP=(aaUnV|QlzXZeAHYXUymwxE) z*KP^#PBQh!qnvFbL-Ph_q}6qQ95sybsE5GsHJZ5u#}$< z=Uvo0P&6-*fS0Dgnlj;xIEbq^pD|bRLPGz;OG-qVCM-kY27{BP@=)u&LRZO-?LYI zJf_Q71*!F{AG1+{cFvR|&W`8n<|CAh!*7iOy34`esD*ZtI?D?us#}%kA*d{gm-Hu? z(QB|4r{Y9w#@g!$w)uIYu>b9bMeT`}2?_l>{?s*58f0FTB>4YPoOuA~|DpE*p#Mv~ cpu2sV*?vap8qq6e|2qM~Vl2^h#_kFK0#mH`bpQYW literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index dc20aafbbe9..3d88585e696 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1043,17 +1043,14 @@ def test_float_coord(layout_engine: ImageFont.Layout, fontmode: str) -> None: if fontmode == "1": d.fontmode = "1" - embedded_color = fontmode == "RGBA" - d.text((9.5, 9.5), txt, font=ttf, fill="#fa6", embedded_color=embedded_color) - try: - assert_image_similar_tofile(im, "Tests/images/text_float_coord.png", 3.9) - except AssertionError: - if fontmode == "1" and layout_engine == ImageFont.Layout.BASIC: - assert_image_similar_tofile( - im, "Tests/images/text_float_coord_1_alt.png", 1 - ) - else: - raise + d.text((9.5, 9.5), txt, font=ttf, fill="#fa6", embedded_color=(fontmode == "RGBA")) + assert_image_similar_tofile( + im, + f"Tests/images/text_float_coord_{fontmode}_{layout_engine.name}.png", + epsilon=( + 1 if fontmode == "1" and layout_engine == ImageFont.Layout.BASIC else 3.9 + ), + ) def test_cbdt(layout_engine: ImageFont.Layout) -> None: From be60ff0f08e27bf5eb0201b7a7868c6163df6b22 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 21:26:51 +0300 Subject: [PATCH 15/17] Font rendering: Render text with subpixel positioning --- Tests/images/bitmap_font_stroke_raqm.png | Bin 2732 -> 2350 bytes Tests/images/test_arabictext_features.png | Bin 1472 -> 1635 bytes Tests/images/test_combine_caron.png | Bin 3875 -> 3259 bytes Tests/images/test_combine_caron_below.png | Bin 3583 -> 3343 bytes Tests/images/test_combine_caron_below_lb.png | Bin 3577 -> 3381 bytes Tests/images/test_combine_caron_below_ld.png | Bin 3627 -> 3365 bytes Tests/images/test_combine_caron_below_ls.png | Bin 3565 -> 3373 bytes Tests/images/test_combine_caron_la.png | Bin 3875 -> 3259 bytes Tests/images/test_combine_caron_ls.png | Bin 3844 -> 3223 bytes Tests/images/test_combine_caron_lt.png | Bin 3926 -> 3269 bytes .../test_combine_double_breve_below.png | Bin 3029 -> 2282 bytes .../test_combine_double_breve_below_ma.png | Bin 3012 -> 2214 bytes .../test_combine_double_breve_below_ra.png | Bin 3011 -> 2261 bytes .../test_combine_multiline_lm_center.png | Bin 4147 -> 3163 bytes .../images/test_combine_multiline_lm_left.png | Bin 4197 -> 3292 bytes .../test_combine_multiline_lm_right.png | Bin 4154 -> 3172 bytes .../test_combine_multiline_mm_center.png | Bin 4204 -> 3245 bytes .../images/test_combine_multiline_mm_left.png | Bin 4189 -> 3310 bytes .../test_combine_multiline_mm_right.png | Bin 4215 -> 3266 bytes .../test_combine_multiline_rm_center.png | Bin 4149 -> 3176 bytes .../images/test_combine_multiline_rm_left.png | Bin 4144 -> 3191 bytes .../test_combine_multiline_rm_right.png | Bin 4186 -> 3210 bytes Tests/images/test_complex_unicode_text.png | Bin 1439 -> 1282 bytes Tests/images/test_direction_ltr.png | Bin 1949 -> 1727 bytes Tests/images/test_direction_rtl.png | Bin 1324 -> 1187 bytes Tests/images/text_float_coord_L_BASIC.png | Bin 2165 -> 2171 bytes Tests/images/text_float_coord_L_RAQM.png | Bin 2165 -> 2171 bytes Tests/images/text_float_coord_RGBA_BASIC.png | Bin 2165 -> 2171 bytes Tests/images/text_float_coord_RGBA_RAQM.png | Bin 2165 -> 2171 bytes Tests/images/text_size_ladder_raqm.png | Bin 10111 -> 10778 bytes Tests/test_imagedraw2.py | 5 +- docs/releasenotes/13.0.0.rst | 12 + src/_imagingft.c | 240 ++++++++++++------ 33 files changed, 172 insertions(+), 85 deletions(-) diff --git a/Tests/images/bitmap_font_stroke_raqm.png b/Tests/images/bitmap_font_stroke_raqm.png index be273d7cb6b5f7dff391fba138877cf2a161f1a0..0539575bd3f9f2681b2f3039db07e5a06d0caa1f 100644 GIT binary patch delta 2340 zcmV+<3ETFp6|NGHBYz0>NklxnS`X0 zCQZ}KL`+K?O(`qQGKZ<`!_3ker_n|$bsU>MOsASA$5bpGAT!N$#7wh6MFiyG>7KLi z-nr~tUOnE!)b4NgkN0-Zy}NsUyJycidqFNPYQR;2ORx)qp0qQF^*>m4t^=b~c?pO&2tdAE%~G9kt;w{YC(5d(0SW-rTp@ z`u3&YerpiHBY#E^FakO}KrCFnLP3c%Wv2~Ksp!;RDH*e^z_sEv==U<3mUt2 ziCX+&kv09sz}mH%N}2Bv5v(H>s8D@;^NlmXqel~v0>%;xP*Kwd9>_#tIwQKwGStQp0{(H(cts#T<+u#k^~)KqHMjt~3j zXm)cRe2~8Wn(TP}b@mVeRoGSR)r)H)BdJRlK5L4LLqagW?fdUpzNUtjE~Ozu=(5Xr z{4p^+$A3waXvYq+aOe=Fr0{_XuuRLAbobr9`Cf7f-EWRA8J%fWt~MC53m=k3VWX{v;DrJ$3{h1`aCur=AJ{!J9WzixytsJs=He z1Al}|U-tr=0J2o-^>T1w8`S|h0TX0YaU%bd2n)OatWut4NKAAV7%A#+a&nLaPXr>N zqdRa4$Z_)ISIe_(QHKsCU_6}B4k%WVFTJD@JOOA99kIX;wY|Rjs-Y%Y4y-rdG;C}+ za4Vegf+aj>j-BAiKr3kAni3H{7X(*IXMYpGXaanu>L*Xu3HEQIHG&1nz^Afn{{UX) zmrJGmm@2Ed0@wyz4@PI8R$MkkaK5biFM(fWgok7W^Z?^J>BUgDVKB8GI|3gACxLQc z4UhmvlDvTi4h-rHme79`7)^!FgFv;co-7EYM*`U4$jr2lDoFK9(Zt2s)g33)+6C^156{=F-vu9v0QRb;O>=C*~h2UtH+NIIDbqMd23=bEDkCvsB>om7Me6v0Gu>Ee3+IjQIEud zu}(6AykLR)$`CLXs=xE**?SzSDyK}LlP77-8r3u$jBM5P!3V(*JRgipfhzgS`YbD> zty?KN`aAfr*9<7EQB9W`4kGsMHL_-g@SpHupK5ySvBnLPykc!u0$sr9V}E+lto{vdz>e~dEF@V z>`A=a7+qA;+_}NT!Q;TV-Kr@nB84$yYJhOHMlEAz*8{+Dib7%{DZ64h6M1>(C=T9Z zj`AHETgT7Nw{!SYD^_J*fQz*P8wX&xSbTw79nVx2up0(NNmos4)T zpP%1Ag5_+w4~)@fC2wDaXP+e?7L4W6#KEqm2g}f$3_mLVos50kuJBfHIkJa9D_ z_e(3Ya_$Kh)og+fdB7In7;r|!Wc)h8`VKyFB={Yyhe;(R4m;RNp?^H|khX)_be%T@ z%WXz!sh!~4Y@UKfFz1qh{`S#Kof=AlW&6$s;})mQ)JG>+KZE(w^VBI?x6a`VUcS6x z1an@+<9S6pgGY_xGj;oR!)>kg%7}{C}Eg4^XW6=cX!`n4v+#6CJ&J?XKU89t1|R&5I6z;3-oymFlU= z;GL{e%gK^CY&N%o1HlCa1Uw4H4U(&>(P;>dU`b`KGDq^fjIC0F=9xfqKLiUBB;qWW zvKHtBMj~)p65~N-vR;0ffRSb#{Gx>B)e@LxLm(3*fYauK2Y&}Z@Z7n&N*gH-hIIU@ z$)#91onTJ516u))mUamZ!D0r&!fJt3IG{U_RX@RbKr`rQ4QyALtc@FkA(%t2goKm8 zKsaNz`7Gysv$F#r_@j>~GLlsqx=faiDA8S>tViDk$olopaj=2DeU-JmRniWgP!cSU zh|^Iiu~1hzHGiVzopU6B9$i1dE5t+(1LDmr@mS!1lAJz01i9!kWutzEGMlK~t$laOghS0XQkaX@hUmjfAh`!t(x`1 zgajtWL0&%Zy5<^I0l+u4xw%%f%9p+^TRJ@Q#TRtXJ=CECg@{mC&n$5 zma=lJp$;3yPYHO;-MXp1c;e6s(0000< KMNUMnLSTXflVqy^ delta 2725 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 literal 3583 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 literal 3577 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 diff --git a/Tests/images/test_combine_caron_below_ld.png b/Tests/images/test_combine_caron_below_ld.png index 540ab7d4264fb4871a60e7d03edf0b9b083984d2..0493de7c3d9d2de26a0edc7122b7b8affa5d7b87 100644 GIT binary patch literal 3365 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 literal 3565 zcmeHKYgAHM8@9=&%rP4qZ6;0AEGakQ zMbNBKYLrVhBi_j}wG=fSFQupzVP23F#0v`FZr1nr`|gcS=`gCsi0*n}nOA5jGUw%G69!p@#hVe0d^V-L=jzcTx z_6bJhKK*qsb5J7s{X0>6QY#KVv)x=$sM%mdy7wE^>Op#kz3z?5C;Fuw_GP(7+pepS zdLg2GOBK>f(dCSD#EBFQ-wK~DEs1KV<3!b@Iw35%Q#CS9$@tx-5uU_CX9cj)-Qr+w zSPf-qj)GsOb#9nTUZW%1g*osoZQWJN&*3u!{}&6I(ZRvNAE{M@(IR=frR)FZP;>C% z!&!3w9j-J)ECPX8YShg^=galSsO{N1k(XwM6UQF^IypJ%7~T2Lz`#}$liRm$eYG7% z8m_*O3D+!2p5JsMJsD|85}1e8l1}%Q9SRBx8ck8l~#Z--JnnS+Npi$1%iC(rtST(=4c9~%(T#^=EzkXd+5gQa1Rv#y> zeSl`0!&B;SL|UXQzNxIJ~b{o~`c>o=KID>~hXAC3Hc8nx-I!d_$Sla`j2oSdAmcm7nr zGS{xHb}BC~Pnvsf^N8x`5;vUi_swX69yT{Ow?REq2ZcgID%=z2B*yIRZkJ+-M53Vn zryLuMq}jL$#qMp*v>>RF$B!Rx$+Rfm(>`27N_YU|{UXWw#qDV{KHykM(CIAuXh7CapLdmy}h2~&U*NBYg9%c3)WX;u?GU_=p63rhlEyID*)uRWo2c=5Z@F%6A$0hr%y{4-#$wR zrRt@b2O0ZMKmKLSKv-~SXhFds>Y_wQP}~`KQD? z?RX}2d4e4P-VojV-8Cf0WRXC@2tJ~Z8aWA zst$%zd1IsJRvY0Py6NkhxfWAGhndU%{gSsQ+nk!L1SR}~ zv-}fJVaq;BT~K#*d5Tx?4u}v(+<9qw@L`50!VP(#tH=Y3#TF(eXjd1=%4;9=bkogV zG&Ygip@J~ts!BHGw){s@@P*mcSnIE_V{twEM#hcrv-`{ z!ti5W-kpA_iY`vB+S26}xC0~UergD3y2hP3^JhiH%9}I`E{3RCS?Vhf$u##^&cmnd z*}HeLmmP>KF0HAlsjQ3_F`PsqQ3z#9Inf*FG5^o<{FEd9!TtOBaB2#&m}Y+Wjve70 ztvs|Q{{7thcJzZ|z|K@|H}p4dEDK=4;w5dBm6dcl9WaX5xI78u%o$A{XOTUDIRTCR z2~ogaHl9PjMw@1GfIQ1gVE z7b_8de$Sh(tRK>N zq?Sq_bvnfSVP7t)L9;xCt%_Ze8(!{GHE#(tb5G#2iV@zKkv%M?sV8L-6o9LzABhTG zs7vqjfH;PL+XBeQL+1qIBzXfcDy9n|T-qOGL(4^#O2LW&^4~CZNdeLt1!e|3)E?jH z9@Blt(wlw|kW)4NYGIfdxFG!AD1?9V*>wtj?SZ=LiGYApakbtqmAkCR?ZfK_VS-Q$ zW>MK2IC^=CqphB9zP82GIW~rc#8;m`5gHnLH^dKcwR3((Tvt{WHPz2~aBT91%SeK} zVIiuMnfQ!xZm0^1;oq4Tbs2gnyMePP&X*_?G$`NBc>twCD#~=9^WB>ZOH#!~M!vV> z)#o%s0R3lrF2|hZ05t^%1@&EW}h9~dvR&* z{diGQ9FQ(&;iydj69ZC!!@J|{J#wLzdJ{te0vG|EW8us+dXeZk*leS|!Gq4hQt3wEhW z;UGJ(7_@u8zTO=lg7}7p=-GXU@fUECX&wYy)q(t_$DD>R2bU1_cFrq80^NCo$!2=t zjCB*=P3*<@rJW$xra~?KzMl>dY=#(}9At4H?Xq^Iizlp_td4-N5$lx`HOLCs0-1Zv zcVq3uNYYM_VND*8oQU(c;w&-NO=GrVz%=V`%}TKwO!W5sfEoZ>quG^vr0j@nTM#~# zcl-gx)-y>2DV<+K=AMq(vUY&a(74*0x;JM?`b}1PyTubGRadQ>{`j}6-~7n6owu_j z`6PC_Zn{qaiES6F=g06P!w&J=)`FCbh-?dxqPqazpy^0$YDBg!m`NY>`roda$82%Y z{=V^FW%M9uh`S&dgYqIU@otRd*QO&dB)`oNq|hSPocM&u=kOVVe_61yp0lHt@@#EP RJNS*$@k0doaJ?dE{{czSBDDYj diff --git a/Tests/images/test_combine_caron_la.png b/Tests/images/test_combine_caron_la.png index 1097f4be59eefd9ed1332e15f063c323ce04f5f0..3729da40d975a67a8d269883a66696dee23db96a 100644 GIT binary patch literal 3259 zcmb_fXH-+!77i(Z1R)~Ch%qnu@7}Zb{!ZCv-*ppC+F5Ls zke2{~KwGUWkDmsCM1J~ui-UnT(!+UKAdn>0>bUWl;DPtO8e2i?lFzu0ROj7CQY~qg zWP~0EBWbw@JYy$VB_HogD%5P$0b^9X;=;J=y%*PShON>&`AF8SpKsgPhx6+_g-1WV9K2+;-= zlwMo|q<;}A8cobn4;)orgBwb|AN2{)((Bw-H!oDhf?{F0%-=y42{2h?R3rE=I7t8= zPZB^9@J%oxiAcZ!P{D2j;HUC85&m%gM-kT8(@TtftLt@-r|08Z$5Z!J4|jO1?P-Mn z_9Lc#|BZ{9^097*Gl%AuKRNI;i+TzNXJ~Hauls*;mrCqYz<&tVqcxE%SJpQK0XIqt z3x6ng_9*G|gp4wS`6Olbptm|IMya)IOo+OKZS~W=N9*EF7}xNXBZ{IUbQ!`2?$`q;ASP+FJMxxBs+7H*J`o|(B*QuLQy;#f@% z_Z69n!{IKnyk39kxOYr;D-?MSj=d4ZPiZ~{3aEmBD6tcdii_KJ1kJx`sH)Pv!c9Bi z6h;!0Lg#uAHN|G{l3-v8i>0|`m)6xYfq@lrC6$%hIyz2`EJ0pwZj?OLl>9Vk%GK31 zo54^eov#Yukms^5*MzSIO})&!$r@l^Q-DD!Fdc}Epqc4CSSgd9ntIyBrO*(M$6wu^ zjta4d^gt-EWuSj8H|V_89a;u|&febM{U-zz$?#CXM?} zkUApjLUDXEer;L1Qu#8TonrqFYAC*WLxNGxq}&nQu&pxOH&s&5R6- zVeVNI&4aF`F4RZpUp7AHwZ(W$rC$aN zO3nv;x+*mjsM>jZ%LD%qm?t)s^pB8)7zsa;*+Z22;7zEXsov zG;WVMzb-t-F!HhCNL8TC*%9RvIgN|XN$)6J>MG?yrPu2{v=qebNbT7nnj)Cx50G z$V!Y)PTFT_m$-Lk4}=#E%dF6E?zQk4srf7%YdMqMh2~y48{IKF^`uJssf`{)Osbn4 z-!Ui;rbKP{y={zTu~?|VX0t@3qmxs)+9F9r2=V+h|913_f&XxQB%eeS#M2E-uPEVg zf&nAbe%Y_JtkThE2IRW!n4x zb$LZj>-5sZ#Kb$Zwu3G#M3&t>-P&LwwdXr#6oIlN;1%4yeY>sgM7VrmaWN}G2qgZG z3lj;+QkZ6GY(9{8W@cuAfq{5dVPPR)oY;OxRKz4#b7X= zQnWmKi=SEMeLy1;*(=AyXo4dgc`LeMN9EtX{!$O>N zacjn*vmO0>IbhY&zDk9FZ46bnaVpl?V!iEIItShH#f0x@DQacrP~^Nn@k;L0szJg- z+n@6-=&ogM36-2{Lr;Z=F10hX5=8My($wcq8O|25guP&RyhQP#3w3U8Zt?9*QWH>> z>LS1R^c38`zo(1)%2cB@^Yg-Fxv~;7N)$5PmU-m9SNpWa_fSVggryR*4N5-(lq)&Y zro^vBU*T@1*^#Jmzoyk2ZNHm6exA)|Tp{Bz*=e z4+15wF^Ajru;dxIRqw?Nzn))1O{$8qDLo>Wm9TwZnJyH0GIDGx*El+21rsy!`4`(s zpjwW-ZbqTVGVN`Sa)=;9GuF%D}OxM;KglPOWcx>EaWV zq$mj$5&CYx5*wEQv&12z=RN0rpXK{}zt4H_o4t+H0mTDi zVq#KoTkFeWV!JYSuKnMECo&Ty1!7{-^Kk3)SKUfZ00HMIqyL7Ssj@icbB?8`;mEYhFUJX<)sSxD;b?G+y%ZwjVX*cV~hZc7o= zA8E2;LHb?absvHpKlqRNCk6it6!cN};_D8kiR(+`e8Wsl)~6P(#o8xFPtUF99+ZGp zX~Kw~USFPuW6W3jG$z{ODDx23l6YF^i4!MWo5Lpv0RtFKd~I#wCa2jZT{)pUZ;zz> zr=+zBw6w|vOxe??PyhV$_&(Y4Yta{Mpo^`K=OFkfjt$?dF)0`K^#d*oe@ zErsiK%?xa{Q$oT<_dQ82cN;omodU}#jO;yXmjh{8>DSHF3=Rs4pDRJ{G=%2cXV>0n zJK)Td1ibf+-)sB|ac`=Mo*il_)K$s$x$5uW=QlM{Znsb780p%ZbWJ!eapf%*i{;Rp zlULsA;CJ2GD(8v730MVrQ=9z3zgVctv?Ppc&up&h5PGD4plp`B$RQLjdn-EA8Cr0k-D_WVr+ zi-$2%Im~z3pg4_2Xz0$%J66rr|3Ufb!ZV$(`;LJb42vsJcV}n`K&~XOMRlB=GL!Ok|$t8e?%Do-Y(63pC_FFan{u9a0 zw(Yq-nyBvDWX6sP(zTZejvJeK=e%>eJ~(~th}%^!=}P;?zGLhtQ<%NP+mpb`rND?A zeah~a8vAys=3BA%%OAS$yUQ;=HzU-Q6d&p2)W%FM&&4Ey;py&zAvRj1Q64NcdUO&E3raX5CMD$@ylP>^=b(FqUM+Tta0t_ACYc`IrWfa^A=af%KdGxL5*!}&ikslSvyz#)dd6Da)!ze! z5}GZNG83$ZxfIJz0>J?7sD2>VIAgb#mKL7QKcBWPnf762WhKgD55cDKk3kMxg8k(ATIUeD`ESM|HEbEvBB8EXq0R6YI zN{>k1=F{%qzt3n#u5svbU;Cbb#!@l8>1$@LM5zCMyi3->{K>`=2=;j(<=9WYuPlY< zyc1@!3!q^)5VL{BNw*MN3O^ZE5l z0>4hwc7K1r0KlWv=5cFl>-_w@oUU_O!|}SB8YPqPPdR!XSgxN>$XIPYIA?y^1-bey zVIr26ot4F#8=ii3dpGdby&b^^)g{c9AX~dX<=N0$G2>+(hZXhW`~N9grY#);ffy=6gq_hwv?2V&cjb2Z4zxN zMnS(NB_$P@+YlTYgA{bcYo0w@o-Ixe7kDTtD%Q6!u`zDi7xQiso`5Cf+QMYgnwKmR z1xVBIO()>M!DRKA6W%@Qc7jz7o%g$a(V6nxquTHlKF!A@n)29U)s;eAI3ngm3T$80cyMLF*^8ZBw7X1CdbTEf zZDHb6J8y(%MKckfA?zhoc66ZrN|mc|uB)!E7fzv7rIk&W=n=3KHNl@7>0^zqww4Dqzp7q>X$!=*N=76>D*YFcXQUTNhI zoyj7c1rjV6Z%4&nx-?kp_T0Tm^xk+uPlZ)sbY4Q9GdDL}nyKS3R67coVMj684V}Wf zG@2tZd}4gWcgOc=|FEzyL|au=70jmO&LPP7uugFzA(lPH>yePvCKs4AJdjJNi{~dT zGTH}~TR;hUx41qVPpu-|c5{MQBjqekcXzjoPtpq(pA2+H&*JqCC{IUGD=x7(Ky8|N z=VHWDU}hi##Upf$Umvc&k$nbatC@FbkR%=>5^e)GSZEioSSLFfYn$V)O{HXoJfK~L z$;u*7H}D={(R?@ad{~-%fcJfDOF#CUc-pP3miNqDA5<`6srQl6ETwodwUnIqb17>V zpUT(Ed`Wn+TI>D)2}^d2neE`L?V@xl*rj60J5|cZE>~tny-Jp zlb=hRjGIiVGd-Yk+jlrU9JDO&mY0_o4E7guu)yI+^!n1&QN!y8kNor*!08(pEI>$% zk<#}xn#+|dWUO&bM~Cq4Db7UOTDsXnJlLBmy6o%>4IU}WIhG==zIY`kvg_?d(2?`+ z@jwK?AYE@XG%%oaHgWwg1s_jO&mFCPq-28PkGg=_1D+H+RCpQ?sBmRwz)>b7Bt%kD zQq;=*uMFtGcOV4M)Gl|B0M9{T1*mO7QS6u-m{b5py<4JYij#%;!d@`5046j^-cVDq zPmeg>*Lg*dUJ8<)pSEsa;aF)8=q)@nluU|r3Jc>*E|@318UFcNuMre_c5A6;aN}Q# z)Jh7F3IbpCTpQ4w%^i{$tM@YV@5|8cEsSF%4CI~yPHtw3I?NRZAV>sjdPtlb2 zFR8ly0|Pqtg?T7ewOrQ)jQ`g9GGIWQS=<5`&^o|j7RXg)OBBha!5hc};8{h$hBLW7 zj8%t+wOmDcj{3Hue3M$`-E&9&o=pBR|DTx>8{06^~VKiq6!1Tj^V0|0#XrbY&YfbrG0Ct-jJUnjF%;j_zhDmEHR;Xl)O zgbypt_Q?W*_LTt`KE$vNw!azx4)q~|hDnAT7z`TpFlgW&=Yg4_gC1zakp~9M6G#3n zIjkcUILQ9*A^zC^e@FO(!JneRboqJDek^znljPtuZ$eJ{s!a^3n~$_goevP-LSS&O zW7V_@ANoW-mUrZNoNnjwsy($%Bg0SC#xU`Le6DLz{nNHvq0R0eJI1<}T8Dq}ZK@q< zuk;w%4SA<2hX!UMIe}a{;KO1AEQkP;2MYW{5JQIOecIRC)vP+ZYQya8Y-xQQv9Fv?tE{Yyi{mX)K%qYG?8X(J(bZ)%G(g>g zf^xs;lMM!0sjOKa0|P2fEb&ESV`FKltP~6m7nDGumka5~e;n~p5$%-R-rgQqQSYa+ z!o7IS_7tAjqBAlxMf}*KICX?Gf@DXBhh=D!lasZzYKCC!4R1XE=aG{b-Y`=NP)Ps$ zy|J`F&g%ug;E<3wZw@v{j(c!$ULVsPUf0;s)Y@88RVDN&KR^H8y+bmW719rji3un~ zw9^@kbI~&c64_UqOiKDUHvEbn)zs+f>0P3gm6i4MSV&E{-@26!7?2I-$|6&+wDNLn z`FS(5vqkrdQ4|UV8E=fe-JG{iQN#%r(x#`Wd8s@uE>0Y7Vr~wLk1V7Y4r*1UrwgyA zQ>l=BCR4B^I06yx30AR!5H|@XQld8Z_NFH%(KHr|m6P-RJ~x}2`ecX@5{1SQf$rYE zf!Wz2^M%{Z&wp#N|IvJ5J%Ae)l*vDiQdG<-D6o`$V91@^RomETYhxp;+S1wzhrxCo zT<+YtbNBAuNUqyFMPm*uk_xJ8(%8w#Y0;^DXJ;oP11aV3WowIXMvAm@z#)JvL^`f_RHZdA*ER$m~wvN{t5Vl9&jKXxGQg$V-Web5e*v zR8(kuJ06%ML&Sn~3(OaO+j;&QsGA#)c6Wmf*(t=nMP>3){+GSIA{%dty}iB9GnSTI zDu)#a&!0bUC*9|3mq4mFcWi70`TcaYj=_)>hYq@?6`M^R|kpq1A8 zdKp@DbacR(8q|4=_^GT5hUoy_uO=R8*8L9~03+ys@jpx&8fk7Q_ig(m2GI^j`1RHJP;56G`m9$JNQ17-87&o!LOC z-RU%)7IK`yA9>|u6q6=B*>o2r7S|&Cx;Dm$FO5pIO2tU`8VRh-3;epfz;KsLdN$Ii z>htYgFIGFReQUU({@1=o79AfC4dLKGGLuIqCQ59g z2L=XWVz@ztmO(!KK@73Al~5ePP`MJ4;+8|6d zv#G6(a`Pq>21~!bwY-eonqOQjefso=ioW)K7;g^=*vEtFmcRrmyazWsu5`HRG!d-eYd(87x3Ah%>VFm^T@xkHF z3ckL+-QC^F%F1y%;J8TQLh1-vcc!JKk<4P4Y`?40OUuf{@ESjQOEsIo~K+~T<|6DxW6-*J!D&Lvng=B)WypyDRvGqu(wrC0;siUJK!nY-U*KA^9VuXg?Hk zQJh62m7416Xe~Lpxk8Alq9UnmZ~;L8fzD8 z5wS#wh@$qbLkT6cYN>sR*dzDj{tx$@d(W94az5vL&o|HK^Sqz;^ZGpBr+-)&iHpjJ z3J3^@BaN@!5D?h+V(%xiA3Tv{zeyDkkZ4C<`~6mM+B`eV!-fm_u>8)vT|0e{ap=T( z+vr<&2wCUdNK!47o}{-FgST>41+}XX=&kEnhfPi(q?Ox0A8vLhG0q%F#SuSqZCz`blxsPrp_sXBo(Dk!W*j>K?GOBtc zVx3*)-mQdOnJeqA@E+1ED9dU+qEdRszyQ|}E#6LqTH;;T<_00*OLZ^K7^=qfWQa9fA`e=!Z5niD;$6Of-9c4yr6VZ_* znNPWvnce!^g>LNx?Q|t`qSH(3be%x^lDi20F0Z}4JyC(A?ryPV$fgH6FvCMqHNCM~ zx+yxjk6nkTtUKGw%@8fjE)E^Gxw&adP?-j~lJ6lzq7V|pfh<%veBE)f)+|$>7k$#F zr7!%+RZ2@d*tSZiq0;X=7B0X4kjk$=*X^%ga=2Pr*K92MTJM`sZoY$qB1;zKIly8>YZwLjOClWYPn*VMpuU z{%uDzH1snuephxeCR)zc^-u6vWVE^E@GQ16I0~_5OKd=3O^j{qhz*U0R7yMPr)JD4 zE&FD%c@U7`-q~su6;2XV>~7fGrzjvZ^&?D7OuDq@cGkirK?`PhSKby^P*}puoAzK3+lp7Du^&1t$Bt$(#zJuqfC0Cc8YI;r z=R!L*SREN~-I$wlqBM;cf+%_b5>A$!`DkhTvA@u+%+d1E+%GD3Il(>rqrjYf1XTy- z)=Yu;NzEhz;WQ8goGm0IH3C)*UaHJu9@ z<+inT;M~CS`vS6znrjP?kX0SDcs*Fl6{P`-I)5|Am8lo9G7kDKlsgkx2Wcj__soeGQJw?yPkxbqxG+5U1oNuU*Th)!XIY(qxq*^=Znwtv zw;0G4clLJb%ku{g94M~c-TGShXVBsZs)2%tJ0>M9jTx>iAyjAI2gcHTCZpjw|AjhD z1PRQuiqTVD=|fbn+g4UqHZ~R5iMCbQEGa1|Nxh{HAa_qrGqazJ(tS8L@HrFhJ<8;H z)7bslro@NyUu3nhuAIqEul_=9|4B2?@i&Gs3zgrW1F0NwJZ}8bvDR2e{*&g~UhUk+ zXD!mssi-g(=(8EXU^qPuzz*DxH!*6pYEzx5LCkdbjKeucrKD;Ac&x3hr=AVdFr(Vl z^MFQfZf<`vj7lvj9!IBy3h4CE^#!&D&cxcfPn9-|zt|26(yg_U=0x=#TARwmk)6B0 zHz#zT;y0bdeBO=!lvNpxAUFp0S!+moj4(n%NzQnMQ5-))bzRSI>|+W)x>329fe(?{ z*qSY7VY^`(QHUcwpp>g?j|7E1Ip;yKNhICTSHQ5@Xaa%Y^QAmLQeA;26sMFGvAs4u ze>2wtkZvX_Qw? zYkn@v)tA#&uyr*I3E32bGM4%sFtP@b4Ci&nR8C!zgse^$4X!MA4&bKfPR$l`d%+Yg zTvbQ)VbclnL*@4qNAyEiJzUJ9o0M!}CM;;SzX)VkWKT6g zOF*H}Obsua^tDWSa}8M@o}>f<|B|jB5en`L5>@6mrw`@#dwptoYyRZo+y`trm0b5e zFE4MR<;Yo!ESi{@*lfVj6cO;Bh|LM21I@?0_LMeud}ov6WqZapp*dFAb+BzGgT{>RvkEN!xH@asK~K+ z4;C3UJ+HgS;6$ChJ4C}2&z|uN5&QA2AqME#1H~{mC+Vl3uibTSkX3 zBT$l70HUNujvXV|Z_+!DR8V;E+TKg1-fIhN2hLaXR83V|%Inicq)RT>I|VN&E2BfF zvsc^4#_lG36g2^_!b4Y$F~#=O_tD%AuroySfi3Va{JC1t!Z6r3CwkwW8yQqyes0G5 zW-AX-p}6Y-i#6*xH028y0J$T1T$X>+DTB!Hre`N=Lb+Rj;|}E>e$&~+e(t&C6@yF77 zy0ny(tbX`qBpz5u4@957SsxV@HH4|H@nVs|jk{Um(%jb8W>aL(WubA|1|<}V@Asy7 zDmv0QG4Z>w$O<$$a?Hcd&TdkU$+e3e-H`aw~++{!kn&MA-ZK}wms07h_f6#*#nIOO(1m7j-Z>9) zg@Duq5FsWAVM3Q)BcX|aQs&AV*Q~eJytkZrKki=Nz2}_$ec##poVD&9%gYz|c!haE zAP}FKsj)Q(#Fp~;;bsTE92+ah27!*=H8VD_4H|yOsOF17KuxSD-7+4noaqT_3xlLS z&%spu*%aw|sl>hxaNqf{o6i0=IDRQI&o1gR=-8SM+bWb7Vu=L(0s0s=UI+m4@IwIM z!18ba@`?WjC=7_8ut+ldAy^`T=+DKLNX7p?`1hFqivI60{88I~6!?!AzHH55pZ^!b zm&N~lhrclVArJ6+D9_)}=nKOr_NI$D)*WL#x!0;h(P(h?d>a zxgIiiGMJX_GBeiheX$)f^)lAhsy$@1_zPKnz^)+k~66KrlzwB3dc)IN^l?p45^rG&CkzIZfolh5EXs5 z8RCy>X>5Gm-F>{Iy88ZmeGI9eY|R)Lh>3~mKtCJO($Y#zOEW1eFPGA?L=yEe--_M4 zKvxYeDlVQ`T)eXY%s+=K*;gQx6c>+=Nhi=LZ!Ol#C;j z$w^6&y_(L>&f#HuWjDOKs;Y4udV!xF$7*P3z+f;g35kirZ{IR9Dk>^+azw3escCAO zM&=XI>o#O6^-_GBWSnzLcvV%^;*0Q*5PWK?)WyI+jm0Q(^cjY5qm!q;ex#5ne>`P2 zF*$iIMF0wezlc{uAS7CBu3Ry@4e-==^y2Zt!DqF#Kdr6Z9|hX=!ELUtrr_}kiDS3* zbaiD_Es+zdTu_)kCW_3C1AFZrRXMAus4nvO@#C2=v3sRMi0{9@G0XWj$% z&}e;qea1}-W7BH_5)Uz?nNtihIhuOygpsT342$*jDKj)QR9RWMke^3RPR{HY(1)^L zV8d7;V1fPp{mgN`(2$S>ekiQc3b|ylt_>uf;e2$QY>&s|ySuw*YNn>9K70ruvw4c* zYTWR10S(U0#gaNYSXWnLx+FE{}96lg*C&Hia`TDrT74WEXL z9-k5+4tbn7aY8R*A9_YovVcIi=h|w2WYCW*n+0ms9%GB7i+bUYTtl{I=FPo=h=_<3 zQ|*tS*KhFSz`T(0Y1Mc@UfhZ|-UtwyyroGkAhwFZ;O(vs9# zk6;6;CinN72?Ql+X(Qd#tgOYYPkJp3FF(Ix=0~7+W8>JygPQ8XLO|l^9^CiviD_~- zg_71|SsSaZt(^z2+}PM)FzhbTbyXI}$H(W33W$-%cqy|!du?x<8j6KGy+seEq@rY? z>2JV^5+AvwUg-Np<&#U|Tiwd4;uN|0Fnpa&YHmDS1}0pB#$E#6UIDfoo(sj?Qg8Fd zZ|snI(sp*+edCVg4Tw*yM``Hj^sLD7Jb3Vc*3+}XVmUcGSJ^SUy1LriId~pqW@_gq zn-kxTkKf78%Cdsa0_L5aodv9U{rZJc`h9drh*l;AbD9H!MiSj?@V#!1C{)eJa+Y~q zm`#~290uV5PICaUZf7U>UT0Cc0yC3(&BDUMj96P+TCTupNCa_$lPL2W9#5PIwbj)I zIfe46Bz$h}rBeDkuJUpxC#Td_=BA4TkYH4MOixemj)S9P`cXz_CznXM6QPGn6%6$9 zii{F~M|#I(n93y3nVaWG_?#S%+7WZg$Vm2f9}x+6kp|XRqQBtuU(m~ltGmp{pf?S+t+jB`3%NS~)ppff15ot+oLOYW-gpmBzhvKO#F1!1vwe*tP^ zBPAM3?J@#BGD(RYF6J~UmCxJ3!C{b_#J;V9bT;T^Zua-MsRCDz6AE>u$}XGwAt^K{ zNJtGiejs2z=RUW*e9I#i&GsH#G34>IvN8qj>#HKlAJ~9WH&w_GRzs4|DP8q-b*<8p z(csmqM69kj57cB*j)Ut**|c9t$1PT04ULb>vA%QqoN@ue*xXd}=8P$@ulluMYOT(L z_``ud$@QGz^=Csw!D#TCla+04MpLh^snDnVw9dqoc+-AhV(2SdSBnk=Z;Rq00JV}X zq?`RHA&@U?(ML!)69uP*GOF(o0gFVqu*x8h{$QKqhx&{@prtO zS2Ym8NgAE%TN0=YVRMQ}Ajv!t0w?`zmH#fMGO|6ND$BX&w5#c*JH|f@3vY2vIf0$L zCno-ALoV@j=^!20eka07)Q#Ga4$AYsm(oj{AyB|P1&=OwCc18~$YCDZo3aE^Rd(^> zuje<2(R)khE4RC+*ED}_dObgXJslx*R5T?N=_Op*>jpieqo5#bDn6%8&gF9SZP^Qa3dvkeIgqq?4xtJr@)DWMC(w{?;9#rG_h z;zhx6ZckO=$-Rt=v+HP_{qoi)0)Zfp+ST73@|bhufP{0~>F^74juxqO%@(#zu6p&a zY$+YHE2WJT{{!-E48>Dqfv=iV+N+kLjY>l`RYEakP$iaWQKi<3B@I(8?zDpnsq1NywCHz-|x4d zY%OG@j!21#iOC=>FWZZW?RmQUllTVA$gy7JfVVR8vf0m(xm*_Zw&S|$o3B&Z&-{(6 zOXYR!^`6X{q11cy`YSG8*|>EmULdbv?jk4>y}EYz+LfzaCo4Ex6kM0#al{9e^xGNl zez(_xQ%NwItBo_o9`6u(XQYxoO-{~H!uAvT1~C`QZf@Xtb_vD1xF{R zXX)uXTv7{$$6zqXwY3^ZeKcpdFvY}p(1pHQYo^kj2ZQq1l!pg&Th{2^)`9Pzs;S;z zrg*e{sU33H($b>-UP>UCL@s;YNYgl2^62G@`ufct=tQnj$S*hu49%(kjI&n3a4MO5 zs(op#+wYH7fjRRZWUgpr;Z&`+}KLzEnSze2MmVF>z@L1nF13`&O1mHn?MTo|HHU zGMcxDUG0HpW@d`Gr2Lrexe?!d!79x(4bs40Y~u5l^5;*_eHDLVn4X?q;aV5?_OSvI zH!v{JyxcCGrfFw#t9yai7SnG7_0~ZX0s72ngpiv19d5sM*sNllxz*_&_WNCEz$hW9 zSXyP6sLoD*TH}(9$dpvv=jkVlQonvEq3Ha*2mkUgJ4xh2@K>60T;|#INA3Uas1LA( z&{>^0^Kq9Lc z9Ez_-tq(dINC)Dv8zUi8?*uDU-4S98pIeuak&$J&TYQh!ot;&h?y%b&Mf^#+8+ye1 ziIQ$_zz~Chj}>l#BH7?Rr`eetHjTOO(`1)#LVc#4qvv&rU5rdQasB1Y_5v~X(}M&0 zW2QUH9h+aL{O{a3VTW(_rQrb`*5}umVKmgeKJ$Hf>hNsO)H0W9A66(;JBxdFjLZt@ zRBjUqG1Kh0ebR)6>Fjo!Br)+PHG=~KJL}qIM}wL}K!b{ZE}JT$=%=Nl6Z7@0QdrQ* z=#>weLjwbcf~_vItM*I%QBZI^tjT2dP@9*$E78403cio7e*MqsjhCWRwvx!fVw;@t zSdk!nficKnERMHeXn6SBw{N|@y*mQT|8kax$ zM7ON0%vfK)u%>41H%<}*Ha0fSZXs_$^2RHjttP_FDUcO>b;kLC>@g#+ z_D7E&Ki;!lSPJZK)j&5QTW5LP~52!dCL= z(WBI^t}DsMq9(9B0k_lzf#?8EnZxgLtGe}pjmBfFo?ml!7j_%aYRF_2{ZRe}H<5#0 zo9)xq)@H37L*{#?DkX}Szkbb~$&U>bx!!PfrNMd302e~zd|5}*1;}*)pC2|sA&b2h(!VewT(@zi1Cd$;*}LD*H)gK3a0Kv`J*3}92O2zpv2W|PhAf1`}1E|EVS{j{&p!nZDBkvWnYbV^63w};7o*Nun6_#`qJ4eE}{QH$5OzV_qZ1G3$L zsz$+|-aObV+SH!vOi|VId}AtGP9XFOa_GvU^&}N@(yUps@%$@h;)vYlXkV&-6Am&r z(I%QJ$;V@7w-f$wV_b~BapT4=@`L5hLxY1@9xDQ~!P(;0O@N||j*d1q)8y5!+zb8; z_6WaZdiap*0!HXx)a@_EnI8eqB9K8zeoQnFHmhkSIaki-;1R=IXN*(3qK+x6r*8%cCVg%4WmwhZ)2QFNO5t^Pv<*<@xb z7K3h@TDsp?SYPqr!j0DZQRpVX=+YAWomhAn0)gSP;T9Jc1JMD)*m!yd5FYb6)uPvg&ryxtsRUxOP{rQ!O`+52_9&D9 z97L|EIkr-SYC@wTSmEICE}^xf97@Vk4ad=HsMwemEB}3{1BUgx1*D=veNgd?8Ygp9H*cVTic2xq^t!{bu6g z)7Vmuvc5@mEhu){E4-rtXKxRGqUG6P{tTkW1O?Og@n!=(uleYiU;Z4e^*7!8^f1#SW~<-MltsQfCKT{@A)W;G z7|w8UD6!2m!A5VqF4qy)LgKWD%`3n}-GLW9R?uqQn$F3CMF>GpeYjty?A5F1`rcg% zz!9gje9@xs)x*a-j_Xz}d6_{&tt1|)UBCE4+`hkt|2+sqdkfG82_Uv2$JNOmRVVvp%H6omWif@d1MqGK{g&3X__fyb}5O5m}p*Qig-k1 zwq=UC=8@(V5gt)m;}vPU29*bFnMXR?lCvLocJ14K*bnbBzj>Z#p5MH`cb@sZDF^Yc z2$&%Z004x$8_o*=K$+hUR85u9k@C0z0IhV#Ir*F$9Ph8x0vw@Wr@4-b=w>YhlcS3v zJr5^kK8B{nYEYR$phOL05+eu%)mMe@7)aDu#8CZ3yp*Cb$qWF)9R)zE8WODvix>=F z#7ML%zqI0#8~>5w|0ycD|B`;>7GOj`z_V8-(D2SJI{)@Wv*S{KR5jfQqTKztr_tKz zT)g}FeK_2Pq^%Tlcf46t*1>({1;#F?OgFM+IBVOP>z9r};E-?#mqc8Bn^~164yl&2 z%OBE`OXoTBgVDpok_0AS@p0(e*9rM1SN_FKykw_$wNSGzB!n3s2YJspm9vaoR&53cXKn2;&__Lpk}$9Vt7+Pb5&K_ z!q%JWT>{1>A%Xwm{lc~Ow=LLhH?lV(0zG%Fw)QNMXh5D&C`_zdwBcAH z9>0w29}uts=}1nH!{Bfe^u1SKXJ;>aszg}J7X7x|;2a;R#H^=238^;MV;2?{^7$r( z_|R>fNrhsIeroRr+)hTD+5uX0w3UOyBY_~uhMT2%L(md!p3~9Lu8)C`42^=RrzJO5*eRo<}M_f7x+7 zzbHT7Ia0;2EFvP}x%EUtBPO?#9vAl%Vo8VS=~F}K^g=;mVj^YRs-Z*cp2?OQ2C+0= z@3gZsLGuoxkkF^4Qy1)=)m5GC!Ij8EI2?GcV{p)LG4rj$#=yW})Z6e%NKDKM9tZ}n z%esDjFA0|P>&K7mxRxNx%fjH~_JrpuEneR#FF!-amfgQ^kHwlsak^zR6P zEiRFa8qJX>Jw0AD+V5Y!e973qS(sWJrQjA7MV>ra5_aLl33xpSPijec`O+uHLx`@s znjsX4tTqab9tJ3sdycCld1kt4lT~3&Q&YFmjl8XKRH|i$=;*YYx0hF*bVew2DZ~>A z1ShBy8RWoJtD8f@n7}L&2DF&0Z<)Dw_wJ_)>-AvGN8fcs*5>Bs#x@~Sg;1jmCeZwc z+npE3FTS=g$#RRZ;r7ytii+~NTyZLc0bFWj5Z_8JwIvSkYR}KhGe(C<6K;o<+7%QO z)M2P%%C<%@3JvVgmIg|kDrVyxTnKWx{8`{JYeNVGG8gASM(F66#H)5X7Sgwv|=3-m>E9vRW5D0?as~0Z_s{P<_KFH;+7j;*xsHv|{86}W_;i}}A z*jPJD%a-x1>~ChKrlZAYCi}t8E4AA#Ng8O4BdxaJ%4qFYUtFmm1`a>lWBRU}DqI7V zShW!weg8mre!^x9XuvPdQ>Pv&=)=k6+38{yvTbL&i1v6T0z*>v)$)` hkGk}KdWzCwT@X5Sms=paL-mIN++Fava_6ALKLM&+P{{xQ literal 3029 zcmeHJdpy&7A734nI4-$N4sp62DkREk%uWY+LX`Hf2pvmevLm@)_MoU|6>`~g7>-GKT^ZS0kzt8vmeZSw|&%N`m zjw(vuDM26*m9u}dcZWc>WPBNlTR}#RRh$ojD3i|GpGHNK_$>VO93i`7srFZu4}-S` zY6fm-JjbU2WF_pWp6K4as<$-g1DS}N}Aye>ccD5H4yfeYWAdF8c^MPD$t z!|FS$AHw_ZAwLqxFk`J%QKpNF%Zs*swU>V)FiB(Uf6v?H3U4ZGRe$GsyStrFRz9$wj1iZ&^!&Jdn!Gs8TJamOg-|Fg_oxgG3DH01Z;!?DR(g?s{Cj02=jRB>CZn$YO91ITe{KCI zWuXQO5pkoVqfk??<3R&YQ&UrEG}>fqTDl=pCi;ZKi$(&i;c&AVhXo!FD`Tdr=yf41 zQ_=_m)K6}1z1EA3wKr$Ga15!a(T?m^WE(d1HtCxk^*25Z4-Z=fbY(}e6PQHp^^3_4 z2gmSosds%yLq?7tUMhSU^Z~emMa=Syg_?sw%VQ0w3Hq1N7kPPkrRVa8`})jdSLfWH zy^LEEbmzm?R##W|nC3XGaJoO$2KF8#u1jY8)>D4d$ThjNJm7rv#PQ=MzKzlSj~z6P zTy1bI{ZAZ=62tDQW`i_(92A842&1t6FXC~08%~kAILD=_*j2`H8_u9 z;`-!V{}YB>7WWLbz~@(ru3uF*IXu~!xxcDeywH_%1YqyWzohsBI@`z5v16Ly)tS#GxT0)c>CfW*u4PGGmc zvg;d)+RnkMWcRl}(34Od@9$m$Oi+tQu4L|a>_Td_C=e`0sW?Bm2+-6zc(5#qsY7V9 zaI?2+d3@ig#9c1R@$}uG@({M!NjpIaf=a6$s`|U5!#q8ivgJUbMJ zGn`1|_*b8d6a8Ts*n_%i<_?yuPNb-ba73q)Q{kL=xpa+Nc`9*pT@TlI4Dh80FN>M$ zD~^bW$SKG?&^-L3E-Snj0RUk&ga@)i0|U8F&t?glC4$`CT(MY;>39gH@;_EjzrClK zpzKkBUXn=`aicGv#qPHRe3-3^6)pfCuS~S0B+#3hn)v?-u4#E?|YL{0h?P43_>sS;}FGBJ-D$`X$R7FGJt zBPAlfWNi@%WbxwX*7kHm{7N^h7bvbJd7G3-Q6Owlglb!0hbu1CUX$``N=r)_O{cki z#dbPD#&1p@Q~(cvc1<|92M81>W3OFm_I!<)TZFg0HP;6cAbsOgea^J!d7os5J1zxq z;bfYjnU+#uZ=uhtUnaN7dWAZI@~Y-MBtDUinJ~m}E+rjZ(R6xIMw)#`gjrcZmF@t1 zSU{z?CgJc%ZJ>OG2#)Vl7d&)d*HYEc_Oh{-mzS3>5UhMNK=-x+p-UGQItEIELpodMjTMJ%IS9cij=I0M-DS7%t3;|J}NjFH?)p70f z=R?uypI--*<#gvq@#~m91=efDX$E-~0bL)fF>;ZEs)5bN zk&%sga#13<46bcUX-Ua!s~VDYGR%L2L#74WOlRIi12-8cah5O472Bm1Mo--n@|beA zo#qcN*F!=Y#S7&f5A12B?oVrs}_F3~S>gn^f*BW3GkwL~<4?4R04%ov-? zgpJ-S8Q?k(msM5C0U6{K%&GHA|KOKE q@el1D{GT48uWi>~vD0jqLsA}DUo5LwTLA4k{n>$^Qm1bsgRS diff --git a/Tests/images/test_combine_double_breve_below_ma.png b/Tests/images/test_combine_double_breve_below_ma.png index aea09538f7ef0a5b14c605f40be654b9749c498f..3ad4f0a7bc67b8b6ecf663f65e4ff7cd5ad4fd5d 100644 GIT binary patch literal 2214 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 a2V3a)vxVGIZVNb_LtMVS>Oj5{n)Pp2SPB0C diff --git a/Tests/images/test_combine_double_breve_below_ra.png b/Tests/images/test_combine_double_breve_below_ra.png index febd3ab670c78396fa74204af766ec30b44175cc..eba8df12bda01aad3149b90934baacbd26868b31 100644 GIT binary patch literal 2261 zcmc&$dr(qY9KI45iWVtdO+>BC(8bDPEz8XC5#S?V>yVa0FO$kFbyHT2RB|G!n%SLc8k_xN@67$?oBKQG`~A-MJLk;J z3khChZfau+L6A9(>Kg_@Mo0B88Uemce{il0g3#qO-_;SR_eXo`%pnJ}DXTGcOM7jj zjJb0*impqxezg)v-h0lxr+R7)V@X59>diPBVpN;>Q z``@I%jW!~?!RbeD&PY`iVl}xarVz^3gBff9t`uPg16af`0F*2KDUDY);nrCDWBYv52@ zfBD7VUzO+Dc@_)@-gr3tZhkuaQuSVSG6_q9yoFFqI1$(0-X0nnN+1woYOS&*cQ!)J z^qMYXV`GbMnGDflV`taqrB1pk%c^Y>iPAZoIf6HDl6D}j&O@%^pFXV;%WmJkEpy^V zQ#7$jNl7#sZDZY4vG~FB9|uohGjUm{<{YMxN(e!T2T%|ihM+8zS0<6g;Y2wVZi$PN z%jFm(;uy84q=XR>;aJ?kr_Xzz10AlYm@6>V@v-Uc1BnWiitcFf`j|*00;z{K>5{LfE+w>axzx z&i;g@StvBDk+AFYTOSn^6x7wZWlJR@*5Z>kc)W@H*)tbId~j&!Kw9y=5mzG7K`=hf z(JO0WJ32aY_{WcDF^#fNA4f(<3xr4nplDdPu;MOtnVp>-mL%C{B3+9@p=L}wBF_%u z?v<9P?>ydfZ45kGZ5O-Vzrw9d>0YKxXk22Jy&IAu_JQ1Oa=De2l`D+thLD(v;;-%%>0eAgE`=fRbyd?m$lw)J= zEM9Kzces4DTCKe^U4RT=?uwo2FC%D<*C!6pjM1SlraK;`PF}9C3upJC4}e9Hd&4lJdQF zOxVyc_UKX8L}Ba3ljY?KrBZzTx~YlD3^cMwKWKGYT3TkN7mHwT9~T!Fj_c~}^&peM zrf|-uQ>ka;@3ZSIC@>6z9Vf)AYdj>=>!S#kmOfq<*4E2btms%2xNFx!*1F4)q8MY1 z7fBZzPcD_oD6fsRPtG?rH7Twrl|hDG();2?0x)yy)~&w2z9AtYsi~Dg5dduJ|5ohKeDwXQz*E%sFqF)$S^q%N= zvV7&rQ>CTVmoBNBF^e1=n90dF%oK348X6j4JU-xR&*1z#-#L7GUtb?s_=oXve}Dh+ z4<_v1nS13)m+Q4&!=866DL=ba7VS}8 zDC&;7+y$F=+ax}n)|`We_ub;!Gk4>_EhR;O+e+UD!1etzd@%6I1igixMh*;c@1S8W w(@5V}z#SL{4nWT*6`b_Iucr7vR920l+B&_GmhaBY0KY2`%`e#ZR|+%hZywH8qW}N^ literal 3011 zcmeHJYc!kL8rGteacS#LX_;23%XA!x>#Q|@y4U*F`qs0*Z@=$;-sgSRoAaHs zlbno(jF_01+^w6Au3}=xo*X^W#{r@+TlGv#Og8tH<2Cn0)aoqqfk&KhXrrqv#~{Qy z_#WFr=EBcLWsi#aB;C{3bRFN}q-An*&wVQJQ&&0__yJk?W#A>1U8i89>%lGT{`6|~ z`vIQkT+4IgEYGrgkpJSYbLc`3&OXmp8(9&<4GQAg4;H0R2uqC(&CSgcqBa(()Y9DC zBU#48oLa0c#)O?zQFOrlq?Rt(uV{2G%kS6~3E4;Iy}tQ^c-&w5^VVlJ{z`4!-R6-@ zp~)wHigBgV=}@+MhlH_jPj`KN{kU6X{YHu6|E|=Hf=1uoo~o)JCSKgVQ)&W9-g;0w zG=Ey(3)UenC2z}nqnE;8*;_2Ix7%fAC|~w;Pu4YwN*~DNEH5vwuAZHo#m59mW>-l| zJeH-8jz;dWN)=WZiHmv4T3Nb^MkcUtza3eoV!+pb&)32yZY(TSB74TqMCEifj*CAD z+gKQQtZItgQc}u<{#TFX!gtnYFGI1gmw|65>fPXQc($CTg@6B}lV>i@By*Z5c7o75 zcSv}x-|Fh>mXTpI9Z|TTIwmUC6op2kcNi%V)CP~a)XfnCmu)^jJ|ThozCD6NYXV22 z>=63`ZZDT41GAODM6MBb7&IC!=HA{m-Iwz8N=<8P-1Mv93|l4C8KNO7Q&ttN)wsXE zuWt6w2^uY5-K-8~!kun~Z+;wsKp+Q{5)w;FCp8kJTk7-CCG+2#P%kS|iE#?Mwlo9| zFE#P$41D{`t)hz+FPWoN4lecwTi|CwLRV)et;xGRmOU;?b+sz?QzpMD)U&a+#uE6` zz21?mVkq|B?{ydqhCm>Q3SI2>9C00LyT9Tdk-x({q*AE>`S5y;zxrM(N!Ln0eSke1 zO)?gL-+RFKmefx>27tV?Ht~LfPh&OVzauZc?K0Ge_DQV@90xoY%5VT&F^PeOg1Pg?dhg5LuKC(Xa$8 zNm&&w>)03L8Lu-H8;=LW;a%JwK|Tn|%cyZG=-K=DOjAQ+5CEyW>uKKFI^dk9c1A=0 z)Ae=<%q4)gg-*RJf}6u>jnMCtSot<Exc6Q@qx!>d9BZ?_To(V{?d|SuN=B1)R7?<_$d#V_6ono0XU&R8?ryl z0Yskw6h)+aD6yiG`~(dctTiXOU4fn2zGY3{O#?qsh1!xgEcSbR zG^?h-ZhC6UzzgQyeZlC-+amq;*4B}7bIup=i_WOYnHk}Pn@jN_7b0Z(GnGACW+MCK z!otETk%xPoah$fW_iYJW#@gE2d|$T9)34_1o`siNMlk-!l$CP!IjN$mNi?j+$1IU6 zY<3z+%N!0RIgOkqE_GO2#a!vWAmK0Vd1`gCrKYp9v!Nm6&6D#PvG)-aG{hlWzNw+1 z0gGjTAbvs_nVY!W7cDIt;ASSpxAmP-UauaET|HddkJ$m*LTrsW9kiz*g&RYZ)N&ni zg`e_7uJUEzFSl#QL1J9|61sivp`btKj7W$>AP^X6Ga8Z-FWSL_Lh%MQBZQlM*X@ah zFE$p3B7hYDC#!X3PqqY5AUm*tfW<61O%p3`5cWtnVePa0$$*0Gt*u{g*J7G`I`R0V z;ip$b3zJH)V!M`EWm&S;)b5g%i^y6>QgEZ?WGE#TN4lb(MQdV2(Cy=SRH*lAVZ`XouQ}s=4 zArKC9w1dc~tIfTK`@>~srKY}&iHWV|;S=JhzG`>F;*Y~+zk`ZK0J)R}q09ajJid3B z2qMdxc8`vYZ49XT4x_Hde0A~XU!Yzn8lbR8$RkzQB!D)HkBd7v6mVJ5c$kTf{X=2y zWvtp2|Nf4sMGXxNEQlBUu5JVk=@N+#6(wa3_(cZGKfmkypT#y{x-(86|2!D{m4m_m dZ$KRsi9ua0ON`QQ62NdScI$?-Blfyq`k%AvD2xCA diff --git a/Tests/images/test_combine_multiline_lm_center.png b/Tests/images/test_combine_multiline_lm_center.png index 6a15130248ad8bed5e13a937d3667eb98624b5bf..afd1e2216101a73730eacfc182b75b74d89bd644 100644 GIT binary patch literal 3163 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 diff --git a/Tests/images/test_combine_multiline_lm_left.png b/Tests/images/test_combine_multiline_lm_left.png index 8eb254fdf26323e778c1ff5e73dab8c8a0b4b638..5ddce6bb76f2a52d13aa48d1292b3d00084f2a35 100644 GIT binary patch literal 3292 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 MqAgJ6#%}R{0XPG0T>t<8 literal 4197 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 literal 4154 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 diff --git a/Tests/images/test_combine_multiline_mm_center.png b/Tests/images/test_combine_multiline_mm_center.png index d1146b8b856bbfe70a8e9ea609f748a1b953fc18..ff4d0ea294fbfd90cda14deedcc5c076c1e9c214 100644 GIT binary patch literal 3245 zcmcJScT`hZ8o-kPp(H_IGEWE?iWG$h1W+N0G+_jkx)L7>5>R9i5RsOFAZ<_(6-7l< zY8a33L`+!@h`HRn!RO_?W z;Znx6M69(6YR(=B2B{&cPC+sEQNIEqi8%$q;Cdq0VFbdG5r{Y^vW^KREWuC!)&ct| z0Dm~g+)qWUd;j+#e#`&ANBC_8|IsqIKD+61!7k0rWdYgbk1J?Q_h;2Z5?La zM7;kM7lBK+$7&k-g>)Y$mufxF#OCS4p(*;+(vL*{1F6P)-^R&jB&>u6Pe zvRo-hzK_a)ckup{HZjrb!b=CDIN972`5E3t?3dY~Fs$j849KvU+rz-Q>xvSx;GK+Z6Yb0^h+3`k;Q1j^O3f4<>>R zLE;YIzMmKjM&V;7ad*TsK5eBn{r2s40&tV&BnO%1F&JLWKZ-N92RxWhGDI5ZzCb|1;Ur5>{2bsM69x$%%=vR+bOADk^B;cNGi%uF66Sw^<k^{rdI#QaC^^o6U||UQ9YnhbP*TyQjW%Y-RtvO5E_P zU14v&=app|QRmGnm*34`s&Mu<_4D`L(CNIj{eDDQn)czvxzUZm!wwlnvCEl+y{ge` zpBsW_yBMK!BLi&EMIM~c3r`ro7w+H^xx6?Bm*KrF%kfa+4%dX=^$4dQdx(iUfv}&(bWcw+2i$Con1lMuAELH1MT*mrz=! zrXCgP>FFC3O$tYH%J7b@Z~>QOn0Jm;SS15RCuX}c4FWk`S-X=(<)ANpLzXBF7_Ca$ zadOOPhMPeS?r8Kvr@b6v9rZHXk$Ydu#Nx{yhBH+>Tc zqBhn4Ah7g%%4u*Naj>;zgURa?r^v=8q=h6QMsh5Kf%6MVNl9Mu<+CYM8-2QtQpmF=+`g1xr7)7Ub&1(Q8cAgHW|z5ExdiY)_L2o z4LPPVoIGE%LEi@nuS7m&3yG@!W6UMeww;AagJteogyV+ONZf|$JuY@xrqNlMnHO`n38Aqhp?0Dki^U3m`^04jv_GJ0pxMoguC3vD zUI;Aqhfgg~tIAX&lgSeYuBWEPNji-2s6EZxSRq~ax}UgI*R-|A$L)0%BO0_QrDnv* z#B*oGpCynJ$EW$i!h`T(>$GWud~@R-wYM9*{+mg*gIve*_HsUIYHEt)q_uhT(Sc%@ z7o#tF^F+ItUy=As+h34$bdkHX@~NC`i?60WwKHD-1wQyL=P%ZM`HVnp{CFi$D^Pzoe4 z7#C}3`2^*jjEsyULt@dr^5m+=uJ8*}N>XTQh|M>$s3NwVvlvj7WzJ-=*zbuHqS>1k zd74xtU!^K+m;$8XMgwRmPu5T)>UHkh|XLfK8{hS8;R&~J2+ z$6^SK4ehRa4^Q*Olr!w_NHx!e?dz{7E-rhW=KX20FX~o7O1!6RmM+A5Pm3&mz4R$Q zV04MoEm%3znB6`5`u#0c8z;OwZ*Ex!ife&$&EUm=`lTB^>PnCp5GN9s;EO|imO;gd zC_#ug5iuZ!iWCFZWhH1`hVgF)|FYq~PN4)@CYIsB!-QRPbJt literal 4204 zcmeHLXH-*J9>;MUECV>AM5&`9j38zdF<>B3ah49F^e%%)lh6`EM?pnF0f7)9lmP{# z7(|2^T0j&;nuZb}1eB5h2}DXlO|qBSJ-d6(eAy5CZRbPIJGpuH|NiAy?#umO%?%`W z%kCBv6O%A9ymUoOY+K6KXXiIyMT(J65EI+0X>{p=RcQ7cBjTPFM%1*B$>?+pI}XLQ zhbKNbX0tE<2rS~m?*@r)lzj@1l>feGP$KfpVD(QO&+bhhkxBJlHZUx7%(_HIS9N4~ zLzWb)-Pm^@&-;+!6eP3lXCixcw!DM29R5j()tRlsipXAIWG{4PqE*t2F64LR>ean` z`LfL50(C!zCnlDZes!DMPMN)T|GNCug8xLpQ_W0M+vHp3Ddt$djG{@QLlVDIWKx*0 zM<+x7>#(_ev+*-@ZI!4tH{i{#uC6YFmBFH-B1OIM{UN%~F~U5twmkidKYri*&6~X^ zlBhz#hGx8twRL}YLr%1=;l*8l0-UsgbSc}MApi5#^ZNSwA>lLB!ZcNj`BXRUz%!5P zTeiadGur?3vin9`l0v3wQE91KT045E2a)z`?2g-q?a-Zg*Jyhyt90|?#Kc5A)OGX_ zVfAZ^R2_z$0hSUH5Q632?!j*_?2=K?M2);oNJy|!>!(tK1Uy>I=0e*YXOXRIlon>T zPq494e5GWjx7aoG%bh#U$b7JazuapfC3p4&&UkKwy!5i;WCVZ6)6u~pDXKct^m=pb zj;X0B2?>c3E1%`*PrTR%>psdtbj*uOVR}3npmY_`b(`fJ-atImwJ)| z^&qa13lqu6XyFlsMJgve0M^cRAFTS+IODQ2D*#pmUIGnSdOlqsQ z#QB4NVCTQMh;~1G_z*nLw79po$c#N(x&Qo|kocpjcB0+tw#1P-tiNq?!PR{=aCx2P zbz8fd8}_zR`6>VSZro_9vNT`E4dMN?Tg9zTKYpjpV*kLvKz~2x-z#qff|ao)Ru6af z@_thgI%!||+hI_XrG=i>Z*T8`f~QbU^`U{a8t@Q~m#4KH3>7)~$UwzBTl?Dk6pnvL zPROvgw2J9tE#HCRYF{bdGaGvNJeeg4XV%u%0y?u4gZiy)F|G4^?d~eCAI8mo>`|f7 zXe4qj_+1Y5I{(?T22rJR*c6UlX6K*NMZ=JAK%X+( zif(f}m|DbKwK#OF4$HM6MJ~3h+ByaP;_mJ~pYiCZL6QK%TiHoaLF` zrDSmvW8RkbfN+4eZ+Nc{ZjWxuk+>0fy&9miVEb!1I?gh*>h;Gt*3c7I5>=k zPCP_Jtxw9xo%-#PrKKf*lylf^;K3oypbj7j6KzT{x}k|%38OMWwnEUJcv?l`KIEMHIdb(Yl(M-<5efXuJg76v7@9LFa&2*Ze4L{}fcbAxNy>$utrKi) zVp7!`u`=HRaThS0gHBvngfHnNSxuDuLM!=iGyLNXq z3fI|j;!=pdHxQ?QWuW_J#OLI$y5FnBPtbI-}w zpY2*LVt2_r*RXr>27{)ArArAVAum5#l;qR zjlMJ@XL+#FUEp$NZtn?)4pGr)Ux}quU8KFey-?MjZ1L7H?DH-04;>vj!9*gd;f|TgGxO!Y(^!o9Y zlo0dBYR8YaBgdjf>%+)#%^gsRt|(9RyBXLJ4W!Q9siiS$W0<_sD(A1e+F4?0uHyj| ze+IpN5N0;MOGbPU(DW4Xa9aB-N|On+p%1`y(=itSbTiIad%tIG@J$;}Lu(tG+<=R1 zF3KJr9&^yI1CIVvdFS(t3%9JGLxY6&Qs0DwSYEus47xuBc$Ee8^E%9bG&^QI!Uluk z?Sa+@v?*Zs2k0xO>R*&h*=P(L;8H{`O*j|~WR+GLt*6t90oMNLhu$TTxG2j%TeUTBr0 zQ*2xfu<^=2luyd(1YJXgcV%g@X%+r4YgOIn)I|_nSy>quFq#iNmj!(OimPkP*Ew$E z#yl8NIw6Xg=?Nq)gVK};ZPX6<+{9T1#G?w4kPtN1FrC==BP8c2H?ymux?NWDx~a3X z^TrnM{V``-Zts#tfR>$wpin56hHze-6ha$4{ZS{FdCkAmxv8SD5h0pA1I|LI;A18k zC)&qG#@{7MY1JHMC{)ZT8+%G(T}~e^>@BJHON4k90|~FY-j$V_ntB$m zTfkWBiMirJZ}07Y_cU03wG%b?;!B^Ug&)h~MbOlTI9er&QG5Bx-!RiteX|NSWn+6I zfq>HwE#}1tGetXc78e(pOy(bfw)`N^O`!fjR;v_P%M;#l4GjUXKQ(oA3!SO-mXl6a zR_K{RJd8#v!VjXOSb8SF3&0H4#@Prv??=(V{LCw2BnR77kK7|F@aMgPm*@{H}D3WYHBfTaK6iXr~t3&Iwn;G z-~OGpvZ-RT@lSXkqrz|1!$4M6D!tXBE@J8I;wdyQ`23eYACUigKl^`vKWi0jOAmF0 VDpae|Tvi1;e76Sa)=$eTT1F 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 AyZ`_I diff --git a/Tests/images/test_combine_multiline_mm_right.png b/Tests/images/test_combine_multiline_mm_right.png index 02634163e1cd1e40aa0791ddd2b8deb148b19f43..f4f25ba4a87fde342552b24cf0bb7645bfc3ca91 100644 GIT binary patch literal 3266 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 tHGpp#03YNhCx6=U-kLCl$sGE{swutcenrm literal 4215 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 Ha8LLb^xtTU diff --git a/Tests/images/test_combine_multiline_rm_center.png b/Tests/images/test_combine_multiline_rm_center.png index 4cce8f6a00ead59f2f061bde6a6b374e9fecbfe3..4ab30549f5b48d978fe76e456ac9348bb558b181 100644 GIT binary patch literal 3176 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 diff --git a/Tests/images/test_combine_multiline_rm_right.png b/Tests/images/test_combine_multiline_rm_right.png index 6c4634560ed136b3b3969051aeec6e7a852e303f..cf572ec320c75686b92834924df4b31b9efa692a 100644 GIT binary patch literal 3210 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 XFp}8n{RI2@O8`KiAIKnb($ zaq5umZ8&wi8~~tkzIdK3%h$ux_jE0-QbaGF!%V3fLHY&i=_2BIPf1Eke0lou zl7tsQIfgrIRYI)FY|wmaBKdlOJuU>xDD)z1R%b`6h^!Ia`ntw%1g7Px@vA5wv$oC5 zubAn%hWc~8-1%4)88s57B>1C$fvg%&Prni1&)s<{NoH9>44fV zM!x(dgwB?_H`0v$gE1&Ur86g9VQ^%T+^&e@Dhpo{&QLpOlzBqeFhSw*$!JXwOcALZ zCl_&z|L6FBWmImG`Tm7 zZoYxK;%C=JIt-~ocqoFny2J_>x1Ydw?bqAAtYI4mqFsI-9}cIa@w}2UJ~8}6wIOlJJgp16GcvLd{Fg}e!Fu{W0_&>_l!^mp9X0ThWdDF?~v{Q;EX;_s*I1%3<$?3BQQ?D^ouf18IqpC7KVDBv>wa#f2fi))2c=|@UkWrj06!CQ;0%GSs} zmI2;ojDMSyB2jN<@7mGf*_nIe9v2T`3>G@yGIua7m{3TJHgI!!zImUX z1d5EsRlC)bhDu%DA68`;CT1osnyl#ockT>?PU*9gh5FA`*VV+Tzk+6sv}%A0AQhoS zY#5_q)pkqu4?#-?+mCDm*Me(=O3r=Y$<3IO#gd3w4Cc8;yLPkf^DD(9RzFE zjh3eUdjAk#R6_iGcKXBW{7SWvanav38j7q_Wrm3JG$s>(7x~#|14D(DTPm;HT~g@b zVOB&N_YO<>*cr2clJ41E%nNJ<90|?+ThR zq@+-vV`@G4J$0cIt?me4mpG8)$b`RY(@!YD^PCprQEKh?Gn2l-)zvJ;@s&^+T`}Cg z4@hHBg`~zGla11>R*5E1I-ArzqT*lU^Iw7Pq@BhB!5Z!gy0emy}i?5h~4B zs+Af{YPlJf&lyBY+rZut8R9+fjb*fID^k1hYb~vt+9tP11V1-R{Z2N>F(ki({$m)D zPOJ34EsMmBu6Y6Tf;U|eT1lO$KIz$kt|69jb<~i^|*UKid79RYR$3KlT4}Wm@ zktvb4CcdlF&nZX7u$u8aH)B4&(9L?7J=XtWA{VB(#8s12E3aJp0=CrX(swRTVo%a> z6QARpp;9#`=&xkUjfl|vGAhLy+1z=6>Wv45YY%6X^TWpI1hzv5s*e7{e`3O7! z6K$kBCNtx=b*3Gv)Zwc4Z~wQ+64uEmxvNqbEkeB0rx(-B!=@WjLU*4ZT`0h|3kW^mY`Rz}d}G|N_o`+DbofT=MTo9`}#ie$Nv8l&K7McOXrl(_AS4NwzCWP`VjB}Z!+tDizIae diff --git a/Tests/images/test_direction_rtl.png b/Tests/images/test_direction_rtl.png index 282eed883933c0c0a9741113120902bc3c459df1..5c52b714fdc9f69c1b7e43078c2a851129356cbc 100644 GIT binary patch delta 1072 zcmV-01kd}d3Zn^-B!6m2L_t(|+U?ofi_~Qt$MN@PJGrIkLSbf5mJyw#F!Z8`qJko& zM2yJj03C^KWsstvU|C9tAxK6PVyF=*WnFBfC?%C;Mp|v_ai2e+n;F)fopMJ< zos0L2=W3W;hWR~je!u5CNRlK;lGLJRKuP_zIvfqJh7zu$&wuXF6$WWIl(0HpIvJr6 zN==NgGc=&75mp(YjL-4jG-7!9kcC%WBfa)cYh>QMTxN9fZA zD+j#c-83BG`+uPfShLbE!u4UpfcM<)4$$#+8f*^x!@@8Pu0G9bqbTz-X8O`x=a}1A4!X)~6y}<}aKncroh<`KTzHlLZ+Te%K8`|9mI*t~H z63(Ei*=NG-p~j+6!X;Gv6}m%>%Njout_*v_D`6rGp?-wlh8iD*63%oVs9COqE#U#` zy8`Zlk@X@xd%%_*+PuL`jRm2Eaa8n%EuqHgjYl{XUJhGQx#N8i9)c4OH62|ZN*L|d zQKL?Vd4FMd*cNW0&txd!8S0w?B|Jz!_dyBogp&X`7iNdQ!?>dzVbkt}wMM}$@OCKS zy%?d6z@@M}+(8|)LkV4BXXp-Nsh9>OJVt$Ep@gO3U?;$*)nUn3p*NK&;>cqa;o4Bb zE%ftR8j7&K&qHq*W`sIyb3zH@sbgIz;VQTWdVjKg?m>XE4{d>6Kc1JzfSge#gc9sNCxMR)?Ns{JRGlj_Pr zM}Ihh`XA;kA4hd07X0r|$uEQw?uQd$Uf2YgikmLz{yc9f=8WAnXk@!$cSZ=fmZ2 zE6fl3DyPB0x(vF*KcNF!-}9k_r`-fP?tgBD-$JRkJ`0yqYdFk+3#fGi>Qz@u84V@e0n5UdVGJ-{ q3SD7Ecn-$-4N8--1Y;O#W&Z+r(9Qyc)7_;20000!14b#N@Yz(mKj=#DWaC9goVn`%~Vq9 zA&JWDrdb)95Y$^1l|2MS6Iphlh#F0^ixxx{(mi@Hno>(Mn=a;!Y&r#nwR&)7n%z0a zIcFT_Wb^-VUiR8+efv9b)_i-dy$2*ok|as`Z#n{UQS>wNAb(u3ALN2%QTVV;BOwNEushfg+!-7W4Z-^2OSpn-!ilgt*c|K(J_#NPhUE=xGa0D@YM{P( zPvwQuwl4N<%zyl_-_H9c;fuk>?6yr~FfQA3ao*O>3&FS9N!aGb%;kmlit3v?l%9;V zPv=#_QSeG|EZhf|B^H*m(CS&}cfUTbI_-kVi4zmU6Dy$#=C|r|4qOSp!h*!0#K6S7 zL=7BU<`cL*D$=LTWTY1DpM>jz!V~gL4yo6B83l6Ia99 z;Fu1{Qe96Ka2hNNE-iHWo?sSifgbQoFu1w>Fn_2jUPI~=!1!$A?TH`Zk>KN?K3Ee> zfi)fTElTn!0MoOLmmEOA9DD$a;DJPcn0Nr8)Rk2M*922xQ=!vi09cT?JkbN5jHX-4 zsSvQWNYESr^Md1AHU0^8kYG6UgQsD1VooQ%j8c~X?g*}h-f%oz1$UQUir)wpz{ut* zdVeNN5AeMQwO4nbKLFnpI{k3s@!+N4oJ1|$46B3QG?m0>i%g-u01SZj!4~)nPNHc- z?t!OZabjCJS?+s8k#BZlS;;}x!gDYKYJ=M5#@Asi>@6pmp)f8lnXlosa)L-Lx&u1^ zI1iTRon8S$;Wiit3lr7Bco+-~@MvOfk$<4m0$?RffDw>jC#-^1iB;uqgmj>}i4AZ~ za5C(G*0YJ)w& zj#jI|HqC@w@ZKTvOzcX?1rHq}+3Nr7BG_8wnfM#Qs6s<#!jx=F72E`Sg2tex6MvE} z->onghCmM(05`(6AQzln>BQ8P{Zj&Zz+^ZhI3Bt|BWy~%3!fJm`C;%ST$QMTfp9*| z4u-?;FfUQpIq8?_{$NgCuswJ?nC-5nDlM}Z>IYv0vlbK6g8oobLVb8C=mVSJqr|K5?ZL`ZlQ9HZlRyLt b7Y^9pNseAMsBw^C00000NkvXXu0mjfvj#rV diff --git a/Tests/images/text_float_coord_L_BASIC.png b/Tests/images/text_float_coord_L_BASIC.png index 14073bd6f40ea1baf27179bf5fc615feac74dc69..266ee5492f9f94353d5bf11428e720759c795da4 100644 GIT binary patch delta 2075 zcmV+$2;}$m5c?32Bw#{GL_t(|+U=Z+QxiuV$Ng(_zNLtDwAErecC^lDwe`{0)JI=l zNJ4@L6tN;iD4_C2Pzi_#1n>nRlH~HM-*LypYXUiB!vWvVWHRyGz02=*Kev0oTe3w6 zA#2JOo&u4NB9lA_4S!q;D=F0n#q`);&$?D?KkOgH!HwRwR#ecw1{_WcEC8m)PTHaep)I^ zJ}BTdq^DAf%#$|N7<%^OesIpc2EM$lu_k)O#zc=)Y*z)=vVYZptJ^CPb0%Rjo;jas zYxN`?ZH3&nc8v~ZNjQ^3!rl-iEH=HN+!9Xq%ZETqC8535DB(v)2vigJc50Z8KB&X4 zUp$c|NLwh=_Nxe>glhttKYU&s(SAw%yF>sb{74DaJT_ED2{*BXHHW1Bs5qkWl+=Df z30FtLYx_~9=6?_++`JOPsNi15-rhb<30FtL+HzE>KS~KVv4m|G@%>W?u@MPNPe2B0 zbI)Kqu290&k+9(ys@yv1b_r2(v`t!nM!MO*8%cty{kVibsFgRb*SCaoQ^+yi_!`4e z#kRNa_}m>p*1Krw8Pj7wT-wSc&*Cp7n>TaV*k7$Nu7B991WP|i0zJH$_1)4q+fCm| zXdiB1{hpp0l7IVmo?KpbFadg5&Y~>Q(K8-5j^RAWY}cnD62JpA#5Ai{4(C0DHG$>|Kg9E`L4#U5+K}t3s6rwQiJ9B@Ctp3hhauG7O2X zmk>ii#dbVrvr+{ueXKczYj9#g^bsgQmMc3i3h{>VIXY-d!=S5$_UVqxE}`jx{Ap_q zvZLc&>gdaojH44em2j{gRUS3EaynXn6!*b})9u4`$V$3h!jV>#kA$T#)SUcDyMO4T z?g*3s0Mo@_shWaXDa=Q1ev>^cAgiu7F1v(iTt%@Z#3ajW6q!$bMmd z)QBxl@2P2|X&~*XAug9n=--8;OM%2+I+YO9`1^n1Y`9QDOpqFmZMYRPD%Ig2n-`l^ z^D%AUme2Gc+m5dn5U+vM|9j?VGRfz;DW!ylxLhh>uoO4UPKI|#=p0u{*nf5rv!I0+ z8%@HI7L*@ul7dia61;0eeWj=O)x!x~`|KA)q^aLBHGq=iof_hDsf4%(#zPvbu${MW zED0z3)pTWpNw|nCBD%GeEF_DFd}GBn3{Coo4ytEym`xeTF4(w#RV{6UIcQc5ak*8( zzACjsXnE3DL$`q>RLkm|4Sxbw(!~I1y3gM46rPRLa7AKZPOX`=e!!?a-oFw)PGcHU*5%fRtm`~ z(}Q8dk%Y4m%m}=O)RyD33nc`vC)E%2RDCKvm1-?w+CnyIeg>zqH>esu6TI6c1Pjll z<}fM-_MrWuL$WxJxZcbm#!gcLNDiZga96CK;9ex+110&I1KrEF6Vt7l!y4jpwS=H2 zfLd6?{Jo6bMVM9+SAYDMW9aJde$cJ)6gsDqewe)&9iLZ6!r5W9a>vOJJ>gZcE$_5D zv7nMPd7n%e7zS~O+@QK?6#SgS!1Z3we(X7V#AW3FYlF)uQxssJX)N*dTrC;(8dy9H zbj5|9UA5m5u`wa^K-zkNqgeN~y|>>o&I;J=(qNBIlf zgzy)*2_ck_5CTd_k%BKKkPu2p2qh$hP(nf|At7Xq_z!#9EspXNwf6u3002ovPDHLk FV1m%9{lEYK delta 2072 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 FV1m%9{lEYK delta 2072 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 FV1m%9{lEYK delta 2072 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 FV1m%9{lEYK delta 2072 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 None: bbox = draw.textbbox((0, 0), "ImageDraw2", font) # Assert - right = 72 if features.check_feature("raqm") else 70 - assert bbox == (0, 2, right, 12) + assert bbox == (0, 2, 72, 12) @skip_unless_feature("freetype2") diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 50ffce87c39..fa2a28435b8 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -109,6 +109,18 @@ The full kerning value is now applied even when libraqm is not available. Other changes ============= +Text is placed at sub-pixel horizontal positions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When drawing horizontal text, each glyph was previously snapped to a whole pixel +independently of its neighbours. Since a glyph's advance is rarely a whole number of +pixels, the distance between two glyphs could be off by up to a full pixel, which reads +as uneven kerning. Glyphs are now shifted by the fractional part of their pen position +before they are rasterized, so they land where the font's metrics say they should. + +This affects anti-aliased horizontal text. Vertical text, 1-bit text and text drawn with +a bitmap font are unchanged, since none of them can represent a sub-pixel shift. + Python 3.15 ^^^^^^^^^^^ diff --git a/src/_imagingft.c b/src/_imagingft.c index 3b5579636d8..302f8280e67 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -27,6 +27,7 @@ #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_BITMAP_H +#include FT_OUTLINE_H #include FT_STROKER_H #include FT_MULTIPLE_MASTERS_H #include FT_SFNT_NAMES_H @@ -532,6 +533,15 @@ text_layout( return count; } +/** + * Parse the possibly NULL `dir` direction string + * and return whether it means horizontal or vertical text layout. + */ +static int +is_horizontal_direction(const char *dir) { + return dir && strcmp(dir, "ttb") == 0 ? 0 : 1; +} + /** Calculate how far the pen advances over a given string. * * This is the sum of the glyph advances, which is not the width of the inked @@ -552,12 +562,9 @@ text_layout( */ static PyObject * font_getlength_impl(FontObject *self, PyObject *args) { - FT_F26Dot6 length; /* length along primary axis */ + FT_F26Dot6 length = 0; /* length along primary axis */ GlyphInfo *glyph_info = NULL; /* computed text layout */ - size_t i, count; /* glyph_info index and length */ - int horizontal_dir; /* is primary axis horizontal? */ - int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ - int color = 0; /* is FT_LOAD_COLOR enabled? */ + size_t count; /* glyph_info length */ const char *mode_name = NULL; const char *dir = NULL; const char *lang = NULL; @@ -570,19 +577,17 @@ font_getlength_impl(FontObject *self, PyObject *args) { return NULL; } - horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - const ModeID mode = findModeID(mode_name); - mask = mode == IMAGING_MODE_1; - color = mode == IMAGING_MODE_RGBA; + int horizontal_dir = is_horizontal_direction(dir); // Drawing horizontally? + int mask = mode == IMAGING_MODE_1; // draw in monochrome (FT_LOAD_TARGET_MONO)? + int color = mode == IMAGING_MODE_RGBA; // can draw colored glyphs (FT_LOAD_COLOR)? count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { return NULL; } - length = 0; - for (i = 0; i < count; i++) { + for (size_t i = 0; i < count; i++) { if (horizontal_dir) { length += glyph_info[i].x_advance; } else { @@ -607,11 +612,61 @@ font_getlength(FontObject *self, PyObject *args) { return result; } +/* + * Load a single glyph and place it at pen position `x_pen`. + * + * FreeType returns glyph bitmaps at integer-pixel offsets. + * To preserve a fractional pen position, this translates a scalable glyph’s + * outline by the pen’s fractional remainder before rasterizing it. + * Otherwise, independently rounding glyph origin can introduce error + * in the spacing between neighbouring glyphs. + * + * Embedded bitmap glyphs cannot be translated this way, + * and monochrome output cannot represent fractional coverage smoothly, + * so bitmap glyphs and mode "1" text use rounded pen positions. + * + * `*px` receives the whole-pixel x position of the resulting glyph bitmap. + */ +static int +load_glyph( + FT_Face face, + FT_UInt index, + FT_Int32 load_flags, + int subpixel, // translate the outline for subpixel positioning? + int monochrome, // render in monochrome mode? + int render, // invoke rendering, or just translate? + FT_F26Dot6 x_pen, // pen position along primary axis + int *px // output whole-pixel X position (must be non-NULL) +) { + int error = FT_Load_Glyph(face, index, load_flags); + if (error) { + return error; + } + + if (subpixel && face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + *px = (int)PIXEL_FLOOR(x_pen); + int frac = (int)(x_pen - PIXEL_TO_FIXED(*px)); + if (frac) { + FT_Outline_Translate(&face->glyph->outline, frac, 0); + } + } else { + *px = (int)PIXEL(x_pen); + } + + if (render) { + error = FT_Render_Glyph( + face->glyph, monochrome ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL + ); + } + return error; +} + static int bounding_box_and_anchors( FT_Face face, const char *anchor, int horizontal_dir, + int subpixel, GlyphInfo *glyph_info, size_t count, int load_flags, @@ -621,70 +676,78 @@ bounding_box_and_anchors( int *y_offset ) { FT_F26Dot6 position; /* pen position along primary axis */ - long advanced; /* pen position along primary axis, in pixels */ - int px, py; /* position of current glyph, in pixels */ + FT_F26Dot6 x_pen; /* horizontal position of current glyph */ + int py; /* vertical position of current glyph, in pixels */ int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */ int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */ - int error; FT_Glyph glyph; FT_BBox bbox; /* glyph bounding box */ - size_t i; /* glyph_info index */ /* * text bounds are given by: * - bounding boxes of individual glyphs - * - pen line, i.e. 0 to `advanced` along primary axis + * - pen line, i.e. 0 to final position along primary axis * this means point (0, 0) is part of the text bounding box */ position = x_min = x_max = y_min = y_max = 0; - for (i = 0; i < count; i++) { + for (size_t i = 0; i < count; i++) { if (horizontal_dir) { - px = PIXEL(position + glyph_info[i].x_offset); + x_pen = position + glyph_info[i].x_offset; py = PIXEL(glyph_info[i].y_offset); position += glyph_info[i].x_advance; - advanced = PIXEL(position); - if (advanced > x_max) { - x_max = advanced; + int position_x = PIXEL(position); + if (position_x > x_max) { + x_max = position_x; } } else { - px = PIXEL(glyph_info[i].x_offset); + x_pen = glyph_info[i].x_offset; py = PIXEL(position + glyph_info[i].y_offset); position += glyph_info[i].y_advance; - advanced = PIXEL(position); - if (advanced < y_min) { - y_min = advanced; + int position_y = PIXEL(position); + if (position_y < y_min) { + y_min = position_y; } } - error = FT_Load_Glyph(face, glyph_info[i].index, load_flags); + int error = FT_Load_Glyph(face, glyph_info[i].index, load_flags); if (error) { geterror(error); return 1; } + if (!subpixel || face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { + // in non-subpixel-vector mode, the glyph will be drawn + // on a whole pixel by load_glyph(), so account for it here + // by truncating to whole pixels before summing into the bounding box + x_pen = PIXEL_TO_FIXED(PIXEL(x_pen)); + } + error = FT_Get_Glyph(face->glyph, &glyph); if (error) { geterror(error); return 1; } - FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox); - bbox.xMax += px; - if (bbox.xMax > x_max) { - x_max = bbox.xMax; + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox); + // x_pen is in 26.6 format, so sum precisely before rounding + int glyph_x_min = (int)PIXEL_FLOOR(bbox.xMin + x_pen); + int glyph_x_max = (int)PIXEL_CEIL(bbox.xMax + x_pen); + // py is already whole pixels + int glyph_y_min = (int)PIXEL_FLOOR(bbox.yMin) + py; + int glyph_y_max = (int)PIXEL_CEIL(bbox.yMax) + py; + + if (glyph_x_min < x_min) { + x_min = glyph_x_min; } - bbox.xMin += px; - if (bbox.xMin < x_min) { - x_min = bbox.xMin; + if (glyph_x_max > x_max) { + x_max = glyph_x_max; } - bbox.yMax += py; - if (bbox.yMax > y_max) { - y_max = bbox.yMax; + if (glyph_y_min < y_min) { + y_min = glyph_y_min; } - bbox.yMin += py; - if (bbox.yMin < y_min) { - y_min = bbox.yMin; + if (glyph_y_max > y_max) { + y_max = glyph_y_max; } FT_Done_Glyph(glyph); @@ -800,15 +863,10 @@ bounding_box_and_anchors( */ static PyObject * font_getsize_impl(FontObject *self, PyObject *args) { - int64_t width, height; - int x_offset, y_offset; - int load_flags; /* FreeType load_flags parameter */ - int error; + int64_t width, height; /* pixels */ + int x_offset, y_offset; /* pixels */ GlyphInfo *glyph_info = NULL; /* computed text layout */ size_t count; /* glyph_info length */ - int horizontal_dir; /* is primary axis horizontal? */ - int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ - int color = 0; /* is FT_LOAD_COLOR enabled? */ const char *mode_name = NULL; const char *dir = NULL; const char *lang = NULL; @@ -829,18 +887,22 @@ font_getsize_impl(FontObject *self, PyObject *args) { return NULL; } - horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - const ModeID mode = findModeID(mode_name); - mask = mode == IMAGING_MODE_1; - color = mode == IMAGING_MODE_RGBA; + int horizontal_dir = is_horizontal_direction(dir); // Drawing horizontally? + int mask = mode == IMAGING_MODE_1; // draw in monochrome (FT_LOAD_TARGET_MONO)? + int color = mode == IMAGING_MODE_RGBA; // can draw colored glyphs (FT_LOAD_COLOR)? + // Only do subpixel layout for horizontal text in non-1-bit modes. + // Vertical text is elided for now since FT glyphs are loaded + // in a mode that grid-fits horizontal features; doing subpixel + // layout in the vertical axis would likely blur those features. + int subpixel = !mask && horizontal_dir; count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { return NULL; } - load_flags = FT_LOAD_DEFAULT; + int load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } @@ -848,10 +910,11 @@ font_getsize_impl(FontObject *self, PyObject *args) { load_flags |= FT_LOAD_COLOR; } - error = bounding_box_and_anchors( + int error = bounding_box_and_anchors( self->face, anchor, horizontal_dir, + subpixel, glyph_info, count, load_flags, @@ -902,7 +965,6 @@ font_getsize(FontObject *self, PyObject *args) { static PyObject * font_render_impl(FontObject *self, PyObject *args) { FT_F26Dot6 x, y; /* pen position */ - int px, py; /* position of current glyph, in pixels */ int x_min, y_max; /* text offset, in pixels */ int load_flags; /* FreeType load_flags parameter */ int error; @@ -914,7 +976,7 @@ font_render_impl(FontObject *self, PyObject *args) { FT_Stroker stroker = NULL; int bitmap_converted_ready = 0; /* has bitmap_converted been initialized */ GlyphInfo *glyph_info = NULL; /* computed text layout */ - size_t i, count; /* glyph_info index and length */ + size_t count; /* glyph_info length */ int xx, yy; /* pixel offset of current glyph bitmap */ int x0, x1; /* horizontal bounds of glyph bitmap to copy */ unsigned int bitmap_y; /* glyph bitmap y index */ @@ -922,9 +984,7 @@ font_render_impl(FontObject *self, PyObject *args) { unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */ PyObject *image; Imaging im; - int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ - int color = 0; /* is FT_LOAD_COLOR enabled? */ - float stroke_width = 0; + float stroke_width = 0; /* stroke width, in possibly non-integer px */ int stroke_filled = 0; PY_LONG_LONG foreground_ink_long = 0; unsigned int foreground_ink; @@ -935,11 +995,10 @@ font_render_impl(FontObject *self, PyObject *args) { PyObject *features = Py_None; PyObject *string; PyObject *fill; - float x_start = 0; - float y_start = 0; + float x_start = 0; /* starting position of the pen, in possibly non-integer px */ + float y_start = 0; /* starting position of the pen, in possibly non-integer px */ int64_t width, height; int x_offset, y_offset; - int horizontal_dir; /* is primary axis horizontal? */ /* render string into given buffer (the buffer *must* have the right size, or this will crash) */ @@ -964,8 +1023,14 @@ font_render_impl(FontObject *self, PyObject *args) { } const ModeID mode = findModeID(mode_name); - mask = mode == IMAGING_MODE_1; - color = mode == IMAGING_MODE_RGBA; + int horizontal_dir = is_horizontal_direction(dir); // Drawing horizontally? + int mask = mode == IMAGING_MODE_1; // draw in monochrome (FT_LOAD_TARGET_MONO)? + int color = mode == IMAGING_MODE_RGBA; // can draw colored glyphs (FT_LOAD_COLOR)? + // Only do subpixel layout for horizontal text in non-1-bit modes. + // Vertical text is elided for now since FT glyphs are loaded + // in a mode that grid-fits horizontal features; doing subpixel + // layout in the vertical axis would likely blur those features. + int subpixel = !mask && horizontal_dir; foreground_ink = foreground_ink_long; @@ -994,13 +1059,11 @@ font_render_impl(FontObject *self, PyObject *args) { if (color) { load_flags |= FT_LOAD_COLOR; } - - horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - error = bounding_box_and_anchors( self->face, anchor, horizontal_dir, + subpixel, glyph_info, count, load_flags, @@ -1062,20 +1125,28 @@ font_render_impl(FontObject *self, PyObject *args) { * calculate x_min and y_max * must match font_getsize or there may be clipping! */ - x = y = x_min = y_max = 0; - for (i = 0; i < count; i++) { - px = PIXEL(x + glyph_info[i].x_offset); - py = PIXEL(y + glyph_info[i].y_offset); - - error = - FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER); + x = PIXEL_FRAC((int)roundf(PIXEL_TO_FIXED(stroke_width + x_start))); + y = x_min = y_max = 0; + for (size_t i = 0; i < count; i++) { + int px; + int py = PIXEL(y + glyph_info[i].y_offset); + + error = load_glyph( + self->face, + glyph_info[i].index, + load_flags, + subpixel, + mask, + 1, + x + glyph_info[i].x_offset, + &px + ); if (error) { geterror(error); goto glyph_error; } glyph_slot = self->face->glyph; - bitmap = glyph_slot->bitmap; if (glyph_slot->bitmap_top + py > y_max) { y_max = glyph_slot->bitmap_top + py; @@ -1092,15 +1163,20 @@ font_render_impl(FontObject *self, PyObject *args) { x = roundf(PIXEL_TO_FIXED(-x_min + stroke_width + x_start)); y = roundf(PIXEL_TO_FIXED(-y_max + (-stroke_width) - y_start)); - if (stroker == NULL) { - load_flags |= FT_LOAD_RENDER; - } - - for (i = 0; i < count; i++) { - px = PIXEL(x + glyph_info[i].x_offset); - py = PIXEL(y + glyph_info[i].y_offset); - - error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags); + for (size_t i = 0; i < count; i++) { + int px; + int py = PIXEL(y + glyph_info[i].y_offset); + + error = load_glyph( + self->face, + glyph_info[i].index, + load_flags, + subpixel, + mask, + stroker == NULL, + x + glyph_info[i].x_offset, + &px + ); if (error) { geterror(error); goto glyph_error; From b934262ecc714c03f6598e4d4e6257a7617377cc Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 26 Aug 2026 22:21:36 +0300 Subject: [PATCH 16/17] Basic text layout: Improve metric accuracy --- Tests/images/default_font_freetype.png | Bin 3686 -> 4983 bytes Tests/images/imagedraw_default_font_size.png | Bin 1999 -> 2478 bytes Tests/images/text_size_ladder_basic.png | Bin 10097 -> 10755 bytes Tests/test_imagedraw.py | 6 +++--- Tests/test_imagefont.py | 17 ++++++++--------- Tests/test_imagetext.py | 6 +++--- docs/releasenotes/13.0.0.rst | 11 +++++++++++ src/_imagingft.c | 19 +++++++++++++++++-- 8 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Tests/images/default_font_freetype.png b/Tests/images/default_font_freetype.png index bc1654a253a741c7dcf56df5d45dcdae9ebf15e7..5212929a6edf72ef88bc7212d4a2027790ee258f 100644 GIT binary patch literal 4983 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 diff --git a/Tests/images/imagedraw_default_font_size.png b/Tests/images/imagedraw_default_font_size.png index f695b5cd6ff2ebc6b50036a17e6c7d6e4b520404..173974ae1a0dafa2661a294e0e4fcfb0d325fb8a 100644 GIT binary patch delta 2469 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"AsYvA5RKE zIeA5S0F$wtI}Q%4poD|zC@~g4Lgru*Np zyy~V@DraV9T3cHe69mIBkx2B%(k(;&V|By+VSaudMbYQapC3JXl)Sss>AZ2{#^N>L z7bU?OU6OQRvn;vSpXc@K*X8Bq(DbXD-{?|hSv)k@s;{qSGMNbp2?YfOLqkJyxxBf# znPimc=;+zm*?%RJ^{-0z-{s@Sk5;P{ntpZj8(pd_OGe<{`LyMO=w&Ye4Ra&q3hdDGR^1%~3|<1q}YtE-ET zj|V-OnSYsHuU8_Ga5$XY++2gf03Rl~C<9|c0*AJ3+h(y?Mn*>R^YfFFlZ8T|*Xxx^ zr7-O0&!3e_Wm;O=-o1Mr4o6E%OV}&~Q&^Z6M61<~kB=uOCm%d`(Btu3zI++#oSK^I z?(WXY$`S|!Jv}{MuQx3%Eo}YSY_`>EeemExW`Aa;Kp@a+wUd*R>({RjTT=WeK@c*^ z@mT;Y7Rz8T^!N9N)gO|ei?Xq?5k*nZmdoXnlap|BWM^k%7{=%GgSrcaLcLxOO{rAs za5#cK8ER36k`#2Psi_G?QT)!ka^;H8=L35k!!V=K2$joZGPBtnHVeTN7VHIZxm@-2 z^?#s!>C&aTx;hxsU@)v&wF;Q4SFZ+VEieh{-_p|Z<;xf7n2?a*^Z8DmJQ=8yz$6Gl zMtNX0#A30HGH5TrYPGIiyB4&Ii;LB2_1xSXF!g%9)9Dll1QQbzfsRtC zR4SFSSgh34R63o`nilym@m_M>d=N^5x5kiHYj!>awyjE|&}6@ce*Vb1^U&43o)(?*ZnfO`Cve zG#cU1{QNwPMl@>x`6>La*Rf;AT3cJiVzJ3&QYaJ~H*WNk4f*!%8$4+U>d#;>@Mk}Q zAk)*+_$^8XGC>HG~Qxm@1V)PIzhmj_1+3kz4S{QH<#vt~_cY3b9aPk~8L|B;cA;^Jbs z)wx`*+wI=BZy%YtL&63jRt{mYST!{@6%`durxV{#pnu)Eb-^{kdl{3-+_h_0U0t2e z=Y!WP1VM6ga^~jdBoYaa$IHpd866!R8yky=h=A_+SKU{yUVZxXX@Ae2JzOsL;>C*= ziv`0lko(@fdv3S8yu3UmB}Fcm8w>_`(ee3wGMOwYDhik`m#eR@FEKGOs9JnFaQhw} z9tOAX^XJd|-M&Lztq8IZOku%Z2!cdLMovvl>2$jE^z@93jCb$eX|-CY({8t`RH}@O zjI6Az{{DUxML8T!*nj%x<>mQ&KBZF0=ks^(-mTZ`$H&LP@*;oHAqYZ7IgF#xXztv( z6OSXT{;8>{6DLj}fxxdy;6FM~o;)cnE%ke*u&{7=c-Z6dm`tX-ckjl=#)g`0Vq&63 zqj5T&Hk+-pv-9lPvlxc4SS$oVVq#+2+uLVmX1;&_e*5%)f+)z#Jb@00|KGE^3VDJ<9vker;{+uLim z+if;mM@I+z-M}zRDwV2KDx1ybbUM4cyOWZVNY=lkq-1n-6b#8^GWar0eo+zxA)}nk zcgj%u$HvAE96LCGQ2Yxa_yo4x2!9Wyp!ma3i;{wZf`WpAf`WpAf`WpAf@0b67q~I} Ulr8RvIsgCw07*qoM6N<$f;c?y{Qv*} diff --git a/Tests/images/text_size_ladder_basic.png b/Tests/images/text_size_ladder_basic.png index 8b2fcf095075bc8891aecf63ae8dc36e50fb9159..663fee109fca4384885087c0eabae5e9238eaafa 100644 GIT binary patch literal 10755 zcmZu%Wn5Iv+ukLXMY_AYyOHirX-Vl=T98;$8kAgG8i7S=kXS(J20;*HX^;jHkb3v| ze||sAoS5I4nKN_Gb={R{pr?-ajQSY>0Kn7KP%%PXS5YMe3_{fu3&TMG0N0qNilWKu zm7}%53{Q13&>8ZCe3h>QD;VeGgq0ASfjl{(j`mOj6Y>&@c37>ht*zz%d+`0J7kEXt zT+}Y})_#2hhX0N^LCbTz)zTs_B1^ z=y2^^{0^z?wVh)|}zLVvxG^JH6V@6h>z9Nx%>L)-E2 z(>LpWl{_}Jt7q{Nyten76ypv~cgO7lG+r%+3@{HONZ;Ms=`*SOv%zt?)7Qfnwff=i z{D9z|LmQu2=-p+>lQsQSje0gy$iMlLmvT?H(-eNIzQfsX*6)7pNKJ?6)dH_a^&^^4 z9rtnWl|)7?yOe~FWjBWHG+qCCllyHXp2pie2=#@W^b$96q+tDix9NO{hQe=*t2%2> z{^@pSWvMr)bFmd}`s;67I~nzsPvo6d=cul?k&@i~20<>5^~STERsU(e?L*tA(2xm7 zdgBrPFGofv5!YWR(Ep%n+Yg2b{&G89`dkb*2&^%&#*oPi*mPLc0@OdPz3<_8=eJp3 zXy6Y zBywijioY%O{%uw2J&O~#@nR@KtuY84vUWx$1Ld-@{)XCQ)4Zc-c*&bj~oAl_+vxRoo=## zk)5F*`EOpX%Y{xaYV!ydOGssh2v2#GXx>X}XTdDJ@AOCwfH< z@Nosj23h~1cF^!7pjsw7w|kpy2erK~n3nn;uW;~4^LJ4M)ki2?dRw0yGQj`WB> zBzkc#({AVp(LVOS`sqLYoC8FQH-1#kg=@m@o#%u`QsC8Q0o0IN<@5GU9^j}0UXR;8 zHVJyzBwxDRo~K9ZSm|PPoT5(PPFs7A7YQw3I3TT!50>M|MfLyI@m26?{Stw<-4<^uzZ89TI9D2 zCtcWCzhMJYI8mWVpECb`d9WCo-qijGOFAzKi9M3~=}IpmQ*dwV2k~Xew!ut0Wq!CePPz1brds96GI1wQK;E>ARShXEr}bB zc>gu$`h7y0f33>v18v)+qZ$X*_&m|<6pX?{P~ouL-_Mu_1sF*0 z;4eS3mGPx2UdMlASWSMz=!2rhu^9S4rJL93VcRTgX7B_BqC=gm@B4#5=s%G^H4oS8 zeS}xGf2nX`MNzRdVPhJry%*VyyeJ;M`}Fq~I&hTcv8WbUrYlTDMTy%6D7;J)<8tdK z%mi94(7!pdFQv&7wRGNO0bTY~0Sh(11;ds|(9`+?Q4{XEp*sVZfvok$3Uple`%nnP z_Be$6@bDkDFJ-1;rwkpkyw1+g5)012UJ!*vlTC+}lK}?S8{e!QCgn@VWs4Fjx}kwf zlksjTsiXkvcd6*B(~*yt?^QGQB>HZ3Z|6%MP4b*IT?qk#Av&0nkJOBrZlbh6Z_R+| z?fvp}exM}AZ{9Fjr$a*amP2Dx1MKx#)+O~bOF$t|t>6`2^}cYD{;>qQ2&PZi7ESJ% zHg3~@NEH{NHL)8;vfk*#jR@c2vE^dY8J>Y?B>+~S1d3f%8-Pa8>TZFL9ERk|Xt3M9 z8!(pxEmIkgrdcdTyKXife+HXfGzY(~78A-}=S`UZZ#>%ONk%(q>W&G%U448`*-O|o zuVk!bI&5LW=k|MJ1X%=;_**DWDWI6~8J{2-GS6Daq?b@PKU|XNdG>GUz}rEKu)obE zYkxLW9By;dwwhVt^7rG3fZ-!G%OL$?BD~@576HK9#@`WiR@0Q3nf@e8H6ytlkJWNl zwXj1s<_)WBqHb~kg`w8E5P0%y;p36ZSskvkGhL$MS0@ur>VauAw3J9iY^0&R923&j z)cIro&m^Od^+U-4VL-}P${M4(2w($}U_QsWR;72cLYN3y0n`Dd?R0HVHK|=ZpiueY z8^$)(ugl`=l)D!n_#YI;RV_i37Pv%}f4<0tqR=Q1r%&(#l1vKSYUC3os4gq*%fRn2 z6}WgUIzkDxmX9iVFSg z)_j`@9v!=YYO^tQxr=wgS{M;COWS{yR4g&IRD9GENfTXo=VZ`l{nFEyeu~s9AP(5s zviJkMkQ-E5LsKG1K~raJ$#5ns>t z%mFxf_5Hkc6P{ye+uEv96MjEjJB#1#bubrmIGxiJV2iHTI~D7|pt~+x4eA8{ zz_TZ70rhT;lpox%C9Yx6{?3t>zdnZt_uzQp_wc#~6!)-pt8`v|8OC)UUf63+Kg$M7 z3Ye-)+Wi&iPeJlztVUT}tX$)fQ-4kIEH}(^4(qq;rXig$eNt~nYatYoCVBB*7En!X zU#N*K+p6zhbZE`tYBL3!-nwbwHtqyjyZ(O1zT;nVXPuH!3wR4qz{zAnz((E!T;Y^6 zr$!a?7}eii7SU`Q|K;4*LN5ePcZ)ExhT;7pRLupwe#MR{B>8(Yt9d3u6fbon&-vwT z1=8Z*>M2xA{0R4M;tVL+!#a$n$E8hP1>5SN57Uq7B<>{-T~-3P=wie~7$Lq%qlGoR zgZej!eezcTBV5Ds38cD-85h3%_oEn+0(qSyg&%h|2qItWMwUr6aXBKH_b1CcL_eU4=VuCvQ{R=ZfxZxg# z=F@Bj0N~CaILj)jl)FhV{3`Wc7i4!}{b`aLFl!>njnn*EJ6+Z)HA%|k*87$bha>VW zT~GpBJudxSSriX3f2LoJ#Ha~$OIu!xh4NLp}%PqiCj(z4FYgAT47ecJF@e^&<%~`qyz<$x)-Y^%9 z3wq2FmJLX&L00XkOh7fFxYk+ksE9%vwcUS@&}ZuNkNe{CD-coUkwbLM?L&H9c=(_p zjeUIziHw9Kq-UT@NXv(3prx-Ea23@!g_(Mwy1zxmY>2Lkp{G`6h&U`3;SwY;>#IX& z=kv5wYg1>u4T23S$nXFqBbL$P^tnpsZ1u2KKax^ge>mm^4i{SE&R4Y09zw0Y9}>ET zAR2AF5%%qW)IR^GEdfy4(*KOzIiLigB3W&wgLy@%=9MX=tKJHsT9l)!5rMKOazE`> z*9Ri7Gq!0rIQ;4M#FaPznrFpHLT?0fi}f_cadKl^2?bsnk!-EA~pQaG!=3p zHZb^AjCu21;VM~tPN```%m~jctw>ln?ky897+vD+_j3$K1-nL#H0>H~+P%UoX^|GT zQ5#wHJyjYSG}< zrezMmzc$>b5e&m3j0a#TaK3~Sy!mw{83fKGsL^Gnq{-u^yn*+i6)Ng(b+`mV9@nMY zv4_L)O>O3#bP-I+G8C6bc}}Ze`~k&|@#g7@k-F35A7}w2RF)Jj)BP%o zM1twi@>JZ{skSiVNBrF{)D2x++`E_}3-#Vet`^pj7H4NKLS{qH(l@u%ZU5xBDPKgY zuA0tJLq+z7;wRA|i*Do5V{!VLt~Be4%<#P1n?z+fT12Wa7g@YCKc6ff@+KHgQ8aO7 zbv4{*9T>C zvW=e)Gx!+9!m8zT-So2hXUst!q)E)=ILsKImwk%HLmWw33<+|8ScN1&{3~u8Joh=q zMON3WLvtU!{v#!z%K*fi+Sw1$8pp0)It8s0BM}kS%J8#zpQz*V`^8Kg*Tm5=IFAJ} z%c~=_Qtd7K(X20b!o=;puW~bG_kFST_hXsI#z$QM5DeDcYES4gwJBV8Lg>IEu;XnD$^-{naFsNo^tQ_e8m1j9(m7v0^#(BjnNg&%aXN8$(?fJtVN1NzAEL)1t9wa=SA1 zEdVQ}ydz1+b>$T~wchjMm3CaoLz;zB(vGo8V>r=1ufix9GIEg`e_tBy1u?STK}%V1 zlG$}2*ba?t!}do>ILNM9zo9S+CYI5!6a#*i&^D?+8$XXWRZ2l-y14bv6)x@SAlRiJ z#kgAyW+utel*N!7CXI4>*}n4gTzjyv2n31Nof9}IXlvIi-s3UP238-9_(LLa-sH8o z9-g&FEmRPZV->s9sVB7&(*b$F5Dd!LpD=+Wb}@>N4?Nn(@$9ufh4v53_Yz$PHN?O! z$&0s-Ezokq21OT$s%0tv*!DVDnlfvOV}~%}fDnlx1Q=5AX_)!_7{0r#gDx2u%Ffh> z$?X=KFy)Mp;$P?Q9VWoXV>fVMu$2}#o8Bg!NZb-U(B-BB7Ok{Ot5_*G|Kw!@V4Wos z%9UuUkVQlq(n0E!S`sp*2Ss6G$h?u{SNSznrvkO;-lo_LnwCz*fVWg%Jx_Qu@jLiQ z(BO^7J=DOrB@|(b@17^x@e<+RXpycB^4iLxD=J%YCfhC$`WOd%hEu!zPSv4s{m3&R zfEcvS9xVKvFvrZMF&hxTD};<4zGY^P_(7tykH89`gODKu%8E<1XdKm471rpe!Co=Y z%ym-Lt2fiOzZF9rhoZ5PYD#}vs1N%A;1N{s*9gM*mCcxu+rTAE|BNSDao8@ad z$ZoGcJxVZ+%4^lO0QPR?ShP)zSol~des|L`jbnG%**gs-sMJM4n_ZBC>h)1x@`?|_ zl5GC|^y6~0IE}PkrdDAVE})gF`quBITY_|o%|Nur)&+0HCR5brd#JeR z6v%qoqfVtW;@#4G4$xB|Tlm*&{ z%k0CiU62XY+IVyFvG;=^87*z~QoyNG4oupohS8zDOTrimPL1**0we{xz*`lYWWv|HmP+N)0wmIj*uxQD5557?d|atM z)&k$=x;^jJs-U=;0d|C?Ngpj?zP2Q%7rTQNcbevLmFFPecp%ws#aGv`q zNHrr~K!y*g!iA~Di<844Hf@9;E371P7TeRegu0QC`C zV49#NE{N$g!3fRJX0;|(C2NLvnqk~fu~*}`G&vvJ6>_FZraFI)J*0wG;#18`t&(vvoHO)-hfdpR+~s38|6T9si#>7Q zk7o!OtG)6d^8o&PjefeN6N?1vi@?JcC~4f~U7BnmcCsczHnO-}0Oe{Mq(J~bdV)7ZQPDpIb&jJ5IA{DQ zQrOCzTW(N5)num8_PI&_GQ@QA??M?F9n4PP;+W^;Mm3J2N@UoiseZQ9(c2A{RPq-_+@b=*Fle zkRKetQYoCBGsc@=v=Xq?5Vr>Z(yl_l2wgSrIw;9se{C`>t=H)2q>sHi<^G@nIxtM8wr(4+KucOd%3KIzFd*uG80^?nPgEhw@VyAJr}2S^4w?24E-86%*bg%s_7=;M(xL4AXO5D zv%)1y(@Z`HqsiNKj?+kYpnq>%FxUg3%0RYPGE}jU#_!>piA(QHTDf>QU?IR%GWc|y zTv;`A3lS~flgySn=w&BEIaxcpUYBa<>-O&A#uvvVkY+J3en8}t3jR{^m)f*gB% zS3`A)V^zXl_PmD@4S~o(=+|LxEMDzQsvv(b2U9_ExP$6z!zC3yVi2vktdMMbj3wu^ zvWaFWpSxc4^YmF{q=xs7k6$!{p(sG53;;ma)!N+v^SLGP&{VTLb0qQ*zTHbriIz zF)DkmqtysnwlA10(bf!48l zd?24M6xe;G^|kGS&$$^4T@aU*C;Iezz;x$H&cz$d61s)Z569bu=7&b?_(BPiO z#&-)f5mJa>KnXiF0Ek4?xk0+r0n(yF7V>!|K!oJY=){=p{$Trh9xd#d zYD@^f(BV?Eu#uoX)$aFu_8}o{%D$ zJC7#{WPYbuh}6>bsyMq}onn;8(x!zPazaKt#B?AySx$%!f*$>L4|qY6hn9bO6Hd!f zz|+YiLQ=l-sbHt)sMHKEE)7;rJwA4)nIml4(p!Lubfw_}Q7h3;No^O=>c0;kslK3* zlC(oJM>&0F%n`7w7zZ|k&)>+cvVcNDt!PRUc1tJAW9hIXODnzLo0lq8Yt;ZO1X>@! z1W)pq9wW)Ry1OcvKJm>oce9HuI!TjYWLsDVNP{%&I^Xy}(g8#VVNn6YI)~NvOfJ_1 zwYTAFAgk_AT6y$x<5jN7&Q+(0m%!@#;{NLPVI9EU%k3OoIdqmhc2EVZi-~v)(Bb2&*mx7&jze>0 z+91i<1*+u3J*^G~w<46`;?G4SOSEP)3QcZHM04-i{4#ExoVJuO#X|*wuZRC3$Fb=W z1^nk+{$FN-$5?&xzrMb(dcP0H;StUkNyy^2Lo4+v&rNxhzcSwk) z4!O~i+iqwB^?w->7c#JakozvhY@asqNhg|#Z8(W2hN{^Us{Gv$;66y1;ncRye(*ak zxMGq6iR~l-Hi)T75c_LEjI(T?;)MU(51oJMk!p`pcpOcV zv_aY|`blm6mKMF6#Y+#^e%$tRg#dy>7smd*{xj)ASt<>qz_Au56YPx`2EDkpCse(xF-`C)UGxsgW3 zk`K2hGHjW1-Ka1nxUNlKw*PLyw7zvE{XHCs-u^2v3oNC!_Dm==G<5e8U9uEgysvDK z_4g)Pibls`rkdv!v|LEv+i-8l;e}gH+BP}C?=E*Fk3mD@7Ai)UqBuhU6J zlim_~DLfD zgx<-B_pYfF_2BMuv2``3a+h5d`bTl$NsvRj(DOc}0e;%f{ za+s*;!KwWT@@^VszeMk#3;NN&CyEsxe8N+6vFs9vose|KX*N2kYKlMXQ~%1Q(by}} zBy}mqbW|Pi)2?1ywoskRz<3zgR$n&-PIHp6bK^KG>mQ5v?W5FZt+6j zNJy%tpsudQO}4}XAyTX9=C&!wMX@<7xuirM@W(19Xv9h6ATRcLW{o!`mLv^N3|D@M zn@JB2zFUvkUDk(Me+e|PyoO$$Ry4rTE3CqO5;cf{D<^ zYaXSmM$2~RxX+jIH7aS~x`l5FszUzlm>rlD^3I4_O%Lqj6aTnQ%{yHj_Q|ac~pfJxrRLI17&U!1IMD&JwcU|!aVF;o_ZsKQGAvgE>SP*D(lE3Drg zVIk%GUqp+Ju*&&{wlz^(S?4_~xo4gbawU^}J}~8Meeh`MAz95$wcbK4jCtLb5Lp<5 z3*d%_B3M1o#u}}~5W-dPH#vz+vi13pntTGgcL2TDFy%$Vj{UIH=Ct+r&kv4}g*xE!uWWvg zH4go;^0+*?%j$CQ`qOOWL`og_ty75HwhS}PwYZ|Q=FIRG5l(SXzC@JxKhbN=tLfs! ztihLA&w@y*rS`CK)!EG-IYgez=RyeUUgWn#6L3&_r*)(~6zO3-gc54-tqQmgB~Eyx zD#`}M1{3o8i<*Tx@-rTs%`#JTN@b%GgA(*yq6?V{xW)pm43mEZd;c;6Q%Hn$zNwwP zrDMwjPP25pQp)rD=4$rlV=m@jPuddQj){Vb@?ynjdy{u`jz1Vn?;I>4@*+QV0j_^Wi8gsG*q%2&V7Blonw=vNx=0J)F0Y8JI-;T@$Dpa+;wI(hx6c z_T29C{Z+}z_rj4%c<*BQY?;zk3THb1!@-NUao}C9*%7#0KIG`7N`jTxA%ywOv+253 zDkc#<8klo0eXfA&9~Xw`Z#gS?j!tTVwMD97R2%zR`Qi{d(l!$C{=)2+{qQ%QIjlc6 zr1TZ}P6Ozsz_%?HooO8X6=Q)v-lZmHAG``C)N@o(`x|9LHu|1_ia&DK2<`QpO#nSZUd@GzaY4QOJQTUGoEGJDG9?29st)SP`0 zI-xt(B~sDyy(bCMOFR^yDcBre)?ClC6d|ti5QD6L#=4XGyJjI4k~gf%&Ye{p|TL;@p}Y@-4` zMj)--@`Z_nWQf-2;12$`?Y;QoPT`{`3q+oHTuJBJjonqYuDEX4zDo zm$Y4RSeu=Dz6>P-q*o662Z5{2)U^|}u{_>!F+`5^_m~6k6r{qMlw*_H72eR%HOk!- zV5?sXAtei}kBH$n{&}&7dEI548(JNxDyb}%=YNG5&Y6TN|GoTw8{}}G03u4BYHWY9 S-=mT{0h+3MDsUyc=>G$NjA8l! literal 10097 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 Hv*7;+c-Kix diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 680a68c302b..487757d67e8 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1534,12 +1534,12 @@ def draw_text() -> None: check(draw_text) def draw_textlength() -> None: - assert draw.textlength(text, font_size=16) == 216 + assert draw.textlength(text, font_size=16) == pytest.approx(205, rel=0.5) check(draw_textlength) def draw_textbbox() -> None: - assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19) + assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 205, 19) check(draw_textbbox) @@ -1555,7 +1555,7 @@ def draw_multiline_text() -> None: check(draw_multiline_text) def draw_multiline_textbbox() -> None: - assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19) + assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 205, 19) check(draw_multiline_textbbox) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 3d88585e696..f7df31949a7 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -181,12 +181,12 @@ def test_textbbox_equal(font: ImageFont.FreeTypeFont) -> None: # basic test ("text", "L", "FreeMono.ttf", 15, 36, 36), ("text", "1", "FreeMono.ttf", 15, 36, 36), - # issue 4177 - ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875), + # issue 4177; mode "1" is placed on whole pixels, so it still differs + ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 22.21875, 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), + ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 31.984375, 31.984375), ("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), ), ) @@ -196,7 +196,7 @@ def test_getlength( fontname: str, size: int, layout_engine: ImageFont.Layout, - length_basic: int, + length_basic: float, length_raqm: float, ) -> None: f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine) @@ -887,10 +887,7 @@ def test_anchor( name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" - if layout_engine == ImageFont.Layout.RAQM: - width, height = (129, 44) - else: - width, height = (128, 44) + width, height = (129, 44) bbox_expected = (left, top, left + width, top + height) @@ -945,7 +942,9 @@ def test_anchor_multiline( d.line(((300, 0), (300, 400)), "gray") d.multiline_text((300, 200), text, fill="black", anchor=anchor, font=f, align=align) - assert_image_similar_tofile(im, target, 4) + # the reference is shared between the layout engines, and they no longer + # differ by more than the GPOS kerning that basic layout cannot apply + assert_image_similar_tofile(im, target, 6) def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None: diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index 507d8240918..03fa8dca55a 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -193,15 +193,15 @@ def test_wrap_shrink() -> None: assert text.font.size == 10 with pytest.raises(ValueError, match="Text could not be scaled"): - text.wrap(50, 15, ("shrink", 9)) + text.wrap(50, 15, ("shrink", 10)) assert text.wrap(50, 15, "shrink") is None - assert text.font.size == 8 + assert text.font.size == 9 text = ImageText.Text("Hello World!") assert text.wrap(50, 15, ("shrink", 7)) is None assert isinstance(text.font, ImageFont.FreeTypeFont) - assert text.font.size == 8 + assert text.font.size == 9 @skip_unless_feature("freetype2") diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index fa2a28435b8..ac49266d71d 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -121,6 +121,17 @@ before they are rasterized, so they land where the font's metrics say they shoul This affects anti-aliased horizontal text. Vertical text, 1-bit text and text drawn with a bitmap font are unchanged, since none of them can represent a sub-pixel shift. +Spacing in the basic layout engine +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The basic layout engine now uses more precise (non-grid-fitted) metrics, +so error accumulates less with longer strings. + +Text measured with :py:meth:`~PIL.ImageFont.FreeTypeFont.getlength` may now be narrower, +and is no longer necessarily a whole number of pixels. + +1-bit text and text drawn with a bitmap font keep the grid-fitted metrics. + Python 3.15 ^^^^^^^^^^^ diff --git a/src/_imagingft.c b/src/_imagingft.c index 302f8280e67..c1ded6e4e55 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -112,6 +112,8 @@ static PyTypeObject Font_Type; #define PIXEL_FRAC(x) ((x) & 63) /* convert from pixels to 26.6 fixed-point */ #define PIXEL_TO_FIXED(x) ((x) * 64) +/* round a 16.16 fixed-point value (e.g. FT_Fixed) to 26.6 */ +#define FIXED_16_16_TO_26_6(x) (((x) + (1 << 9)) >> 10) static PyObject * geterror(int code) { @@ -485,13 +487,21 @@ text_layout_fallback( glyph = self->face->glyph; (*glyph_info)[i].x_offset = 0; (*glyph_info)[i].y_offset = 0; + + // Use non-grid-fitted metrics when eventual rendering will honour them. + // Grid-fitted metrics will make spacing between glyphs drift away + // from the font's design over longer spans. + // Monochrome and bitmap glyphs retain grid-fitted metrics so they render + // with suitable crispness. + int unfitted = !mask && glyph->format == FT_GLYPH_FORMAT_OUTLINE; + if (kerning && last_index && (*glyph_info)[i].index) { FT_Vector delta; if (FT_Get_Kerning( self->face, last_index, (*glyph_info)[i].index, - ft_kerning_default, + unfitted ? FT_KERNING_UNFITTED : FT_KERNING_DEFAULT, &delta ) == 0) { (*glyph_info)[i - 1].x_advance += delta.x; @@ -499,7 +509,12 @@ text_layout_fallback( } } - (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance; + // linearHoriAdvance is the unhinted advance, in 16.16 (IOW, 1/65536 of a px) + // (see https://freetype.org/freetype2/docs/tutorial/step2.html) + // and metrics.horiAdvance is in 26.6 (1/64 of a px). + (*glyph_info)[i].x_advance = unfitted + ? FIXED_16_16_TO_26_6(glyph->linearHoriAdvance) + : glyph->metrics.horiAdvance; // y_advance is only used in ttb, which is not supported by basic layout (*glyph_info)[i].y_advance = 0; last_index = (*glyph_info)[i].index; From 98241491052f3cacdb91dc7d064a1c560f0976d8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 27 Aug 2026 11:02:32 +0300 Subject: [PATCH 17/17] test_imagefontctl.py: adjust epsilon --- Tests/test_imagefontctl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 633f6756b0c..23009351b9d 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -183,7 +183,7 @@ def test_x_max_and_y_offset() -> None: draw.text((0, 0), "لح", font=ttf, fill=500) target = "Tests/images/test_x_max_and_y_offset.png" - assert_image_similar_tofile(im, target, 3.8) + assert_image_similar_tofile(im, target, 4.2) def test_language() -> None: