Use fractional coordinates when drawing text
| 
		 Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB  | 
| 
		 Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB  | 
| 
		 Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB  | 
| 
		 Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 809 B  | 
| 
						 | 
					@ -1238,6 +1238,27 @@ def test_stroke_descender():
 | 
				
			||||||
    assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76)
 | 
					    assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
 | 
					def test_split_word():
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    im = Image.new("RGB", (230, 55))
 | 
				
			||||||
 | 
					    expected = im.copy()
 | 
				
			||||||
 | 
					    expected_draw = ImageDraw.Draw(expected)
 | 
				
			||||||
 | 
					    font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 48)
 | 
				
			||||||
 | 
					    expected_draw.text((0, 0), "paradise", font=font)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Act
 | 
				
			||||||
 | 
					    draw.text((0, 0), "par", font=font)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    length = draw.textlength("par", font=font)
 | 
				
			||||||
 | 
					    draw.text((length, 0), "adise", font=font)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    assert_image_equal(im, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@skip_unless_feature("freetype2")
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
def test_stroke_multiline():
 | 
					def test_stroke_multiline():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -452,7 +452,11 @@ class ImageDraw:
 | 
				
			||||||
            mode = self.fontmode
 | 
					            mode = self.fontmode
 | 
				
			||||||
            if stroke_width == 0 and embedded_color:
 | 
					            if stroke_width == 0 and embedded_color:
 | 
				
			||||||
                mode = "RGBA"
 | 
					                mode = "RGBA"
 | 
				
			||||||
            coord = xy
 | 
					            coord = []
 | 
				
			||||||
 | 
					            start = []
 | 
				
			||||||
 | 
					            for i in range(2):
 | 
				
			||||||
 | 
					                coord.append(int(xy[i]))
 | 
				
			||||||
 | 
					                start.append(math.modf(xy[i])[0])
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                mask, offset = font.getmask2(
 | 
					                mask, offset = font.getmask2(
 | 
				
			||||||
                    text,
 | 
					                    text,
 | 
				
			||||||
| 
						 | 
					@ -463,6 +467,7 @@ class ImageDraw:
 | 
				
			||||||
                    stroke_width=stroke_width,
 | 
					                    stroke_width=stroke_width,
 | 
				
			||||||
                    anchor=anchor,
 | 
					                    anchor=anchor,
 | 
				
			||||||
                    ink=ink,
 | 
					                    ink=ink,
 | 
				
			||||||
 | 
					                    start=start,
 | 
				
			||||||
                    *args,
 | 
					                    *args,
 | 
				
			||||||
                    **kwargs,
 | 
					                    **kwargs,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
| 
						 | 
					@ -478,6 +483,7 @@ class ImageDraw:
 | 
				
			||||||
                        stroke_width,
 | 
					                        stroke_width,
 | 
				
			||||||
                        anchor,
 | 
					                        anchor,
 | 
				
			||||||
                        ink,
 | 
					                        ink,
 | 
				
			||||||
 | 
					                        start=start,
 | 
				
			||||||
                        *args,
 | 
					                        *args,
 | 
				
			||||||
                        **kwargs,
 | 
					                        **kwargs,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					@ -490,7 +496,7 @@ class ImageDraw:
 | 
				
			||||||
                # extract mask and set text alpha
 | 
					                # extract mask and set text alpha
 | 
				
			||||||
                color, mask = mask, mask.getband(3)
 | 
					                color, mask = mask, mask.getband(3)
 | 
				
			||||||
                color.fillband(3, (ink >> 24) & 0xFF)
 | 
					                color.fillband(3, (ink >> 24) & 0xFF)
 | 
				
			||||||
                x, y = (int(c) for c in coord)
 | 
					                x, y = coord
 | 
				
			||||||
                self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
 | 
					                self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.draw.draw_bitmap(coord, mask, ink)
 | 
					                self.draw.draw_bitmap(coord, mask, ink)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
| 
						 | 
					@ -588,6 +589,7 @@ class FreeTypeFont:
 | 
				
			||||||
        stroke_width=0,
 | 
					        stroke_width=0,
 | 
				
			||||||
        anchor=None,
 | 
					        anchor=None,
 | 
				
			||||||
        ink=0,
 | 
					        ink=0,
 | 
				
			||||||
 | 
					        start=None,
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Create a bitmap for the text.
 | 
					        Create a bitmap for the text.
 | 
				
			||||||
| 
						 | 
					@ -659,6 +661,7 @@ class FreeTypeFont:
 | 
				
			||||||
            stroke_width=stroke_width,
 | 
					            stroke_width=stroke_width,
 | 
				
			||||||
            anchor=anchor,
 | 
					            anchor=anchor,
 | 
				
			||||||
            ink=ink,
 | 
					            ink=ink,
 | 
				
			||||||
 | 
					            start=start,
 | 
				
			||||||
        )[0]
 | 
					        )[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getmask2(
 | 
					    def getmask2(
 | 
				
			||||||
| 
						 | 
					@ -672,6 +675,7 @@ class FreeTypeFont:
 | 
				
			||||||
        stroke_width=0,
 | 
					        stroke_width=0,
 | 
				
			||||||
        anchor=None,
 | 
					        anchor=None,
 | 
				
			||||||
        ink=0,
 | 
					        ink=0,
 | 
				
			||||||
 | 
					        start=None,
 | 
				
			||||||
        *args,
 | 
					        *args,
 | 
				
			||||||
        **kwargs,
 | 
					        **kwargs,
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
| 
						 | 
					@ -750,12 +754,23 @@ class FreeTypeFont:
 | 
				
			||||||
        size, offset = self.font.getsize(
 | 
					        size, offset = self.font.getsize(
 | 
				
			||||||
            text, mode, direction, features, language, anchor
 | 
					            text, mode, direction, features, language, anchor
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
 | 
					        if start is None:
 | 
				
			||||||
 | 
					            start = (0, 0)
 | 
				
			||||||
 | 
					        size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
 | 
				
			||||||
        offset = offset[0] - stroke_width, offset[1] - stroke_width
 | 
					        offset = offset[0] - stroke_width, offset[1] - stroke_width
 | 
				
			||||||
        Image._decompression_bomb_check(size)
 | 
					        Image._decompression_bomb_check(size)
 | 
				
			||||||
        im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
 | 
					        im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
 | 
				
			||||||
        self.font.render(
 | 
					        self.font.render(
 | 
				
			||||||
            text, im.id, mode, direction, features, language, stroke_width, ink
 | 
					            text,
 | 
				
			||||||
 | 
					            im.id,
 | 
				
			||||||
 | 
					            mode,
 | 
				
			||||||
 | 
					            direction,
 | 
				
			||||||
 | 
					            features,
 | 
				
			||||||
 | 
					            language,
 | 
				
			||||||
 | 
					            stroke_width,
 | 
				
			||||||
 | 
					            ink,
 | 
				
			||||||
 | 
					            start[0],
 | 
				
			||||||
 | 
					            start[1],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return im, offset
 | 
					        return im, offset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -777,13 +777,15 @@ font_render(FontObject *self, PyObject *args) {
 | 
				
			||||||
    const char *lang = NULL;
 | 
					    const char *lang = NULL;
 | 
				
			||||||
    PyObject *features = Py_None;
 | 
					    PyObject *features = Py_None;
 | 
				
			||||||
    PyObject *string;
 | 
					    PyObject *string;
 | 
				
			||||||
 | 
					    float x_start = 0;
 | 
				
			||||||
 | 
					    float y_start = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* render string into given buffer (the buffer *must* have
 | 
					    /* render string into given buffer (the buffer *must* have
 | 
				
			||||||
       the right size, or this will crash) */
 | 
					       the right size, or this will crash) */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!PyArg_ParseTuple(
 | 
					    if (!PyArg_ParseTuple(
 | 
				
			||||||
            args,
 | 
					            args,
 | 
				
			||||||
            "On|zzOziL:render",
 | 
					            "On|zzOziLff:render",
 | 
				
			||||||
            &string,
 | 
					            &string,
 | 
				
			||||||
            &id,
 | 
					            &id,
 | 
				
			||||||
            &mode,
 | 
					            &mode,
 | 
				
			||||||
| 
						 | 
					@ -791,7 +793,9 @@ font_render(FontObject *self, PyObject *args) {
 | 
				
			||||||
            &features,
 | 
					            &features,
 | 
				
			||||||
            &lang,
 | 
					            &lang,
 | 
				
			||||||
            &stroke_width,
 | 
					            &stroke_width,
 | 
				
			||||||
            &foreground_ink_long)) {
 | 
					            &foreground_ink_long,
 | 
				
			||||||
 | 
					            &x_start,
 | 
				
			||||||
 | 
					            &y_start)) {
 | 
				
			||||||
        return NULL;
 | 
					        return NULL;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -876,8 +880,8 @@ font_render(FontObject *self, PyObject *args) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* set pen position to text origin */
 | 
					    /* set pen position to text origin */
 | 
				
			||||||
    x = (-x_min + stroke_width) << 6;
 | 
					    x = (-x_min + stroke_width + x_start) * 64;
 | 
				
			||||||
    y = (-y_max + (-stroke_width)) << 6;
 | 
					    y = (-y_max + (-stroke_width) - y_start) * 64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (stroker == NULL) {
 | 
					    if (stroker == NULL) {
 | 
				
			||||||
        load_flags |= FT_LOAD_RENDER;
 | 
					        load_flags |= FT_LOAD_RENDER;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||